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 {'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
* @returns {Expression}
*/
export function read_expression(parser, until, opening_token = '{') {
export function read_expression(parser, stop_at, opening_token = '{') {
const start = parser.index;
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));
return answer.node;
} catch (err) {

@ -817,18 +817,19 @@ function read_attribute_value(parser) {
if (quote_mark) return parser.match(quote_mark);
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) {
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];
if (pos !== undefined && parser.template.slice(pos - 1, pos + 1) === '/>') {
parser.index = pos;
e.expected_token(pos, quote_mark || '}');
}
const pos = error.position?.[0];
if (
error.code === 'js_parse_error' &&
pos !== undefined &&
parser.template.startsWith('/>', pos)
) {
parser.index = pos;
e.expected_token(pos, quote_mark || '}');
}
throw error;
}
@ -850,9 +851,10 @@ function read_attribute_value(parser) {
* @param {Parser} parser
* @param {() => boolean} done
* @param {string} location
* @param {string[]} [stop_at] the template's tokens that end an expression, see `read_expression`
* @returns {any[]}
*/
function read_sequence(parser, done, location) {
function read_sequence(parser, done, location, stop_at) {
/** @type {Array<AST.Text | AST.ExpressionTag>} */
const chunks = [];
let chunk_start = parser.index;
@ -895,7 +897,7 @@ function read_sequence(parser, done, location) {
flush(parser.index - 1);
parser.allow_whitespace();
const expression = read_expression(parser);
const expression = read_expression(parser, stop_at);
parser.allow_whitespace();
parser.eat('}', true);

@ -178,16 +178,11 @@ function open(parser) {
if (parser.eat('each')) {
parser.require_whitespace();
// the list ends at the `as` that names the item, so a TypeScript assertion in it needs parens
let expression = read_expression(parser, 'as');
// the list ends at the `as` naming the item or the `,` before the index, so an assertion in it needs parens
const expression = read_expression(parser, ['as', ',']);
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} */
let context = null;
let index;
@ -197,11 +192,6 @@ function open(parser) {
parser.require_whitespace();
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();
@ -225,24 +215,7 @@ function open(parser) {
parser.allow_whitespace();
}
const matches = 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
}
}
parser.eat('}', true, false);
/** @type {AST.EachBlock} */
const block = parser.append({
@ -265,7 +238,7 @@ function open(parser) {
if (parser.eat('await')) {
parser.require_whitespace();
const expression = read_expression(parser);
const expression = read_expression(parser, ['then', 'catch']);
parser.allow_whitespace();
/** @type {AST.AwaitBlock} */
@ -311,38 +284,7 @@ function open(parser) {
parser.fragments.push(block.pending);
}
const matches = 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.eat('}', true, false);
parser.stack.push(block);

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

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

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