A Go static-analysis toolkit — the CLDK backend that emits a canonical symbol table and call graph as analysis.json.
cango is a static analyzer for Go built on golang.org/x/tools/go/packages
(AST + full type resolution). It produces the canonical CodeLLM-DevKit (CLDK) analysis.json — a
symbol table plus a resolver-based call graph — consumable by the Python SDK via
CLDK(language="go").analysis(project_path=...). It is the Go backend behind
CLDK, mirroring its
Python,
TypeScript, and
Java siblings.
The binary is fully self-contained: a single static executable with no runtime dependencies.
- Features
- Installation
- Usage
- Analysis levels
- Output schema
- Python SDK (CLDK) integration
- Architecture & Tooling
- Development
- License
- Symbol table — packages, structs, interfaces, fields, methods, package-level functions, imports, struct tags, and generic type parameters, with precise source spans.
- Call graph — a resolver-based call graph via
go/types: each call site is resolved to its full import-path signature, with project-internal edges emitted (Level 2). - Generics-aware — Go 1.18+ generics (
Set[T], union-constraint interfaces, multi-type-param functions) are modeled in the unified type/callable schema. - Self-contained binary — a single static executable (
go build,CGO_ENABLED=0); no runtime dependencies for SDK users. - CLDK canonical schema — output is spine-compatible with the Java/Python/TypeScript analyzers, loadable directly by the Python SDK.
Running a prebuilt cango binary requires nothing — it is fully self-contained. To analyze a
project, that project should be a normal Go module (contain a go.mod) so the type checker can
resolve imports. Building cango from source requires Go 1.25+.
Download and install the prebuilt binary for your platform from the latest release:
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/codellm-devkit/codeanalyzer-go/releases/latest/download/cango-installer.sh | shThe installer drops cango into ~/.local/bin (override with CANGO_INSTALL_DIR) and can pin a
version with CANGO_VERSION=vX.Y.Z. It also creates a codeanalyzer-go alias symlink. Supports
macOS (arm64/x86_64) and Linux (x86_64/aarch64).
brew install codellm-devkit/homebrew-tap/codeanalyzer-goThe wheel bundles the prebuilt, self-contained binary for your platform (no Go toolchain required):
pip install codeanalyzer-go
cango --helpThe wheel installs both a cango launcher and a codeanalyzer-go alias on PATH. This is also the
package CLDK's Python SDK depends on to locate the analyzer backend; it exposes
codeanalyzer_go.bin_path().
git clone https://github.com/codellm-devkit/codeanalyzer-go
cd codeanalyzer-go
go build -o cango ./cmd/codeanalyzerThis produces a single static binary cango with no runtime dependencies. You can also run the
analyzer directly from source without compiling:
go run ./cmd/codeanalyzer -i /path/to/project -a 2cango -i /path/to/go/projectcodeanalyzer-go produces analysis.json (symbol table + call graph) for Go projects.
Usage:
cango [flags]
Aliases:
cango, codeanalyzer-go
Flags:
-a, --analysis-level int Analysis level: 1=symbol table only, 2=+resolver call graph (default 1)
-c, --cache-dir string Cache directory (default: ~/.cldk/go-cache)
--codeql Enable CodeQL framework-based call graph (level 2, stub)
--eager Force clean rebuild (ignore cache)
-f, --format string Output format: json|msgpack (default "json")
-h, --help help for cango
-i, --input string Project root to analyze (required)
-o, --output string Output directory for analysis.json (default: stdout)
--skip-tests Skip *_test.go files (default true)
-t, --target-files strings Restrict analysis to specific files (incremental mode)
-v, --verbose count Verbosity (repeat for more detail)
--version Print version and exit
Symbol table only (Level 1, default):
cango -i ./my-go-projectPrints analysis.json to stdout.
Symbol table + call graph (Level 2):
cango -i ./my-go-project -a 2Write output to a directory:
cango -i ./my-go-project -a 2 -o /path/to/output/
# Writes: /path/to/output/analysis.jsonIncremental analysis (specific files only):
cango -i ./my-go-project -t pkg/server/server.go -t pkg/server/handler.goForce rebuild, ignore cache:
cango -i ./my-go-project --eagerVerbose output:
cango -i ./my-go-project -a 2 -vv| Level | Flag | What runs | Status |
|---|---|---|---|
| 1 | -a 1 (default) |
Symbol table only — types, functions, call sites | Implemented |
| 2 | -a 2 |
Level 1 + resolver-based call graph via go/types |
Implemented |
| — | --codeql |
CodeQL framework-based call graph (merged with Level 2 edges) | Stub (not yet implemented) |
Level 1 loads each package with packages.NeedSyntax | NeedTypes | NeedTypesInfo and walks the AST file by file. Call sites are recorded with callee_signature = null at this stage.
Level 2 adds a resolver pass: for each call site, go/types resolves the callee to its full import-path signature (pkgImportPath.TypeName.MethodName). Only project-internal edges (both endpoints present in the symbol table) are emitted. callee_signature is backfilled on all successfully resolved sites.
The root object is GoApplication:
{
"symbol_table": {
"pkg/greeter/greeter.go": {
"file_path": "pkg/greeter/greeter.go",
"module_name": "greeter",
"imports": [...],
"classes": {
"Greeter": {
"name": "Greeter",
"signature": "example.com/pkg/greeter.Greeter",
"is_interface": false,
"fields": [{ "name": "Prefix", "type": "string", "tags": {"json": "prefix"} }],
"methods": { ... }
}
},
"functions": { ... }
}
},
"call_graph": [
{
"source": "example.com/main.main",
"target": "example.com/pkg/greeter.Greeter.Greet",
"type": "CALL_DEP",
"weight": 1,
"provenance": ["go/types"]
}
],
"entrypoints": {}
}Key schema properties:
symbol_table— keyed by file path relative to the project root (never absolute)classes— JSON key for types (spine compatibility with Java/Python schemas); value isGoTypemodule_name— JSON key for the Go package name (spine compatibility)GoType.is_interface: bool— unified type model; structs and interfaces are bothGoTypeGoCallable.receiver_type / receiver_name— non-empty for methods, empty for package-level functionsGoCallable.return_types: List[str]— individual return types (Go-specific extension)GoCallsite.is_goroutine: bool— true when the call is preceded by thegokeywordGoCallEdge.provenance: List[str]— resolver identifiers, e.g.["go/types"]or["go/types","codeql"]- Call edges are identity-only: source and target are
GoCallable.signaturestrings that exist in the symbol table
from cldk import CLDK
analysis = CLDK(language="go").analysis(project_path="/path/to/go/project")
for file_path, go_file in analysis.get_symbol_table().items():
print(file_path, go_file.module_name)The SDK locates the analyzer via the codeanalyzer-go command on PATH (installed by any of the
methods above, including pip install codeanalyzer-go). See
python-sdk for full API documentation.
| Slot | Choice | Rationale |
|---|---|---|
| Runtime | Go binary | Self-contained; no runtime dep for SDK users |
| Structural parser | go/ast (stdlib) |
Part of the standard toolchain; no external dep |
| Type resolver | golang.org/x/tools/go/packages |
Single API for both AST + full type resolution; handles modules natively |
| Optional enrichment | CodeQL (stubbed) | Same enrichment path as Python/Java analyzers; stubbed for Level 1 |
| Build/dep materialization | go mod download |
Required before packages.Load so the module cache is warm; result cached by go.sum hash |
| Packaging | Native binary (go build) |
Zero-runtime-dep distribution; matches Rust/C++ analyzers |
| Analysis depth | Level 1 (rapid) | Symbol table + resolver call graph; CodeQL stub wired but not implemented |
| Call-graph dispatch | Declared-type resolution via go/types.Selections |
CHA-equivalent; sufficient for cross-package reachability at Level 1 |
codeanalyzer-go/
├── cmd/codeanalyzer/ # CLI entry point (cobra) — builds the `cango` binary
├── internal/
│ ├── core/ # Orchestrator — delegates only, no inlined analysis
│ ├── schema/ # GoApplication, GoFile, GoType, GoCallable, … (schema.go)
│ ├── options/ # AnalysisOptions + AnalysisLevel constants
│ ├── syntactic_analysis/ # SymbolTableBuilder (packages.Load → AST walk)
│ ├── semantic_analysis/ # CallGraphBuilder (go/types resolver)
│ │ └── codeql/ # CodeQL backend subpackage (stubbed)
│ ├── analysis/ # Pluggable pass interface + registry (topo-ordered pipeline)
│ ├── frameworks/ # BaseEntrypointFinder — extension seam for framework passes
│ └── utils/ # DiscoverGoFiles, IsVendored, IsTestFile, logging
├── packaging/
│ ├── python/ # PyPI wheel wrapper (bundles the prebuilt binary per platform)
│ ├── homebrew/ # generate_formula.sh — Homebrew tap formula generator
│ └── install/ # cango-installer.sh — curl | sh installer
├── testdata/
│ ├── greeter/ # Minimal two-package fixture (basic struct/interface/call sites)
│ ├── multipackage/ # Richer fixture covering embedded fields, variadic params, goroutines, …
│ ├── generics/ # Go 1.18+ generics fixture (Set[T], union-constraint interfaces, Map[T,U])
│ └── chi/ # External-dep fixture (chi v5, vendored) for HTTP handler patterns
The core package is a pure orchestrator: it calls syntactic_analysis → semantic_analysis → analysis.RunPipeline → optional CodeQL in sequence, with no inlined parsing logic. Framework-specific analysis extends through the analysis/ + frameworks/ layer without touching core.
go test ./...Tests run against four fixtures: testdata/greeter/ (basic), testdata/multipackage/ (multi-file packages, goroutines, variadic params), testdata/generics/ (Go 1.18+ generics — Set[T], union constraints, multi-type-param functions), and testdata/chi/ (external dependency via vendored chi v5, HTTP handler patterns). All 105 tests cover symbol table correctness, generic receiver attribution, call graph edges, JSON round-trip, output format validation, caching behaviour, and error paths.
go test caches passing results by source hash. To force a full re-run:
go clean -testcache && go test ./...The analyzer's own CacheDir (used inside tests for analysis_cache.json and go_mod_hash) is written to OS temp directories that are wiped automatically when the test binary exits — there is no persistent on-disk state between test runs. The chi fixture is fully vendored, so tests never require network access.
By default the CLI writes its cache to ~/.cldk/go-cache. To bypass it for a single run:
cango -i ./my-project --eagerTo delete it entirely:
rm -rf ~/.cldk/go-cacheIf you pass a custom --cache-dir, remove that directory instead.
Releases are cut by pushing a vX.Y.Z tag. The release workflow
cross-compiles the cango binary for all supported platforms and publishes:
- the raw binaries +
cango-installer.shas GitHub Release assets, - platform-tagged wheels to PyPI as
codeanalyzer-go(via Trusted Publishing/OIDC), and - a Homebrew formula pushed to
codellm-devkit/homebrew-tap.
The version is injected into the binary at build time via -ldflags "-X main.version=<tag>", so
cango --version, the wheel version, and the git tag always stay in lockstep. See
packaging/ for the build scripts.
Apache 2.0 — see LICENSE.