diff --git a/packages/svelte/package.json b/packages/svelte/package.json index b44c2fcda5..86e7990d69 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -174,7 +174,7 @@ "dependencies": { "@jridgewell/remapping": "^2.3.5", "@jridgewell/sourcemap-codec": "^1.6.0", - "@teasel/parser": "link:../../../teasel/bindings/node", + "@teasel/parser": "link:../../../teasel/package", "@types/estree": "^1.0.9", "aria-query": "5.3.1", "axobject-query": "^4.1.0", @@ -182,7 +182,6 @@ "devalue": "^5.9.2", "esm-env": "^1.2.1", "esrap": "^2.3.6", - "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" diff --git a/packages/svelte/src/compiler/phases/1-parse/index.js b/packages/svelte/src/compiler/phases/1-parse/index.js index fa3c2ff808..f7e5e3e161 100644 --- a/packages/svelte/src/compiler/phases/1-parse/index.js +++ b/packages/svelte/src/compiler/phases/1-parse/index.js @@ -3,6 +3,7 @@ /** @import * as ESTree from 'estree' */ import { Source, isIdentifierStart, isIdentifierChar } from '@teasel/parser'; import fragment from './state/fragment.js'; +import { value_names } from '../../utils/ast.js'; import * as e from '../../errors.js'; import { create_fragment } from './utils/create.js'; import read_options from './read/options.js'; @@ -96,6 +97,7 @@ export class Parser { typescript: this.ts && (erase ? 'erase' : true), comments: true, locations: true, + scopes: true, // a script may export what the component declares elsewhere allowUndeclaredExports: true }); @@ -259,7 +261,8 @@ export class Parser { } } - return { + /** @type {ESTree.Identifier & { start: number, end: number, loc: { start: Location, end: Location } }} */ + const identifier = { type: 'Identifier', name, start, @@ -269,6 +272,8 @@ export class Parser { end: state.locator(end) } }; + value_names.add(identifier); + return identifier; } /** @param {string} delimiter */ diff --git a/packages/svelte/src/compiler/phases/1-parse/js.js b/packages/svelte/src/compiler/phases/1-parse/js.js index b40bc057dc..89864328b7 100644 --- a/packages/svelte/src/compiler/phases/1-parse/js.js +++ b/packages/svelte/src/compiler/phases/1-parse/js.js @@ -18,7 +18,8 @@ export function parse(source, comments, typescript) { sourceType: 'module', typescript, comments: true, - locations: true + locations: true, + scopes: true }); } catch (err) { return handle_parse_error(err); @@ -26,6 +27,8 @@ export function parse(source, comments, typescript) { add_comments(source, comments, /** @type {teasel.Comment[]} */ (ast.comments)); delete ast.comments; + delete ast.scopes; + delete ast.bindings; return ast; } @@ -51,6 +54,8 @@ export function parse_script(parser, start, end) { /** @type {teasel.Comment[]} */ (ast.comments) ); delete ast.comments; + delete ast.scopes; + delete ast.bindings; unsupported(ast.typescript); delete ast.typescript; 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 220fe24b9d..49a31e6f3c 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/element.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/element.js @@ -11,7 +11,7 @@ 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 } 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'; @@ -712,6 +712,7 @@ function read_attribute(parser) { type: 'Identifier', name: directive.name }); + value_names.add(/** @type {Identifier} */ (directive.expression)); } return directive; @@ -949,7 +950,8 @@ function read_tag(parser, attribute = false) { const name = read_tag_name(parser, attribute); const end = parser.index; - return { + /** @type {Identifier & { start: number, end: number, loc: SourceLocation }} */ + const identifier = { type: 'Identifier', name, start, @@ -959,6 +961,8 @@ function read_tag(parser, attribute = false) { end: locator(end) } }; + value_names.add(identifier); + return identifier; } /** @param {Parser} parser */ diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 9a79871992..82ae6e9e85 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -9,6 +9,7 @@ import * as w from '../../warnings.js'; import { extract_identifiers, has_await_expression, + is_reference, object, unwrap_pattern } from '../../utils/ast.js'; @@ -83,7 +84,6 @@ import { TransitionDirective } from './visitors/TransitionDirective.js'; import { UpdateExpression } from './visitors/UpdateExpression.js'; import { UseDirective } from './visitors/UseDirective.js'; import { VariableDeclarator } from './visitors/VariableDeclarator.js'; -import is_reference from 'is-reference'; import { mark_subtree_dynamic } from './visitors/shared/fragment.js'; import * as state from '../../state.js'; @@ -657,9 +657,7 @@ export function analyze_component(root, source, options) { // @ts-expect-error _: set_scope, Identifier(node, context) { - const parent = /** @type {ESTree.Expression} */ (context.path.at(-1)); - - if (is_reference(node, parent)) { + if (is_reference(node, context.path.at(-1))) { const binding = context.state.scope.get(node.name); if ( @@ -981,8 +979,7 @@ function calculate_blockers(instance, analysis) { }, ImportDeclaration(node) {}, Identifier(node, context) { - const parent = /** @type {ESTree.Node} */ (context.path.at(-1)); - if (is_reference(node, parent)) { + if (is_reference(node, context.path.at(-1))) { const binding = context.state.scope.get(node.name); if (binding) { touched.add(binding); @@ -1059,8 +1056,7 @@ function calculate_blockers(instance, analysis) { touch(node, context.state.scope, writes, writes_seen); }, Identifier(node, context) { - const parent = /** @type {ESTree.Node} */ (context.path.at(-1)); - if (is_reference(node, parent)) { + if (is_reference(node, context.path.at(-1))) { const binding = context.state.scope.get(node.name); if (binding) { reads.add(binding); diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js index ebb2fc2b67..9eba9c29b7 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js @@ -1,7 +1,7 @@ /** @import { Expression, Identifier } from 'estree' */ /** @import { Context } from '../types' */ -import is_reference from 'is-reference'; import { should_proxy } from '../../3-transform/client/utils.js'; +import { is_reference } from '../../../utils/ast.js'; import * as e from '../../../errors.js'; import * as w from '../../../warnings.js'; import { is_rune } from '../../../../utils.js'; diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Identifier.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Identifier.js index b43ec7891e..7b621f35b0 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Identifier.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Identifier.js @@ -1,7 +1,7 @@ /** @import { Identifier, Node } from 'estree' */ /** @import { Context } from '../types' */ -import is_reference from 'is-reference'; import * as b from '#compiler/builders'; +import { is_reference } from '../../../../utils/ast.js'; import { build_getter } from '../utils.js'; /** diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js index 64ae573984..e8b23daf2d 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js @@ -2,11 +2,10 @@ /** @import { AST } from '#compiler' */ /** @import { ComponentClientTransformState, ComponentContext, Context } from '../../types' */ import { walk } from 'zimmerframe'; -import { object } from '../../../../../utils/ast.js'; +import { is_reference, object } from '../../../../../utils/ast.js'; import * as b from '#compiler/builders'; import { sanitize_template_string } from '../../../../../utils/sanitize_template_string.js'; import { regex_is_valid_identifier } from '../../../../patterns.js'; -import is_reference from 'is-reference'; import { dev, is_ignored, locator, component_name } from '../../../../../state.js'; import { async_thunk, build_getter, is_state_source } from '../../utils.js'; import { ExpressionMetadata } from '../../../../nodes.js'; @@ -266,8 +265,7 @@ export function build_bind_this(expression, value, { state, visit }) { if (seen.includes(node.name)) return; seen.push(node.name); - const parent = /** @type {Expression} */ (path.at(-1)); - if (!is_reference(node, parent)) return; + if (!is_reference(node, path.at(-1))) return; const binding = state.scope.get(node.name); if (!binding) return; diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/Identifier.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/Identifier.js index 460a3411c2..4256289b2a 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/Identifier.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/Identifier.js @@ -1,7 +1,7 @@ -/** @import { Identifier, Node } from 'estree' */ +/** @import { Identifier } from 'estree' */ /** @import { Context } from '../types.js' */ -import is_reference from 'is-reference'; import * as b from '#compiler/builders'; +import { is_reference } from '../../../../utils/ast.js'; import { build_getter } from './shared/utils.js'; /** @@ -9,7 +9,7 @@ import { build_getter } from './shared/utils.js'; * @param {Context} context */ export function Identifier(node, context) { - if (is_reference(node, /** @type {Node} */ (context.path.at(-1)))) { + if (is_reference(node, context.path.at(-1))) { if (node.name === '$$props') { return b.id('$$sanitized_props'); } diff --git a/packages/svelte/src/compiler/phases/3-transform/utils.js b/packages/svelte/src/compiler/phases/3-transform/utils.js index c7fce61ff9..90e8f89ef4 100644 --- a/packages/svelte/src/compiler/phases/3-transform/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/utils.js @@ -8,9 +8,8 @@ import { } from '../patterns.js'; import * as e from '../../errors.js'; import { walk } from 'zimmerframe'; -import { extract_identifiers } from '../../utils/ast.js'; +import { extract_identifiers, is_reference } from '../../utils/ast.js'; import check_graph_for_cycles from '../2-analyze/utils/check_graph_for_cycles.js'; -import is_reference from 'is-reference'; import { set_scope } from '../scope.js'; /** @@ -46,9 +45,7 @@ function sort_const_tags(nodes, state) { // @ts-expect-error don't know, don't care _: set_scope, Identifier(node, context) { - const parent = /** @type {Expression} */ (context.path.at(-1)); - - if (is_reference(node, parent)) { + if (is_reference(node, context.path.at(-1))) { const binding = context.state.scope.get(node.name); if (binding) deps.add(binding); } diff --git a/packages/svelte/src/compiler/phases/scope.js b/packages/svelte/src/compiler/phases/scope.js index f7d57248f6..5a3ace3ffd 100644 --- a/packages/svelte/src/compiler/phases/scope.js +++ b/packages/svelte/src/compiler/phases/scope.js @@ -1,7 +1,6 @@ /** @import { BinaryOperator, ClassDeclaration, Expression, FunctionDeclaration, Identifier, ImportDeclaration, MemberExpression, LogicalOperator, Node, Pattern, UnaryOperator, VariableDeclarator, Super, SimpleLiteral, FunctionExpression, ArrowFunctionExpression } from 'estree' */ /** @import { Context, Visitor } from 'zimmerframe' */ /** @import { AST, BindingKind, DeclarationKind } from '#compiler' */ -import is_reference from 'is-reference'; import { walk } from 'zimmerframe'; import { ExpressionMetadata } from './nodes.js'; import * as b from '#compiler/builders'; @@ -9,6 +8,7 @@ import * as e from '../errors.js'; import { extract_identifiers, extract_identifiers_from_destructuring, + is_reference, object, unwrap_pattern } from '../utils/ast.js'; @@ -1046,17 +1046,8 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { context.next(); }, - // references Identifier(node, { path, state }) { - const parent = path.at(-1); - if ( - parent && - is_reference(node, /** @type {Node} */ (parent)) && - // TSTypeAnnotation, TSInterfaceDeclaration etc - these are normally already filtered out, - // but for the migration they aren't, so we need to filter them out here - // TODO -> once migration script is gone we can remove this check - !parent.type.startsWith('TS') - ) { + if (is_reference(node, path.at(-1))) { references.push([state.scope, { node, path: path.slice() }]); } }, diff --git a/packages/svelte/src/compiler/utils/ast.js b/packages/svelte/src/compiler/utils/ast.js index 927674bd18..b226fb23c0 100644 --- a/packages/svelte/src/compiler/utils/ast.js +++ b/packages/svelte/src/compiler/utils/ast.js @@ -1,6 +1,7 @@ /** @import { AST, Scope } from '#compiler' */ /** @import * as ESTree from 'estree' */ import { walk } from 'zimmerframe'; +import { bindingOf } from '@teasel/parser'; import * as b from '#compiler/builders'; /** @@ -634,3 +635,36 @@ export function has_await_expression(node) { export function save(expression) { return b.call(b.await(b.call('$.save', expression))); } + +/** @type {WeakSet} the identifiers the parser makes itself for names in template syntax */ +export const value_names = new WeakSet(); + +/** + * Whether an identifier names a value. The parser answers for what it parsed and marks what it + * made; an identifier the compiler built is judged by where it sits. + * @param {ESTree.Identifier} node + * @param {ESTree.Node | AST.SvelteNode | null | undefined} parent + */ +export function is_reference(node, parent) { + const binding = bindingOf(node); + if (binding !== undefined || value_names.has(node)) return true; + if (!parent) return true; + switch (parent.type) { + case 'MemberExpression': + return parent.computed || node === parent.object; + case 'MethodDefinition': + return parent.computed; + case 'PropertyDefinition': + case 'Property': + return parent.computed || node === parent.value; + case 'ExportSpecifier': + case 'ImportSpecifier': + return node === parent.local; + case 'LabeledStatement': + case 'BreakStatement': + case 'ContinueStatement': + return false; + default: + return !parent.type.startsWith('TS'); + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 923ea9154b..4a146bcdf7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -75,8 +75,8 @@ importers: specifier: ^1.6.0 version: 1.6.0 '@teasel/parser': - specifier: link:../../../teasel/bindings/node - version: link:../../../teasel/bindings/node + specifier: link:../../../teasel/package + version: link:../../../teasel/package '@types/estree': specifier: ^1.0.9 version: 1.0.9 @@ -98,9 +98,6 @@ importers: esrap: specifier: ^2.3.6 version: 2.3.6(@typescript-eslint/types@8.59.4) - is-reference: - specifier: ^3.0.3 - version: 3.0.3 locate-character: specifier: ^3.0.0 version: 3.0.0 @@ -1642,9 +1639,6 @@ packages: is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} - is-reference@3.0.3: - resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} - is-subdir@1.2.0: resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} engines: {node: '>=4'} @@ -3854,10 +3848,6 @@ snapshots: dependencies: '@types/estree': 1.0.9 - is-reference@3.0.3: - dependencies: - '@types/estree': 1.0.9 - is-subdir@1.2.0: dependencies: better-path-resolve: 1.0.0