diff --git a/packages/svelte/src/compiler/phases/1-parse/js.js b/packages/svelte/src/compiler/phases/1-parse/js.js index 35ba76f4cd..2882536ab4 100644 --- a/packages/svelte/src/compiler/phases/1-parse/js.js +++ b/packages/svelte/src/compiler/phases/1-parse/js.js @@ -13,36 +13,23 @@ import { keep_tables } from '../../utils/ast.js'; * @returns {Program} */ export function parse(source, comments, typescript) { - let ast; + let answer; try { - ast = teasel.parse(source, { + answer = new teasel.Source(source, { sourceType: 'module', typescript, comments: true, locations: true, scopes: true - }); + }).parse(); } catch (err) { return handle_parse_error(err); } - add_comments(source, comments, /** @type {teasel.Comment[]} */ (ast.comments)); - delete ast.comments; - keep_tables(ast, tables(ast)); - - return ast; -} + add_comments(source, comments, /** @type {teasel.Comment[]} */ (answer.comments)); + keep_tables(answer.node, answer); -/** - * Takes the tables off an answer, so they reach the scope analysis without showing in the tree. - * @param {{ scopes?: any; bindings?: any; references?: any }} answer - */ -function tables(answer) { - const { scopes, bindings, references } = answer; - delete answer.scopes; - delete answer.bindings; - delete answer.references; - return { scopes, bindings, references }; + return answer.node; } /** @@ -53,9 +40,9 @@ function tables(answer) { * @returns {Program} */ export function parse_script(parser, start, end) { - let ast; + let answer; try { - ast = parser.js.parse(start, end); + answer = parser.js.parse('program', start, { end }); } catch (err) { return handle_parse_error(err); } @@ -63,15 +50,12 @@ export function parse_script(parser, start, end) { add_comments( parser.template, parser.root.comments, - /** @type {teasel.Comment[]} */ (ast.comments) + /** @type {teasel.Comment[]} */ (answer.comments) ); - delete ast.comments; - delete ast.errors; - keep_tables(ast, tables(ast)); - unsupported(ast.typescript); - delete ast.typescript; + keep_tables(answer.node, answer); + unsupported(answer.typescript); - return ast; + return answer.node; } /** @@ -109,8 +93,8 @@ function read(parser, run) { */ export function read_expression(parser, stop_at) { const start = parser.index; - const answer = read(parser, (js) => js.parseExpressionAt(start, stop_at)); - keep_tables(answer.node, tables(answer)); + const answer = read(parser, (js) => js.parse('expression', start, { stopAt: stop_at })); + keep_tables(answer.node, answer); const node = answer.node; // the language tools copy an expression's text by its node's range, so the placeholder standing for one that could not be read spans what was read if (node.type === 'Identifier' && node.name === '' && node.start === node.end) { @@ -146,8 +130,8 @@ export function read_pattern(parser) { } } - const answer = read(parser, (js) => js.parsePatternAt(start)); - keep_tables(answer.node, tables(answer)); + const answer = read(parser, (js) => js.parse('pattern', start)); + keep_tables(answer.node, answer); return answer.node; } @@ -157,9 +141,9 @@ export function read_pattern(parser) { */ export function read_params(parser) { const start = parser.index; - const answer = read(parser, (js) => js.parseParamsAt(start)); - keep_tables(answer.params, tables(answer)); - return answer.params; + const answer = read(parser, (js) => js.parse('params', start)); + keep_tables(answer.node, answer); + return answer.node; } /** @@ -167,7 +151,7 @@ export function read_params(parser) { */ export function read_type_parameters(parser) { const start = parser.index; - read(parser, (js) => js.parseTypeParametersAt(start)); + read(parser, (js) => js.parse('typeParameters', start)); } /** @@ -177,8 +161,8 @@ export function read_type_parameters(parser) { export function read_statement(parser) { const start = parser.index; - const answer = read(parser, (js) => js.parseStatementAt(start)); - keep_tables(answer.node, tables(answer)); + const answer = read(parser, (js) => js.parse('statement', start)); + keep_tables(answer.node, answer); return answer.node; } 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 005e182504..5e4bfdcfba 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/tag.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/tag.js @@ -174,7 +174,7 @@ function open(parser) { if (parser.eat('(')) { parser.allow_whitespace(); - key = read_expression(parser, undefined, '('); + key = read_expression(parser); parser.allow_whitespace(); parser.eat(')', true); parser.allow_whitespace(); diff --git a/packages/svelte/src/compiler/phases/scope.js b/packages/svelte/src/compiler/phases/scope.js index c4c413cef0..007b221bf4 100644 --- a/packages/svelte/src/compiler/phases/scope.js +++ b/packages/svelte/src/compiler/phases/scope.js @@ -1004,14 +1004,14 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { const from_parser = new Map(); /** - * Declares in `scope` what the parser found declared in `parsed`: its parameters, or the rest. + * Declares in `scope` what the parser found declared in one of its scopes: the parameters, or the rest. * @param {Scope} scope - * @param {import('@teasel/parser').Scope} parsed + * @param {import('@teasel/parser').Binding[]} bindings * @param {boolean} params * @param {boolean} [template] the declarations are a const tag's */ - function declare_parsed(scope, parsed, params, template = false) { - for (const binding of parsed.bindings) { + function declare_parsed(scope, bindings, params, template = false) { + for (const binding of bindings) { if ((binding.kind === 'param') !== params) continue; // no node: `arguments`, or a declaration erased with the TypeScript it belonged to if (binding.node === null || binding.kind === 'class-name' || binding.kind === 'pattern') @@ -1086,6 +1086,15 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { } return; } + /** @type {Map} the answer's bindings by the scope that holds them */ + const by_scope = new Map(); + for (const binding of answer.bindings) { + const held = by_scope.get(binding.scope); + if (held === undefined) by_scope.set(binding.scope, [binding]); + else held.push(binding); + } + /** @param {import('@teasel/parser').Scope} parsed */ + const of = (parsed) => by_scope.get(parsed) ?? []; /** @type {Map} the parser's scopes and ours; a function's holds its parameters */ const ours = new Map([[outermost, scope]]); /** @type {Map} a function's body, which holds the rest */ @@ -1108,7 +1117,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { return /** @type {Scope} */ (ours.get(scope)); } - declare_parsed(scope, outermost, false, template); + declare_parsed(scope, of(outermost), false, template); for (let i = 1; i < answer.scopes.length; i++) { const parsed = answer.scopes[i]; @@ -1124,18 +1133,18 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { scopes.set(node, params); ours.set(parsed, params); if (parsed.parent?.kind === 'function-name') ours.set(parsed.parent, params); - declare_parsed(params, parsed, true); + declare_parsed(params, of(parsed), true); if ( node.body.type !== 'BlockStatement' || (node.type === 'FunctionExpression' && node.id) ) { - declare_parsed(params, parsed, false); + declare_parsed(params, of(parsed), false); } if (node.body.type === 'BlockStatement') { const body = params.child(); scopes.set(node.body, body); bodies.set(parsed, body); - declare_parsed(body, parsed, false); + declare_parsed(body, of(parsed), false); } break; } @@ -1147,7 +1156,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { const inside = parent.child(true); scopes.set(node, inside); ours.set(parsed, inside); - declare_parsed(inside, parsed, false); + declare_parsed(inside, of(parsed), false); break; } default: diff --git a/packages/svelte/src/compiler/utils/ast.js b/packages/svelte/src/compiler/utils/ast.js index eacb53829f..e019f0652e 100644 --- a/packages/svelte/src/compiler/utils/ast.js +++ b/packages/svelte/src/compiler/utils/ast.js @@ -4,21 +4,21 @@ import { walk } from 'zimmerframe'; import { bindingOf } from '@teasel/parser'; /** - * @typedef {{ scopes: import('@teasel/parser').Scope[]; bindings: import('@teasel/parser').Binding[]; references: import('@teasel/parser').Reference[] }} Tables - * the parser's scope, binding and reference tables for one answer + * @typedef {Required, 'scopes' | 'bindings' | 'references'>>} Tables + * the parser's scope, binding and reference tables for one answer, parsed with `scopes` */ // a symbol on the root: a property load where the walk asks every node, not a map lookup const PARSED = Symbol('parsed'); /** - * Keeps the parser's tables on the root it parsed, a script's program or a template's - * expression, pattern, parameter list or declaration. + * Keeps the parser's answer on the root it parsed, a script's program or a template's + * expression, pattern, parameter list or declaration, for its tables. * @param {object} root - * @param {Tables} tables + * @param {import('@teasel/parser').Parsed} answer */ -export function keep_tables(root, tables) { - /** @type {any} */ (root)[PARSED] = tables; +export function keep_tables(root, answer) { + /** @type {any} */ (root)[PARSED] = answer; } /**