ReviewOS

stacks/stx

A binding expression that references an out-of-scope identifier renders silently, with no diagnostic

#2132
Closed glennmichael123 opened this 5 days ago · {{ comments.length }} {{ comments.length === 1 ? 'comment' : 'comments' }}
5 days ago

When an identifier in a binding expression is not in the setup scope, it evaluates as undefined and the element renders accordingly, with nothing to say so. There is no console warning, nothing in the compiled output, and the source reads as correct.

The two shapes it takes are opposites, which is what makes it expensive:

  • :if="x" renders never
  • :if="!x" renders always

Both look like a condition that is simply false or true. Neither looks like a missing name.

Where it cost us

A screen in an app of ours (stacksjs/hq.training#344) wraps its native setup in a function and hands the template what it may read through a return { … } plus the matching destructuring. A const was added to the setup, used in an :if, and left out of both lists. The compiled binding was intact:

{id:197,bindings:[{name:":if",value:"!appGym || appAttribution",kind:"if"}, …]}

so every artifact agreed the template was right, while the name resolved to nothing. The element was a legally required attribution line in a white-labelled build, and the only reason anything failed was an unrelated test asserting that text.

Finding it took a long bisect, and along the way the silence actively produced false leads: substituting literals into the condition, and a debug element whose own expression failed to evaluate — rendering empty rather than erroring — which read as "the condition is the problem" rather than "this name does not exist".

What would have made it a minute's work

A compile-time or first-evaluation warning naming the identifier, the expression and the file, in the register stx already uses:

[STX Native] unknown-binding: n197: appAttribution is not in the setup scope (:if="!appGym || appAttribution")

The compiler already knows both halves: extractExports produces the scope's names, and each binding's expression is retained verbatim in the manifest. A scan of the identifiers in a binding expression against that set — minus globals and locals introduced by the expression itself, such as a :for item — would catch this class before it ships.

unknown-style and unknown-class already warn for the same kind of mistake one layer down, so an unresolvable identifier warning would be consistent with how stx reports a binding it cannot make sense of.

Not a scope-extraction bug

Worth stating, since I first reported it as one. A const referenced only by an :if resolves correctly; I have a minimal repro confirming the node renders. The scope extraction was right. It is only the diagnostic that is missing.

5 days ago

Implemented on main in 5f2a80d, 7ed19f4, 5b7a053, 7fcc35a and e96ade2.

This uses the first-evaluation option requested here, not a heuristic compile-time identifier scan. A real unresolved ReferenceError now produces an STX Native unknown-binding warning naming the missing identifier, expression, screen source and node. Reports are deduplicated per node/expression. Existing binding fallback behavior is unchanged; one missing name or even a throwing diagnostic sink cannot stop healthy bindings.

Coverage includes bare/negated/compound conditions (including the omitted return/destructuring repro), if-chains, show, loop sources, attributes/interpolation, model, class/style and forwarded component props. Valid loop locals, destructured callback parameters, local declarations, globals, literals, declared undefined values and typeof probes do not produce missing-name warnings. Short-circuited names are diagnosed when execution first reaches them.

Maintained regressions: packages/stx/test/native/binding-diagnostics.test.ts and binding-diagnostics-fields.test.ts, both normal/minified bundles. Together with runtime parse checks: 11 passed, 0 failed. Removing the hook makes both original attribute regression variants fail. Typecheck, lint, formatting and release preflight pass. Full main CI is running: https://github.com/stacksjs/stx/actions/runs/40710365757

Version status: source fix after v0.2.736; NOT yet in a published npm release. No released version floor is claimed. Craft was untouched.

Sign in to comment on this issue.