Skip to content

Upgrades#9

Draft
tooolbox wants to merge 33 commits intotypelate:mainfrom
tooolbox:next-march-adv
Draft

Upgrades#9
tooolbox wants to merge 33 commits intotypelate:mainfrom
tooolbox:next-march-adv

Conversation

@tooolbox
Copy link

@tooolbox tooolbox commented Mar 18, 2026

Summary

This branch adds five new static analysis checks, a VS Code extension for in-template diagnostics, and CI for automated releases.

New checks

  • Printf format validation{{printf "%d" .Name}} where .Name is a string is now a type error. Format verbs are validated against argument types; %v is always accepted.
  • template.Execute support — 2-arg Execute calls are now discovered and checked, not just ExecuteTemplate.
  • Unused variable detection (W005){{$x := .Foo}} where $x is never referenced.
  • Dead conditional branches (W006){{if true}}...{{else}}unreachable{{end}} and similar literal-constant conditions.
  • Inconsistent sub-template types (W007) — warns when the same {{template "name"}} is called with incompatible data types from different call sites.

Improved existing checks

  • Non-static ExecuteTemplate name resolution — call-graph tracing now resolves template names passed through function parameters, covering helper/wrapper patterns.
  • ParseFiles and ParseGlob support — template initialization via ParseFiles(...) and ParseGlob(...) is now traced alongside the existing ParseFS support.
  • Robust FuncMap type-checking — replaced fragile types.Eval approach with typesInfo.TypeOf for resolving FuncMap entries.
  • Per-value nil guard tracking (W003) — pointer dereference warnings now track which specific value was guarded, not just whether any guard exists in scope.

VS Code extension

New vscode-go-template-check/ extension shows diagnostics inside template files (.gohtml, .tmpl, .gotmpl):

  • Red squiggles on {{.MissingField}}
  • Yellow squiggles for W001–W007 warnings
  • Syntax highlighting for Go template directives
  • Runs on save; prompts to install check-templates if not found

CI / Release automation

.github/workflows/release.yml — on GitHub release creation, builds check-templates binaries for linux/darwin/windows (amd64 + arm64) and packages the .vsix, uploading all as release assets.

What we didn't ship

  • go/analysis analyzer / vettool integration — We built a go/analysis.Analyzer and wired it into the CLI as a go vet -vettool= backend. We reverted and then removed it entirely. The go/analysis framework doesn't provide access to EmbedFiles or cross-package deferred resolution, so it gives incomplete coverage compared to the standalone CLI — a false sense of security. The VS Code extension calls the CLI directly, which is the correct entry point.

Testing

  • 40+ new txtar script tests covering all new error and warning cases
  • Real on-disk fixture tests for cross-package scenarios (caught a bug with path handling on Windows)

Warning reference

Code Category
W001 Non-static ExecuteTemplate name
W002 Unused template
W003 Unguarded pointer dereference
W004 Interface field access
W005 Unused variable
W006 Dead conditional branch
W007 Inconsistent sub-template types

Test plan

  • go test ./... passes
  • go tool check-templates ./... runs cleanly on a sample project
  • VS Code extension loads and shows diagnostics in .gohtml files
  • Release workflow runs on tag push and uploads assets

tooolbox and others added 30 commits March 6, 2026 16:09
Add WarningFunc to the check.Package API and a -w CLI flag that emits
warnings when ExecuteTemplate is called with a non-static template name.
Warnings are off by default to avoid noise in existing workflows.

Also add tests for ./... with deeply nested packages and document how
the CLI discovers templates (embed.FS requirement, supported patterns).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The CLI can now analyze templates loaded via template.ParseFiles() and
template.ParseGlob() with string literal arguments, in addition to the
existing embed.FS + ParseFS support.

Supported in all contexts: package-level calls (template.ParseFiles),
variable receiver calls (ts.ParseFiles), and chained calls
(template.New("x").ParseFiles).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
types.Eval required formatting AST back to source text and re-evaluating,
which was fragile for method values and package-level variables. Using
typesInfo.TypeOf directly resolves function signatures from the already-
computed type information, handling all expression forms correctly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add TemplateNames() to the Template interface to enumerate all defined
templates. After type-checking, compare referenced templates (from
ExecuteTemplate calls and {{template}} actions) against all available
templates and warn about unreferenced ones.

Templates with no content (e.g. the root container from template.New)
are excluded from the check.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When dot or a field is an interface type (e.g. any), field access like
.Title cannot be statically verified. Instead of producing a hard error,
emit a warning when -w is enabled and continue type-checking with the
field treated as an empty interface.

Adds WarningFunc to Global for template-level warnings, PackageWarningFunc
for package-level warnings, and bridges them through checkCalls.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a template accesses fields on a pointer type (e.g. *Page) without
a {{with}} or {{if}} guard, the access may panic at runtime if the
pointer is nil. With -w enabled, emit a warning suggesting a nil guard.

Adds dotGuarded tracking to scope: set to true inside {{with}} and
{{if}} blocks so that guarded pointer access does not trigger warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Verifies that .Inner.Value warns when Inner is *Inner, catching
pointer dereference at any depth in a field chain.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the coarse dotGuarded bool with a per-field-path guarded set.
{{with .Foo}} guards only .Foo (and dot inside the block), not unrelated
fields like .Bar. {{if .Bar}} guards .Bar within its block.

Guarding .Foo does NOT guard .Foo.Baz — if Baz is a pointer type,
accessing fields through it still warns unless .Foo.Baz is separately
guarded.

Adds pipeFieldPath helper to extract the tested field path from
{{with}}/{{if}} pipe expressions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add WarningCategory enum to PackageWarningFunc callback so library
consumers can filter warnings by type. CLI continues to use single
-w flag that enables all categories.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Document all four warning categories with Go code and template
examples showing what triggers each warning and how to fix it.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Change from "warning: pos: message" to "pos: warning - message"
to be consistent with Go tooling diagnostic conventions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Warnings: file:line:col: message (W00N)
Errors:   file:line:col: executing "name" at <.Field>: message (E001)

Remove the "type check failed:" prefix from errors and "warning -"
prefix from warnings to match go vet / staticcheck conventions.
Add diagnostic codes (W001-W004, E001) for machine-parseable output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use case-insensitive filepath comparison in check_test.go and
stdlib_test.go to handle Windows drive letter casing differences
between runtime.Caller and packages.Load. Also update
convertTextExecError to match the new diagnostic format with
category codes introduced in 22bf787.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add ResolveStringExpr to statically resolve template names from named
constants, simple variable assignments, and function parameters by
tracing through the intra-package and cross-package call graph.

Tier 1: Named constants (const name = "page")
Tier 2: Simple variables (name := "page"), with reassignment detection
Tier 3: Function parameter tracing via call-site index
Tier 4: Interface data type resolution (any → concrete type at call site)
Tier 5: Multi-level call chains with fixed-point iteration (up to 5 levels)
Tier 6: Cross-package tracing via PackageWithDeferred/DeferredCall API

This replaces the single BasicLiteralString check in findExecuteCalls
with a progressive resolution pipeline that handles the most common
patterns for wrapping ExecuteTemplate in helper functions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ection

- checkPrintf in func.go validates format verb count and types (W-codes
  emitted via existing error path; arg-count mismatch and type mismatch
  both reported as hard errors consistent with other type checks)
- package.go now recognises tpl.Execute(w, data) 2-arg calls and
  resolves the template name from the receiver's root template
- check.go tracks variable declarations vs uses per scope and emits
  W005 (WarnUnusedVariable) for $x declared but never read
- Script tests added for all three features (pass/err/warn variants)
- PLAN.md added documenting the full 7-feature roadmap

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add WarnDeadBranch warning emitted when an {{if}} or {{with}} block
uses a literal true or false BoolNode as its condition, making one
branch statically unreachable. The nil literal case is excluded because
the template parser itself rejects {{if nil}} before analysis runs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Track every (templateName, dataType) pair seen at {{template "name" .Data}}
call sites during Execute. After the walk, emit WarnInconsistentTemplateTypes
(W007) if the same sub-template is invoked with mutually non-assignable
types across different call sites. Untyped nil and empty interface are
excluded from the check as they carry no structural information.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
analyzer/analyzer.go wraps check.Package as a go/analysis.Analyzer.
It constructs a packages.Package directly from the analysis.Pass fields
(reusing the already type-checked AST) and expands //go:embed directives
from AST comments to populate EmbedFiles without a second packages.Load.
Diagnostics are reported at the ExecuteTemplate call site in Go source
so gopls shows squiggles in .go files. The -w flag enables warnings.

cmd/templatecheck/main.go is a singlechecker binary so the analyzer can
be used as: go vet -vettool=$(which templatecheck) ./...

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
vscode-go-template-check/ is a VS Code extension that spawns
check-templates on save and places error/warning squiggles inside
.gohtml template files at the exact positions the tool reports.

Key design points:
- No LSP: lightweight CLI-spawn on save, stderr parsed into
  DiagnosticCollection entries per template file URI
- New language ID `gohtml` for .gohtml files only — no conflict
  with vscode-go's `gotmpl` registration
- TextMate grammar (text.html.gotemplate) embeds text.html.basic
  and injects Go template keyword/variable/field highlighting
- Binary distribution follows gopls pattern: check on activation,
  prompt user to `go install` if not found on PATH
- Build requires only `go install github.com/evanw/esbuild/cmd/esbuild@latest`;
  npm is only needed for `npx @vscode/vsce package`
- esbuild listed as devDependency so vscode:prepublish works via vsce

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Detect go vet vettool probe flags (-V, -flags, -json) and delegate to
singlechecker.Main, eliminating the separate cmd/templatecheck binary.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TestFixtures discovers subdirs under testdata/fixtures/, reads fixture.json
for flags/expectations, and runs check-templates against actual Go and
.gotmpl files. Covers pass, error, and warning cases.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The go/analysis path only sees what go vet provides — no EmbedFiles,
no cross-package deferred resolution — so it gives incomplete coverage
and a false sense of security. The standalone binary is the right entry
point. The analyzer package is kept for gopls/editor integration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… Windows

filepath.Match("templates/*.gotmpl", "templates\page.gotmpl") returns false
on Windows because the embedded file path uses backslashes while the pattern
from the Go source literal uses forward slashes. Apply filepath.FromSlash to
the pattern before matching, consistent with embeddedFilesMatchingTemplateNameList.

Verified with new fixtures: cross_pkg_subdir_pass and cross_pkg_subdir_err,
both using templates in a pkg/templates/ subdirectory called from pkg/exec.go.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Builds check-templates for linux/darwin/windows (amd64+arm64) and
packages the .vsix, uploading both as GitHub release assets.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
tooolbox and others added 3 commits March 18, 2026 03:49
Add documentation for W005-W007 warnings, printf format validation,
Execute support, gopls analyzer, and VS Code extension. Remove PLAN.md
now that all planned features are implemented.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The go/analysis framework doesn't provide EmbedFiles or cross-package
deferred resolution, so the analyzer gives incomplete coverage compared
to the standalone CLI. The VS Code extension already calls the CLI
directly, making this package unused dead code.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Go's template parser produces errors like "template: name:line: message"
which the VS Code extension regex couldn't match. Rewrite them to
"filepath:line:1: message (E001)" so parse errors show up as squiggles.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant