fix(browse): chained IIFE + second statement no longer misclassified as one expression

isSingleParenOrIifeExpression accepted any tail after the initial group's
close as long as trailing chars looked chain-ish, so
`(async()=>{await 1})().then(x=>x); console.log('done')` classified as a
single expression and the expression wrapper emitted a SyntaxError. The
tail is now consumed as a strict member/call/index/optional-chain walk to
END of input via a shared string/escape-aware findBalancedClose scanner;
anything else (';', operators) demotes to the block wrapper. Negative +
positive tests added.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-09-01 16:07:22 +00:00
co-authored by Claude Fable 5
parent 159b48e813
commit ce3f3848a7
2 changed files with 74 additions and 46 deletions
+22
View File
@@ -6,6 +6,28 @@ import {
wrapForEvaluate,
} from '../src/read-commands';
// Regression: a chained IIFE followed by a SECOND statement was classified as
// a single expression, and the expression wrapper emitted a SyntaxError. The
// tail after the initial group must be a continuous member/call/index chain
// to END of input.
describe('chained IIFE followed by a second statement', () => {
it('is NOT a single expression and wraps to syntactically valid code', () => {
const code = "(async()=>{await 1})().then(x=>x); console.log('done')";
expect(isSingleParenOrIifeExpression(code)).toBe(false);
const wrapped = wrapForEvaluate(code);
expect(() => new Function('return ' + wrapped)).not.toThrow();
});
it('still accepts a pure chained IIFE (with optional chaining and index access)', () => {
expect(isSingleParenOrIifeExpression('(async()=>{await 1})().then(x=>x)')).toBe(true);
expect(isSingleParenOrIifeExpression('(getObj())?.items[0].run()')).toBe(true);
});
it('rejects an operator tail', () => {
expect(isSingleParenOrIifeExpression('(a)() + 1')).toBe(false);
});
});
describe('browse js / eval wrapping (#2727)', () => {
it('detects presence of await keyword', () => {
expect(hasAwait('await Promise.resolve(1)')).toBe(true);