Socket
Socket
Sign inDemoInstall

svelte

Package Overview
Dependencies
Maintainers
3
Versions
735
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

svelte - npm Package Compare versions

Comparing version 5.0.0-next.202 to 5.0.0-next.203

src/compiler/phases/2-analyze/visitors/ArrowFunctionExpression.js

2

package.json

@@ -5,3 +5,3 @@ {

"license": "MIT",
"version": "5.0.0-next.202",
"version": "5.0.0-next.203",
"type": "module",

@@ -8,0 +8,0 @@ "types": "./types/index.d.ts",

@@ -1,35 +0,14 @@

/** @import { ArrowFunctionExpression, CallExpression, Expression, FunctionDeclaration, FunctionExpression, Identifier, LabeledStatement, Literal, Node, Program, Super } from 'estree' */
/** @import { Attribute, BindDirective, Binding, DelegatedEvent, RegularElement, Root, Script, SvelteNode, Text, ValidatedCompileOptions, ValidatedModuleCompileOptions } from '#compiler' */
/** @import { AnalysisState, Context, LegacyAnalysisState, Visitors } from './types' */
/** @import { Node, Program } from 'estree' */
/** @import { Root, Script, SvelteNode, ValidatedCompileOptions, ValidatedModuleCompileOptions } from '#compiler' */
/** @import { AnalysisState, Visitors } from './types' */
/** @import { Analysis, ComponentAnalysis, Js, ReactiveStatement, Template } from '../types' */
import is_reference from 'is-reference';
import { walk } from 'zimmerframe';
import * as e from '../../errors.js';
import * as w from '../../warnings.js';
import {
extract_identifiers,
extract_all_identifiers_from_expression,
extract_paths,
is_event_attribute,
is_text_attribute,
object,
unwrap_optional,
get_attribute_expression,
get_attribute_chunks
} from '../../utils/ast.js';
import { is_text_attribute } from '../../utils/ast.js';
import * as b from '../../utils/builders.js';
import { MathMLElements, ReservedKeywords, Runes, SVGElements } from '../constants.js';
import { Scope, ScopeRoot, create_scopes, get_rune, set_scope } from '../scope.js';
import { merge } from '../visitors.js';
import { validation_legacy, validation_runes, validation_runes_js } from './validation.js';
import { ReservedKeywords, Runes } from '../constants.js';
import { Scope, ScopeRoot, create_scopes, get_rune } from '../scope.js';
import check_graph_for_cycles from './utils/check_graph_for_cycles.js';
import { regex_starts_with_newline } from '../patterns.js';
import { create_attribute, is_element_node } from '../nodes.js';
import {
DelegatedEvents,
is_capture_event,
namespace_mathml,
namespace_svg
} from '../../../constants.js';
import { should_proxy_or_freeze } from '../3-transform/client/utils.js';
import { create_attribute } from '../nodes.js';
import { analyze_css } from './css/css-analyze.js';

@@ -41,5 +20,153 @@ import { prune } from './css/css-prune.js';

import { ignore_map, ignore_stack, pop_ignore, push_ignore } from '../../state.js';
import { equal } from '../../utils/assert.js';
import { ArrowFunctionExpression } from './visitors/ArrowFunctionExpression.js';
import { AssignmentExpression } from './visitors/AssignmentExpression.js';
import { Attribute } from './visitors/Attribute.js';
import { AwaitBlock } from './visitors/AwaitBlock.js';
import { BindDirective } from './visitors/BindDirective.js';
import { CallExpression } from './visitors/CallExpression.js';
import { ClassBody } from './visitors/ClassBody.js';
import { ClassDeclaration } from './visitors/ClassDeclaration.js';
import { ClassDirective } from './visitors/ClassDirective.js';
import { Component } from './visitors/Component.js';
import { ConstTag } from './visitors/ConstTag.js';
import { DebugTag } from './visitors/DebugTag.js';
import { EachBlock } from './visitors/EachBlock.js';
import { ExportDefaultDeclaration } from './visitors/ExportDefaultDeclaration.js';
import { ExportNamedDeclaration } from './visitors/ExportNamedDeclaration.js';
import { ExportSpecifier } from './visitors/ExportSpecifier.js';
import { ExpressionStatement } from './visitors/ExpressionStatement.js';
import { ExpressionTag } from './visitors/ExpressionTag.js';
import { FunctionDeclaration } from './visitors/FunctionDeclaration.js';
import { FunctionExpression } from './visitors/FunctionExpression.js';
import { HtmlTag } from './visitors/HtmlTag.js';
import { Identifier } from './visitors/Identifier.js';
import { IfBlock } from './visitors/IfBlock.js';
import { ImportDeclaration } from './visitors/ImportDeclaration.js';
import { KeyBlock } from './visitors/KeyBlock.js';
import { LabeledStatement } from './visitors/LabeledStatement.js';
import { LetDirective } from './visitors/LetDirective.js';
import { MemberExpression } from './visitors/MemberExpression.js';
import { NewExpression } from './visitors/NewExpression.js';
import { OnDirective } from './visitors/OnDirective.js';
import { RegularElement } from './visitors/RegularElement.js';
import { RenderTag } from './visitors/RenderTag.js';
import { SlotElement } from './visitors/SlotElement.js';
import { SnippetBlock } from './visitors/SnippetBlock.js';
import { SpreadAttribute } from './visitors/SpreadAttribute.js';
import { StyleDirective } from './visitors/StyleDirective.js';
import { SvelteComponent } from './visitors/SvelteComponent.js';
import { SvelteElement } from './visitors/SvelteElement.js';
import { SvelteFragment } from './visitors/SvelteFragment.js';
import { SvelteHead } from './visitors/SvelteHead.js';
import { SvelteSelf } from './visitors/SvelteSelf.js';
import { Text } from './visitors/Text.js';
import { TitleElement } from './visitors/TitleElement.js';
import { UpdateExpression } from './visitors/UpdateExpression.js';
import { VariableDeclarator } from './visitors/VariableDeclarator.js';
/**
* @type {Visitors}
*/
const visitors = {
_(node, { state, next, path }) {
const parent = path.at(-1);
/** @type {string[]} */
const ignores = [];
if (parent?.type === 'Fragment' && node.type !== 'Comment' && node.type !== 'Text') {
const idx = parent.nodes.indexOf(/** @type {any} */ (node));
for (let i = idx - 1; i >= 0; i--) {
const prev = parent.nodes[i];
if (prev.type === 'Comment') {
ignores.push(
...extract_svelte_ignore(
prev.start + 4 /* '<!--'.length */,
prev.data,
state.analysis.runes
)
);
} else if (prev.type !== 'Text') {
break;
}
}
} else {
const comments = /** @type {any} */ (node).leadingComments;
if (comments) {
for (const comment of comments) {
ignores.push(
...extract_svelte_ignore(
comment.start + 2 /* '//'.length */,
comment.value,
state.analysis.runes
)
);
}
}
}
if (ignores.length > 0) {
push_ignore(ignores);
}
ignore_map.set(node, structuredClone(ignore_stack));
const scope = state.scopes.get(node);
next(scope !== undefined && scope !== state.scope ? { ...state, scope } : state);
if (ignores.length > 0) {
pop_ignore();
}
},
ArrowFunctionExpression,
AssignmentExpression,
Attribute,
AwaitBlock,
BindDirective,
CallExpression,
ClassBody,
ClassDeclaration,
ClassDirective,
Component,
ConstTag,
DebugTag,
EachBlock,
ExportDefaultDeclaration,
ExportNamedDeclaration,
ExportSpecifier,
ExpressionStatement,
ExpressionTag,
FunctionDeclaration,
FunctionExpression,
HtmlTag,
Identifier,
IfBlock,
ImportDeclaration,
KeyBlock,
LabeledStatement,
LetDirective,
MemberExpression,
NewExpression,
OnDirective,
RegularElement,
RenderTag,
SlotElement,
SnippetBlock,
SpreadAttribute,
StyleDirective,
SvelteHead,
SvelteElement,
SvelteFragment,
SvelteComponent,
SvelteSelf,
Text,
TitleElement,
UpdateExpression,
VariableDeclarator
};
/**
* @param {Script | null} script

@@ -81,153 +208,2 @@ * @param {ScopeRoot} root

/**
* Checks if given event attribute can be delegated/hoisted and returns the corresponding info if so
* @param {string} event_name
* @param {Expression | null} handler
* @param {Context} context
* @returns {null | DelegatedEvent}
*/
function get_delegated_event(event_name, handler, context) {
// Handle delegated event handlers. Bail-out if not a delegated event.
if (!handler || !DelegatedEvents.includes(event_name)) {
return null;
}
// If we are not working with a RegularElement, then bail-out.
const element = context.path.at(-1);
if (element?.type !== 'RegularElement') {
return null;
}
/** @type {DelegatedEvent} */
const non_hoistable = { type: 'non-hoistable' };
/** @type {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression | null} */
let target_function = null;
let binding = null;
if (element.metadata.has_spread) {
// event attribute becomes part of the dynamic spread array
return non_hoistable;
}
if (handler.type === 'ArrowFunctionExpression' || handler.type === 'FunctionExpression') {
target_function = handler;
} else if (handler.type === 'Identifier') {
binding = context.state.scope.get(handler.name);
if (context.state.analysis.module.scope.references.has(handler.name)) {
// If a binding with the same name is referenced in the module scope (even if not declared there), bail-out
return non_hoistable;
}
if (binding != null) {
for (const { path } of binding.references) {
const parent = path.at(-1);
if (parent == null) return non_hoistable;
const grandparent = path.at(-2);
/** @type {RegularElement | null} */
let element = null;
/** @type {string | null} */
let event_name = null;
if (parent.type === 'OnDirective') {
element = /** @type {RegularElement} */ (grandparent);
event_name = parent.name;
} else if (
parent.type === 'ExpressionTag' &&
grandparent?.type === 'Attribute' &&
is_event_attribute(grandparent)
) {
element = /** @type {RegularElement} */ (path.at(-3));
const attribute = /** @type {Attribute} */ (grandparent);
event_name = get_attribute_event_name(attribute.name);
}
if (element && event_name) {
if (
element.type !== 'RegularElement' ||
determine_element_spread(element).metadata.has_spread ||
!DelegatedEvents.includes(event_name)
) {
return non_hoistable;
}
} else if (parent.type !== 'FunctionDeclaration' && parent.type !== 'VariableDeclarator') {
return non_hoistable;
}
}
}
// If the binding is exported, bail-out
if (context.state.analysis.exports.find((node) => node.name === handler.name)) {
return non_hoistable;
}
if (binding !== null && binding.initial !== null && !binding.mutated && !binding.is_called) {
const binding_type = binding.initial.type;
if (
binding_type === 'ArrowFunctionExpression' ||
binding_type === 'FunctionDeclaration' ||
binding_type === 'FunctionExpression'
) {
target_function = binding.initial;
}
}
}
// If we can't find a function, bail-out
if (target_function == null) return non_hoistable;
// If the function is marked as non-hoistable, bail-out
if (target_function.metadata.hoistable === 'impossible') return non_hoistable;
// If the function has more than one arg, then bail-out
if (target_function.params.length > 1) return non_hoistable;
const visited_references = new Set();
const scope = target_function.metadata.scope;
for (const [reference] of scope.references) {
// Bail-out if the arguments keyword is used
if (reference === 'arguments') return non_hoistable;
// Bail-out if references a store subscription
if (scope.get(`$${reference}`)?.kind === 'store_sub') return non_hoistable;
const binding = scope.get(reference);
const local_binding = context.state.scope.get(reference);
// If we are referencing a binding that is shadowed in another scope then bail out.
if (local_binding !== null && binding !== null && local_binding.node !== binding.node) {
return non_hoistable;
}
// If we have multiple references to the same store using $ prefix, bail out.
if (
binding !== null &&
binding.kind === 'store_sub' &&
visited_references.has(reference.slice(1))
) {
return non_hoistable;
}
// If we reference the index within an each block, then bail-out.
if (binding !== null && binding.initial?.type === 'EachBlock') return non_hoistable;
if (
binding !== null &&
// Bail-out if the the binding is a rest param
(binding.declaration_kind === 'rest_param' ||
// Bail-out if we reference anything from the EachBlock (for now) that mutates in non-runes mode,
(((!context.state.analysis.runes && binding.kind === 'each') ||
// or any normal not reactive bindings that are mutated.
binding.kind === 'normal' ||
// or any reactive imports (those are rewritten) (can only happen in legacy mode)
binding.kind === 'legacy_reactive_import') &&
binding.mutated))
) {
return non_hoistable;
}
visited_references.add(reference);
}
return { type: 'hoistable', function: target_function };
}
/**
* @param {Program} ast

@@ -249,5 +225,9 @@ * @param {ValidatedModuleCompileOptions} options

/** @type {Node} */ (ast),
{ scope, analysis: { runes: true } },
// @ts-expect-error TODO clean this mess up
merge(set_scope(scopes), validation_runes_js, runes_scope_js_tweaker)
{
scope,
scopes,
// @ts-expect-error TODO
analysis: { runes: true }
},
visitors
);

@@ -447,2 +427,3 @@

scope,
scopes,
analysis,

@@ -457,10 +438,9 @@ options,

private_derived_state: [],
function_depth: scope.function_depth
function_depth: scope.function_depth,
instance_scope: instance.scope,
reactive_statement: null,
reactive_statements: new Map()
};
walk(
/** @type {SvelteNode} */ (ast),
state,
merge(set_scope(scopes), validation_runes, runes_scope_tweaker, common_visitors)
);
walk(/** @type {SvelteNode} */ (ast), state, visitors);
}

@@ -486,3 +466,3 @@

type === 'BindDirective' &&
/** @type {BindDirective} */ (path[i]).name === 'this'
/** @type {import('#compiler').BindDirective} */ (path[i]).name === 'this'
) {

@@ -516,5 +496,6 @@ for (let j = i - 1; j >= 0; j -= 1) {

for (const { ast, scope, scopes } of [module, instance, template]) {
/** @type {LegacyAnalysisState} */
/** @type {AnalysisState} */
const state = {
scope,
scopes,
analysis,

@@ -535,8 +516,3 @@ options,

walk(
/** @type {SvelteNode} */ (ast),
state,
// @ts-expect-error TODO
merge(set_scope(scopes), validation_legacy, legacy_scope_tweaker, common_visitors)
);
walk(/** @type {SvelteNode} */ (ast), state, visitors);
}

@@ -598,3 +574,3 @@

/** @type {Attribute | undefined} */
/** @type {import('#compiler').Attribute | undefined} */
let class_attribute = undefined;

@@ -618,3 +594,3 @@

} else {
/** @type {Text} */
/** @type {import('#compiler').Text} */
const css_text = {

@@ -659,952 +635,7 @@ type: 'Text',

/** @type {Visitors<LegacyAnalysisState>} */
const legacy_scope_tweaker = {
LabeledStatement(node, { next, path, state }) {
if (
state.ast_type !== 'instance' ||
node.label.name !== '$' ||
/** @type {SvelteNode} */ (path.at(-1)).type !== 'Program'
) {
return next();
}
// Find all dependencies of this `$: {...}` statement
/** @type {ReactiveStatement} */
const reactive_statement = {
assignments: new Set(),
dependencies: []
};
next({ ...state, reactive_statement, function_depth: state.scope.function_depth + 1 });
// Every referenced binding becomes a dependency, unless it's on
// the left-hand side of an `=` assignment
for (const [name, nodes] of state.scope.references) {
const binding = state.scope.get(name);
if (binding === null) continue;
for (const { node, path } of nodes) {
/** @type {Expression} */
let left = node;
let i = path.length - 1;
let parent = /** @type {Expression} */ (path.at(i));
while (parent.type === 'MemberExpression') {
left = parent;
parent = /** @type {Expression} */ (path.at(--i));
}
if (
parent.type === 'AssignmentExpression' &&
parent.operator === '=' &&
parent.left === left
) {
continue;
}
reactive_statement.dependencies.push(binding);
break;
}
}
state.reactive_statements.set(node, reactive_statement);
// Ideally this would be in the validation file, but that isn't possible because this visitor
// calls "next" before setting the reactive statements.
if (
reactive_statement.dependencies.length &&
reactive_statement.dependencies.every(
(d) => d.scope === state.analysis.module.scope && d.declaration_kind !== 'const'
)
) {
w.reactive_declaration_module_script(node);
}
if (
node.body.type === 'ExpressionStatement' &&
node.body.expression.type === 'AssignmentExpression'
) {
let ids = extract_identifiers(node.body.expression.left);
if (node.body.expression.left.type === 'MemberExpression') {
const id = object(node.body.expression.left);
if (id !== null) {
ids = [id];
}
}
for (const id of ids) {
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) {
const id = node.left.type === 'MemberExpression' ? object(node.left) : node.left;
if (id !== null) {
for (const id of extract_identifiers(node.left)) {
const binding = state.scope.get(id.name);
if (binding) {
state.reactive_statement.assignments.add(binding);
}
}
}
}
next();
},
UpdateExpression(node, { state, next }) {
if (state.reactive_statement) {
const id = node.argument.type === 'MemberExpression' ? object(node.argument) : node.argument;
if (id?.type === 'Identifier') {
const binding = state.scope.get(id.name);
if (binding) {
state.reactive_statement.assignments.add(binding);
}
}
}
next();
},
Identifier(node, { state, path }) {
const parent = /** @type {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?.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 = 'legacy_reactive_import';
}
} 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 if (binding?.kind === 'each' && binding.mutated) {
// Ensure that the array is marked as reactive even when only its entries are mutated
let i = path.length;
while (i--) {
const ancestor = path[i];
if (
ancestor.type === 'EachBlock' &&
state.analysis.template.scopes.get(ancestor)?.declarations.get(node.name) === binding
) {
for (const binding of ancestor.metadata.references) {
if (binding.kind === 'normal') {
binding.kind = 'state';
}
}
break;
}
}
}
}
},
ExportNamedDeclaration(node, { next, state }) {
if (state.ast_type !== 'instance') {
return next();
}
state.analysis.needs_props = true;
if (!node.declaration) {
for (const specifier of node.specifiers) {
const binding = /** @type {Binding} */ (state.scope.get(specifier.local.name));
if (
binding !== null &&
(binding.kind === 'state' ||
binding.kind === 'frozen_state' ||
(binding.kind === 'normal' &&
(binding.declaration_kind === 'let' || binding.declaration_kind === 'var')))
) {
binding.kind = 'bindable_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' ||
node.declaration.type === 'ClassDeclaration'
) {
state.analysis.exports.push({
name: /** @type {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 {Binding} */ (state.scope.get(id.name));
binding.kind = 'bindable_prop';
}
}
}
},
StyleDirective(node, { state }) {
// the case for node.value different from true is already covered by the Identifier visitor
if (node.value === true) {
// get the binding for node.name and change the binding to state
let binding = state.scope.get(node.name);
if (binding?.mutated && binding.kind === 'normal') {
binding.kind = 'state';
}
}
}
};
/** @type {Visitors} */
const runes_scope_js_tweaker = {
VariableDeclarator(node, { state }) {
if (node.init?.type !== 'CallExpression') return;
const rune = get_rune(node.init, state.scope);
if (rune === null) return;
const callee = node.init.callee;
if (callee.type !== 'Identifier' && callee.type !== 'MemberExpression') return;
if (
rune !== '$state' &&
rune !== '$state.frozen' &&
rune !== '$derived' &&
rune !== '$derived.by'
)
return;
for (const path of extract_paths(node.id)) {
// @ts-ignore this fails in CI for some insane reason
const binding = /** @type {Binding} */ (state.scope.get(path.node.name));
binding.kind =
rune === '$state' ? 'state' : rune === '$state.frozen' ? 'frozen_state' : 'derived';
}
}
};
/** @type {Visitors} */
const runes_scope_tweaker = {
CallExpression(node, { state, next }) {
const rune = get_rune(node, state.scope);
// `$inspect(foo)` should not trigger the `static-state-reference` warning
if (rune === '$inspect') {
next({ ...state, function_depth: state.function_depth + 1 });
}
},
VariableDeclarator(node, { state }) {
const init = node.init;
if (!init || init.type !== 'CallExpression') return;
const rune = get_rune(init, state.scope);
if (rune === null) return;
const callee = init.callee;
if (callee.type !== 'Identifier' && callee.type !== 'MemberExpression') return;
if (
rune !== '$state' &&
rune !== '$state.frozen' &&
rune !== '$derived' &&
rune !== '$derived.by' &&
rune !== '$props'
)
return;
for (const path of extract_paths(node.id)) {
// @ts-ignore this fails in CI for some insane reason
const binding = /** @type {Binding} */ (state.scope.get(path.node.name));
binding.kind =
rune === '$state'
? 'state'
: rune === '$state.frozen'
? 'frozen_state'
: rune === '$derived' || rune === '$derived.by'
? 'derived'
: path.is_rest
? 'rest_prop'
: 'prop';
}
if (rune === '$props') {
state.analysis.needs_props = true;
if (node.id.type === 'Identifier') {
const binding = /** @type {Binding} */ (state.scope.get(node.id.name));
binding.initial = null; // else would be $props()
binding.kind = 'rest_prop';
} else {
equal(node.id.type, 'ObjectPattern');
for (const property of node.id.properties) {
if (property.type !== 'Property') continue;
const name =
property.value.type === 'AssignmentPattern'
? /** @type {Identifier} */ (property.value.left).name
: /** @type {Identifier} */ (property.value).name;
const alias =
property.key.type === 'Identifier'
? property.key.name
: String(/** @type {Literal} */ (property.key).value);
let initial = property.value.type === 'AssignmentPattern' ? property.value.right : null;
const binding = /** @type {Binding} */ (state.scope.get(name));
binding.prop_alias = alias;
// rewire initial from $props() to the actual initial value, stripping $bindable() if necessary
if (
initial?.type === 'CallExpression' &&
initial.callee.type === 'Identifier' &&
initial.callee.name === '$bindable'
) {
binding.initial = /** @type {Expression | null} */ (initial.arguments[0] ?? null);
binding.kind = 'bindable_prop';
} else {
binding.initial = initial;
}
}
}
}
},
ExportSpecifier(node, { state }) {
if (state.ast_type !== 'instance') return;
state.analysis.exports.push({
name: node.local.name,
alias: node.exported.name
});
const binding = state.scope.get(node.local.name);
if (binding) binding.reassigned = true;
},
ExportNamedDeclaration(node, { next, state }) {
if (!node.declaration || state.ast_type !== 'instance') {
return next();
}
if (
node.declaration.type === 'FunctionDeclaration' ||
node.declaration.type === 'ClassDeclaration'
) {
state.analysis.exports.push({
name: /** @type {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 });
}
}
}
}
};
/**
* @param {CallExpression} node
* @param {Context} context
* @returns {boolean}
* @param {Map<import('estree').LabeledStatement, ReactiveStatement>} unsorted_reactive_declarations
*/
function is_known_safe_call(node, context) {
const callee = node.callee;
// String / Number / BigInt / Boolean casting calls
if (callee.type === 'Identifier') {
const name = callee.name;
const binding = context.state.scope.get(name);
if (
binding === null &&
(name === 'BigInt' || name === 'String' || name === 'Number' || name === 'Boolean')
) {
return true;
}
}
// TODO add more cases
return false;
}
/**
* @param {ArrowFunctionExpression | FunctionExpression | FunctionDeclaration} node
* @param {Context} context
*/
const function_visitor = (node, context) => {
// TODO retire this in favour of a more general solution based on bindings
node.metadata = {
// module context -> already hoisted
hoistable: context.state.ast_type === 'module' ? 'impossible' : false,
hoistable_params: [],
scope: context.state.scope
};
context.next({
...context.state,
function_depth: context.state.function_depth + 1
});
};
/**
* A 'safe' identifier means that the `foo` in `foo.bar` or `foo()` will not
* call functions that require component context to exist
* @param {Expression | Super} expression
* @param {Scope} scope
*/
function is_safe_identifier(expression, scope) {
let node = expression;
while (node.type === 'MemberExpression') node = node.object;
if (node.type !== 'Identifier') return false;
const binding = scope.get(node.name);
if (!binding) return true;
if (binding.kind === 'store_sub') {
return is_safe_identifier({ name: node.name.slice(1), type: 'Identifier' }, scope);
}
return (
binding.declaration_kind !== 'import' &&
binding.kind !== 'prop' &&
binding.kind !== 'bindable_prop' &&
binding.kind !== 'rest_prop'
);
}
/** @type {Visitors} */
const common_visitors = {
_(node, { state, next, path }) {
ignore_map.set(node, structuredClone(ignore_stack));
const parent = path.at(-1);
if (parent?.type === 'Fragment' && node.type !== 'Comment' && node.type !== 'Text') {
const idx = parent.nodes.indexOf(/** @type {any} */ (node));
/** @type {string[]} */
const ignores = [];
for (let i = idx - 1; i >= 0; i--) {
const prev = parent.nodes[i];
if (prev.type === 'Comment') {
ignores.push(
...extract_svelte_ignore(
prev.start + 4 /* '<!--'.length */,
prev.data,
state.analysis.runes
)
);
} else if (prev.type !== 'Text') {
break;
}
}
if (ignores.length > 0) {
push_ignore(ignores);
ignore_map.set(node, structuredClone(ignore_stack));
next();
pop_ignore();
}
} else {
const comments = /** @type {any} */ (node).leadingComments;
if (comments) {
/** @type {string[]} */
const ignores = [];
for (const comment of comments) {
ignores.push(
...extract_svelte_ignore(
comment.start + 2 /* '//'.length */,
comment.value,
state.analysis.runes
)
);
}
if (ignores.length > 0) {
push_ignore(ignores);
ignore_map.set(node, structuredClone(ignore_stack));
next();
pop_ignore();
}
}
}
},
Attribute(node, context) {
if (node.value === true) return;
context.next();
for (const chunk of get_attribute_chunks(node.value)) {
if (chunk.type !== 'ExpressionTag') continue;
if (
chunk.expression.type === 'FunctionExpression' ||
chunk.expression.type === 'ArrowFunctionExpression'
) {
continue;
}
node.metadata.expression.has_state ||= chunk.metadata.expression.has_state;
node.metadata.expression.has_call ||= chunk.metadata.expression.has_call;
}
if (is_event_attribute(node)) {
const parent = context.path.at(-1);
if (parent?.type === 'RegularElement' || parent?.type === 'SvelteElement') {
context.state.analysis.uses_event_attributes = true;
}
const expression = get_attribute_expression(node);
const delegated_event = get_delegated_event(node.name.slice(2), expression, context);
if (delegated_event !== null) {
if (delegated_event.type === 'hoistable') {
delegated_event.function.metadata.hoistable = true;
}
node.metadata.delegated = delegated_event;
}
}
},
ClassDirective(node, context) {
context.next({ ...context.state, expression: node.metadata.expression });
},
SpreadAttribute(node, context) {
context.next({ ...context.state, expression: node.metadata.expression });
},
SlotElement(node, context) {
let name = 'default';
for (const attr of node.attributes) {
if (attr.type === 'Attribute' && attr.name === 'name' && is_text_attribute(attr)) {
name = attr.value[0].data;
break;
}
}
context.state.analysis.slot_names.set(name, node);
},
StyleDirective(node, context) {
if (node.value === true) {
const binding = context.state.scope.get(node.name);
if (binding?.kind !== 'normal') {
node.metadata.expression.has_state = true;
}
} else {
context.next();
for (const chunk of get_attribute_chunks(node.value)) {
if (chunk.type !== 'ExpressionTag') continue;
node.metadata.expression.has_state ||= chunk.metadata.expression.has_state;
node.metadata.expression.has_call ||= chunk.metadata.expression.has_call;
}
}
},
ExpressionTag(node, context) {
context.next({ ...context.state, expression: node.metadata.expression });
},
Identifier(node, context) {
const parent = /** @type {Node} */ (context.path.at(-1));
if (!is_reference(node, parent)) return;
if (node.name === '$$slots') {
context.state.analysis.uses_slots = true;
return;
}
// If we are using arguments outside of a function, then throw an error
if (
node.name === 'arguments' &&
context.path.every((n) => n.type !== 'FunctionDeclaration' && n.type !== 'FunctionExpression')
) {
e.invalid_arguments_usage(node);
}
const binding = context.state.scope.get(node.name);
if (binding && context.state.expression) {
context.state.expression.dependencies.add(binding);
if (binding.kind !== 'normal') {
context.state.expression.has_state = true;
}
}
// if no binding, means some global variable
if (binding && binding.kind !== 'normal') {
if (context.state.expression) {
context.state.expression.has_state = true;
}
// TODO it would be better to just bail out when we hit the ExportSpecifier node but that's
// not currently possibly because of our visitor merging, which I desperately want to nuke
const is_export_specifier =
/** @type {SvelteNode} */ (context.path.at(-1)).type === 'ExportSpecifier';
if (
context.state.analysis.runes &&
node !== binding.node &&
context.state.function_depth === binding.scope.function_depth &&
// If we have $state that can be proxied or frozen and isn't re-assigned, then that means
// it's likely not using a primitive value and thus this warning isn't that helpful.
((binding.kind === 'state' &&
(binding.reassigned ||
(binding.initial?.type === 'CallExpression' &&
binding.initial.arguments.length === 1 &&
binding.initial.arguments[0].type !== 'SpreadElement' &&
!should_proxy_or_freeze(binding.initial.arguments[0], context.state.scope)))) ||
binding.kind === 'frozen_state' ||
binding.kind === 'derived') &&
!is_export_specifier &&
// We're only concerned with reads here
(parent.type !== 'AssignmentExpression' || parent.left !== node) &&
parent.type !== 'UpdateExpression'
) {
w.state_referenced_locally(node);
}
}
},
CallExpression(node, context) {
const { expression, render_tag } = context.state;
if (expression && !is_known_safe_call(node, context)) {
expression.has_call = true;
expression.has_state = true;
}
if (render_tag) {
// Find out which of the render tag arguments contains this call expression
const arg_idx = unwrap_optional(render_tag.expression).arguments.findIndex(
(arg) => arg === node || context.path.includes(arg)
);
// -1 if this is the call expression of the render tag itself
if (arg_idx !== -1) {
render_tag.metadata.args_with_call_expression.add(arg_idx);
}
}
const callee = node.callee;
const rune = get_rune(node, context.state.scope);
if (callee.type === 'Identifier') {
const binding = context.state.scope.get(callee.name);
if (binding !== null) {
binding.is_called = true;
}
if (rune === '$derived') {
// special case — `$derived(foo)` is treated as `$derived(() => foo)`
// for the purposes of identifying static state references
context.next({
...context.state,
function_depth: context.state.function_depth + 1
});
return;
}
}
if (rune === '$effect' || rune === '$effect.pre') {
// `$effect` needs context because Svelte needs to know whether it should re-run
// effects that invalidate themselves, and that's determined by whether we're in runes mode
context.state.analysis.needs_context = true;
} else if (rune === null) {
if (!is_safe_identifier(callee, context.state.scope)) {
context.state.analysis.needs_context = true;
}
}
context.next();
},
MemberExpression(node, context) {
if (context.state.expression) {
context.state.expression.has_state = true;
}
if (!is_safe_identifier(node, context.state.scope)) {
context.state.analysis.needs_context = true;
}
context.next();
},
OnDirective(node, { state, path, next }) {
const parent = path.at(-1);
if (parent?.type === 'SvelteElement' || parent?.type === 'RegularElement') {
state.analysis.event_directive_node ??= node;
}
next({ ...state, expression: node.metadata.expression });
},
BindDirective(node, context) {
let i = context.path.length;
while (i--) {
const parent = context.path[i];
if (
parent.type === 'Component' ||
parent.type === 'SvelteComponent' ||
parent.type === 'SvelteSelf'
) {
if (node.name !== 'this') {
context.state.analysis.uses_component_bindings = true;
}
break;
} else if (is_element_node(parent)) {
break;
}
}
if (node.name !== 'group') return;
// Traverse the path upwards and find all EachBlocks who are (indirectly) contributing to bind:group,
// i.e. one of their declarations is referenced in the binding. This allows group bindings to work
// correctly when referencing a variable declared in an EachBlock by using the index of the each block
// entries as keys.
i = context.path.length;
const each_blocks = [];
const [keypath, expression_ids] = extract_all_identifiers_from_expression(node.expression);
let ids = expression_ids;
while (i--) {
const parent = context.path[i];
if (parent.type === 'EachBlock') {
const references = ids.filter((id) => parent.metadata.declarations.has(id.name));
if (references.length > 0) {
parent.metadata.contains_group_binding = true;
for (const binding of parent.metadata.references) {
binding.mutated = true;
}
each_blocks.push(parent);
ids = ids.filter((id) => !references.includes(id));
ids.push(...extract_all_identifiers_from_expression(parent.expression)[1]);
}
}
}
// The identifiers that make up the binding expression form they key for the binding group.
// If the same identifiers in the same order are used in another bind:group, they will be in the same group.
// (there's an edge case where `bind:group={a[i]}` will be in a different group than `bind:group={a[j]}` even when i == j,
// but this is a limitation of the current static analysis we do; it also never worked in Svelte 4)
const bindings = expression_ids.map((id) => context.state.scope.get(id.name));
let group_name;
outer: for (const [[key, b], group] of context.state.analysis.binding_groups) {
if (b.length !== bindings.length || key !== keypath) continue;
for (let i = 0; i < bindings.length; i++) {
if (bindings[i] !== b[i]) continue outer;
}
group_name = group;
}
if (!group_name) {
group_name = context.state.scope.root.unique('binding_group');
context.state.analysis.binding_groups.set([keypath, bindings], group_name);
}
node.metadata = {
binding_group_name: group_name,
parent_each_blocks: each_blocks
};
},
ArrowFunctionExpression: function_visitor,
FunctionExpression: function_visitor,
FunctionDeclaration: function_visitor,
RegularElement(node, context) {
if (context.state.options.namespace !== 'foreign') {
if (SVGElements.includes(node.name)) node.metadata.svg = true;
else if (MathMLElements.includes(node.name)) node.metadata.mathml = true;
}
determine_element_spread(node);
// Special case: Move the children of <textarea> into a value attribute if they are dynamic
if (
context.state.options.namespace !== 'foreign' &&
node.name === 'textarea' &&
node.fragment.nodes.length > 0
) {
if (node.fragment.nodes.length > 1 || node.fragment.nodes[0].type !== 'Text') {
const first = node.fragment.nodes[0];
if (first.type === 'Text') {
// The leading newline character needs to be stripped because of a qirk:
// It is ignored by browsers if the tag and its contents are set through
// innerHTML, but we're now setting it through the value property at which
// point it is _not_ ignored, so we need to strip it ourselves.
// see https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions
// see https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element
first.data = first.data.replace(regex_starts_with_newline, '');
first.raw = first.raw.replace(regex_starts_with_newline, '');
}
node.attributes.push(
create_attribute(
'value',
/** @type {Text} */ (node.fragment.nodes.at(0)).start,
/** @type {Text} */ (node.fragment.nodes.at(-1)).end,
// @ts-ignore
node.fragment.nodes
)
);
node.fragment.nodes = [];
}
}
// Special case: single expression tag child of option element -> add "fake" attribute
// to ensure that value types are the same (else for example numbers would be strings)
if (
context.state.options.namespace !== 'foreign' &&
node.name === 'option' &&
node.fragment.nodes?.length === 1 &&
node.fragment.nodes[0].type === 'ExpressionTag' &&
!node.attributes.some(
(attribute) => attribute.type === 'Attribute' && attribute.name === 'value'
)
) {
const child = node.fragment.nodes[0];
node.attributes.push(create_attribute('value', child.start, child.end, [child]));
}
context.state.analysis.elements.push(node);
},
SvelteElement(node, context) {
context.state.analysis.elements.push(node);
for (const attribute of node.attributes) {
if (attribute.type === 'Attribute') {
if (attribute.name === 'xmlns' && is_text_attribute(attribute)) {
node.metadata.svg = attribute.value[0].data === namespace_svg;
node.metadata.mathml = attribute.value[0].data === namespace_mathml;
return;
}
}
}
for (let i = context.path.length - 1; i >= 0; i--) {
const ancestor = context.path[i];
if (
ancestor.type === 'Component' ||
ancestor.type === 'SvelteComponent' ||
ancestor.type === 'SvelteFragment' ||
ancestor.type === 'SnippetBlock'
) {
// Inside a slot or a snippet -> this resets the namespace, so assume the component namespace
node.metadata.svg = context.state.options.namespace === 'svg';
node.metadata.mathml = context.state.options.namespace === 'mathml';
return;
}
if (ancestor.type === 'SvelteElement' || ancestor.type === 'RegularElement') {
node.metadata.svg =
ancestor.type === 'RegularElement' && ancestor.name === 'foreignObject'
? false
: ancestor.metadata.svg;
node.metadata.mathml =
ancestor.type === 'RegularElement' && ancestor.name === 'foreignObject'
? false
: ancestor.metadata.mathml;
return;
}
}
},
Component(node, context) {
const binding = context.state.scope.get(
node.name.includes('.') ? node.name.slice(0, node.name.indexOf('.')) : node.name
);
node.metadata.dynamic = binding !== null && binding.kind !== 'normal';
},
RenderTag(node, context) {
context.next({ ...context.state, render_tag: node });
},
EachBlock(node) {
if (node.key) {
// treat `{#each items as item, i (i)}` as a normal indexed block, everything else as keyed
node.metadata.keyed =
node.key.type !== 'Identifier' || !node.index || node.key.name !== node.index;
}
}
};
/**
* @param {RegularElement} node
*/
function determine_element_spread(node) {
let has_spread = false;
for (const attribute of node.attributes) {
if (!has_spread && attribute.type === 'SpreadAttribute') {
has_spread = true;
}
}
node.metadata.has_spread = has_spread;
return node;
}
/**
* @param {string} event_name
*/
function get_attribute_event_name(event_name) {
if (is_capture_event(event_name, 'include-on')) {
event_name = event_name.slice(0, -7);
}
event_name = event_name.slice(2);
return event_name;
}
/**
* @param {Map<LabeledStatement, ReactiveStatement>} unsorted_reactive_declarations
*/
function order_reactive_statements(unsorted_reactive_declarations) {
/** @typedef {[LabeledStatement, ReactiveStatement]} Tuple */
/** @typedef {[import('estree').LabeledStatement, ReactiveStatement]} Tuple */

@@ -1642,3 +673,3 @@ /** @type {Map<string, Array<Tuple>>} */

// We use a map and take advantage of the fact that the spec says insertion order is preserved when iterating
/** @type {Map<LabeledStatement, ReactiveStatement>} */
/** @type {Map<import('estree').LabeledStatement, ReactiveStatement>} */
const reactive_declarations = new Map();

@@ -1648,3 +679,3 @@

*
* @param {LabeledStatement} node
* @param {import('estree').LabeledStatement} node
* @param {ReactiveStatement} declaration

@@ -1651,0 +682,0 @@ * @returns

import type { Scope } from '../scope.js';
import type { ComponentAnalysis, ReactiveStatement } from '../types.js';
import type {
ClassDirective,
ExpressionMetadata,
ExpressionTag,
OnDirective,
RenderTag,
SpreadAttribute,
SvelteNode,
ValidatedCompileOptions
} from '#compiler';
import type { ExpressionMetadata, RenderTag, SvelteNode, ValidatedCompileOptions } from '#compiler';
import type { LabeledStatement } from 'estree';

@@ -17,2 +8,3 @@

scope: Scope;
scopes: Map<SvelteNode, Scope>;
analysis: ComponentAnalysis;

@@ -31,5 +23,4 @@ options: ValidatedCompileOptions;

function_depth: number;
}
export interface LegacyAnalysisState extends AnalysisState {
// legacy stuff
instance_scope: Scope;

@@ -36,0 +27,0 @@ reactive_statement: null | ReactiveStatement;

@@ -12,3 +12,3 @@ import type {

} from '#compiler';
import type { Identifier, LabeledStatement, Program, Statement, VariableDeclaration } from 'estree';
import type { Identifier, LabeledStatement, Program, VariableDeclaration } from 'estree';
import type { Scope, ScopeRoot } from './scope.js';

@@ -15,0 +15,0 @@

@@ -11,2 +11,3 @@ /** @import { Visitors, Context } from 'zimmerframe' */

// TODO get rid of this
/**

@@ -13,0 +14,0 @@ * @template {{ type: string }} T

@@ -142,2 +142,3 @@ /** @import { Attribute, Text, ExpressionTag, SvelteNode } from '#compiler' */

* Extracts all identifiers and a stringified keypath from an expression.
* TODO replace this with `expression.dependencies`
* @param {ESTree.Expression} expr

@@ -144,0 +145,0 @@ * @returns {[keypath: string, ids: ESTree.Identifier[]]}

@@ -229,6 +229,6 @@ // This should contain all the public interfaces (not all of them are actually importable, check current Svelte for which ones are).

*/
export type ComponentProps<Comp extends SvelteComponent | Component<any>> =
export type ComponentProps<Comp extends SvelteComponent | Component<any, any>> =
Comp extends SvelteComponent<infer Props>
? Props
: Comp extends Component<infer Props>
: Comp extends Component<infer Props, any>
? Props

@@ -235,0 +235,0 @@ : never;

@@ -9,3 +9,3 @@ // generated during release, do not modify

*/
export const VERSION = '5.0.0-next.202';
export const VERSION = '5.0.0-next.203';
export const PUBLIC_VERSION = '5';

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc