diff --git a/packages/svelte/src/compiler/phases/1-parse/js.js b/packages/svelte/src/compiler/phases/1-parse/js.js index 89864328b7..2e34814dd0 100644 --- a/packages/svelte/src/compiler/phases/1-parse/js.js +++ b/packages/svelte/src/compiler/phases/1-parse/js.js @@ -3,6 +3,7 @@ /** @import { Parser } from './index.js' */ import * as teasel from '@teasel/parser'; import * as e from '../../errors.js'; +import { find_matching_bracket } from './utils/bracket.js'; /** * A standalone module, as `analyze_module` reads one. @@ -63,88 +64,113 @@ export function parse_script(parser, start, end) { } /** + * Reads JavaScript at the cursor with the template's parser and moves the cursor past it. + * @template {{ end: number, comments?: teasel.Comment[], typescript?: teasel.Kept[] }} T * @param {Parser} parser - * @param {number} index - * @param {'as'} [until] the host's `as` follows the expression, as an each block's item does - * @returns {{ node: Expression, end: number }} + * @param {(js: teasel.Source) => T} run + * @returns {T} */ -export function parse_expression_at(parser, index, until) { +function read(parser, run) { let answer; try { - answer = parser.js.parseExpressionAt(index, until); + answer = run(parser.js); } catch (err) { + // the parser's syntax errors; a compile error thrown while reading is the host's own + if (!(err instanceof SyntaxError)) throw err; return handle_parse_error(err); } - return accept(parser, answer, answer.node); + add_comments( + parser.template, + parser.root.comments, + /** @type {teasel.Comment[]} */ (answer.comments) + ); + unsupported(answer.typescript); + parser.index = answer.end; + + return answer; } /** * @param {Parser} parser - * @param {number} index - * @returns {{ node: Pattern, end: number }} + * @param {'as'} [until] the host's `as` follows the expression, as an each block's item does + * @param {string} [opening_token] the bracket the expression sits in, for loose mode + * @returns {Expression} */ -export function parse_pattern_at(parser, index) { - let answer; +export function read_expression(parser, until, opening_token = '{') { + const start = parser.index; + try { - answer = parser.js.parsePatternAt(index); + return read(parser, (js) => js.parseExpressionAt(start, until)).node; } catch (err) { - return handle_parse_error(err); - } + if (parser.loose) { + // Find the next } and treat it as the end of the expression + const end = find_matching_bracket(parser.template, start, opening_token); + if (end !== undefined) { + parser.index = end; + // We don't know what the expression is and signal this by returning an empty identifier + return { type: 'Identifier', start, end, name: '' }; + } + } - return accept(parser, answer, answer.node); + throw err; + } } /** * @param {Parser} parser - * @param {number} index the opening paren - * @returns {{ node: Pattern[], end: number }} + * @returns {Pattern} */ -export function parse_params_at(parser, index) { - let answer; - try { - answer = parser.js.parseParamsAt(index); - } catch (err) { - return handle_parse_error(err); +export function read_pattern(parser) { + const start = parser.index; + + const id = parser.read_identifier(); + + if (id.name !== '') { + const after = parser.index; + parser.allow_whitespace(); + + // a type annotation makes it a job for the parser + if (!parser.match(':')) { + parser.index = after; + return id; + } + } else { + const char = parser.template[start]; + + if (char !== '{' && char !== '[') { + e.expected_pattern(start); + } } - return accept(parser, answer, answer.params); + return read(parser, (js) => js.parsePatternAt(start)).node; } /** - * @param {Parser} parser - * @param {number} index - * @returns {{ node: Statement, end: number }} + * @param {Parser} parser at the opening paren + * @returns {Pattern[]} */ -export function parse_statement_at(parser, index) { - let answer; - try { - answer = parser.js.parseStatementAt(index); - } catch (err) { - if (/** @type {any} */ (err).code === 'unexpected_eof') e.unexpected_eof(parser.template.length); - return handle_parse_error(err); - } - - return accept(parser, answer, answer.node); +export function read_params(parser) { + const start = parser.index; + return read(parser, (js) => js.parseParamsAt(start)).params; } /** - * Keeps an answer's comments, rejects what erasure could not express, and hands back the node - * with the offset the parse stopped at. - * @template T * @param {Parser} parser - * @param {{ end: number, comments?: teasel.Comment[], typescript?: teasel.Kept[] }} answer - * @param {T} node - * @returns {{ node: T, end: number }} + * @returns {Statement} */ -function accept(parser, answer, node) { - add_comments( - parser.template, - parser.root.comments, - /** @type {teasel.Comment[]} */ (answer.comments) - ); - unsupported(answer.typescript); - return { node, end: answer.end }; +export function read_statement(parser) { + const start = parser.index; + + return read(parser, (js) => { + try { + return js.parseStatementAt(start); + } catch (err) { + if (/** @type {any} */ (err).code === 'unexpected_eof') + e.unexpected_eof(parser.template.length); + throw err; + } + }).node; } /** What erasure leaves in place needs a compiler, not this one */ diff --git a/packages/svelte/src/compiler/phases/1-parse/read/context.js b/packages/svelte/src/compiler/phases/1-parse/read/context.js deleted file mode 100644 index 874b30c28e..0000000000 --- a/packages/svelte/src/compiler/phases/1-parse/read/context.js +++ /dev/null @@ -1,36 +0,0 @@ -/** @import { Pattern } from 'estree' */ -/** @import { Parser } from '../index.js' */ -import { parse_pattern_at } from '../js.js'; -import * as e from '../../../errors.js'; - -/** - * @param {Parser} parser - * @returns {Pattern} - */ -export default function read_pattern(parser) { - const start = parser.index; - - const id = parser.read_identifier(); - - if (id.name !== '') { - const after = parser.index; - parser.allow_whitespace(); - - // a type annotation makes it a job for the parser - if (!parser.match(':')) { - parser.index = after; - return id; - } - } else { - const char = parser.template[start]; - - if (char !== '{' && char !== '[') { - e.expected_pattern(start); - } - } - - const { node, end } = parse_pattern_at(parser, start); - parser.index = end; - - return node; -} diff --git a/packages/svelte/src/compiler/phases/1-parse/read/expression.js b/packages/svelte/src/compiler/phases/1-parse/read/expression.js deleted file mode 100644 index e8f816a2b4..0000000000 --- a/packages/svelte/src/compiler/phases/1-parse/read/expression.js +++ /dev/null @@ -1,52 +0,0 @@ -/** @import { Expression, Identifier } from 'estree' */ -/** @import { Parser } from '../index.js' */ -import { parse_expression_at } from '../js.js'; -import * as e from '../../../errors.js'; -import { find_matching_bracket } from '../utils/bracket.js'; - -/** - * @param {Parser} parser - * @param {string} [opening_token] - * @returns {Expression | undefined} - */ -export function get_loose_identifier(parser, opening_token) { - // Find the next } and treat it as the end of the expression - const end = find_matching_bracket(parser.template, parser.index, opening_token ?? '{'); - if (end) { - const start = parser.index; - parser.index = end; - // We don't know what the expression is and signal this by returning an empty identifier - return { - type: 'Identifier', - start, - end, - name: '' - }; - } -} - -/** - * @param {Parser} parser - * @param {string} [opening_token] - * @param {boolean} [disallow_loose] - * @param {'as'} [until] the host's `as` follows the expression, as an each block's item does - * @returns {Expression} - */ -export default function read_expression(parser, opening_token, disallow_loose, until) { - try { - const { node, end } = parse_expression_at(parser, parser.index, until); - parser.index = end; - return node; - } catch (err) { - // If we are in an each loop we need the error to be thrown in cases like - // `as { y = z }` so we still throw and handle the error there - if (parser.loose && !disallow_loose) { - const expression = get_loose_identifier(parser, opening_token); - if (expression) { - return expression; - } - } - - throw err; - } -} diff --git a/packages/svelte/src/compiler/phases/1-parse/state/element.js b/packages/svelte/src/compiler/phases/1-parse/state/element.js index 49a31e6f3c..0c3399a27e 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/element.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/element.js @@ -3,7 +3,7 @@ /** @import { AST } from '#compiler' */ /** @import { Parser } from '../index.js' */ import { is_void, REGEX_VALID_TAG_NAME } from '../../../../utils.js'; -import read_expression from '../read/expression.js'; +import { read_expression } from '../js.js'; import { read_script } from '../read/script.js'; import read_style from '../read/style.js'; import { decode_character_references } from '../utils/html.js'; @@ -11,7 +11,11 @@ import * as e from '../../../errors.js'; import * as w from '../../../warnings.js'; import { create_fragment } from '../utils/create.js'; import { create_attribute, ExpressionMetadata, is_element_node } from '../../nodes.js'; -import { get_attribute_expression, is_expression_attribute, value_names } from '../../../utils/ast.js'; +import { + get_attribute_expression, + is_expression_attribute, + value_names +} from '../../../utils/ast.js'; import { closing_tag_omitted } from '../../../../html-tree-validation.js'; import { list } from '../../../utils/string.js'; import { locator } from '../../../state.js'; diff --git a/packages/svelte/src/compiler/phases/1-parse/state/tag.js b/packages/svelte/src/compiler/phases/1-parse/state/tag.js index 1c556b744d..e514126639 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/tag.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/tag.js @@ -3,9 +3,7 @@ /** @import { Parser } from '../index.js' */ import * as e from '../../../errors.js'; import { ExpressionMetadata } from '../../nodes.js'; -import { parse_params_at, parse_statement_at } from '../js.js'; -import read_pattern from '../read/context.js'; -import read_expression from '../read/expression.js'; +import { read_expression, read_params, read_pattern, read_statement } from '../js.js'; import { create_fragment } from '../utils/create.js'; import { find_matching_bracket, match_bracket } from '../utils/bracket.js'; @@ -89,14 +87,12 @@ function read_declaration(parser) { /** @type {import('estree').Statement | import('estree').VariableDeclaration} */ let declaration; - /** @type {number} */ - let end; try { - ({ node: declaration, end } = parse_statement_at(parser, start)); + declaration = read_statement(parser); } catch (error) { if (!parser.loose) throw error; - end = /** @type {number} */ (find_matching_bracket(parser.template, start, '{')); + const end = find_matching_bracket(parser.template, start, '{'); if (end === undefined) throw error; parser.index = end; @@ -126,6 +122,7 @@ function read_declaration(parser) { if (declaration.type !== 'VariableDeclaration') { if (declaration.type === 'ExpressionStatement') { + parser.index = start; parser.root.comments.length = initial_comment_count; // Else they show up duplicated return null; } else { @@ -142,7 +139,6 @@ function read_declaration(parser) { e.declaration_tag_invalid_type(declaration); } - parser.index = end; parser.allow_whitespace(); parser.eat('}', true); @@ -184,7 +180,7 @@ function open(parser) { 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, undefined, false, 'as'); + let expression = read_expression(parser, 'as'); parser.allow_whitespace(); @@ -224,7 +220,7 @@ function open(parser) { if (parser.eat('(')) { parser.allow_whitespace(); - key = read_expression(parser, '('); + key = read_expression(parser, undefined, '('); parser.allow_whitespace(); parser.eat(')', true); parser.allow_whitespace(); @@ -410,16 +406,14 @@ function open(parser) { /** @type {import('estree').Pattern[]} */ let parameters = []; - if (parser.eat('(', true, false)) { - const open = parser.index - 1; - - if (find_matching_bracket(parser.template, parser.index, '(') === undefined) { + if (parser.match('(')) { + if (find_matching_bracket(parser.template, parser.index + 1, '(') === undefined) { e.expected_token(parser.template.length, ')'); } - const { node, end } = parse_params_at(parser, open); - parameters = node; - parser.index = end; + parameters = read_params(parser); + } else if (!parser.loose) { + e.expected_token(parser.index, '('); } parser.allow_whitespace();