Skip to content

Commit eb0b4dc

Browse files
authored
chore: add inline new class warning (#9583)
* chore: add inline new class warning * Address feedback * address feedback * more tests
1 parent d57eff7 commit eb0b4dc

File tree

15 files changed

+122
-7
lines changed

15 files changed

+122
-7
lines changed

.changeset/cold-birds-own.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
chore: add inline new class warning

packages/svelte/src/compiler/phases/2-analyze/index.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -201,16 +201,20 @@ export function analyze_module(ast, options) {
201201
}
202202
}
203203

204+
/** @type {import('../types').RawWarning[]} */
205+
const warnings = [];
206+
207+
const analysis = {
208+
warnings
209+
};
210+
204211
walk(
205212
/** @type {import('estree').Node} */ (ast),
206-
{ scope },
213+
{ scope, analysis },
207214
// @ts-expect-error TODO clean this mess up
208215
merge(set_scope(scopes), validation_runes_js, runes_scope_js_tweaker)
209216
);
210217

211-
/** @type {import('../types').RawWarning[]} */
212-
const warnings = [];
213-
214218
// If we are in runes mode, then check for possible misuses of state runes
215219
for (const [, scope] of scopes) {
216220
for (const [name, binding] of scope.declarations) {
@@ -608,7 +612,7 @@ const legacy_scope_tweaker = {
608612
}
609613
};
610614

611-
/** @type {import('zimmerframe').Visitors<import('#compiler').SvelteNode, { scope: Scope }>} */
615+
/** @type {import('zimmerframe').Visitors<import('#compiler').SvelteNode, { scope: Scope, analysis: { warnings: import('../types').RawWarning[] } }>} */
612616
const runes_scope_js_tweaker = {
613617
VariableDeclarator(node, { state }) {
614618
if (node.init?.type !== 'CallExpression') return;

packages/svelte/src/compiler/phases/2-analyze/validation.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,26 @@ export const validation_runes_js = {
579579
...context.state,
580580
private_derived_state
581581
});
582+
},
583+
NewExpression(node, context) {
584+
const callee = node.callee;
585+
586+
const binding = callee.type === 'Identifier' ? context.state.scope.get(callee.name) : null;
587+
const is_module = context.state.ast_type === 'module';
588+
// In modules, we allow top-level module scope only, in components, we allow the component scope,
589+
// which is function_depth of 1. With the exception of `new class` which is also not allowed at
590+
// component scope level either.
591+
const allowed_depth = is_module ? 0 : 1;
592+
593+
if (
594+
(callee.type === 'ClassExpression' && context.state.scope.function_depth > 0) ||
595+
(binding !== null &&
596+
binding.initial !== null &&
597+
binding.initial.type === 'ClassDeclaration' &&
598+
binding.scope.function_depth > allowed_depth)
599+
) {
600+
warn(context.state.analysis.warnings, node, context.path, 'inline-new-class');
601+
}
582602
}
583603
};
584604

@@ -721,5 +741,6 @@ export const validation_runes = merge(validation, a11y_validators, {
721741
}
722742
}
723743
},
724-
ClassBody: validation_runes_js.ClassBody
744+
ClassBody: validation_runes_js.ClassBody,
745+
NewExpression: validation_runes_js.NewExpression
725746
});

packages/svelte/src/compiler/phases/scope.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
437437
SwitchStatement: create_block_scope,
438438

439439
ClassDeclaration(node, { state, next }) {
440-
if (node.id) state.scope.declare(node.id, 'normal', 'const');
440+
if (node.id) state.scope.declare(node.id, 'normal', 'const', node);
441441
next();
442442
},
443443

packages/svelte/src/compiler/warnings.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,12 +191,20 @@ const state = {
191191
`State referenced in its own scope will never update. Did you mean to reference it inside a closure?`
192192
};
193193

194+
/** @satisfies {Warnings} */
195+
const performance = {
196+
'inline-new-class': () =>
197+
`Creating inline classes will likely cause performance issues. ` +
198+
`Instead, declare the class at the module-level and create new instances from the class reference.`
199+
};
200+
194201
/** @satisfies {Warnings} */
195202
const warnings = {
196203
...css,
197204
...attributes,
198205
...runes,
199206
...a11y,
207+
...performance,
200208
...state
201209
};
202210

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<script>
2+
function bar() {
3+
class Foo {
4+
foo = $state(0)
5+
}
6+
const a = new Foo();
7+
}
8+
</script>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
{
3+
"code": "inline-new-class",
4+
"message": "Creating inline classes will likely cause performance issues. Instead, declare the class at the module-level and create new instances from the class reference.",
5+
"start": {
6+
"column": 12,
7+
"line": 6
8+
},
9+
"end": {
10+
"column": 21,
11+
"line": 6
12+
}
13+
}
14+
]
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<script>
2+
class Foo {
3+
foo = $state(0)
4+
}
5+
function bar() {
6+
const a = new Foo();
7+
}
8+
</script>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script>
2+
function bar() {
3+
const a = new class Foo {
4+
foo = $state(0)
5+
}
6+
}
7+
</script>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
{
3+
"code": "inline-new-class",
4+
"message": "Creating inline classes will likely cause performance issues. Instead, declare the class at the module-level and create new instances from the class reference.",
5+
"start": {
6+
"column": 12,
7+
"line": 3
8+
},
9+
"end": {
10+
"column": 3,
11+
"line": 5
12+
}
13+
}
14+
]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script context="module">
2+
const a = new class Foo {
3+
foo = $state(0)
4+
}
5+
</script>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script>
2+
const a = new class {
3+
foo = $state(0)
4+
}
5+
</script>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
{
3+
"code": "inline-new-class",
4+
"message": "Creating inline classes will likely cause performance issues. Instead, declare the class at the module-level and create new instances from the class reference.",
5+
"start": {
6+
"column": 11,
7+
"line": 2
8+
},
9+
"end": {
10+
"column": 2,
11+
"line": 4
12+
}
13+
}
14+
]

0 commit comments

Comments
 (0)