Skip to content

Repository files navigation

CodeLLM-DevKit

codeanalyzer-go (cango)

A Go static-analysis toolkit — the CLDK backend that emits a canonical symbol table and call graph as analysis.json.

PyPIGoReleaseLicense


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.

Table of Contents

Features

  • 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.

Installation

Prerequisites

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+.

Install via shell script

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 | sh

The 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).

Install via Homebrew

brew install codellm-devkit/homebrew-tap/codeanalyzer-go

Install via pip (PyPI)

The wheel bundles the prebuilt, self-contained binary for your platform (no Go toolchain required):

pip install codeanalyzer-go
cango --help

The 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().

Build from source

git clone https://github.com/codellm-devkit/codeanalyzer-go
cd codeanalyzer-go
go build -o cango ./cmd/codeanalyzer

This 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 2

Usage

cango -i /path/to/go/project

Command-line options

codeanalyzer-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

Examples

Symbol table only (Level 1, default):

cango -i ./my-go-project

Prints analysis.json to stdout.

Symbol table + call graph (Level 2):

cango -i ./my-go-project -a 2

Write output to a directory:

cango -i ./my-go-project -a 2 -o /path/to/output/
# Writes: /path/to/output/analysis.json

Incremental analysis (specific files only):

cango -i ./my-go-project -t pkg/server/server.go -t pkg/server/handler.go

Force rebuild, ignore cache:

cango -i ./my-go-project --eager

Verbose output:

cango -i ./my-go-project -a 2 -vv

Analysis levels

LevelFlagWhat runsStatus
1-a 1 (default)Symbol table only — types, functions, call sitesImplemented
2-a 2Level 1 + resolver-based call graph via go/typesImplemented
--codeqlCodeQL 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.

Output schema

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 is GoType
  • module_name — JSON key for the Go package name (spine compatibility)
  • GoType.is_interface: bool — unified type model; structs and interfaces are both GoType
  • GoCallable.receiver_type / receiver_name — non-empty for methods, empty for package-level functions
  • GoCallable.return_types: List[str] — individual return types (Go-specific extension)
  • GoCallsite.is_goroutine: bool — true when the call is preceded by the go keyword
  • GoCallEdge.provenance: List[str] — resolver identifiers, e.g. ["go/types"] or ["go/types","codeql"]
  • Call edges are identity-only: source and target are GoCallable.signature strings that exist in the symbol table

Python SDK (CLDK) integration

fromcldkimportCLDKanalysis=CLDK(language="go").analysis(project_path="/path/to/go/project")
forfile_path, go_fileinanalysis.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.

Architecture & Tooling

SlotChoiceRationale
RuntimeGo binarySelf-contained; no runtime dep for SDK users
Structural parsergo/ast (stdlib)Part of the standard toolchain; no external dep
Type resolvergolang.org/x/tools/go/packagesSingle API for both AST + full type resolution; handles modules natively
Optional enrichmentCodeQL (stubbed)Same enrichment path as Python/Java analyzers; stubbed for Level 1
Build/dep materializationgo mod downloadRequired before packages.Load so the module cache is warm; result cached by go.sum hash
PackagingNative binary (go build)Zero-runtime-dep distribution; matches Rust/C++ analyzers
Analysis depthLevel 1 (rapid)Symbol table + resolver call graph; CodeQL stub wired but not implemented
Call-graph dispatchDeclared-type resolution via go/types.SelectionsCHA-equivalent; sufficient for cross-package reachability at Level 1

Package structure

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_analysissemantic_analysisanalysis.RunPipeline → optional CodeQL in sequence, with no inlined parsing logic. Framework-specific analysis extends through the analysis/ + frameworks/ layer without touching core.

Development

Running tests

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.

Clearing the production cache

By default the CLI writes its cache to ~/.cldk/go-cache. To bypass it for a single run:

cango -i ./my-project --eager

To delete it entirely:

rm -rf ~/.cldk/go-cache

If you pass a custom --cache-dir, remove that directory instead.

Releasing

Releases are cut by pushing a vX.Y.Z tag. The release workflow cross-compiles the cango binary for all supported platforms and publishes:

  1. the raw binaries + cango-installer.sh as GitHub Release assets,
  2. platform-tagged wheels to PyPI as codeanalyzer-go (via Trusted Publishing/OIDC), and
  3. 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.

License

Apache 2.0 — see LICENSE.

About

The static code analysis engine for Go

Resources

Contributing

Stars

0 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages