diff --git a/packages/svelte/messages/compile-warnings/template.md b/packages/svelte/messages/compile-warnings/template.md index 9cf98b8c88..f5572f8302 100644 --- a/packages/svelte/messages/compile-warnings/template.md +++ b/packages/svelte/messages/compile-warnings/template.md @@ -14,6 +14,10 @@ > '%wrong%' is not a valid HTML attribute. Did you mean '%right%'? +## attribute_quoted + +> Quoted attributes on components and custom elements will be stringified in a future version of Svelte. If this isn't what you want, remove the quotes. + ## bind_invalid_each_rest > The rest operator (...) will create a new object and binding '%name%' with the original object will not work diff --git a/packages/svelte/src/compiler/phases/1-parse/state/element.js b/packages/svelte/src/compiler/phases/1-parse/state/element.js index bfab14c422..161b5fbc61 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/element.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/element.js @@ -541,7 +541,7 @@ function read_attribute(parser) { } }; - return create_attribute(name, start, parser.index, [expression]); + return create_attribute(name, start, parser.index, expression); } } diff --git a/packages/svelte/src/compiler/phases/2-analyze/validation.js b/packages/svelte/src/compiler/phases/2-analyze/validation.js index bd027239b8..29758b6b2f 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/validation.js +++ b/packages/svelte/src/compiler/phases/2-analyze/validation.js @@ -34,8 +34,23 @@ import { Scope, get_rune } from '../scope.js'; import { merge } from '../visitors.js'; import { a11y_validators } from './a11y.js'; -/** @param {import('#compiler').Attribute} attribute */ -function validate_attribute(attribute) { +/** + * @param {import('#compiler').Attribute} attribute + * @param {import('#compiler').ElementLike} parent + */ +function validate_attribute(attribute, parent) { + if ( + Array.isArray(attribute.value) && + attribute.value.length === 1 && + attribute.value[0].type === 'ExpressionTag' && + (parent.type === 'Component' || + parent.type === 'SvelteComponent' || + parent.type === 'SvelteSelf' || + (parent.type === 'RegularElement' && is_custom_element_node(parent))) + ) { + w.attribute_quoted(attribute); + } + if (attribute.value === true || !Array.isArray(attribute.value) || attribute.value.length === 1) { return; } @@ -72,7 +87,7 @@ function validate_component(node, context) { if (attribute.type === 'Attribute') { if (context.state.analysis.runes) { - validate_attribute(attribute); + validate_attribute(attribute, node); if (is_expression_attribute(attribute)) { const expression = get_attribute_expression(attribute); @@ -125,7 +140,7 @@ function validate_element(node, context) { const is_expression = is_expression_attribute(attribute); if (context.state.analysis.runes) { - validate_attribute(attribute); + validate_attribute(attribute, node); if (is_expression) { const expression = get_attribute_expression(attribute); diff --git a/packages/svelte/src/compiler/warnings.js b/packages/svelte/src/compiler/warnings.js index 7396bf40fb..cef78b4486 100644 --- a/packages/svelte/src/compiler/warnings.js +++ b/packages/svelte/src/compiler/warnings.js @@ -108,6 +108,7 @@ export const codes = [ "attribute_global_event_reference", "attribute_illegal_colon", "attribute_invalid_property_name", + "attribute_quoted", "bind_invalid_each_rest", "block_empty", "component_name_lowercase", @@ -686,6 +687,14 @@ export function attribute_invalid_property_name(node, wrong, right) { w(node, "attribute_invalid_property_name", `'${wrong}' is not a valid HTML attribute. Did you mean '${right}'?`); } +/** + * Quoted attributes on components and custom elements will be stringified in a future version of Svelte. If this isn't what you want, remove the quotes. + * @param {null | NodeLike} node + */ +export function attribute_quoted(node) { + w(node, "attribute_quoted", "Quoted attributes on components and custom elements will be stringified in a future version of Svelte. If this isn't what you want, remove the quotes."); +} + /** * The rest operator (...) will create a new object and binding '%name%' with the original object will not work * @param {null | NodeLike} node diff --git a/packages/svelte/tests/validator/samples/attribute-quoted/input.svelte b/packages/svelte/tests/validator/samples/attribute-quoted/input.svelte new file mode 100644 index 0000000000..9252d9b19a --- /dev/null +++ b/packages/svelte/tests/validator/samples/attribute-quoted/input.svelte @@ -0,0 +1,21 @@ + + + + + +

+ + + + + + + + + + +{#if foo} + +{/if} + + diff --git a/packages/svelte/tests/validator/samples/attribute-quoted/warnings.json b/packages/svelte/tests/validator/samples/attribute-quoted/warnings.json new file mode 100644 index 0000000000..a5a7aa4d1c --- /dev/null +++ b/packages/svelte/tests/validator/samples/attribute-quoted/warnings.json @@ -0,0 +1,50 @@ +[ + { + "code": "attribute_quoted", + "message": "Quoted attributes on components and custom elements will be stringified in a future version of Svelte. If this isn't what you want, remove the quotes.", + "start": { + "column": 11, + "line": 13 + }, + "end": { + "column": 24, + "line": 13 + } + }, + { + "code": "attribute_quoted", + "message": "Quoted attributes on components and custom elements will be stringified in a future version of Svelte. If this isn't what you want, remove the quotes.", + "start": { + "column": 29, + "line": 15 + }, + "end": { + "column": 42, + "line": 15 + } + }, + { + "code": "attribute_quoted", + "message": "Quoted attributes on components and custom elements will be stringified in a future version of Svelte. If this isn't what you want, remove the quotes.", + "start": { + "column": 14, + "line": 18 + }, + "end": { + "column": 27, + "line": 18 + } + }, + { + "code": "attribute_quoted", + "message": "Quoted attributes on components and custom elements will be stringified in a future version of Svelte. If this isn't what you want, remove the quotes.", + "start": { + "column": 16, + "line": 21 + }, + "end": { + "column": 29, + "line": 21 + } + } +] diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index b0c8c026d3..86f33588a6 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -1153,6 +1153,16 @@ declare module 'svelte/compiler' { outro: boolean; } + /** A `style:` directive */ + interface LegacyStyleDirective extends BaseNode_1 { + type: 'StyleDirective'; + /** The 'x' in `style:x` */ + name: string; + /** The 'y' in `style:x={y}` */ + value: true | Array; + modifiers: Array<'important'>; + } + interface LegacyWindow extends BaseElement_1 { type: 'Window'; } @@ -1171,7 +1181,7 @@ declare module 'svelte/compiler' { | LegacyClass | LegacyLet | LegacyEventHandler - | StyleDirective + | LegacyStyleDirective | LegacyTransition | LegacyAction; @@ -1634,7 +1644,7 @@ declare module 'svelte/compiler' { /** The 'x' in `style:x` */ name: string; /** The 'y' in `style:x={y}` */ - value: true | Array; + value: true | ExpressionTag | Array; modifiers: Array<'important'>; metadata: { dynamic: boolean; @@ -1855,7 +1865,7 @@ declare module 'svelte/compiler' { interface Attribute extends BaseNode { type: 'Attribute'; name: string; - value: true | Array; + value: true | ExpressionTag | Array; metadata: { dynamic: boolean; /** May be set if this is an event attribute */