fix(lsp): unify codegen/lsp dict-key validation, fix Children false-positive
Eliminates a class of LSP/codegen drift that surfaced as 'unknown prop
"Children"' errors in git-pm and silent failures across nested-component
auto-import suggestions. Single source of truth refactor across the four
parallel implementations of the same logic, plus regression coverage.
# Phase 1 — extract shared helpers in codegen
- codegen.SyntheticPropKey: classifies Children / __children / regular keys
- codegen.TemplateRendersChildren: replaces strings.Contains(body, '{{ .Children }}')
in compiler.go and shadow/workspace.go; also handles {{- .Children -}} variants
- codegen.ScanComponents + ComponentSchema + PropsByPath: canonical project
walker, replaces three parallel scans (compiler.gatherComponentSchemas,
shadow.scanComponents, lsp/server.discoverComponentsIn)
- codegen.DiscoverProjects: locates every gastro project under a root via
pages/ + components/ structural markers
# Phase 2 — LSP delegates to codegen for dict-key validation
- codegen.ValidateDictKeysFromAST is the new canonical validator (position-
rich, accepts both pre-transform wrap form and post-transform bare-call form)
- ValidateDictKeysOptions.EmitMissingProps lets the LSP emit missing-prop
warnings while gastro generate stays silent (preserves --strict CI behaviour)
- LSP DiagnoseComponentProps reduced to a thin wrapper that lifts codegen
diagnostics into LSP format
- Wrap-form fallback restored via tiny regex path that delegates synthetic-
key classification to codegen.SyntheticPropKey (parser rejects {{ wrap X }}
as a block, so AST path is unreachable for that form)
# Phase 3 — Children false-positive fix
- Children dict key now silently accepted on layouts (was the original
git-pm bug)
- __children deprecated-key hint mirrored from codegen into the LSP so
users see migration guidance in their editor without running gastro generate
- Two regression tests in completions_test.go
# Phase 4 — chained-field hover, definition, completions
- HoverTarget extended with Chain []string and ChainIdx int
- NodeAtCursor walks every segment of .A.B.C-style references; works around
Go parser quirk where FieldNode.Pos for chained fields points at the
second segment's leading dot, not the first
- Hover (hoverChainSegment), definition (chainedFieldDefinition with
injected probe lines), and completions (chainedFieldCompletions +
detectChainPrefix) all support arbitrary chain depth at top-level and
inside range/with
- Tests for the chain plumbing in walk_test.go and detectChainPrefix_test.go
# Phase 5 — auditshadow auto-discovery
- cmd/auditshadow now walks the supplied root for every gastro project
via codegen.DiscoverProjects, audits each independently, aggregates a
summary
- scripts/audit/_default points at the repo root: 'mise audit' covers
all 5 example projects + the scaffold template fixture in one pass
- 'auditshadow /path/to/git-pm' correctly resolves internal/web
# Phase 6 — drift detection
- internal/codegen/testdata/drift/ corpus seeded with three fixtures
(children-via-dict, deprecated-children, unknown-prop)
- drift_test.go runs every fixture through codegen.ValidateDictKeysFromAST
AND lsptemplate.Diagnose, normalises, and asserts equivalence; if anyone
reintroduces a parallel implementation the test fails on the next run
# Pre-existing issues handled (per AGENTS.md)
- Wrap-form prop validation: a Phase 2 regression I introduced by removing
the regex fallback; restored via the codegen-delegating fallback above
- Auto-import name mismatch for nested components: discoverComponentsIn
produced 'Button' for components/ui/button.gastro while codegen actually
generates Render.UiButton; going through codegen.ScanComponents aligns
the suggestion with the generated symbol. Discover_test.go updated to
encode the corrected naming.
Verified via:
- go test ./... -race (all packages green)
- mise audit (5 projects, 34 files, 0 diagnostics)
- mise verify-bootstrap (scaffold-build still works)
- auditshadow on git-pm (46 files, 0 diagnostics)