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..42ee3517c6 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,21 @@ function validate_block_not_empty(node, context) { * @type {Visitors} */ const validation = { + ExpressionStatement(node, { state }) { + if (node.expression.type === 'NewExpression' && node.expression.callee.type === 'Identifier') { + const binding = state.scope.get(node.expression.callee.name); + if ( + binding?.kind === 'normal' && + binding.declaration_kind === 'import' && + // Theoretically someone could import a class from a `.svelte.js` module, but that's too rare to worry about + /** @type {string} */ ( + /** @type {ImportDeclaration} */ (binding.initial).source.value + )?.endsWith('.svelte') + ) { + 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..908682023d --- /dev/null +++ b/packages/svelte/tests/validator/samples/component-legacy-instantiation/input.svelte @@ -0,0 +1,10 @@ + 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..19dff7d0a8 --- /dev/null +++ b/packages/svelte/tests/validator/samples/component-legacy-instantiation/warnings.json @@ -0,0 +1,14 @@ +[ + { + "code": "legacy_component_creation", + "end": { + "column": 10, + "line": 7 + }, + "message": "Svelte 5 components are no longer classes. Instantiate them using `mount` or `hydrate` (imported from 'svelte') instead.", + "start": { + "column": 1, + "line": 7 + } + } +]