M2 follow-ups: related spans, stability registry, corpus coverage (#302, #304, #305)#307
Conversation
Follow-up work on the M2 compiler/language-contract issues, after closing #301 (folded into #306) and #303 (false positive: the LSP single-document check can never surface a cross-file related location). - #302: extend related "first declared here" spans to duplicate_page_id, duplicate_layout_id, duplicate_page_store, duplicate_component_name, duplicate_component_emit, and both duplicate_gowdk_use_alias sites. Left duplicate_route_param (no per-segment span) and duplicate_css_selection (spanForName returns the first match) nil per the issue's hedge. - #305: add a code-level construct stability registry (lang.ConstructStabilities) covering blocks, metadata keywords, g: directives, and endpoint forms, with tests asserting the registry covers the code construct-sets and that docs/language/stability.md matches the registry. - #304: expand the conformance corpus with build-data, planned-directive, foreign-template, and legacy-metadata cases, and add a coverage test that fails when a parser-surfaced rejection contract loses its reject case. Markup rejections currently surface as parse_error (tracked); their cases pin that and ratchet to specific codes later. Tests: go test ./internal/... ./cmd/... . all pass; gofmt/vet clean; node editor tests pass (39).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7df9007f58
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| {Name: "g:slot", Kind: ConstructDirective, Tier: TierPartial}, | ||
|
|
||
| // Planned g: directives, rejected on use. |
There was a problem hiding this comment.
Include directive families in stability registry
Because this registry is introduced as the code-level source of truth for g: directive stability, omitting the accepted directive families makes consumers classify valid syntax as unknown. internal/view/directives.go accepts any g:on:* and the four g:message:* rules, and docs/language/stability.md documents both as Partial, but ConstructStabilities() ends the supported directive list without entries for those families; the guard test also won't catch this because SupportedDirectiveNames() explicitly excludes them. Add family-level entries such as g:on:* and g:message:* or derive/check those families alongside the exact-name directives.
Useful? React with 👍 / 👎.
| {Name: "g:document", Kind: ConstructDirective, Tier: TierPlanned, DiagnosticCode: "unsupported_markup_directive"}, | ||
| {Name: "g:body", Kind: ConstructDirective, Tier: TierPlanned, DiagnosticCode: "unsupported_markup_directive"}, | ||
| {Name: "g:head", Kind: ConstructDirective, Tier: TierPlanned, DiagnosticCode: "unsupported_markup_directive"}, | ||
| {Name: "g:await", Kind: ConstructDirective, Tier: TierPlanned, DiagnosticCode: "unsupported_markup_directive"}, |
There was a problem hiding this comment.
Register the diagnostic code actually emitted
For planned g: directives that are checked through lang.CheckSource, this DiagnosticCode does not match the diagnostic users receive: the new planned_directive.gwdk conformance case still expects parse_error, and internal/lang/tools.go falls back to parse_error unless the parser returns a typed diagnostic. Tooling that consumes ConstructStabilities() to know which rejection code to expect will therefore look for unsupported_markup_directive and miss the actual diagnostic until markup errors carry typed codes; either leave this unset/use parse_error for now, or wire the specific parser diagnostic before advertising it here.
Useful? React with 👍 / 👎.
| if !strings.Contains(doc, construct.Name) { | ||
| t.Errorf("construct %q (%s) is missing from %s", construct.Name, construct.Kind, path) |
There was a problem hiding this comment.
Match stability entries exactly
This check uses a raw substring search, so it does not reliably prove the stability table contains a construct row. Short construct names like go, act, and api can be removed from their table rows while the test still passes because those strings appear in unrelated prose or inside other words such as diagnostics and contract, which defeats the drift guard this commit adds. Match the documented backticked construct/table cell instead of any substring.
Useful? React with 👍 / 👎.
- Include the accepted g: directive families g:on:* and g:message:* in
lang.ConstructStabilities(); SupportedDirectiveNames() excludes them, so the
registry would otherwise classify valid syntax as unknown. A test guards them
against removal.
- Stop advertising unsupported_markup_directive for planned g: directives: that
code is registered but never emitted, so users actually receive parse_error
(proven by the conformance corpus). DiagnosticCode is left unset for planned
directives, and the table documents the parse_error reality.
- Match the stability table by exact backticked tokens instead of raw substring,
so short construct names (go, act, api) can no longer pass by appearing inside
unrelated prose; registry block names align to their backticked `{}` forms and
the endpoint table lists bare `act`/`api`/`fragment`.
Tests: go test ./internal/... ./cmd/... . all pass; gofmt/vet clean.
Follow-up to #300. After reviewing the six follow-up issues I'd filed, I closed two and implemented the three that hold up.
Issue triage first
CheckSourceon a single document, which builds a single-page program — so a cross-file related location can't occur, and the currentdoc.URImapping is already correct for every reachable case.Implemented
#302 — Related spans on more duplicate diagnostics
Extended the "first declared here" related location (added in #300) to
duplicate_page_id,duplicate_layout_id,duplicate_page_store,duplicate_component_name,duplicate_component_emit, and bothduplicate_gowdk_use_aliassites. Leftduplicate_route_param(no per-segment span) andduplicate_css_selection(spanForNamereturns the first match)nil, per the issue's "leave nil when unavailable" hedge.#305 — Code-level construct stability registry
Added
lang.ConstructStabilities()— a code registry of stability tiers for blocks, metadata keywords,g:directives, and endpoint forms (mirroring the diagnostics registry'sStability). Two guard tests: one asserts the registry covers every keyword/directive in code, the other assertsdocs/language/stability.mdmatches the registry. Neither the table nor the registry can drift now.#304 — Conformance corpus coverage
Added build-data, planned-directive, foreign-template, and legacy-metadata cases, plus
TestConformanceCorpusCoversRejectionContractswhich fails when a parser-surfaced rejection contract loses its reject case. Honest finding documented: markup rejections (g:await,{#if}) currently surface as genericparse_error, not their specific codes — those cases pinparse_errorand will ratchet to the specific code when markup diagnostics are wired (tracked with #250).Not in this PR
#306 (full tokenizer + recursive-descent parser rewrite) is the keystone but it's a genuine multi-phase effort — shared tokenizer, recovery, AST-equivalence gating, per-declaration cutover. Cramming it in here would be reckless; it stays open as the dedicated next effort, with #301's offset work folded in.
Verification
go test ./internal/... ./cmd/... .— all passgofmt -l+go vet— cleannode --test editors/vscode/*.test.js— 39 pass / 0 fail🤖 Generated with Claude Code