Found by measurement while producing the blast-radius census for #13131.
The shape
parseParamNames in scripts/check-dispatcher-error-vocabulary.mjs cleans a parameter before reading its name:
const cleaned = raw.replace(/^\s*(?:readonly|public|private|protected|\.\.\.)\s+/g, '').trim();
const m = /^([A-Za-z_$][\w$]*)/.exec(cleaned.replace(/^\.\.\./, ''));
The g flag reads as "strip them all", but the pattern is anchored with ^ and carries no m flag, so it matches at position 0 once. TypeScript parameter properties routinely carry two modifiers (private readonly, public readonly, protected readonly), and the second one survives the strip and is then read as the parameter's NAME.
Measured, through the real exported function
"private readonly src: string, private readonly opts: ParseOptions" -> ["readonly","readonly"]
"public readonly code: string, msg: string" -> ["readonly","msg"]
"readonly code: string" -> ["code"] <- control
"private code: string" -> ["code"] <- control
"code: string, msg: string" -> ["code","msg"] <- control
The three single-modifier controls parse correctly, so this is the second modifier and nothing else.
Why it matters
parseParamNames is the structural half of the codehelper shape: helperCodesFor does parseParamNames(decl.params).indexOf(ident). A wrong name means indexOf returns -1, the identifier is not recognised as a parameter, and the whole helper is dropped — no site, no unresolved, nothing reported. Same silent-drop failure class as #9223 / #9460 / #10918 / #13131, one layer further in.
End to end, through the real deriveSites, with an unregistered code:
class E {
constructor(private readonly code: string, readonly message: string) {
(this as any).code = code;
}
}
throw new E('PARAM_PROP_CODE', 'x');
A constructor code helper written with a parameter property is invisible, and the new E(...) call-site scan helperCodesFor already implements would otherwise have read the literal straight out of it.
There is also a wrong-index hazard beyond the miss: in ["readonly","msg"] the real code parameter is gone but the arity is preserved, so if a caller's helper genuinely had a parameter named readonly the index would resolve to the wrong argument and report a value from another position. Not observed in this tree; noted because the failure would read as a normal finding rather than as a bug.
Live witness
packages/sdui-parser/src/parse.ts, class Parser's constructor:
constructor(private readonly src: string, private readonly opts: ParseOptions)
-> parseParamNames -> ["readonly","readonly"]
What a fix would have to decide
Mechanically the strip wants to loop (or the pattern wants (?:(?:readonly|public|private|protected)\s+)*), and ... needs to stay handled where it is. Not decided here:
- whether
--self-test should pin the parameter-property spelling as its own case (the published-shapes discipline in this file's header says a spelling that is reached for must be pinned in the same edit);
- whether accessor modifiers this repo does not yet use (
override, in/out on type params) belong in the same class.
Filed unassigned. Related: #13131 (the object-literal stamp position) and the class-method sibling finding — three distinct mechanisms, one failure class.
Found by measurement while producing the blast-radius census for #13131.
The shape
parseParamNamesinscripts/check-dispatcher-error-vocabulary.mjscleans a parameter before reading its name:The
gflag reads as "strip them all", but the pattern is anchored with^and carries nomflag, so it matches at position 0 once. TypeScript parameter properties routinely carry two modifiers (private readonly,public readonly,protected readonly), and the second one survives the strip and is then read as the parameter's NAME.Measured, through the real exported function
The three single-modifier controls parse correctly, so this is the second modifier and nothing else.
Why it matters
parseParamNamesis the structural half of thecodehelpershape:helperCodesFordoesparseParamNames(decl.params).indexOf(ident). A wrong name meansindexOfreturns -1, the identifier is not recognised as a parameter, and the whole helper is dropped — no site, no unresolved, nothing reported. Same silent-drop failure class as #9223 / #9460 / #10918 / #13131, one layer further in.End to end, through the real
deriveSites, with an unregistered code:A constructor code helper written with a parameter property is invisible, and the
new E(...)call-site scanhelperCodesForalready implements would otherwise have read the literal straight out of it.There is also a wrong-index hazard beyond the miss: in
["readonly","msg"]the realcodeparameter is gone but the arity is preserved, so if a caller's helper genuinely had a parameter namedreadonlythe index would resolve to the wrong argument and report a value from another position. Not observed in this tree; noted because the failure would read as a normal finding rather than as a bug.Live witness
packages/sdui-parser/src/parse.ts,class Parser's constructor:What a fix would have to decide
Mechanically the strip wants to loop (or the pattern wants
(?:(?:readonly|public|private|protected)\s+)*), and...needs to stay handled where it is. Not decided here:--self-testshould pin the parameter-property spelling as its own case (the published-shapes discipline in this file's header says a spelling that is reached for must be pinned in the same edit);override,in/outon type params) belong in the same class.Filed unassigned. Related: #13131 (the object-literal stamp position) and the class-method sibling finding — three distinct mechanisms, one failure class.