Most Go API documentation tools require you to litter your code with annotations. I wanted to see how far static analysis could go — extracting routes, parameters, request/response types, and even auth patterns purely from the AST. Turns out, pretty far.
This is the story of building GoDoc Live, an open-source CLI that reads your Go HTTP handlers and generates interactive documentation without touching your code.
The Problem
If you've used swaggo, you know the drill: dozens of comment annotations above every handler. They drift out of sync with the actual code. You end up maintaining two sources of truth.
I wanted a tool where the Go source code IS the documentation. Point it at your project, get docs.
The Architecture
The analysis pipeline has 8 stages:
Stage 1-2: Load & Detect
Everything starts with go/packages in LoadAllSyntax mode. This gives us both the full AST and type information for every package. Then we walk import declarations to detect whether the project uses chi, gin, or net/http stdlib.
Stage 3-4: Extract Routes & Resolve Handlers
This is where it gets interesting. We walk the AST of main() and init() functions, looking for router method calls like .Get(), .Post(), .Group(), and .Mount(). For net/http stdlib, we detect the new Go 1.22+ "METHOD /path" patterns in http.HandleFunc and mux.Handle calls.
Each route registration references a handler — sometimes a function literal, sometimes an identifier, sometimes a selector expression like handlers.GetUser. We resolve each one back to its *ast.FuncDecl to analyze the actual handler body.
Stage 5: Contract Extraction (The Hard Part)
This is where most of the complexity lives. For each handler, we extract:
-
Path parameters: Name heuristics (
{id}→ uuid,{page}→ integer) combined with handler body analysis to see how the param is actually used after extraction -
Query parameters: Pattern-matching on
r.URL.Query().Get()and gin'sc.DefaultQuery()to detect required vs. optional and default values -
Request bodies: Matching
json.NewDecoder(r.Body).Decode(&target)andc.ShouldBindJSON(&target), then resolvingtargetthrough the type checker to get full struct definitions with JSON tags -
Responses: Branch-aware analysis that pairs each
WriteHeader()orc.JSON()call with the response body type in that branch's scope
Stage 6: Type Mapping
We convert types.Type into a recursive TypeDef structure. This handles nested structs, slices, maps, pointer types — all with their JSON tags, field names, and example values. The type checker from go/types does the heavy lifting here, resolving identifiers across packages.
Stage 7: Auth Detection
We scan middleware function bodies for patterns like r.Header.Get("Authorization"), JWT-related function calls, and API key extraction. This identifies whether endpoints are protected and what scheme they use.
Stage 8: Generate
Finally, all endpoint contracts get transformed into an interactive HTML documentation site with a dark/light theme, search, and a "Try It" panel.
The Hardest Problems
Branch-aware response extraction: A single handler might return 200 with a user object, 404 with an error, or 500 with a different error — all from different if/else branches. Walking the AST tree and tracking which response type belongs to which status code required careful scope tracking.
Helper function tracing: Real-world Go code rarely calls w.WriteHeader() directly. Instead, developers use helpers like respond(w, 200, data) or writeJSON(w, result). GoDoc Live traces one level deep — when it encounters an unknown function call in a handler, it resolves and analyzes that function's body to find the actual HTTP response patterns.
net/http stdlib detection: Go 1.22 introduced "GET /users/{id}" patterns in http.HandleFunc. Detecting these requires parsing the string literal in the first argument of the call expression and extracting both the method and path pattern — a different approach than chi/gin where methods are encoded in the function name.
What I Learned
go/ast combined with go/types is remarkably powerful. The type checker handles the hard parts — resolving identifiers across packages, interface satisfaction, and concrete types from expressions. The real challenge wasn't parsing Go code; it was handling the sheer variety of patterns developers use to write HTTP handlers.
The tool currently hits 100% accuracy across 42 test endpoints for route detection, path params, query params, response status codes, and auth detection. The main limitation is single-level helper tracing — deeply nested abstractions will produce incomplete contracts.
Try It
go install github.com/syst3mctl/godoclive/cmd/godoclive@latest
godoclive generate ./...
open docs/index.html
Or with live reload:
godoclive watch --serve :8080 ./...
The project is MIT licensed: github.com/syst3mctl/godoclive
I'd love to hear about edge cases in handler patterns I haven't considered, or if anyone has tackled similar static analysis problems in Go.
Top comments (0)