From 37488fa65c092c815584133b539ee79f570d2c76 Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti Date: Tue, 27 May 2025 15:02:19 +0200 Subject: [PATCH] fix: warn when using rest or identifier in custom elements without props option (#16003) * fix: warn when using rest or identifier in custom elements without props option * chore: update message * tweak message * update tests --------- Co-authored-by: Rich Harris --- .changeset/mighty-rabbits-teach.md | 5 ++++ .../.generated/compile-warnings.md | 6 +++++ .../messages/compile-warnings/script.md | 4 +++ .../2-analyze/visitors/VariableDeclarator.js | 14 ++++++++++ packages/svelte/src/compiler/warnings.js | 9 +++++++ .../input.svelte | 7 +++++ .../warnings.json | 14 ++++++++++ .../input.svelte | 5 ++++ .../warnings.json | 26 +++++++++++++++++++ .../input.svelte | 5 ++++ .../warnings.json | 26 +++++++++++++++++++ 11 files changed, 121 insertions(+) create mode 100644 .changeset/mighty-rabbits-teach.md create mode 100644 packages/svelte/tests/validator/samples/custom-element-props-identifier-props-option/input.svelte create mode 100644 packages/svelte/tests/validator/samples/custom-element-props-identifier-props-option/warnings.json create mode 100644 packages/svelte/tests/validator/samples/custom-element-props-identifier-rest/input.svelte create mode 100644 packages/svelte/tests/validator/samples/custom-element-props-identifier-rest/warnings.json create mode 100644 packages/svelte/tests/validator/samples/custom-element-props-identifier/input.svelte create mode 100644 packages/svelte/tests/validator/samples/custom-element-props-identifier/warnings.json diff --git a/.changeset/mighty-rabbits-teach.md b/.changeset/mighty-rabbits-teach.md new file mode 100644 index 0000000000..0d80ddd51a --- /dev/null +++ b/.changeset/mighty-rabbits-teach.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: warn when using rest or identifier in custom elements without props option diff --git a/documentation/docs/98-reference/.generated/compile-warnings.md b/documentation/docs/98-reference/.generated/compile-warnings.md index b579d38602..2af9021a6a 100644 --- a/documentation/docs/98-reference/.generated/compile-warnings.md +++ b/documentation/docs/98-reference/.generated/compile-warnings.md @@ -632,6 +632,12 @@ In some situations a selector may target an element that is not 'visible' to the ``` +### custom_element_props_identifier + +``` +Using a rest element or a non-destructured declaration with `$props()` means that Svelte can't infer what properties to expose when creating a custom element. Consider destructuring all the props or explicitly specifying the `customElement.props` option. +``` + ### element_implicitly_closed ``` diff --git a/packages/svelte/messages/compile-warnings/script.md b/packages/svelte/messages/compile-warnings/script.md index 6603759156..26bd0c9027 100644 --- a/packages/svelte/messages/compile-warnings/script.md +++ b/packages/svelte/messages/compile-warnings/script.md @@ -1,3 +1,7 @@ +## custom_element_props_identifier + +> Using a rest element or a non-destructured declaration with `$props()` means that Svelte can't infer what properties to expose when creating a custom element. Consider destructuring all the props or explicitly specifying the `customElement.props` option. + ## export_let_unused > Component has unused export property '%name%'. If it is for external reference only, please consider using `export const %name%` diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js index a7d08d315d..c4b4272aba 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js @@ -4,6 +4,7 @@ import { get_rune } from '../../scope.js'; import { ensure_no_module_import_conflict, validate_identifier_name } from './shared/utils.js'; import * as e from '../../../errors.js'; +import * as w from '../../../warnings.js'; import { extract_paths } from '../../../utils/ast.js'; import { equal } from '../../../utils/assert.js'; @@ -52,6 +53,19 @@ export function VariableDeclarator(node, context) { e.props_invalid_identifier(node); } + if ( + context.state.analysis.custom_element && + context.state.options.customElementOptions?.props == null + ) { + let warn_on; + if ( + node.id.type === 'Identifier' || + (warn_on = node.id.properties.find((p) => p.type === 'RestElement')) != null + ) { + w.custom_element_props_identifier(warn_on ?? node.id); + } + } + context.state.analysis.needs_props = true; if (node.id.type === 'Identifier') { diff --git a/packages/svelte/src/compiler/warnings.js b/packages/svelte/src/compiler/warnings.js index 2a8316308c..1226190891 100644 --- a/packages/svelte/src/compiler/warnings.js +++ b/packages/svelte/src/compiler/warnings.js @@ -96,6 +96,7 @@ export const codes = [ 'options_removed_hydratable', 'options_removed_loop_guard_timeout', 'options_renamed_ssr_dom', + 'custom_element_props_identifier', 'export_let_unused', 'legacy_component_creation', 'non_reactive_update', @@ -592,6 +593,14 @@ export function options_renamed_ssr_dom(node) { w(node, 'options_renamed_ssr_dom', `\`generate: "dom"\` and \`generate: "ssr"\` options have been renamed to "client" and "server" respectively\nhttps://svelte.dev/e/options_renamed_ssr_dom`); } +/** + * Using a rest element or a non-destructured declaration with `$props()` means that Svelte can't infer what properties to expose when creating a custom element. Consider destructuring all the props or explicitly specifying the `customElement.props` option. + * @param {null | NodeLike} node + */ +export function custom_element_props_identifier(node) { + w(node, 'custom_element_props_identifier', `Using a rest element or a non-destructured declaration with \`$props()\` means that Svelte can't infer what properties to expose when creating a custom element. Consider destructuring all the props or explicitly specifying the \`customElement.props\` option.\nhttps://svelte.dev/e/custom_element_props_identifier`); +} + /** * Component has unused export property '%name%'. If it is for external reference only, please consider using `export const %name%` * @param {null | NodeLike} node diff --git a/packages/svelte/tests/validator/samples/custom-element-props-identifier-props-option/input.svelte b/packages/svelte/tests/validator/samples/custom-element-props-identifier-props-option/input.svelte new file mode 100644 index 0000000000..bb7b930dc3 --- /dev/null +++ b/packages/svelte/tests/validator/samples/custom-element-props-identifier-props-option/input.svelte @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/custom-element-props-identifier-props-option/warnings.json b/packages/svelte/tests/validator/samples/custom-element-props-identifier-props-option/warnings.json new file mode 100644 index 0000000000..b880fe146c --- /dev/null +++ b/packages/svelte/tests/validator/samples/custom-element-props-identifier-props-option/warnings.json @@ -0,0 +1,14 @@ +[ + { + "code": "options_missing_custom_element", + "end": { + "column": 2, + "line": 3 + }, + "message": "The `customElement` option is used when generating a custom element. Did you forget the `customElement: true` compile option?", + "start": { + "column": 16, + "line": 1 + } + } +] diff --git a/packages/svelte/tests/validator/samples/custom-element-props-identifier-rest/input.svelte b/packages/svelte/tests/validator/samples/custom-element-props-identifier-rest/input.svelte new file mode 100644 index 0000000000..207b554527 --- /dev/null +++ b/packages/svelte/tests/validator/samples/custom-element-props-identifier-rest/input.svelte @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/custom-element-props-identifier-rest/warnings.json b/packages/svelte/tests/validator/samples/custom-element-props-identifier-rest/warnings.json new file mode 100644 index 0000000000..61e11ab108 --- /dev/null +++ b/packages/svelte/tests/validator/samples/custom-element-props-identifier-rest/warnings.json @@ -0,0 +1,26 @@ +[ + { + "code": "options_missing_custom_element", + "end": { + "column": 34, + "line": 1 + }, + "message": "The `customElement` option is used when generating a custom element. Did you forget the `customElement: true` compile option?", + "start": { + "column": 16, + "line": 1 + } + }, + { + "code": "custom_element_props_identifier", + "end": { + "column": 15, + "line": 4 + }, + "message": "Using a rest element or a non-destructured declaration with `$props()` means that Svelte can't infer what properties to expose when creating a custom element. Consider destructuring all the props or explicitly specifying the `customElement.props` option.", + "start": { + "column": 7, + "line": 4 + } + } +] diff --git a/packages/svelte/tests/validator/samples/custom-element-props-identifier/input.svelte b/packages/svelte/tests/validator/samples/custom-element-props-identifier/input.svelte new file mode 100644 index 0000000000..ca5b16f8c3 --- /dev/null +++ b/packages/svelte/tests/validator/samples/custom-element-props-identifier/input.svelte @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/custom-element-props-identifier/warnings.json b/packages/svelte/tests/validator/samples/custom-element-props-identifier/warnings.json new file mode 100644 index 0000000000..4c50bbd116 --- /dev/null +++ b/packages/svelte/tests/validator/samples/custom-element-props-identifier/warnings.json @@ -0,0 +1,26 @@ +[ + { + "code": "options_missing_custom_element", + "end": { + "column": 34, + "line": 1 + }, + "message": "The `customElement` option is used when generating a custom element. Did you forget the `customElement: true` compile option?", + "start": { + "column": 16, + "line": 1 + } + }, + { + "code": "custom_element_props_identifier", + "end": { + "column": 10, + "line": 4 + }, + "message": "Using a rest element or a non-destructured declaration with `$props()` means that Svelte can't infer what properties to expose when creating a custom element. Consider destructuring all the props or explicitly specifying the `customElement.props` option.", + "start": { + "column": 5, + "line": 4 + } + } +]