fix: mixed loop/outer expressions fall back; globals are neither free nor bound

cursor[bot]: an expression mixing loop bindings with outer free names
(fmt(r.label) where fmt lives in the route script) was left verbatim, so
the detached preview referenced an undeclared identifier and failed at
mount, past the compile gate, because globals make it legal to the
compiler. Such expressions now mark the analysis unsupported and the
session takes source-preview mode. A globals allowlist makes Math/JSON
and friends count as neither free nor bound, which also fixes a latent
bug where a pure-global expression minted a nonsense prop.

Won't-fix on the same pass: the live-setup.md filename cross-reference
matches the repo's established reference-link convention.

This work was produced with AI assistance (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-07-28 14:22:57 -07:00
co-authored by Claude Code
parent dc5420b64f
commit 24d69675e0
2 changed files with 72 additions and 6 deletions
+22
View File
@@ -236,3 +236,25 @@ describe('review regressions: directives', () => {
assert.equal(res.ok, true, res.reason);
});
});
describe('review regressions: mixed and global identifiers', () => {
it('falls back for expressions mixing loop bindings with outer names', () => {
const src = `<ul>{#each rows as r}<li>{fmt(r.label)}</li>{/each}</ul>`;
const res = analyzeSvelteMarkup(src, parse);
assert.equal(res.ok, false);
assert.match(res.reason, /mixing loop and outer identifiers/);
});
it('treats known globals as neither free nor bound', () => {
// Global + bound: stays verbatim, no prop, no fallback.
const okRes = analyzeSvelteMarkup(`<ul>{#each rows as r}<li>{Math.round(r.score)}</li>{/each}</ul>`, parse);
assert.equal(okRes.ok, true, okRes.reason);
assert.equal(okRes.contract.some((c) => c.prop === 'round'), false);
assert.match(okRes.markupWithProps, /\{Math\.round\(r\.score\)\}/);
// Pure-global expression at top level: no prop minted either.
const topRes = analyzeSvelteMarkup(`<p>{JSON.stringify(navigator.language)}</p>`, parse);
assert.equal(topRes.ok, true, topRes.reason);
assert.equal(topRes.contract.length, 0);
});
});