|
|
|
@ -1,3 +1,6 @@
|
|
|
|
|
/** @import { ClassDeclaration, Expression, FunctionDeclaration, Identifier, ImportDeclaration, MemberExpression, Node, Pattern, VariableDeclarator } from 'estree' */
|
|
|
|
|
/** @import { Context, Visitor, Visitors } from 'zimmerframe' */
|
|
|
|
|
/** @import { AnimateDirective, Binding, DeclarationKind, EachBlock, ElementLike, LetDirective, SvelteNode, TransitionDirective, UseDirective } from '#compiler' */
|
|
|
|
|
import is_reference from 'is-reference';
|
|
|
|
|
import { walk } from 'zimmerframe';
|
|
|
|
|
import { is_element_node } from './nodes.js';
|
|
|
|
@ -30,20 +33,20 @@ export class Scope {
|
|
|
|
|
/**
|
|
|
|
|
* A map of every identifier declared by this scope, and all the
|
|
|
|
|
* identifiers that reference it
|
|
|
|
|
* @type {Map<string, import('#compiler').Binding>}
|
|
|
|
|
* @type {Map<string, Binding>}
|
|
|
|
|
*/
|
|
|
|
|
declarations = new Map();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A map of declarators to the bindings they declare
|
|
|
|
|
* @type {Map<import('estree').VariableDeclarator | import('#compiler').LetDirective, import('#compiler').Binding[]>}
|
|
|
|
|
* @type {Map<VariableDeclarator | LetDirective, Binding[]>}
|
|
|
|
|
*/
|
|
|
|
|
declarators = new Map();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A set of all the names referenced with this scope
|
|
|
|
|
* — useful for generating unique names
|
|
|
|
|
* @type {Map<string, { node: import('estree').Identifier; path: import('#compiler').SvelteNode[] }[]>}
|
|
|
|
|
* @type {Map<string, { node: Identifier; path: SvelteNode[] }[]>}
|
|
|
|
|
*/
|
|
|
|
|
references = new Map();
|
|
|
|
|
|
|
|
|
@ -67,11 +70,11 @@ export class Scope {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {import('estree').Identifier} node
|
|
|
|
|
* @param {import('#compiler').Binding['kind']} kind
|
|
|
|
|
* @param {import('#compiler').DeclarationKind} declaration_kind
|
|
|
|
|
* @param {null | import('estree').Expression | import('estree').FunctionDeclaration | import('estree').ClassDeclaration | import('estree').ImportDeclaration | import('../types/template.js').EachBlock} initial
|
|
|
|
|
* @returns {import('#compiler').Binding}
|
|
|
|
|
* @param {Identifier} node
|
|
|
|
|
* @param {Binding['kind']} kind
|
|
|
|
|
* @param {DeclarationKind} declaration_kind
|
|
|
|
|
* @param {null | Expression | FunctionDeclaration | ClassDeclaration | ImportDeclaration | EachBlock} initial
|
|
|
|
|
* @returns {Binding}
|
|
|
|
|
*/
|
|
|
|
|
declare(node, kind, declaration_kind, initial = null) {
|
|
|
|
|
if (node.name === '$') {
|
|
|
|
@ -103,7 +106,7 @@ export class Scope {
|
|
|
|
|
e.declaration_duplicate(node, node.name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @type {import('#compiler').Binding} */
|
|
|
|
|
/** @type {Binding} */
|
|
|
|
|
const binding = {
|
|
|
|
|
node,
|
|
|
|
|
references: [],
|
|
|
|
@ -157,15 +160,15 @@ export class Scope {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {string} name
|
|
|
|
|
* @returns {import('#compiler').Binding | null}
|
|
|
|
|
* @returns {Binding | null}
|
|
|
|
|
*/
|
|
|
|
|
get(name) {
|
|
|
|
|
return this.declarations.get(name) ?? this.parent?.get(name) ?? null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {import('estree').VariableDeclarator | import('#compiler').LetDirective} node
|
|
|
|
|
* @returns {import('#compiler').Binding[]}
|
|
|
|
|
* @param {VariableDeclarator | LetDirective} node
|
|
|
|
|
* @returns {Binding[]}
|
|
|
|
|
*/
|
|
|
|
|
get_bindings(node) {
|
|
|
|
|
const bindings = this.declarators.get(node);
|
|
|
|
@ -184,8 +187,8 @@ export class Scope {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {import('estree').Identifier} node
|
|
|
|
|
* @param {import('#compiler').SvelteNode[]} path
|
|
|
|
|
* @param {Identifier} node
|
|
|
|
|
* @param {SvelteNode[]} path
|
|
|
|
|
*/
|
|
|
|
|
reference(node, path) {
|
|
|
|
|
path = [...path]; // ensure that mutations to path afterwards don't affect this reference
|
|
|
|
@ -231,7 +234,7 @@ export class ScopeRoot {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {import('#compiler').SvelteNode} ast
|
|
|
|
|
* @param {SvelteNode} ast
|
|
|
|
|
* @param {ScopeRoot} root
|
|
|
|
|
* @param {boolean} allow_reactive_declarations
|
|
|
|
|
* @param {Scope | null} parent
|
|
|
|
@ -241,7 +244,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A map of node->associated scope. A node appearing in this map does not necessarily mean that it created a scope
|
|
|
|
|
* @type {Map<import('#compiler').SvelteNode, Scope>}
|
|
|
|
|
* @type {Map<SvelteNode, Scope>}
|
|
|
|
|
*/
|
|
|
|
|
const scopes = new Map();
|
|
|
|
|
const scope = new Scope(root, parent, false);
|
|
|
|
@ -250,21 +253,21 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
|
|
|
|
|
/** @type {State} */
|
|
|
|
|
const state = { scope };
|
|
|
|
|
|
|
|
|
|
/** @type {[Scope, { node: import('estree').Identifier; path: import('#compiler').SvelteNode[] }][]} */
|
|
|
|
|
/** @type {[Scope, { node: Identifier; path: SvelteNode[] }][]} */
|
|
|
|
|
const references = [];
|
|
|
|
|
|
|
|
|
|
/** @type {[Scope, import('estree').Pattern | import('estree').MemberExpression][]} */
|
|
|
|
|
/** @type {[Scope, Pattern | MemberExpression][]} */
|
|
|
|
|
const updates = [];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* An array of reactive declarations, i.e. the `a` in `$: a = b * 2`
|
|
|
|
|
* @type {import('estree').Identifier[]}
|
|
|
|
|
* @type {Identifier[]}
|
|
|
|
|
*/
|
|
|
|
|
const possible_implicit_declarations = [];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {Scope} scope
|
|
|
|
|
* @param {import('estree').Pattern[]} params
|
|
|
|
|
* @param {Pattern[]} params
|
|
|
|
|
*/
|
|
|
|
|
function add_params(scope, params) {
|
|
|
|
|
for (const param of params) {
|
|
|
|
@ -275,7 +278,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @type {import('zimmerframe').Visitor<import('estree').Node, State, import('#compiler').SvelteNode>}
|
|
|
|
|
* @type {Visitor<Node, State, SvelteNode>}
|
|
|
|
|
*/
|
|
|
|
|
const create_block_scope = (node, { state, next }) => {
|
|
|
|
|
const scope = state.scope.child(true);
|
|
|
|
@ -285,7 +288,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @type {import('zimmerframe').Visitor<import('#compiler').ElementLike, State, import('#compiler').SvelteNode>}
|
|
|
|
|
* @type {Visitor<ElementLike, State, SvelteNode>}
|
|
|
|
|
*/
|
|
|
|
|
const SvelteFragment = (node, { state, next }) => {
|
|
|
|
|
const [scope] = analyze_let_directives(node, state.scope);
|
|
|
|
@ -294,7 +297,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {import('#compiler').ElementLike} node
|
|
|
|
|
* @param {ElementLike} node
|
|
|
|
|
* @param {Scope} parent
|
|
|
|
|
*/
|
|
|
|
|
function analyze_let_directives(node, parent) {
|
|
|
|
@ -303,7 +306,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
|
|
|
|
|
|
|
|
|
|
for (const attribute of node.attributes) {
|
|
|
|
|
if (attribute.type === 'LetDirective') {
|
|
|
|
|
/** @type {import('#compiler').Binding[]} */
|
|
|
|
|
/** @type {Binding[]} */
|
|
|
|
|
const bindings = [];
|
|
|
|
|
scope.declarators.set(attribute, bindings);
|
|
|
|
|
|
|
|
|
@ -317,7 +320,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
|
|
|
|
|
bindings.push(binding);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
/** @type {import('estree').Identifier} */
|
|
|
|
|
/** @type {Identifier} */
|
|
|
|
|
const id = {
|
|
|
|
|
name: attribute.name,
|
|
|
|
|
type: 'Identifier',
|
|
|
|
@ -336,7 +339,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @type {import('zimmerframe').Visitor<import('#compiler').AnimateDirective | import('#compiler').TransitionDirective | import('#compiler').UseDirective, State, import('#compiler').SvelteNode>}
|
|
|
|
|
* @type {Visitor<AnimateDirective | TransitionDirective | UseDirective, State, SvelteNode>}
|
|
|
|
|
*/
|
|
|
|
|
const SvelteDirective = (node, { state, path, visit }) => {
|
|
|
|
|
state.scope.reference(b.id(node.name.split('.')[0]), path);
|
|
|
|
@ -350,7 +353,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
|
|
|
|
|
// references
|
|
|
|
|
Identifier(node, { path, state }) {
|
|
|
|
|
const parent = path.at(-1);
|
|
|
|
|
if (parent && is_reference(node, /** @type {import('estree').Node} */ (parent))) {
|
|
|
|
|
if (parent && is_reference(node, /** @type {Node} */ (parent))) {
|
|
|
|
|
references.push([state.scope, { node, path: path.slice() }]);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
@ -432,12 +435,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
UpdateExpression(node, { state, next }) {
|
|
|
|
|
updates.push([
|
|
|
|
|
state.scope,
|
|
|
|
|
/** @type {import('estree').Identifier | import('estree').MemberExpression} */ (
|
|
|
|
|
node.argument
|
|
|
|
|
)
|
|
|
|
|
]);
|
|
|
|
|
updates.push([state.scope, /** @type {Identifier | MemberExpression} */ (node.argument)]);
|
|
|
|
|
next();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
@ -489,7 +487,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
|
|
|
|
|
VariableDeclaration(node, { state, path, next }) {
|
|
|
|
|
const is_parent_const_tag = path.at(-1)?.type === 'ConstTag';
|
|
|
|
|
for (const declarator of node.declarations) {
|
|
|
|
|
/** @type {import('#compiler').Binding[]} */
|
|
|
|
|
/** @type {Binding[]} */
|
|
|
|
|
const bindings = [];
|
|
|
|
|
|
|
|
|
|
state.scope.declarators.set(declarator, bindings);
|
|
|
|
@ -525,7 +523,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
|
|
|
|
|
|
|
|
|
|
EachBlock(node, { state, visit }) {
|
|
|
|
|
// Array part is still from the scope above
|
|
|
|
|
/** @type {Set<import('estree').Identifier>} */
|
|
|
|
|
/** @type {Set<Identifier>} */
|
|
|
|
|
const references_within = new Set();
|
|
|
|
|
const idx = references.length;
|
|
|
|
|
visit(node.expression);
|
|
|
|
@ -600,7 +598,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
|
|
|
|
|
item: node.context.type === 'Identifier' ? node.context : b.id('$$item'),
|
|
|
|
|
declarations: scope.declarations,
|
|
|
|
|
references: [...references_within]
|
|
|
|
|
.map((id) => /** @type {import('#compiler').Binding} */ (state.scope.get(id.name)))
|
|
|
|
|
.map((id) => /** @type {Binding} */ (state.scope.get(id.name)))
|
|
|
|
|
.filter(Boolean),
|
|
|
|
|
is_controlled: false
|
|
|
|
|
};
|
|
|
|
@ -673,9 +671,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
|
|
|
|
|
BindDirective(node, context) {
|
|
|
|
|
updates.push([
|
|
|
|
|
context.state.scope,
|
|
|
|
|
/** @type {import('estree').Identifier | import('estree').MemberExpression} */ (
|
|
|
|
|
node.expression
|
|
|
|
|
)
|
|
|
|
|
/** @type {Identifier | MemberExpression} */ (node.expression)
|
|
|
|
|
]);
|
|
|
|
|
context.next();
|
|
|
|
|
},
|
|
|
|
@ -718,7 +714,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
|
|
|
|
|
object = object.object;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const binding = scope.get(/** @type {import('estree').Identifier} */ (object).name);
|
|
|
|
|
const binding = scope.get(/** @type {Identifier} */ (object).name);
|
|
|
|
|
if (binding) binding.mutated = true;
|
|
|
|
|
} else {
|
|
|
|
|
unwrap_pattern(node).forEach((node) => {
|
|
|
|
@ -742,15 +738,15 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @template {{ scope: Scope }} State
|
|
|
|
|
* @param {Map<import('#compiler').SvelteNode, Scope>} scopes
|
|
|
|
|
* @returns {import('zimmerframe').Visitors<import('#compiler').SvelteNode, State>}
|
|
|
|
|
* @param {Map<SvelteNode, Scope>} scopes
|
|
|
|
|
* @returns {Visitors<SvelteNode, State>}
|
|
|
|
|
*/
|
|
|
|
|
export function set_scope(scopes) {
|
|
|
|
|
return {
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* @param {import('#compiler').SvelteNode} node
|
|
|
|
|
* @param {import('zimmerframe').Context<import('#compiler').SvelteNode, State>} context
|
|
|
|
|
* @param {SvelteNode} node
|
|
|
|
|
* @param {Context<SvelteNode, State>} context
|
|
|
|
|
*/
|
|
|
|
|
_(node, { next, state }) {
|
|
|
|
|
const scope = scopes.get(node);
|
|
|
|
@ -761,7 +757,7 @@ export function set_scope(scopes) {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the name of the rune if the given expression is a `CallExpression` using a rune.
|
|
|
|
|
* @param {import('estree').Node | import('../types/template.js').EachBlock | null | undefined} node
|
|
|
|
|
* @param {Node | EachBlock | null | undefined} node
|
|
|
|
|
* @param {Scope} scope
|
|
|
|
|
* @returns {Runes[number] | null}
|
|
|
|
|
*/
|
|
|
|
|