Saltar a contenido

Week 14 - go/ast, go/parser, go/types: Static Analysis

14.1 Conceptual Core

  • The go/ast package represents Go source as a syntax tree. The go/parser package parses source files into ast.Files. The go/types package performs type checking and resolves identifiers to declarations.
  • The triad (ast + parser + types) is the foundation for every serious Go tool: gofmt, goimports, gopls, golangci-lint, staticcheck, mockgen, sqlc.
  • golang.org/x/tools/go/packages is the modern entry point for loading a Go program for analysis. It handles modules, build tags, and CGO transparently. Use this; do not call parser.ParseFile directly except for single-file tools.
  • golang.org/x/tools/go/analysis is the framework for writing analyzers-small, composable passes consumed by go vet, golangci-lint, and standalone drivers.

14.2 Mechanical Detail

  • Loading a package:
    cfg := &packages.Config{Mode: packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo}
    pkgs, _ := packages.Load(cfg, "./...")
    
    The Mode flags determine cost; load only what you need.
  • Walking AST:
    ast.Inspect(file, func(n ast.Node) bool {
        if call, ok := n.(*ast.CallExpr); ok { /* ... */ }
        return true
    })
    
  • Type information:
  • pkg.TypesInfo.Types[expr] → the type of an expression.
  • pkg.TypesInfo.Defs[ident] / Uses[ident] → the object an identifier defines or uses.
  • pkg.TypesInfo.ObjectOf(ident) → the resolved object (can be a *types.Var, *types.Func, *types.TypeName, etc.).
  • Writing an analyzer:
    var Analyzer = &analysis.Analyzer{
        Name: "noprintln",
        Doc:  "disallow fmt.Println in production code",
        Run:  func(pass *analysis.Pass) (any, error) { /* walk pass.Files */ },
    }
    
    Compile as a binary using unitchecker or load via the golangci-lint plugin system.
  • Common pitfalls: position information (token.Pos) is meaningless without the token.FileSet it was created from; always pass them together. Comment groups are a separate field on ast.File, not attached to AST nodes by default-ast.CommentMap bridges them.

14.3 Lab-"Build a Custom Analyzer"

Write an analyzer that flags: 1. context.Background() calls outside main and *_test.go files. 2. time.After inside a select body (the classic timer-leak pattern). 3. Goroutines launched with closures capturing a context.Context parameter named ctx of an enclosing HTTP handler (heuristic; document the false-positive risk).

Wire as a unitchecker binary. Run on a real codebase and triage findings. Document each false positive in ANALYZER_NOTES.md.

14.4 Idiomatic & golangci-lint Drill

  • Read staticcheck's source for two of its analyzers (e.g., SA1015 and SA4006). Internalize the analyzer-author idioms.

14.5 Production Hardening Slice

  • Publish your analyzer as a module. Add a golangci-lint custom plugin entry so it runs alongside the standard suite. CI now enforces your project's idioms automatically.

Comments