feat: warn on obvious legacy component instantiation

Adds a compiler warning that warns about legacy component instantiation (i.e. using `new Component(..)`). This won't catch all cases, but the most obvious ones which probably make up ~80%
pull/12648/head
Simon Holthausen 2 years ago
parent e417d3a2d2
commit 4d8bcd9302

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

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

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

@ -0,0 +1,10 @@
<script>
import Foo from './Somewhere.svelte';
import Bar from './somewhereelse';
let Baz;
new Foo();
new Bar();
new Baz();
</script>

@ -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
}
}
]
Loading…
Cancel
Save