mirror of https://github.com/sveltejs/svelte
parent
425077a92c
commit
10a4b37763
@ -0,0 +1,22 @@
|
||||
import { extract_paths } from '../../../utils/ast.js';
|
||||
import { get_rune } from '../../scope.js';
|
||||
|
||||
/** @type {import('zimmerframe').Visitors<import('#compiler').SvelteNode, { scope: import('../../scope').Scope }>} */
|
||||
export const analyze_scope_runes_module = {
|
||||
VariableDeclarator(node, { state }) {
|
||||
if (node.init?.type !== 'CallExpression') return;
|
||||
if (get_rune(node.init, state.scope) === null) return;
|
||||
|
||||
const callee = node.init.callee;
|
||||
if (callee.type !== 'Identifier') return;
|
||||
|
||||
const name = callee.name;
|
||||
if (name !== '$state' && name !== '$derived') return;
|
||||
|
||||
for (const path of extract_paths(node.id)) {
|
||||
// @ts-ignore this fails in CI for some insane reason
|
||||
const binding = /** @type {import('#compiler').Binding} */ (state.scope.get(path.node.name));
|
||||
binding.kind = name === '$state' ? 'state' : 'derived';
|
||||
}
|
||||
}
|
||||
};
|
@ -0,0 +1,184 @@
|
||||
import is_reference from 'is-reference';
|
||||
import { extract_identifiers } from '../../../utils/ast.js';
|
||||
|
||||
/** @type {import('../types').Visitors<import('../types').LegacyAnalysisState>} */
|
||||
export const analyze_scope_legacy = {
|
||||
LabeledStatement(node, { next, path, state }) {
|
||||
if (
|
||||
state.ast_type !== 'instance' ||
|
||||
node.label.name !== '$' ||
|
||||
/** @type {import('#compiler').SvelteNode} */ (path.at(-1)).type !== 'Program'
|
||||
) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// Find all dependencies of this `$: {...}` statement
|
||||
/** @type {import('../../types.js').ReactiveStatement} */
|
||||
const reactive_statement = {
|
||||
assignments: new Set(),
|
||||
dependencies: new Set()
|
||||
};
|
||||
|
||||
next({ ...state, reactive_statement, function_depth: state.scope.function_depth + 1 });
|
||||
|
||||
for (const [name, nodes] of state.scope.references) {
|
||||
const binding = state.scope.get(name);
|
||||
if (binding === null) continue;
|
||||
|
||||
// Include bindings that have references other than assignments and their own declarations
|
||||
if (
|
||||
nodes.some((n) => n.node !== binding.node && !reactive_statement.assignments.has(n.node))
|
||||
) {
|
||||
reactive_statement.dependencies.add(binding);
|
||||
}
|
||||
}
|
||||
|
||||
state.reactive_statements.set(node, reactive_statement);
|
||||
|
||||
if (
|
||||
node.body.type === 'ExpressionStatement' &&
|
||||
node.body.expression.type === 'AssignmentExpression'
|
||||
) {
|
||||
for (const id of extract_identifiers(node.body.expression.left)) {
|
||||
const binding = state.scope.get(id.name);
|
||||
if (binding?.kind === 'legacy_reactive') {
|
||||
// TODO does this include `let double; $: double = x * 2`?
|
||||
binding.legacy_dependencies = Array.from(reactive_statement.dependencies);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
AssignmentExpression(node, { state, next }) {
|
||||
if (state.reactive_statement && node.operator === '=') {
|
||||
for (const id of extract_identifiers(node.left)) {
|
||||
state.reactive_statement.assignments.add(id);
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
},
|
||||
Identifier(node, { state, path }) {
|
||||
const parent = /** @type {import('estree').Node} */ (path.at(-1));
|
||||
if (is_reference(node, parent)) {
|
||||
if (node.name === '$$props') {
|
||||
state.analysis.uses_props = true;
|
||||
}
|
||||
|
||||
if (node.name === '$$restProps') {
|
||||
state.analysis.uses_rest_props = true;
|
||||
}
|
||||
|
||||
if (node.name === '$$slots') {
|
||||
state.analysis.uses_slots = true;
|
||||
}
|
||||
|
||||
let binding = state.scope.get(node.name);
|
||||
|
||||
if (binding?.kind === 'store_sub') {
|
||||
// get the underlying store to mark it as reactive in case it's mutated
|
||||
binding = state.scope.get(node.name.slice(1));
|
||||
}
|
||||
|
||||
if (
|
||||
binding !== null &&
|
||||
binding.kind === 'normal' &&
|
||||
((binding.scope === state.instance_scope && binding.declaration_kind !== 'function') ||
|
||||
binding.declaration_kind === 'import')
|
||||
) {
|
||||
if (binding.declaration_kind === 'import') {
|
||||
if (
|
||||
binding.mutated &&
|
||||
// TODO could be more fine-grained - not every mention in the template implies a state binding
|
||||
(state.reactive_statement || state.ast_type === 'template') &&
|
||||
parent.type === 'MemberExpression'
|
||||
) {
|
||||
binding.kind = 'state';
|
||||
}
|
||||
} else if (
|
||||
binding.mutated &&
|
||||
// TODO could be more fine-grained - not every mention in the template implies a state binding
|
||||
(state.reactive_statement || state.ast_type === 'template')
|
||||
) {
|
||||
binding.kind = 'state';
|
||||
} else if (
|
||||
state.reactive_statement &&
|
||||
parent.type === 'AssignmentExpression' &&
|
||||
parent.left === binding.node
|
||||
) {
|
||||
binding.kind = 'derived';
|
||||
} else {
|
||||
let idx = -1;
|
||||
let ancestor = path.at(idx);
|
||||
while (ancestor) {
|
||||
if (ancestor.type === 'EachBlock') {
|
||||
// Ensures that the array is reactive when only its entries are mutated
|
||||
// TODO: this doesn't seem correct. We should be checking at the points where
|
||||
// the identifier (the each expression) is used in a way that makes it reactive.
|
||||
// This just forces the collection identifier to always be reactive even if it's
|
||||
// not.
|
||||
if (ancestor.expression === (idx === -1 ? node : path.at(idx + 1))) {
|
||||
binding.kind = 'state';
|
||||
break;
|
||||
}
|
||||
}
|
||||
ancestor = path.at(--idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ExportNamedDeclaration(node, { next, state }) {
|
||||
if (state.ast_type !== 'instance') {
|
||||
return next();
|
||||
}
|
||||
|
||||
if (!node.declaration) {
|
||||
for (const specifier of node.specifiers) {
|
||||
const binding = /** @type {import('#compiler').Binding} */ (
|
||||
state.scope.get(specifier.local.name)
|
||||
);
|
||||
if (
|
||||
binding.kind === 'state' ||
|
||||
(binding.kind === 'normal' && binding.declaration_kind === 'let')
|
||||
) {
|
||||
binding.kind = 'prop';
|
||||
if (specifier.exported.name !== specifier.local.name) {
|
||||
binding.prop_alias = specifier.exported.name;
|
||||
}
|
||||
} else {
|
||||
state.analysis.exports.push({
|
||||
name: specifier.local.name,
|
||||
alias: specifier.exported.name
|
||||
});
|
||||
}
|
||||
}
|
||||
return next();
|
||||
}
|
||||
|
||||
if (node.declaration.type === 'FunctionDeclaration') {
|
||||
state.analysis.exports.push({
|
||||
name: /** @type {import('estree').Identifier} */ (node.declaration.id).name,
|
||||
alias: null
|
||||
});
|
||||
return next();
|
||||
}
|
||||
|
||||
if (node.declaration.type === 'VariableDeclaration') {
|
||||
if (node.declaration.kind === 'const') {
|
||||
for (const declarator of node.declaration.declarations) {
|
||||
for (const node of extract_identifiers(declarator.id)) {
|
||||
state.analysis.exports.push({ name: node.name, alias: null });
|
||||
}
|
||||
}
|
||||
return next();
|
||||
}
|
||||
|
||||
for (const declarator of node.declaration.declarations) {
|
||||
for (const id of extract_identifiers(declarator.id)) {
|
||||
const binding = /** @type {import('#compiler').Binding} */ (state.scope.get(id.name));
|
||||
binding.kind = 'prop';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
@ -0,0 +1,76 @@
|
||||
import { extract_identifiers, extract_paths } from '../../../utils/ast.js';
|
||||
import { get_rune } from '../../scope.js';
|
||||
|
||||
/** @type {import('../types').Visitors} */
|
||||
export const analyze_scope_runes_component = {
|
||||
VariableDeclarator(node, { state }) {
|
||||
if (node.init?.type !== 'CallExpression') return;
|
||||
if (get_rune(node.init, state.scope) === null) return;
|
||||
|
||||
const callee = node.init.callee;
|
||||
if (callee.type !== 'Identifier') return;
|
||||
|
||||
const name = callee.name;
|
||||
if (name !== '$state' && name !== '$derived' && name !== '$props') return;
|
||||
|
||||
for (const path of extract_paths(node.id)) {
|
||||
// @ts-ignore this fails in CI for some insane reason
|
||||
const binding = /** @type {import('#compiler').Binding} */ (state.scope.get(path.node.name));
|
||||
binding.kind =
|
||||
name === '$state'
|
||||
? 'state'
|
||||
: name === '$derived'
|
||||
? 'derived'
|
||||
: path.is_rest
|
||||
? 'rest_prop'
|
||||
: 'prop';
|
||||
}
|
||||
|
||||
if (name === '$props') {
|
||||
for (const property of /** @type {import('estree').ObjectPattern} */ (node.id).properties) {
|
||||
if (property.type !== 'Property') continue;
|
||||
|
||||
const name =
|
||||
property.value.type === 'AssignmentPattern'
|
||||
? /** @type {import('estree').Identifier} */ (property.value.left).name
|
||||
: /** @type {import('estree').Identifier} */ (property.value).name;
|
||||
const alias =
|
||||
property.key.type === 'Identifier'
|
||||
? property.key.name
|
||||
: /** @type {string} */ (/** @type {import('estree').Literal} */ (property.key).value);
|
||||
const initial = property.value.type === 'AssignmentPattern' ? property.value.right : null;
|
||||
|
||||
const binding = /** @type {import('#compiler').Binding} */ (state.scope.get(name));
|
||||
binding.prop_alias = alias;
|
||||
binding.initial = initial; // rewire initial from $props() to the actual initial value
|
||||
}
|
||||
}
|
||||
},
|
||||
ExportSpecifier(node, { state }) {
|
||||
state.analysis.exports.push({
|
||||
name: node.local.name,
|
||||
alias: node.exported.name
|
||||
});
|
||||
},
|
||||
ExportNamedDeclaration(node, { next, state }) {
|
||||
if (!node.declaration) {
|
||||
return next();
|
||||
}
|
||||
|
||||
if (node.declaration.type === 'FunctionDeclaration') {
|
||||
state.analysis.exports.push({
|
||||
name: /** @type {import('estree').Identifier} */ (node.declaration.id).name,
|
||||
alias: null
|
||||
});
|
||||
return next();
|
||||
}
|
||||
|
||||
if (node.declaration.type === 'VariableDeclaration' && node.declaration.kind === 'const') {
|
||||
for (const declarator of node.declaration.declarations) {
|
||||
for (const node of extract_identifiers(declarator.id)) {
|
||||
state.analysis.exports.push({ name: node.name, alias: null });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
Loading…
Reference in new issue