diff --git a/packages/svelte/src/compiler/phases/2-analyze/validation.js b/packages/svelte/src/compiler/phases/2-analyze/validation.js index dc63d6e9c..5c416037f 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/validation.js +++ b/packages/svelte/src/compiler/phases/2-analyze/validation.js @@ -330,102 +330,6 @@ function validate_block_not_empty(node, context) { } } -/** - * @type {import('zimmerframe').Visitors} - */ -export const validation_runes_js = { - ImportDeclaration(node) { - if (typeof node.source.value === 'string' && node.source.value.startsWith('svelte/internal')) { - e.import_svelte_internal_forbidden(node); - } - }, - ExportSpecifier(node, { state }) { - validate_export(node, state.scope, node.local.name); - }, - ExportNamedDeclaration(node, { state, next }) { - if (node.declaration?.type !== 'VariableDeclaration') return; - - // visit children, so bindings are correctly initialised - next(); - - for (const declarator of node.declaration.declarations) { - for (const id of extract_identifiers(declarator.id)) { - validate_export(node, state.scope, id.name); - } - } - }, - CallExpression(node, { state, path }) { - if (get_rune(node, state.scope) === '$host') { - e.host_invalid_placement(node); - } - validate_call_expression(node, state.scope, path); - }, - VariableDeclarator(node, { state }) { - const init = node.init; - const rune = get_rune(init, state.scope); - - if (rune === null) return; - - const args = /** @type {import('estree').CallExpression} */ (init).arguments; - - if ((rune === '$derived' || rune === '$derived.by') && args.length !== 1) { - e.rune_invalid_arguments_length(node, rune, 'exactly one argument'); - } else if (rune === '$state' && args.length > 1) { - e.rune_invalid_arguments_length(node, rune, 'zero or one arguments'); - } else if (rune === '$props') { - e.props_invalid_placement(node); - } else if (rune === '$bindable') { - e.bindable_invalid_location(node); - } - }, - AssignmentExpression(node, { state }) { - validate_assignment(node, node.left, state); - }, - UpdateExpression(node, { state }) { - validate_assignment(node, node.argument, state); - }, - ClassBody(node, context) { - /** @type {string[]} */ - const private_derived_state = []; - - for (const definition of node.body) { - if ( - definition.type === 'PropertyDefinition' && - definition.key.type === 'PrivateIdentifier' && - definition.value?.type === 'CallExpression' - ) { - const rune = get_rune(definition.value, context.state.scope); - if (rune === '$derived' || rune === '$derived.by') { - private_derived_state.push(definition.key.name); - } - } - } - - context.next({ - ...context.state, - private_derived_state - }); - }, - ClassDeclaration(node, context) { - // In modules, we allow top-level module scope only, in components, we allow the component scope, - // which is function_depth of 1. With the exception of `new class` which is also not allowed at - // component scope level either. - const allowed_depth = context.state.ast_type === 'module' ? 0 : 1; - - if (context.state.scope.function_depth > allowed_depth) { - w.perf_avoid_nested_class(node); - } - }, - NewExpression(node, context) { - if ( - node.callee.type === 'ClassExpression' && - !can_hoist_inline_class_expression(node, context.state.ast_type, context.state.scope) - ) { - w.perf_avoid_inline_class(node); - } - } -}; - /** * @type {import('zimmerframe').Visitors} */ @@ -904,8 +808,7 @@ export const validation_legacy = merge(validation, a11y_validators, { }, UpdateExpression(node, { state }) { validate_assignment(node, node.argument, state); - }, - NewExpression: validation_runes_js.NewExpression + } }); /** @@ -1031,6 +934,102 @@ function ensure_no_module_import_conflict(node, state) { } } +/** + * @type {import('zimmerframe').Visitors} + */ +export const validation_runes_js = { + ImportDeclaration(node) { + if (typeof node.source.value === 'string' && node.source.value.startsWith('svelte/internal')) { + e.import_svelte_internal_forbidden(node); + } + }, + ExportSpecifier(node, { state }) { + validate_export(node, state.scope, node.local.name); + }, + ExportNamedDeclaration(node, { state, next }) { + if (node.declaration?.type !== 'VariableDeclaration') return; + + // visit children, so bindings are correctly initialised + next(); + + for (const declarator of node.declaration.declarations) { + for (const id of extract_identifiers(declarator.id)) { + validate_export(node, state.scope, id.name); + } + } + }, + CallExpression(node, { state, path }) { + if (get_rune(node, state.scope) === '$host') { + e.host_invalid_placement(node); + } + validate_call_expression(node, state.scope, path); + }, + VariableDeclarator(node, { state }) { + const init = node.init; + const rune = get_rune(init, state.scope); + + if (rune === null) return; + + const args = /** @type {import('estree').CallExpression} */ (init).arguments; + + if ((rune === '$derived' || rune === '$derived.by') && args.length !== 1) { + e.rune_invalid_arguments_length(node, rune, 'exactly one argument'); + } else if (rune === '$state' && args.length > 1) { + e.rune_invalid_arguments_length(node, rune, 'zero or one arguments'); + } else if (rune === '$props') { + e.props_invalid_placement(node); + } else if (rune === '$bindable') { + e.bindable_invalid_location(node); + } + }, + AssignmentExpression(node, { state }) { + validate_assignment(node, node.left, state); + }, + UpdateExpression(node, { state }) { + validate_assignment(node, node.argument, state); + }, + ClassBody(node, context) { + /** @type {string[]} */ + const private_derived_state = []; + + for (const definition of node.body) { + if ( + definition.type === 'PropertyDefinition' && + definition.key.type === 'PrivateIdentifier' && + definition.value?.type === 'CallExpression' + ) { + const rune = get_rune(definition.value, context.state.scope); + if (rune === '$derived' || rune === '$derived.by') { + private_derived_state.push(definition.key.name); + } + } + } + + context.next({ + ...context.state, + private_derived_state + }); + }, + ClassDeclaration(node, context) { + // In modules, we allow top-level module scope only, in components, we allow the component scope, + // which is function_depth of 1. With the exception of `new class` which is also not allowed at + // component scope level either. + const allowed_depth = context.state.ast_type === 'module' ? 0 : 1; + + if (context.state.scope.function_depth > allowed_depth) { + w.perf_avoid_nested_class(node); + } + }, + NewExpression(node, context) { + if ( + node.callee.type === 'ClassExpression' && + !can_hoist_inline_class_expression(node, context.state.scope) + ) { + w.perf_avoid_inline_class(node); + } + } +}; + /** * @param {import('../../errors.js').NodeLike} node * @param {import('estree').Pattern | import('estree').Expression} argument diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index e58985270..0bc579b23 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -544,7 +544,6 @@ export function client_module(analysis, options) { const state = { analysis, options, - ast_type: 'module', scope: analysis.module.scope, scopes: analysis.module.scopes, hoisted: [b.import_all('$', 'svelte/internal/client')], diff --git a/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts b/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts index 136e0d902..c52c31644 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts +++ b/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts @@ -22,8 +22,6 @@ export interface ClientTransformState extends TransformState { /** The $: calls, which will be ordered in the end */ readonly legacy_reactive_statements: Map; - - readonly ast_type: 'module' | 'template'; } export type SourceLocation = diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js index fbfb0f175..63e94421b 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js @@ -501,7 +501,7 @@ export const javascript_visitors_runes = { const state = context.state; if ( node.callee.type === 'ClassExpression' && - can_hoist_inline_class_expression(node, state.ast_type, state.scope) + can_hoist_inline_class_expression(node, state.scope) ) { const id = node.callee.id?.name || state.scope.generate('hoisted_inline_class'); diff --git a/packages/svelte/src/compiler/phases/3-transform/utils.js b/packages/svelte/src/compiler/phases/3-transform/utils.js index 29d052cfe..e9bf876e3 100644 --- a/packages/svelte/src/compiler/phases/3-transform/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/utils.js @@ -411,12 +411,9 @@ export function transform_inspect_rune(node, context) { /** * @param {import("estree").NewExpression} node - * @param {string} ast_type * @param {import("../scope.js").Scope} scope */ -export function can_hoist_inline_class_expression(node, ast_type, scope) { - const allowed_depth = ast_type === 'module' ? 0 : 1; - +export function can_hoist_inline_class_expression(node, scope) { if (node.callee.type !== 'ClassExpression') { return false; } @@ -430,7 +427,7 @@ export function can_hoist_inline_class_expression(node, ast_type, scope) { return false; } // If the class is top level, do not bother inlining it. - if (scope.function_depth < allowed_depth) { + if (scope.function_depth === 0) { return false; } const body = node.callee.body.body;