Documentation
¶
Overview ¶
Package codeintel holds the static-analysis engines behind Atlas's code-intelligence tools.
Everything here parses Go source with go/ast and go/parser only -- no type checking, no build step, no module download. That makes the analyses fast and usable on a tree that does not currently compile, which is exactly the state an agent most often finds a repository in. The trade-off is that results are heuristics rather than proofs, and each analysis documents where it can be wrong.
Index ¶
- type APIPackage
- type APIResult
- type APISymbol
- type AntiPatternFinding
- type AntiPatternOptions
- type AntiPatternResult
- type Caller
- type CodeSymbol
- type ConcreteType
- type DeadCodeResult
- type DeadSymbol
- type DocstringOptions
- type DocstringResult
- type DocstringSuggestion
- type EnvAuditResult
- type EnvVarUsage
- type FileMetrics
- type FuncMetrics
- type FuncRef
- type HierarchyResult
- type ImpactResult
- type Implementation
- type ImportGraphResult
- type Interface
- type Method
- type MetricIndexResult
- type MetricsResult
- type PackageNode
- type PromMetric
- type SearchOptions
- type SearchResult
- type SecurityFinding
- type SecurityScanOptions
- type SecurityScanResult
- type SymbolMatch
- type TestSkeleton
- type Todo
- type TodoOptions
- type TodoResult
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type APIPackage ¶
type APIPackage struct {
Name string
Dir string
Symbols []APISymbol
// Undocumented counts exported symbols with no doc comment.
Undocumented int
}
APIPackage is one package's exported surface.
type APIResult ¶
type APIResult struct {
Packages []APIPackage
FilesScanned int
}
APIResult is one scan.
func APISurface ¶
APISurface reports the exported declarations of every package under root.
This is what a consumer of the package can actually reach, which is a different and usually much smaller thing than what the package contains. It is also the part that cannot be changed without breaking somebody, so it is the right thing to look at before a rename, a signature change, or a release.
Struct fields are included because an exported field on an exported struct is part of the contract just as much as a method -- changing its type breaks callers exactly the same way.
type APISymbol ¶
type APISymbol struct {
Name string
// Kind is "func", "method", "type", "const", "var", or "field".
Kind string
// Recv is the receiver type for a method, or the owning struct for a
// field.
Recv string
// Signature is the rendered declaration, e.g.
// "func New(cfg Config) (*Client, error)".
Signature string
// Doc is the first line of the doc comment, which is where Go
// convention puts the summary.
Doc string
// Deprecated reports a "Deprecated:" paragraph in the doc comment.
Deprecated bool
File string
Line int
}
APISymbol is one exported declaration.
type AntiPatternFinding ¶
type AntiPatternFinding struct {
// Kind is one of "swallowed-error", "context-not-first",
// "panic-in-library".
Kind string
File string
Line int
Func string
Message string
Snippet string
}
AntiPatternFinding is one syntactic smell found in a Go source file.
type AntiPatternOptions ¶
type AntiPatternOptions struct {
// IncludeTests scans _test.go files too. Off by default: a test
// deliberately panicking on a bad fixture, or discarding an error
// from a throwaway helper, is normal and not worth flagging.
IncludeTests bool
}
AntiPatternOptions narrows a scan.
type AntiPatternResult ¶
type AntiPatternResult struct {
Findings []AntiPatternFinding
FilesScanned int
ByKind map[string]int
}
AntiPatternResult is the outcome of a scan.
func ScanAntiPatterns ¶
func ScanAntiPatterns(root string, opts AntiPatternOptions) (AntiPatternResult, error)
ScanAntiPatterns walks root's Go source for three syntactic smells:
- swallowed-error: an `if err != nil { }` whose body does nothing, or an error-like value explicitly discarded with `_ = err`. Both compile cleanly and both can hide a real failure.
- context-not-first: a function with a context.Context parameter that isn't the first one, which breaks the convention every caller and linter expects.
- panic-in-library: a panic outside package main, a test file, or a function named MustXxx (where panicking is the documented contract). A library that panics instead of returning an error takes the decision to crash away from its caller.
This is name- and shape-based, not type-checked: it can flag a `_ =` discard on a value that only looks like an error, and it cannot see through an error wrapped in a different variable name. Findings are candidates for review, not a list of confirmed bugs.
type Caller ¶
type Caller struct {
Func FuncRef
Depth int
// Via names the function it calls that leads to the target, so a
// multi-hop result reads as a chain instead of a flat list.
Via string
}
Caller is one function that calls the symbol under analysis, together with how many hops away it is.
type CodeSymbol ¶
type CodeSymbol struct {
Name string
Kind string // "func", "method", "type", "const", "var"
Recv string
Signature string
// Doc is the full doc comment text, not just its first line -- a
// search wants every word available to match against.
Doc string
File string
Line int
}
CodeSymbol is one top-level declaration -- exported or not, unlike APISurface, because the target of a search across an unfamiliar codebase is just as often an unexported helper as a public entry point.
type ConcreteType ¶
type ConcreteType struct {
Name string
Package string
File string
Line int
Methods []Method
// PointerOnly is true when at least one method has a pointer
// receiver, meaning only *T satisfies an interface, not T.
PointerOnly bool
}
ConcreteType is a named type together with the methods declared on it.
type DeadCodeResult ¶
type DeadCodeResult struct {
Symbols []DeadSymbol
FilesScanned int
// SkippedTests reports whether _test.go files were excluded. When they
// are, a symbol used only by tests still counts as dead, which is
// usually what a cleanup pass wants but is worth stating.
SkippedTests bool
}
DeadCodeResult is the outcome of a dead-code scan.
func FindDeadCode ¶
func FindDeadCode(root string, includeTests bool, includeUnexported bool) (DeadCodeResult, error)
FindDeadCode reports declarations in root that nothing else references.
The analysis is deliberately syntactic: it collects every top-level declaration, then counts identifier occurrences across the whole tree. A declaration whose name is never mentioned outside its own declaration site is reported.
It is a heuristic, and it is conservative in one direction and not the other:
- It never misses a genuine use through a plain call or reference, because any mention of the name counts.
- It CAN report a false positive for a symbol reached only through reflection, a struct tag, code generation, a build-tagged file that was skipped, or an interface satisfied implicitly (a method that exists only to satisfy an interface is never named at the call site).
- It CAN also report a false positive for an exported symbol consumed by a downstream module, since only the given tree is scanned.
Callers must present the output as candidates to review, not as a list safe to delete blindly.
type DeadSymbol ¶
type DeadSymbol struct {
Name string
Kind string // "func", "method", "type", "const", "var"
File string
Line int
Exported bool
}
DeadSymbol is one declaration that no other file in the scanned tree appears to reference.
type DocstringOptions ¶
type DocstringOptions struct {
// IncludeTests scans _test.go files too. Off by default: exported
// names in test files are rarely part of anything's public surface.
IncludeTests bool
// Symbol restricts suggestions to a single exported name. Empty
// scans every undocumented exported declaration.
Symbol string
}
DocstringOptions narrows a scan.
type DocstringResult ¶
type DocstringResult struct {
Suggestions []DocstringSuggestion
FilesScanned int
}
DocstringResult is the outcome of a scan.
func GenerateDocstrings ¶
func GenerateDocstrings(root string, opts DocstringOptions) (DocstringResult, error)
GenerateDocstrings finds exported declarations under root that have no doc comment and proposes a stub for each, shaped from the declaration's own signature: a parameter is listed by name and type, and a non-error return is called out, but the description text is left as a TODO for a human or a follow-up model call to fill in -- this tool reads syntax, it does not know what the code is for.
type DocstringSuggestion ¶
type DocstringSuggestion struct {
Name string
// Kind is "func", "method", or "type".
Kind string
// Recv is the receiver type for a method.
Recv string
Signature string
// Stub is the suggested comment block, following Go's convention of
// starting the comment with the declared name. The prose is a
// placeholder -- describing what the name already says would add
// nothing -- but the shape (summary line, parameter list, return
// note) is filled in from the signature so only the wording is left
// to do.
Stub string
File string
Line int
}
DocstringSuggestion is a ready-to-paste doc comment for one exported declaration that currently has none.
type EnvAuditResult ¶
type EnvAuditResult struct {
Usages []EnvVarUsage
// Undocumented lists the distinct literal names read but not found
// in an example env file, sorted. Empty when no such file exists to
// compare against -- see EnvFileFound.
Undocumented []string
// EnvFileFound reports whether a .env.example / .env.sample was
// found to compare against at all. When false, Undocumented is not
// meaningful -- there was nothing to check documentation against.
EnvFileFound bool
EnvFilePath string
FilesScanned int
}
EnvAuditResult is the outcome of a scan.
func AuditEnvVars ¶
func AuditEnvVars(root string, includeTests bool) (EnvAuditResult, error)
AuditEnvVars walks root for Go source and lists every os.Getenv, os.LookupEnv, and os.Setenv call, then cross-checks the variables read against an example env file (.env.example, .env.sample, or .env.dist, whichever is found first at root) if one exists.
type EnvVarUsage ¶
type EnvVarUsage struct {
// Name is the literal environment variable name, or "(dynamic)" when
// the call built its key from something other than a string
// literal -- a format string, a variable, string concatenation.
Name string
// Kind is "read" (Getenv, LookupEnv) or "write" (Setenv).
Kind string
File string
Line int
}
EnvVarUsage is one os.Getenv/LookupEnv/Setenv call site.
type FileMetrics ¶
FileMetrics aggregates one file.
type FuncMetrics ¶
type FuncMetrics struct {
Name string
Recv string
File string
Line int
// Complexity is cyclomatic complexity: the number of linearly
// independent paths through the body. One plus a branch point count.
Complexity int
// Lines is the span of the declaration in source lines, braces
// included.
Lines int
// Params and Results count the signature's arity. A long parameter
// list is a design smell the complexity number does not capture.
Params int
Results int
// Nesting is the deepest block nesting inside the body. Two functions
// can share a complexity score while one is flat and the other is a
// staircase, and the staircase is the harder one to read.
Nesting int
// Returns counts return statements, which is what makes a function
// hard to reason about when combined with deep nesting.
Returns int
}
FuncMetrics is the measured shape of one function.
type FuncRef ¶
type FuncRef struct {
Name string
// Recv is the receiver type name for a method, empty for a plain
// function.
Recv string
Package string
File string
Line int
}
FuncRef locates one function or method declaration.
func (FuncRef) Key ¶
Key is the name the call graph is indexed by. Methods are indexed by bare method name rather than receiver.method, because a call site written as x.Close() carries no type information without a type checker -- so any Close() is a possible target. Over-approximating here is the safe direction for an impact question: it can name callers that are not really affected, never miss one that is.
type HierarchyResult ¶
type HierarchyResult struct {
Interfaces []Interface
Types []ConcreteType
Implementations []Implementation
FilesScanned int
}
HierarchyResult is the outcome of a type-hierarchy scan.
func TypeHierarchy ¶
func TypeHierarchy(root string, includeTests bool) (HierarchyResult, error)
TypeHierarchy reports which named types in root satisfy which interfaces declared in the same tree.
Like the rest of this package the analysis is syntactic: method sets are matched by name and by the printed text of the signature. That is exact for the common case and wrong in three specific ways, all of which callers must surface rather than hide:
- Two identical types spelled differently (an alias, a dot-import, a package qualifier present in one file and absent in the other) will not match, so a real implementation can be missed.
- Interfaces declared outside the scanned tree -- io.Reader, or anything from a dependency -- are not known, so satisfying them is invisible here.
- Generic type parameters are compared as written, so two instantiations that are identical after substitution may not match.
Embedded interfaces are flattened when the embedded interface is also in the tree; when it is not, its name is kept in Embeds and its methods are simply unknown, which can only cause a missed match, never a false one.
type ImpactResult ¶
type ImpactResult struct {
Target FuncRef
// Ambiguous holds the other declarations sharing the target's name.
// Their presence means callers may belong to any of them.
Ambiguous []FuncRef
Callers []Caller
// Reached counts distinct functions in the transitive caller set.
Reached int
MaxDepth int
FilesScanned int
// Truncated reports that the walk stopped at the depth limit and the
// real caller set is larger.
Truncated bool
}
ImpactResult is the outcome of a reverse-call-graph walk.
func ImpactAnalysis ¶
func ImpactAnalysis(root, symbol string, maxDepth int, includeTests bool) (ImpactResult, error)
ImpactAnalysis reports which functions transitively call symbol, out to maxDepth hops, so the blast radius of changing it is visible before the change is made.
Call sites are matched on the identifier alone. Without a type checker there is no way to tell x.Close() on one type from x.Close() on another, so every declaration named Close is a candidate and every Close() call site is an edge. This over-approximates: the result may name a caller that is not truly affected. That is deliberate -- for "what breaks if I change this", a false alarm costs a moment's reading and a miss costs a broken build. Ambiguous names are reported alongside the result so the caller knows when to distrust the breadth.
type Implementation ¶
type Implementation struct {
Type ConcreteType
Interface Interface
// ViaPointer records that the satisfying type is *T rather than T.
ViaPointer bool
}
Implementation pairs a concrete type with an interface it satisfies.
type ImportGraphResult ¶
type ImportGraphResult struct {
Module string
Packages []PackageNode
// Cycles holds each import cycle found among internal packages, as an
// ordered list of import paths where the last element imports the
// first.
Cycles [][]string
}
ImportGraphResult is the outcome of an import-graph scan.
func ImportGraph ¶
func ImportGraph(root string, includeTests bool) (ImportGraphResult, error)
ImportGraph builds the internal package dependency graph rooted at root and reports any import cycles in it.
Package identity is the directory, which is how Go itself defines it. Import paths are resolved through the module path in go.mod when one is found at or above root; without a go.mod, packages are identified by their path relative to root and only same-tree edges are recognised.
Only static import declarations are read. An import injected by build tags in a file excluded from the scan, or a dependency expressed through a plugin or reflection, is not visible -- so the graph can be missing an edge, and a cycle it does not report may still exist.
type Interface ¶
type Interface struct {
Name string
Package string
File string
Line int
Methods []Method
// Embeds names interfaces this one embeds. They are resolved when
// the embedded interface is also in the scanned tree, and left here
// for reporting either way.
Embeds []string
}
Interface is one interface declaration found in the scanned tree.
type Method ¶
type Method struct {
Name string
// Sig is the rendered parameter and result list, e.g.
// "(ctx context.Context) (int, error)". It is compared textually,
// which is why the analysis is a heuristic -- see TypeHierarchy.
Sig string
}
Method is one method in an interface's method set or on a concrete type, reduced to the shape that decides assignability.
type MetricIndexResult ¶
type MetricIndexResult struct {
Metrics []PromMetric
FilesScanned int
}
MetricIndexResult is the outcome of a scan.
func IndexMetrics ¶
func IndexMetrics(root string, includeTests bool) (MetricIndexResult, error)
IndexMetrics walks root for Go source and lists every Prometheus metric constructed with prometheus.NewXxx or promauto.NewXxx (with or without a preceding .With(...) registerer call).
A metric whose Opts argument isn't a literal at the call site -- built in a variable, a helper function, or a loop -- is still counted, but with its name reported as "(unknown)" and its Help and Labels empty, since reading through an arbitrary expression back to its literal values is not something syntax alone can promise.
type MetricsResult ¶
type MetricsResult struct {
Functions []FuncMetrics
Files []FileMetrics
FilesScanned int
TotalLines int
}
MetricsResult is the outcome of a metrics scan.
func Metrics ¶
func Metrics(root string, includeTests bool) (MetricsResult, error)
Metrics measures every function under root.
Cyclomatic complexity is counted the standard way: one, plus one for each if, for, range, case, branching comm clause, and each && or ||. That matches gocyclo and golangci-lint's cyclop, so a number here is comparable to a number from those.
The measurements are exact -- unlike the rest of this package there is no approximation, because counting syntax needs no type information. What is a judgement call is what the numbers mean, and that belongs to the caller: a 30-branch switch over an enum is fine, a 30-branch nest of conditionals is not, and no threshold tells them apart.
type PackageNode ¶
type PackageNode struct {
// ImportPath is the module-qualified path, e.g.
// "example.com/mod/internal/agent". It falls back to the directory
// path relative to the root when no module can be determined.
ImportPath string
Dir string
Name string
// Internal holds imports that resolve to another package in this
// same module, already normalised to import paths.
Internal []string
// External holds third-party and standard-library imports.
External []string
Files int
}
PackageNode is one directory of Go source, with the imports its files declare.
type PromMetric ¶
type PromMetric struct {
// Name is Namespace_Subsystem_Name joined per Prometheus convention,
// or "(unknown)" when the opts weren't a literal this tool could
// read.
Name string
// Type is "counter", "gauge", "histogram", or "summary".
Type string
Help string
Labels []string
File string
Line int
}
PromMetric is one Prometheus metric registration found in source.
type SearchOptions ¶
type SearchOptions struct {
IncludeTests bool
// Limit caps how many matches are returned. Zero means 10.
Limit int
}
SearchOptions narrows a search.
type SearchResult ¶
type SearchResult struct {
Matches []SymbolMatch
FilesScanned int
}
SearchResult is the outcome of a search.
func SemanticSearch ¶
func SemanticSearch(root, query string, opts SearchOptions) (SearchResult, error)
SemanticSearch indexes every top-level declaration under root and ranks them against query by matching words in the declaration's name and doc comment -- not an embedding, just tokenised keyword overlap, weighted so a name match counts for more than a doc match. That is a real limitation: it finds "which symbol's name or comment mentions these words", not "which symbol does what you mean" -- a function with no doc comment and a name that doesn't share vocabulary with the query will not be found even if it's exactly the right one.
type SecurityFinding ¶
type SecurityFinding struct {
// Kind is one of "hardcoded-credential", "weak-crypto", "insecure-tls",
// "sql-injection-risk", "command-injection-risk".
Kind string
File string
Line int
Func string
Message string
Snippet string
}
SecurityFinding is one syntactic security smell found in a Go source file.
type SecurityScanOptions ¶
type SecurityScanOptions struct {
// IncludeTests scans _test.go files too. Off by default: fixtures and
// mock secrets in tests are normal and not worth flagging.
IncludeTests bool
}
SecurityScanOptions narrows a scan.
type SecurityScanResult ¶
type SecurityScanResult struct {
Findings []SecurityFinding
FilesScanned int
ByKind map[string]int
}
SecurityScanResult is the outcome of a scan.
func SecurityScan ¶
func SecurityScan(root string, opts SecurityScanOptions) (SecurityScanResult, error)
SecurityScan walks root's Go source and flags syntactic security smells: hardcoded credentials, use of broken cryptographic primitives, disabled TLS verification, and string-built SQL or shell commands.
type SymbolMatch ¶
type SymbolMatch struct {
CodeSymbol
Score int
// MatchedTerms lists which query words contributed to the score, so
// a result can be explained rather than trusted blindly.
MatchedTerms []string
}
SymbolMatch is one search result.
type TestSkeleton ¶
type TestSkeleton struct {
FuncName string
Skeleton string
// Imports lists the packages the skeleton references, so the caller
// knows what to add alongside it -- this is a snippet to paste into
// an existing test file, not a standalone one, so it deliberately
// does not print its own import block.
Imports []string
File string
Line int
}
TestSkeleton is a generated table-driven test for one function.
func GenerateTestSkeleton ¶
func GenerateTestSkeleton(root, symbol string) (TestSkeleton, error)
GenerateTestSkeleton finds symbol -- a package-level function, not a method -- under root and generates a table-driven test skeleton shaped from its parameters and return values.
Methods are not supported: constructing a receiver generically isn't possible from syntax alone, and a wrong guess (a zero value, say) would produce a skeleton that compiles but tests nothing meaningful. A plain function's parameters can all become table fields instead.
type Todo ¶
type Todo struct {
// Kind is the normalised marker: TODO, FIXME, HACK, XXX, BUG,
// NOTE, OPTIMIZE, DEPRECATED.
Kind string
// Owner is the name in "TODO(alice):" when there is one. An owned
// marker is one somebody can be asked about; an unowned one usually
// belongs to nobody.
Owner string
// Ticket is an issue reference found in the text, e.g. "#1234" or
// "PROJ-42".
Ticket string
File string
Line int
Text string
Context string
}
Todo is one marker found in a comment.
type TodoOptions ¶
type TodoOptions struct {
// Kinds, when non-empty, restricts to these markers (case
// insensitive).
Kinds []string
// MaxResults caps findings. Zero means 500.
MaxResults int
// IncludeTests scans _test.go and similar files too.
IncludeTests bool
// Extensions, when non-empty, restricts to these file extensions
// (with the leading dot).
Extensions []string
}
TodoOptions narrows a scan.
type TodoResult ¶
type TodoResult struct {
Todos []Todo
FilesScanned int
// ByKind counts each marker type.
ByKind map[string]int
// Truncated reports that the scan stopped at its limit.
Truncated bool
}
TodoResult is one scan.
func FindTodos ¶
func FindTodos(root string, opts TodoOptions) (TodoResult, error)
FindTodos scans root for markers left in comments.