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