The generated TypeScript parser of a key form names its own parameter and locals apart from the form's parts

A key form such as <node>.<module>.<key> made the parser declare the parameter key and the part key together (TS2300 Duplicate identifier), which the per-package unit level now surfaces. The parameter and the locals are now _key, _rest and _at<n>; a test row with such a form fails on the old output. Issue 1966.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-05 21:24:07 +01:00
parent 625d627794
commit 2dc9e07e88
3 changed files with 10 additions and 6 deletions
@@ -232,24 +232,24 @@ func tsStates(b *bytes.Buffer, states []seatdef.State) error {
}
fmt.Fprintf(b, "/** The key of one value of the state %s under the form %s. */\nexport function %sKey(%s): string {\n return %s;\n}\n\n",
s.Name, strconv.Quote(kf.Key), fn, strings.Join(args, ", "), strings.Join(expr, " + "))
fmt.Fprintf(b, "/** The parts of a key of the form %s, or undefined for a key of another form. */\nexport function parse%sKey(key: string): { %s } | undefined {\n let rest = key;\n",
fmt.Fprintf(b, "/** The parts of a key of the form %s, or undefined for a key of another form. */\nexport function parse%sKey(_key: string): { %s } | undefined {\n let _rest = _key;\n",
strconv.Quote(kf.Key), ident(fn), strings.Join(fields, "; "))
for i := 0; i < len(ts); {
t := ts[i]
switch {
case !t.part:
fmt.Fprintf(b, " if (!rest.startsWith(%s)) return undefined;\n rest = rest.slice(%d);\n", strconv.Quote(t.text), len(t.text))
fmt.Fprintf(b, " if (!_rest.startsWith(%s)) return undefined;\n _rest = _rest.slice(%d);\n", strconv.Quote(t.text), len(t.text))
i++
case i+1 < len(ts):
fmt.Fprintf(b, " const at%d = rest.indexOf(%s);\n if (at%d <= 0) return undefined;\n const %s = rest.slice(0, at%d);\n rest = rest.slice(at%d + %d);\n",
fmt.Fprintf(b, " const _at%d = _rest.indexOf(%s);\n if (_at%d <= 0) return undefined;\n const %s = _rest.slice(0, _at%d);\n _rest = _rest.slice(_at%d + %d);\n",
i, strconv.Quote(ts[i+1].text), i, paramName(t.text), i, i, len(ts[i+1].text))
i += 2
default:
fmt.Fprintf(b, " if (rest === \"\") return undefined;\n const %s = rest;\n rest = \"\";\n", paramName(t.text))
fmt.Fprintf(b, " if (_rest === \"\") return undefined;\n const %s = _rest;\n _rest = \"\";\n", paramName(t.text))
i++
}
}
fmt.Fprintf(b, " if (rest !== \"\") return undefined;\n return { %s };\n}\n\n", strings.Join(got, ", "))
fmt.Fprintf(b, " if (_rest !== \"\") return undefined;\n return { %s };\n}\n\n", strings.Join(got, ", "))
}
}
return nil
@@ -43,7 +43,10 @@ func TestAStateHasItsNamesKeyFormsAndStrictValues(t *testing.T) {
}
ts := string(files["ts/seats/aregistry/src/index.ts"])
for _, want := range []string{"export const states = {", "export function asksByAskerIDKey(asker: string, id: string): string",
"export function parseAsksByAskerIDKey(key: string)", "export interface AsksByAskerIDValue"} {
"export function parseAsksByAskerIDKey(_key: string)", "export interface AsksByAskerIDValue",
// a key form with a part named key: the parser's own parameter and locals are named apart from the parts
"export function parseAsksByNodeModuleKeyKey(_key: string): { node: string; module: string; key: string } | undefined",
"const key = _rest;"} {
if !strings.Contains(ts, want) {
t.Errorf("the seat's npm package lacks %q:\n%s", want, ts)
}
@@ -6,6 +6,7 @@
"key-forms":[
{"key":"<asker>.<id>","value":{"type":"object","properties":{"state":{"type":"string"},"since":{"type":"integer"}},"required":["state"]}},
{"key":"<asker>.<id>.done","value":{"type":"object","properties":{"outcome":{"type":"string"}},"required":["outcome"]}},
{"key":"<node>.<module>.<key>","value":{"type":"object","description":"a part named key beside the parser's own parameter"}},
{"key":"defaults","value":{"type":"object","description":"free"}}]},
{"name":"local-name","template":"events","max-age-days":3,
"key-forms":[{"key":"seen.<id-of-ask>","value":{"type":"string"}}]}]}