new-class-opt
Dominic Gannaway 3 weeks ago
parent 3a219db578
commit 69e23e3609

@ -330,102 +330,6 @@ function validate_block_not_empty(node, context) {
}
}
/**
* @type {import('zimmerframe').Visitors<import('#compiler').SvelteNode, import('./types.js').AnalysisState>}
*/
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<import('#compiler').SvelteNode, import('./types.js').AnalysisState>}
*/
@ -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<import('#compiler').SvelteNode, import('./types.js').AnalysisState>}
*/
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

@ -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')],

@ -22,8 +22,6 @@ export interface ClientTransformState extends TransformState {
/** The $: calls, which will be ordered in the end */
readonly legacy_reactive_statements: Map<LabeledStatement, Statement>;
readonly ast_type: 'module' | 'template';
}
export type SourceLocation =

@ -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');

@ -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;

Loading…
Cancel
Save