stop sets instead of rewinds: the parser ends the list at as or the comma, the promise at then or catch, a value at />

goodbye-acorn
Nic 2 days ago
parent 9bc9689083
commit 76c64bd0ce

@ -104,15 +104,15 @@ function read(parser, run) {
/** /**
* @param {Parser} parser * @param {Parser} parser
* @param {'as'} [until] the host's `as` follows the expression, as an each block's item does * @param {string[]} [stop_at] the template's own tokens after the expression, which end it
* @param {string} [opening_token] the bracket the expression sits in, for loose mode * @param {string} [opening_token] the bracket the expression sits in, for loose mode
* @returns {Expression} * @returns {Expression}
*/ */
export function read_expression(parser, until, opening_token = '{') { export function read_expression(parser, stop_at, opening_token = '{') {
const start = parser.index; const start = parser.index;
try { try {
const answer = read(parser, (js) => js.parseExpressionAt(start, until)); const answer = read(parser, (js) => js.parseExpressionAt(start, stop_at));
keep_tables(answer.node, tables(answer)); keep_tables(answer.node, tables(answer));
return answer.node; return answer.node;
} catch (err) { } catch (err) {

@ -817,19 +817,20 @@ function read_attribute_value(parser) {
if (quote_mark) return parser.match(quote_mark); if (quote_mark) return parser.match(quote_mark);
return !!parser.match_regex(regex_invalid_unquoted_attribute_value); return !!parser.match_regex(regex_invalid_unquoted_attribute_value);
}, },
'in attribute value' 'in attribute value',
// `<Component test={ />` is an unclosed value, not a regex
['/>']
); );
} catch (/** @type {any} */ error) { } catch (/** @type {any} */ error) {
if (error.code === 'js_parse_error') {
// if the attribute value didn't close + self-closing tag
// eg: `<Component test={{a:1} />`
// acorn may throw a `Unterminated regular expression` because of `/>`
const pos = error.position?.[0]; const pos = error.position?.[0];
if (pos !== undefined && parser.template.slice(pos - 1, pos + 1) === '/>') { if (
error.code === 'js_parse_error' &&
pos !== undefined &&
parser.template.startsWith('/>', pos)
) {
parser.index = pos; parser.index = pos;
e.expected_token(pos, quote_mark || '}'); e.expected_token(pos, quote_mark || '}');
} }
}
throw error; throw error;
} }
@ -850,9 +851,10 @@ function read_attribute_value(parser) {
* @param {Parser} parser * @param {Parser} parser
* @param {() => boolean} done * @param {() => boolean} done
* @param {string} location * @param {string} location
* @param {string[]} [stop_at] the template's tokens that end an expression, see `read_expression`
* @returns {any[]} * @returns {any[]}
*/ */
function read_sequence(parser, done, location) { function read_sequence(parser, done, location, stop_at) {
/** @type {Array<AST.Text | AST.ExpressionTag>} */ /** @type {Array<AST.Text | AST.ExpressionTag>} */
const chunks = []; const chunks = [];
let chunk_start = parser.index; let chunk_start = parser.index;
@ -895,7 +897,7 @@ function read_sequence(parser, done, location) {
flush(parser.index - 1); flush(parser.index - 1);
parser.allow_whitespace(); parser.allow_whitespace();
const expression = read_expression(parser); const expression = read_expression(parser, stop_at);
parser.allow_whitespace(); parser.allow_whitespace();
parser.eat('}', true); parser.eat('}', true);

@ -178,16 +178,11 @@ function open(parser) {
if (parser.eat('each')) { if (parser.eat('each')) {
parser.require_whitespace(); parser.require_whitespace();
// the list ends at the `as` that names the item, so a TypeScript assertion in it needs parens // the list ends at the `as` naming the item or the `,` before the index, so an assertion in it needs parens
let expression = read_expression(parser, 'as'); const expression = read_expression(parser, ['as', ',']);
parser.allow_whitespace(); parser.allow_whitespace();
// {#each} blocks must declare a context {#each list as item}
if (!parser.match('as') && expression.type === 'SequenceExpression') {
expression = expression.expressions[0];
}
/** @type {Pattern | null} */ /** @type {Pattern | null} */
let context = null; let context = null;
let index; let index;
@ -197,11 +192,6 @@ function open(parser) {
parser.require_whitespace(); parser.require_whitespace();
context = read_pattern(parser); context = read_pattern(parser);
} else {
// {#each Array.from({ length: 10 }), i} is read as a sequence expression,
// which is set back above - we now gotta reset the index as a consequence
// to properly read the , i part
parser.index = /** @type {number} */ (expression.end);
} }
parser.allow_whitespace(); parser.allow_whitespace();
@ -225,24 +215,7 @@ function open(parser) {
parser.allow_whitespace(); parser.allow_whitespace();
} }
const matches = parser.eat('}', true, false); parser.eat('}', true, false);
if (!matches) {
// Parser may have read the `as` as part of the expression (e.g. in `{#each foo. as x}`)
if (parser.template.slice(parser.index - 4, parser.index) === ' as ') {
const prev_index = parser.index;
context = read_pattern(parser);
parser.eat('}', true);
expression = {
type: 'Identifier',
name: '',
start: expression.start,
end: prev_index - 4
};
} else {
parser.eat('}', true); // rerun to produce the parser error
}
}
/** @type {AST.EachBlock} */ /** @type {AST.EachBlock} */
const block = parser.append({ const block = parser.append({
@ -265,7 +238,7 @@ function open(parser) {
if (parser.eat('await')) { if (parser.eat('await')) {
parser.require_whitespace(); parser.require_whitespace();
const expression = read_expression(parser); const expression = read_expression(parser, ['then', 'catch']);
parser.allow_whitespace(); parser.allow_whitespace();
/** @type {AST.AwaitBlock} */ /** @type {AST.AwaitBlock} */
@ -311,38 +284,7 @@ function open(parser) {
parser.fragments.push(block.pending); parser.fragments.push(block.pending);
} }
const matches = parser.eat('}', true, false); parser.eat('}', true, false);
// Parser may have read the `then/catch` as part of the expression (e.g. in `{#await foo. then x}`)
if (!matches) {
if (parser.template.slice(parser.index - 6, parser.index) === ' then ') {
const prev_index = parser.index;
block.value = read_pattern(parser);
parser.eat('}', true);
block.expression = {
type: 'Identifier',
name: '',
start: expression.start,
end: prev_index - 6
};
block.then = block.pending;
block.pending = null;
} else if (parser.template.slice(parser.index - 7, parser.index) === ' catch ') {
const prev_index = parser.index;
block.error = read_pattern(parser);
parser.eat('}', true);
block.expression = {
type: 'Identifier',
name: '',
start: expression.start,
end: prev_index - 7
};
block.catch = block.pending;
block.pending = null;
} else {
parser.eat('}', true); // rerun to produce the parser error
}
}
parser.stack.push(block); parser.stack.push(block);

@ -4,6 +4,6 @@ export default test({
error: { error: {
code: 'expected_token', code: 'expected_token',
message: 'Expected token }', message: 'Expected token }',
position: [19, 19] position: [18, 18]
} }
}); });

@ -405,29 +405,12 @@
"start": 219, "start": 219,
"end": 246, "end": 246,
"children": [], "children": [],
"context": { "context": null,
"type": "Identifier",
"name": "item",
"start": 234,
"end": 238,
"loc": {
"start": {
"line": 17,
"column": 15,
"character": 234
},
"end": {
"line": 17,
"column": 19,
"character": 238
}
}
},
"expression": { "expression": {
"type": "Identifier", "type": "Identifier",
"name": "",
"start": 226, "start": 226,
"end": 230 "end": 238,
"name": ""
} }
}, },
{ {
@ -484,42 +467,25 @@
"end": 295, "end": 295,
"expression": { "expression": {
"type": "Identifier", "type": "Identifier",
"name": "",
"start": 277, "start": 277,
"end": 279
},
"value": {
"type": "Identifier",
"name": "y",
"start": 285,
"end": 286, "end": 286,
"loc": { "name": ""
"start": {
"line": 21,
"column": 16,
"character": 285
},
"end": {
"line": 21,
"column": 17,
"character": 286
}
}
}, },
"value": null,
"error": null, "error": null,
"pending": { "pending": {
"type": "PendingBlock", "type": "PendingBlock",
"start": null, "start": 287,
"end": null, "end": 287,
"children": [], "children": [],
"skip": true "skip": false
}, },
"then": { "then": {
"type": "ThenBlock", "type": "ThenBlock",
"start": 287, "start": null,
"end": 267, "end": null,
"children": [], "children": [],
"skip": false "skip": true
}, },
"catch": { "catch": {
"type": "CatchBlock", "type": "CatchBlock",
@ -542,35 +508,18 @@
"end": 324, "end": 324,
"expression": { "expression": {
"type": "Identifier", "type": "Identifier",
"name": "",
"start": 305, "start": 305,
"end": 307
},
"value": null,
"error": {
"type": "Identifier",
"name": "y",
"start": 314,
"end": 315, "end": 315,
"loc": { "name": ""
"start": {
"line": 23,
"column": 17,
"character": 314
},
"end": {
"line": 23,
"column": 18,
"character": 315
}
}
}, },
"value": null,
"error": null,
"pending": { "pending": {
"type": "PendingBlock", "type": "PendingBlock",
"start": null, "start": 316,
"end": null, "end": 316,
"children": [], "children": [],
"skip": true "skip": false
}, },
"then": { "then": {
"type": "ThenBlock", "type": "ThenBlock",
@ -581,10 +530,10 @@
}, },
"catch": { "catch": {
"type": "CatchBlock", "type": "CatchBlock",
"start": 316, "start": null,
"end": 295, "end": null,
"children": [], "children": [],
"skip": false "skip": true
} }
} }
] ]

@ -497,32 +497,15 @@
"end": 246, "end": 246,
"expression": { "expression": {
"type": "Identifier", "type": "Identifier",
"name": "",
"start": 226, "start": 226,
"end": 230 "end": 238,
"name": ""
}, },
"body": { "body": {
"type": "Fragment", "type": "Fragment",
"nodes": [] "nodes": []
}, },
"context": { "context": null
"type": "Identifier",
"name": "item",
"start": 234,
"end": 238,
"loc": {
"start": {
"line": 17,
"column": 15,
"character": 234
},
"end": {
"line": 17,
"column": 19,
"character": 238
}
}
}
}, },
{ {
"type": "Text", "type": "Text",
@ -563,34 +546,17 @@
"end": 295, "end": 295,
"expression": { "expression": {
"type": "Identifier", "type": "Identifier",
"name": "",
"start": 277, "start": 277,
"end": 279
},
"value": {
"type": "Identifier",
"name": "y",
"start": 285,
"end": 286, "end": 286,
"loc": { "name": ""
"start": {
"line": 21,
"column": 16,
"character": 285
},
"end": {
"line": 21,
"column": 17,
"character": 286
}
}
}, },
"value": null,
"error": null, "error": null,
"pending": null, "pending": {
"then": {
"type": "Fragment", "type": "Fragment",
"nodes": [] "nodes": []
}, },
"then": null,
"catch": null "catch": null
}, },
{ {
@ -606,37 +572,21 @@
"end": 324, "end": 324,
"expression": { "expression": {
"type": "Identifier", "type": "Identifier",
"name": "",
"start": 305, "start": 305,
"end": 307
},
"value": null,
"error": {
"type": "Identifier",
"name": "y",
"start": 314,
"end": 315, "end": 315,
"loc": { "name": ""
"start": {
"line": 23,
"column": 17,
"character": 314
},
"end": {
"line": 23,
"column": 18,
"character": 315
}
}
}, },
"pending": null, "value": null,
"then": null, "error": null,
"catch": { "pending": {
"type": "Fragment", "type": "Fragment",
"nodes": [] "nodes": []
} },
"then": null,
"catch": null
} }
] ]
}, },
"options": null "options": null,
"comments": []
} }
Loading…
Cancel
Save