diff --git a/.changeset/hip-months-breathe.md b/.changeset/hip-months-breathe.md new file mode 100644 index 0000000000..618e7a17f5 --- /dev/null +++ b/.changeset/hip-months-breathe.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: show correct errors for invalid runes in `.svelte.js` files diff --git a/packages/svelte/src/compiler/phases/2-analyze/validation.js b/packages/svelte/src/compiler/phases/2-analyze/validation.js index 882b0a0e46..fcbb227a71 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/validation.js +++ b/packages/svelte/src/compiler/phases/2-analyze/validation.js @@ -1043,6 +1043,41 @@ export const validation_runes_js = { if (node.callee.type === 'ClassExpression' && context.state.scope.function_depth > 0) { w.perf_avoid_inline_class(node); } + }, + Identifier(node, { path, state }) { + let i = path.length; + let parent = /** @type {import('estree').Expression} */ (path[--i]); + + if ( + Runes.includes(/** @type {Runes[number]} */ (node.name)) && + is_reference(node, parent) && + state.scope.get(node.name) === null && + state.scope.get(node.name.slice(1)) === null + ) { + /** @type {import('estree').Expression} */ + let current = node; + let name = node.name; + + while (parent.type === 'MemberExpression') { + if (parent.computed) e.rune_invalid_computed_property(parent); + name += `.${/** @type {import('estree').Identifier} */ (parent.property).name}`; + + current = parent; + parent = /** @type {import('estree').Expression} */ (path[--i]); + + if (!Runes.includes(/** @type {Runes[number]} */ (name))) { + if (name === '$effect.active') { + e.rune_renamed(parent, '$effect.active', '$effect.tracking'); + } + + e.rune_invalid_name(parent, name); + } + } + + if (parent.type !== 'CallExpression') { + e.rune_missing_parentheses(current); + } + } } }; @@ -1153,41 +1188,6 @@ export const validation_runes = merge(validation, a11y_validators, { e.import_svelte_internal_forbidden(node); } }, - Identifier(node, { path, state }) { - let i = path.length; - let parent = /** @type {import('estree').Expression} */ (path[--i]); - - if ( - Runes.includes(/** @type {Runes[number]} */ (node.name)) && - is_reference(node, parent) && - state.scope.get(node.name) === null && - state.scope.get(node.name.slice(1)) === null - ) { - /** @type {import('estree').Expression} */ - let current = node; - let name = node.name; - - while (parent.type === 'MemberExpression') { - if (parent.computed) e.rune_invalid_computed_property(parent); - name += `.${/** @type {import('estree').Identifier} */ (parent.property).name}`; - - current = parent; - parent = /** @type {import('estree').Expression} */ (path[--i]); - - if (!Runes.includes(/** @type {Runes[number]} */ (name))) { - if (name === '$effect.active') { - e.rune_renamed(parent, '$effect.active', '$effect.tracking'); - } - - e.rune_invalid_name(parent, name); - } - } - - if (parent.type !== 'CallExpression') { - e.rune_missing_parentheses(current); - } - } - }, LabeledStatement(node, { path }) { if (node.label.name !== '$' || path.at(-1)?.type !== 'Program') return; e.legacy_reactive_statement_invalid(node); @@ -1368,5 +1368,6 @@ export const validation_runes = merge(validation, a11y_validators, { // TODO this is a code smell. need to refactor this stuff ClassBody: validation_runes_js.ClassBody, ClassDeclaration: validation_runes_js.ClassDeclaration, + Identifier: validation_runes_js.Identifier, NewExpression: validation_runes_js.NewExpression }); diff --git a/packages/svelte/tests/compiler-errors/samples/effect-active-rune/_config.js b/packages/svelte/tests/compiler-errors/samples/effect-active-rune/_config.js new file mode 100644 index 0000000000..e12e4046f3 --- /dev/null +++ b/packages/svelte/tests/compiler-errors/samples/effect-active-rune/_config.js @@ -0,0 +1,8 @@ +import { test } from '../../test'; + +export default test({ + error: { + code: 'rune_renamed', + message: '`$effect.active` is now `$effect.tracking`' + } +}); diff --git a/packages/svelte/tests/compiler-errors/samples/effect-active-rune/main.svelte.js b/packages/svelte/tests/compiler-errors/samples/effect-active-rune/main.svelte.js new file mode 100644 index 0000000000..c33c104aac --- /dev/null +++ b/packages/svelte/tests/compiler-errors/samples/effect-active-rune/main.svelte.js @@ -0,0 +1 @@ +$effect.active(); diff --git a/packages/svelte/tests/compiler-errors/samples/invalid-rune-name/_config.js b/packages/svelte/tests/compiler-errors/samples/invalid-rune-name/_config.js new file mode 100644 index 0000000000..ae88941393 --- /dev/null +++ b/packages/svelte/tests/compiler-errors/samples/invalid-rune-name/_config.js @@ -0,0 +1,8 @@ +import { test } from '../../test'; + +export default test({ + error: { + code: 'rune_invalid_name', + message: '`$effect.imnotarune` is not a valid rune' + } +}); diff --git a/packages/svelte/tests/compiler-errors/samples/invalid-rune-name/main.svelte.js b/packages/svelte/tests/compiler-errors/samples/invalid-rune-name/main.svelte.js new file mode 100644 index 0000000000..0d7da19aa4 --- /dev/null +++ b/packages/svelte/tests/compiler-errors/samples/invalid-rune-name/main.svelte.js @@ -0,0 +1 @@ +$effect.imnotarune();