Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fast-ducks-drop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"svelte": patch
---

perf: use walk_readonly for analysis-phase AST walks
12 changes: 6 additions & 6 deletions packages/svelte/src/compiler/migrate/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/** @import { VariableDeclarator, Node, Identifier, AssignmentExpression, LabeledStatement, ExpressionStatement } from 'estree' */
/** @import { Visitors } from 'zimmerframe' */
/** @import { ReadonlyVisitors } from 'zimmerframe' */
/** @import { ComponentAnalysis } from '../phases/types.js' */
/** @import { Scope } from '../phases/scope.js' */
/** @import { AST, Binding, ValidatedCompileOptions } from '#compiler' */
import MagicString from 'magic-string';
import { walk } from 'zimmerframe';
import { walk_readonly } from 'zimmerframe';
import { parse } from '../phases/1-parse/index.js';
import { regex_valid_component_name } from '../phases/1-parse/state/element.js';
import { analyze_component } from '../phases/2-analyze/index.js';
Expand Down Expand Up @@ -213,11 +213,11 @@ export function migrate(source, { filename, use_ts } = {}) {
}

if (parsed.instance) {
walk(parsed.instance.content, state, instance_script);
walk_readonly(parsed.instance.content, state, instance_script);
}

state = { ...state, scope: analysis.template.scope };
walk(parsed.fragment, state, template);
walk_readonly(parsed.fragment, state, template);

let insertion_point = parsed.instance
? /** @type {number} */ (parsed.instance.content.start)
Expand Down Expand Up @@ -481,7 +481,7 @@ export function migrate(source, { filename, use_ts } = {}) {
* }} State
*/

/** @type {Visitors<AST.SvelteNode, State>} */
/** @type {ReadonlyVisitors<AST.SvelteNode, State>} */
const instance_script = {
_(node, { state, next }) {
// @ts-expect-error
Expand Down Expand Up @@ -1052,7 +1052,7 @@ function trim_block(state, start, end) {
}
}

/** @type {Visitors<AST.SvelteNode, State>} */
/** @type {ReadonlyVisitors<AST.SvelteNode, State>} */
const template = {
Identifier(node, { state, path }) {
handle_identifier(node, state, path);
Expand Down
12 changes: 6 additions & 6 deletions packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/** @import { ComponentAnalysis } from '../../types.js' */
/** @import { AST } from '#compiler' */
/** @import { Visitors } from 'zimmerframe' */
import { walk } from 'zimmerframe';
/** @import { ReadonlyVisitors } from 'zimmerframe' */
import { walk_readonly } from 'zimmerframe';
import * as e from '../../../errors.js';
import { is_keyframes_node } from '../../css.js';
import { is_global, is_unscoped_pseudo_class } from './utils.js';
Expand All @@ -15,7 +15,7 @@ import { is_global, is_unscoped_pseudo_class } from './utils.js';
*/

/**
* @typedef {Visitors<AST.CSS.Node, CssState>} CssVisitors
* @typedef {ReadonlyVisitors<AST.CSS.Node, CssState>} CssVisitors
*/

/**
Expand Down Expand Up @@ -183,7 +183,7 @@ const css_visitors = {
if (node.metadata.is_global_like || node.metadata.is_global) {
// So that nested selectors like `:root:not(.x)` are not marked as unused
for (const child of node.selectors) {
walk(/** @type {AST.CSS.Node} */ (child), null, {
walk_readonly(/** @type {AST.CSS.Node} */ (child), null, {
ComplexSelector(node, context) {
node.metadata.used = true;
context.next();
Expand Down Expand Up @@ -222,7 +222,7 @@ const css_visitors = {
node.metadata.is_global_block = is_global_block = true;

for (let i = 1; i < child.selectors.length; i++) {
walk(/** @type {AST.CSS.Node} */ (child.selectors[i]), null, {
walk_readonly(/** @type {AST.CSS.Node} */ (child.selectors[i]), null, {
ComplexSelector(node) {
node.metadata.used = true;
}
Expand Down Expand Up @@ -327,5 +327,5 @@ export function analyze_css(stylesheet, analysis) {
analysis
};

walk(stylesheet, css_state, css_visitors);
walk_readonly(stylesheet, css_state, css_visitors);
}
14 changes: 7 additions & 7 deletions packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/** @import * as Compiler from '#compiler' */
import { walk } from 'zimmerframe';
import { walk_readonly } from 'zimmerframe';
import {
get_parent_rules,
get_possible_values,
Expand Down Expand Up @@ -128,7 +128,7 @@ const seen = new Set();
* @param {Iterable<Compiler.AST.RegularElement | Compiler.AST.SvelteElement>} elements
*/
export function prune(stylesheet, elements) {
walk(/** @type {Compiler.AST.CSS.Node} */ (stylesheet), null, {
walk_readonly(/** @type {Compiler.AST.CSS.Node} */ (stylesheet), null, {
Rule(node, context) {
if (node.metadata.is_global_block) {
context.visit(node.prelude);
Expand Down Expand Up @@ -176,7 +176,7 @@ function get_relative_selectors(node) {

// nesting could be inside pseudo classes like :is, :has or :where
for (let selector of selectors) {
walk(selector, null, {
walk_readonly(selector, null, {
// @ts-ignore
NestingSelector() {
has_explicit_nesting_selector = true;
Expand Down Expand Up @@ -495,7 +495,7 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
// with descendants, in which case we scope them all.
if (name === 'not' && selector.args) {
for (const complex_selector of selector.args.children) {
walk(complex_selector, null, {
walk_readonly(complex_selector, null, {
ComplexSelector(node, context) {
node.metadata.used = true;
context.next();
Expand Down Expand Up @@ -830,7 +830,7 @@ function get_ancestor_elements(node, adjacent_only, seen = new Set()) {
if (select_element && (!adjacent_only || is_direct_child)) {
/** @type {Compiler.AST.RegularElement | null} */
let selectedcontent_element = null;
walk(select_element, null, {
walk_readonly(select_element, null, {
RegularElement(child, context) {
if (child.name === 'selectedcontent') {
selectedcontent_element = child;
Expand Down Expand Up @@ -873,7 +873,7 @@ function get_descendant_elements(node, adjacent_only, seen = new Set()) {
* @param {Compiler.AST.SvelteNode} node
*/
function walk_children(node) {
walk(node, null, {
walk_readonly(node, null, {
_(node, context) {
if (node.type === 'RegularElement' || node.type === 'SvelteElement') {
descendants.push(node);
Expand Down Expand Up @@ -907,7 +907,7 @@ function get_descendant_elements(node, adjacent_only, seen = new Set()) {
);

if (select_element) {
walk(
walk_readonly(
select_element,
{ inside_option: false },
{
Expand Down
8 changes: 4 additions & 4 deletions packages/svelte/src/compiler/phases/2-analyze/css/css-warn.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
/** @import { Visitors } from 'zimmerframe' */
/** @import { ReadonlyVisitors } from 'zimmerframe' */
/** @import { AST } from '#compiler' */
import { walk } from 'zimmerframe';
import { walk_readonly } from 'zimmerframe';
import * as w from '../../../warnings.js';
import { is_keyframes_node } from '../../css.js';

/**
* @param {AST.CSS.StyleSheet} stylesheet
*/
export function warn_unused(stylesheet) {
walk(stylesheet, { stylesheet }, visitors);
walk_readonly(stylesheet, { stylesheet }, visitors);
}

/** @type {Visitors<AST.CSS.Node, { stylesheet: AST.CSS.StyleSheet }>} */
/** @type {ReadonlyVisitors<AST.CSS.Node, { stylesheet: AST.CSS.StyleSheet }>} */
const visitors = {
Atrule(node, context) {
if (!is_keyframes_node(node)) {
Expand Down
16 changes: 8 additions & 8 deletions packages/svelte/src/compiler/phases/2-analyze/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/** @import { Binding, AST, ValidatedCompileOptions, ValidatedModuleCompileOptions } from '#compiler' */
/** @import { AnalysisState, Visitors } from './types' */
/** @import { Analysis, ComponentAnalysis, Js, ReactiveStatement, Template } from '../types' */
import { walk } from 'zimmerframe';
import { walk_readonly } from 'zimmerframe';
import { parse } from '../1-parse/acorn.js';
import * as e from '../../errors.js';
import * as w from '../../warnings.js';
Expand Down Expand Up @@ -295,7 +295,7 @@ export function analyze_module(source, options) {
runes: true
});

walk(
walk_readonly(
/** @type {ESTree.Node} */ (ast),
{
scope,
Expand Down Expand Up @@ -637,15 +637,15 @@ export function analyze_component(root, source, options) {

// more legacy nonsense: if an `each` binding is reassigned/mutated,
// treat the expression as being mutated as well
walk(/** @type {AST.SvelteNode} */ (template.ast), null, {
walk_readonly(/** @type {AST.SvelteNode} */ (template.ast), null, {
EachBlock(node) {
const scope = /** @type {Scope} */ (template.scopes.get(node));

for (const binding of scope.declarations.values()) {
if (binding.updated) {
const state = { scope: /** @type {Scope} */ (scope.parent), scopes: template.scopes };

walk(node.expression, state, {
walk_readonly(node.expression, state, {
// @ts-expect-error
_: set_scope,
Identifier(node, context) {
Expand Down Expand Up @@ -722,7 +722,7 @@ export function analyze_component(root, source, options) {
derived_function_depth: -1
};

walk(/** @type {AST.SvelteNode} */ (ast), state, visitors);
walk_readonly(/** @type {AST.SvelteNode} */ (ast), state, visitors);
}

// warn on any nonstate declarations that are a) reassigned and b) referenced in the template
Expand Down Expand Up @@ -790,7 +790,7 @@ export function analyze_component(root, source, options) {
derived_function_depth: -1
};

walk(/** @type {AST.SvelteNode} */ (ast), state, visitors);
walk_readonly(/** @type {AST.SvelteNode} */ (ast), state, visitors);
}

for (const [name, binding] of instance.scope.declarations) {
Expand Down Expand Up @@ -952,7 +952,7 @@ function calculate_blockers(instance, analysis) {
if (seen.has(expression)) return;
seen.add(expression);

walk(
walk_readonly(
expression,
{ scope },
{
Expand Down Expand Up @@ -1005,7 +1005,7 @@ function calculate_blockers(instance, analysis) {
}
}

walk(
walk_readonly(
node,
{ scope },
{
Expand Down
12 changes: 4 additions & 8 deletions packages/svelte/src/compiler/phases/2-analyze/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,8 @@ export interface AnalysisState {
derived_function_depth: number;
}

export type Context<State extends AnalysisState = AnalysisState> = import('zimmerframe').Context<
AST.SvelteNode,
State
>;
export type Context<State extends AnalysisState = AnalysisState> =
import('zimmerframe').ReadonlyContext<AST.SvelteNode, State>;

export type Visitors<State extends AnalysisState = AnalysisState> = import('zimmerframe').Visitors<
AST.SvelteNode,
State
>;
export type Visitors<State extends AnalysisState = AnalysisState> =
import('zimmerframe').ReadonlyVisitors<AST.SvelteNode, State>;
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import {
} from '../../../../patterns.js';
import { is_event_attribute, is_text_attribute } from '../../../../../utils/ast.js';
import { list } from '../../../../../utils/string.js';
import { walk } from 'zimmerframe';
import { walk_readonly } from 'zimmerframe';
import fuzzymatch from '../../../../1-parse/utils/fuzzymatch.js';
import { is_content_editable_binding } from '../../../../../../utils.js';
import * as w from '../../../../../warnings.js';
Expand Down Expand Up @@ -456,7 +456,7 @@ export function check_element(node, context) {
/** @param {AST.TemplateNode} node */
const has_input_child = (node) => {
let has = false;
walk(
walk_readonly(
node,
{},
{
Expand Down
18 changes: 9 additions & 9 deletions packages/svelte/src/compiler/phases/scope.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/** @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 { ReadonlyContext, ReadonlyVisitor } from 'zimmerframe' */
/** @import { AST, BindingKind, DeclarationKind } from '#compiler' */
import is_reference from 'is-reference';
import { walk } from 'zimmerframe';
import { walk_readonly } from 'zimmerframe';
import { ExpressionMetadata } from './nodes.js';
import * as b from '#compiler/builders';
import * as e from '../errors.js';
Expand Down Expand Up @@ -915,7 +915,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
}

/**
* @type {Visitor<Node, State, AST.SvelteNode>}
* @type {ReadonlyVisitor<Node, State, AST.SvelteNode>}
*/
const create_block_scope = (node, { state, next }) => {
const scope = state.scope.child(true);
Expand All @@ -925,7 +925,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
};

/**
* @type {Visitor<AST.ElementLike, State, AST.SvelteNode>}
* @type {ReadonlyVisitor<AST.ElementLike, State, AST.SvelteNode>}
*/
const SvelteFragment = (node, { state, next }) => {
const scope = state.scope.child();
Expand All @@ -934,7 +934,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
};

/**
* @type {Visitor<AST.Component | AST.SvelteComponent | AST.SvelteSelf, State, AST.SvelteNode>}
* @type {ReadonlyVisitor<AST.Component | AST.SvelteComponent | AST.SvelteSelf, State, AST.SvelteNode>}
*/
const Component = (node, context) => {
node.metadata.scopes = {
Expand Down Expand Up @@ -975,7 +975,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
};

/**
* @type {Visitor<AST.AnimateDirective | AST.TransitionDirective | AST.UseDirective, State, AST.SvelteNode>}
* @type {ReadonlyVisitor<AST.AnimateDirective | AST.TransitionDirective | AST.UseDirective, State, AST.SvelteNode>}
*/
const SvelteDirective = (node, { state, path, visit }) => {
state.scope.reference(b.id(node.name.split('.')[0]), path);
Expand All @@ -987,7 +987,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {

let has_await = false;

walk(ast, state, {
walk_readonly(ast, state, {
AwaitExpression(node, context) {
// this doesn't _really_ belong here, but it allows us to
// automatically opt into runes mode on encountering
Expand Down Expand Up @@ -1203,7 +1203,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {

let inside_rest = false;
let is_rest_id = false;
walk(node.context, null, {
walk_readonly(node.context, null, {
Identifier(node) {
if (inside_rest && node === id) {
is_rest_id = true;
Expand Down Expand Up @@ -1376,7 +1376,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
/**
* @template {{ scope: Scope, scopes: Map<AST.SvelteNode, Scope> }} State
* @param {AST.SvelteNode} node
* @param {Context<AST.SvelteNode, State>} context
* @param {ReadonlyContext<AST.SvelteNode, State>} context
*/
export function set_scope(node, { next, state }) {
const scope = state.scopes.get(node);
Expand Down
6 changes: 3 additions & 3 deletions packages/svelte/src/compiler/utils/ast.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/** @import { AST, Scope } from '#compiler' */
/** @import * as ESTree from 'estree' */
import { walk } from 'zimmerframe';
import { walk_readonly } from 'zimmerframe';
Copy link
Copy Markdown
Contributor

@vercel vercel bot Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR imports non-existent walk_readonly, ReadonlyContext, and ReadonlyVisitors from zimmerframe, causing build failure

Fix on Vercel

import * as b from '#compiler/builders';

/**
Expand Down Expand Up @@ -149,7 +149,7 @@ export function extract_all_identifiers_from_expression(expr) {
/** @type {string[]} */
let keypath = [];

walk(
walk_readonly(
expr,
{},
{
Expand Down Expand Up @@ -616,7 +616,7 @@ export function build_assignment_value(operator, left, right) {
export function has_await_expression(node) {
let has_await = false;

walk(node, null, {
walk_readonly(node, null, {
AwaitExpression(_node, context) {
has_await = true;
context.stop();
Expand Down
Loading