Skip to content

[finding] codehelper is blind to a code helper declared as a CLASS METHOD — enclosingDeclaration has no header form for one #13226

Description

@os-elon

Found by measurement while producing the blast-radius census for #13131. Recorded rather than fixed there: that card is fenced to the object-literal stamp POSITION, and this is a different mechanism — the DECLARATION FORM — so not a rider.

The shape

codehelper is structural: it fires only when the assigned identifier is a parameter of the enclosing declaration, resolved by enclosingDeclaration in scripts/check-dispatcher-error-vocabulary.mjs. That function's DECL_HEADER_RE recognises exactly three header forms:

function NAME( constructor( const|let|var NAME = (

There is no form for a class method. So a code-carrying helper that happens to be a method is unreachable even in the assignment position codehelper already implements — the identifier is never recognised as a parameter, helperCodesFor returns null, and the site falls through to resolveConstant, which reduces nothing. No site, no unresolved, nothing reported.

Measured, with a same-genre positive control

Same body, same unregistered probe code, one declaration form apart, through the real deriveSites:

#declaration formbodyvocabulary gatecasing gate
K (control)function fail(code, message)e.code = code1 site, shape codehelper0
Iprivate fail(code, message) in a classe.code = code0, and 0 unresolved0
Jprivate fail(code, message) in a class{ severity, code, message }0, and 0 unresolved0

K is the control that proves the instrument fires. I differs from K only in being a method.

Why it matters

It is the #9223 / #9460 / #10918 / #13131 genre once more: a shape the gate's prose claims to cover, implemented for one syntactic form and silently absent in a sibling form. The declared bound — a value the gate cannot reduce is REPORTED, never dropped — cannot hold here, because nothing is recognised in the first place.

It also blocks #13131 from delivering its stated value. That card's headline live instance, Parser#error(code, message, start?, tag?) in packages/sdui-parser/src/parse.ts, is a class method AND stamps through an object literal. Widening the position alone would still not reach it. Measured directly: enclosingDeclaration at that stamp offset answers with the Parser CONSTRUCTOR, whose parsed parameter names are ["readonly","readonly"] — see the sibling finding on parseParamNames.

Blast radius, measured on packages/** non-test source

Of the 13 helpers matching #13131's object-literal predicate, 4 are invisible to enclosingDeclaration:

4 helpers - 25 in-file call sites - 0 new verdict rows - 3 undischargeable `unresolved` findings

The 3 unresolved are the expensive half (an unresolved entry is pushed unconditionally and no declaration row discharges it):

  • packages/sdui-parser/src/parse.tsParser#error (16 call sites, all kebab literals the gate's grammar refuses to reduce)
  • packages/runtime/src/http-dispatcher.tsHttpDispatcher#error (7 call sites)
  • packages/plugins/plugin-auth/src/auth-manager.tsAuthManager#deliverPhoneOtp (2 call sites)

0 SCREAMING_SNAKE unregistered codes are hiding behind it today, so the value is preventing a future defect.

What a fix would have to decide

Not decided here:

  • whether DECL_HEADER_RE should gain a class-method form, which is harder than the other three because a method header is just NAME( — the discriminator is being directly inside a class body, and a textual scan has to earn that;
  • whether the anonymous-arrow form ((code) => ({ code })) is in or out: it has no name for a call scan to anchor on, so it can produce neither a site nor an unresolved, and 1 of the 13 helpers is one (packages/spec/src/system/i18n-resolver.ts).

The blindness itself is now recorded and pinned in OBJECT_LITERAL_CODE_HELPER_BLINDNESS in scripts/check-dispatcher-error-vocabulary.mjs, with the class-method half pinned on SITES rather than on matches (the recognizer does match here; the structural resolver is what drops it).

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions