diff --git a/packages/svelte/messages/compile-warnings/script.md b/packages/svelte/messages/compile-warnings/script.md index af8b635bb6..a31655b972 100644 --- a/packages/svelte/messages/compile-warnings/script.md +++ b/packages/svelte/messages/compile-warnings/script.md @@ -6,6 +6,10 @@ > Component has unused export property '%name%'. If it is for external reference only, please consider using `export const %name%` +## legacy_component_creation + +> Svelte 5 components are no longer classes. Instantiate them using `mount` or `hydrate` (imported from 'svelte') instead. + ## non_reactive_update > `%name%` is updated, but is not declared with `$state(...)`. Changing its value will not correctly trigger updates diff --git a/packages/svelte/src/compiler/phases/2-analyze/validation.js b/packages/svelte/src/compiler/phases/2-analyze/validation.js index 296104b050..67459fe5a1 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/validation.js +++ b/packages/svelte/src/compiler/phases/2-analyze/validation.js @@ -1,4 +1,4 @@ -/** @import { AssignmentExpression, CallExpression, Expression, Identifier, Node, Pattern, PrivateIdentifier, Super, UpdateExpression, VariableDeclarator } from 'estree' */ +/** @import { AssignmentExpression, CallExpression, Expression, ImportDeclaration, Identifier, Node, Pattern, PrivateIdentifier, Super, UpdateExpression, VariableDeclarator } from 'estree' */ /** @import { Attribute, Component, ElementLike, Fragment, RegularElement, SvelteComponent, SvelteElement, SvelteNode, SvelteSelf, TransitionDirective } from '#compiler' */ /** @import { NodeLike } from '../../errors.js' */ /** @import { AnalysisState, Context, Visitors } from './types.js' */ @@ -362,6 +362,32 @@ function validate_block_not_empty(node, context) { * @type {Visitors} */ const validation = { + ExpressionStatement(node, { state }) { + if ( + node.expression.type === 'NewExpression' && + node.expression.callee.type === 'Identifier' && + node.expression.arguments.length === 1 && + node.expression.arguments[0].type === 'ObjectExpression' && + node.expression.arguments[0].properties.some( + (p) => p.type === 'Property' && p.key.type === 'Identifier' && p.key.name === 'target' + ) + ) { + const binding = state.scope.get(node.expression.callee.name); + if (binding?.kind === 'normal' && binding.declaration_kind === 'import') { + const declaration = /** @type {ImportDeclaration} */ (binding.initial); + + // Theoretically someone could import a class from a `.svelte.js` module, but that's too rare to worry about + if ( + /** @type {string} */ (declaration.source.value)?.endsWith('.svelte') && + declaration.specifiers.find( + (s) => s.local.name === binding.node.name && s.type === 'ImportDefaultSpecifier' + ) + ) { + w.legacy_component_creation(node.expression); + } + } + } + }, MemberExpression(node, context) { if (node.object.type === 'Identifier' && node.property.type === 'Identifier') { const binding = context.state.scope.get(node.object.name); diff --git a/packages/svelte/src/compiler/warnings.js b/packages/svelte/src/compiler/warnings.js index ca3eaf13f6..facd53cdec 100644 --- a/packages/svelte/src/compiler/warnings.js +++ b/packages/svelte/src/compiler/warnings.js @@ -96,6 +96,7 @@ export const codes = [ "options_renamed_ssr_dom", "derived_iife", "export_let_unused", + "legacy_component_creation", "non_reactive_update", "perf_avoid_inline_class", "perf_avoid_nested_class", @@ -586,6 +587,14 @@ export function export_let_unused(node, name) { w(node, "export_let_unused", `Component has unused export property '${name}'. If it is for external reference only, please consider using \`export const ${name}\``); } +/** + * Svelte 5 components are no longer classes. Instantiate them using `mount` or `hydrate` (imported from 'svelte') instead. + * @param {null | NodeLike} node + */ +export function legacy_component_creation(node) { + w(node, "legacy_component_creation", "Svelte 5 components are no longer classes. Instantiate them using `mount` or `hydrate` (imported from 'svelte') instead."); +} + /** * `%name%` is updated, but is not declared with `$state(...)`. Changing its value will not correctly trigger updates * @param {null | NodeLike} node diff --git a/packages/svelte/tests/validator/samples/component-legacy-instantiation/input.svelte b/packages/svelte/tests/validator/samples/component-legacy-instantiation/input.svelte new file mode 100644 index 0000000000..e0bf24f348 --- /dev/null +++ b/packages/svelte/tests/validator/samples/component-legacy-instantiation/input.svelte @@ -0,0 +1,13 @@ + diff --git a/packages/svelte/tests/validator/samples/component-legacy-instantiation/warnings.json b/packages/svelte/tests/validator/samples/component-legacy-instantiation/warnings.json new file mode 100644 index 0000000000..9ec8f9fa3e --- /dev/null +++ b/packages/svelte/tests/validator/samples/component-legacy-instantiation/warnings.json @@ -0,0 +1,14 @@ +[ + { + "code": "legacy_component_creation", + "message": "Svelte 5 components are no longer classes. Instantiate them using `mount` or `hydrate` (imported from 'svelte') instead.", + "start": { + "column": 1, + "line": 7 + }, + "end": { + "column": 26, + "line": 7 + } + } +]