From a88e8148a703cee70b37945b5bb4c565f884de97 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Wed, 27 Nov 2024 12:08:49 +0100 Subject: [PATCH] warn on duplicates --- .../.generated/shared-warnings.md | 8 ++++++ .../messages/shared-warnings/warnings.md | 6 +++++ .../3-transform/client/visitors/SvelteHTML.js | 6 +++++ .../svelte/src/internal/client/validate.js | 27 ++++++++++++++++++- .../src/internal/server/blocks/svelte-html.js | 4 +++ .../svelte/src/internal/shared/warnings.js | 13 +++++++++ 6 files changed, 63 insertions(+), 1 deletion(-) diff --git a/documentation/docs/98-reference/.generated/shared-warnings.md b/documentation/docs/98-reference/.generated/shared-warnings.md index e9327e1e98..ecc48468dc 100644 --- a/documentation/docs/98-reference/.generated/shared-warnings.md +++ b/documentation/docs/98-reference/.generated/shared-warnings.md @@ -17,3 +17,11 @@ The following properties cannot be cloned with `$state.snapshot` — the return %properties% ``` + +### svelte_html_duplicate_attribute + +``` +Duplicate attribute '%name%' across multiple `` blocks, the latest value will be used. +``` + +This warning appears when you have multiple `` blocks across several files, and they set the same attribute. In that case, the latest value wins. On the server and on the client for static attributes, that's the last occurence of the attribute. On the client for dynamic attributes that's the value which was updated last across all `` blocks. diff --git a/packages/svelte/messages/shared-warnings/warnings.md b/packages/svelte/messages/shared-warnings/warnings.md index c6cc81761e..df0473555a 100644 --- a/packages/svelte/messages/shared-warnings/warnings.md +++ b/packages/svelte/messages/shared-warnings/warnings.md @@ -9,3 +9,9 @@ > The following properties cannot be cloned with `$state.snapshot` — the return value contains the originals: > > %properties% + +## svelte_html_duplicate_attribute + +> Duplicate attribute '%name%' across multiple `` blocks, the latest value will be used. + +This warning appears when you have multiple `` blocks across several files, and they set the same attribute. In that case, the latest value wins. On the server and on the client for static attributes, that's the last occurence of the attribute. On the client for dynamic attributes that's the value which was updated last across all `` blocks. diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteHTML.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteHTML.js index 929b360349..082c82aa81 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteHTML.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteHTML.js @@ -47,6 +47,12 @@ export function SvelteHTML(element, context) { } else { context.state.init.push(update); } + + if (context.state.options.dev) { + context.state.init.push( + b.stmt(b.call('$.validate_svelte_html_attribute', b.literal(name))) + ); + } } } } diff --git a/packages/svelte/src/internal/client/validate.js b/packages/svelte/src/internal/client/validate.js index 951feee33b..88b6e2abb8 100644 --- a/packages/svelte/src/internal/client/validate.js +++ b/packages/svelte/src/internal/client/validate.js @@ -2,9 +2,10 @@ import { dev_current_component_function } from './runtime.js'; import { get_descriptor, is_array } from '../shared/utils.js'; import * as e from './errors.js'; import { FILENAME } from '../../constants.js'; -import { render_effect } from './reactivity/effects.js'; +import { render_effect, teardown } from './reactivity/effects.js'; import * as w from './warnings.js'; import { capture_store_binding } from './reactivity/store.js'; +import { svelte_html_duplicate_attribute } from '../shared/warnings.js'; /** * @param {() => any} collection @@ -104,3 +105,27 @@ export function validate_binding(binding, get_object, get_property, line, column } }); } + +let svelte_html_attributes = new Map(); + +/** + * @param {string} name + */ +export function validate_svelte_html_attribute(name) { + const count = svelte_html_attributes.get(name) || 0; + + if (count > 0) { + svelte_html_duplicate_attribute(name); + } + + svelte_html_attributes.set(name, count + 1); + + teardown(() => { + const count = svelte_html_attributes.get(name) || 1; + if (count === 1) { + svelte_html_attributes.delete(name); + } else { + svelte_html_attributes.set(name, count - 1); + } + }); +} diff --git a/packages/svelte/src/internal/server/blocks/svelte-html.js b/packages/svelte/src/internal/server/blocks/svelte-html.js index 10e8be917f..31ac205c85 100644 --- a/packages/svelte/src/internal/server/blocks/svelte-html.js +++ b/packages/svelte/src/internal/server/blocks/svelte-html.js @@ -1,6 +1,7 @@ /** @import { Payload } from '#server' */ import { escape_html } from '../../../escaping.js'; +import { svelte_html_duplicate_attribute } from '../../shared/warnings.js'; /** * @param {Payload} payload @@ -8,6 +9,9 @@ import { escape_html } from '../../../escaping.js'; */ export function svelte_html(payload, attributes) { for (const name in attributes) { + if (payload.htmlAttributes.has(name)) { + svelte_html_duplicate_attribute(name); + } payload.htmlAttributes.set(name, escape_html(attributes[name], true)); } } diff --git a/packages/svelte/src/internal/shared/warnings.js b/packages/svelte/src/internal/shared/warnings.js index 37269a674e..ac31c88af5 100644 --- a/packages/svelte/src/internal/shared/warnings.js +++ b/packages/svelte/src/internal/shared/warnings.js @@ -35,4 +35,17 @@ ${properties}` // TODO print a link to the documentation console.warn("state_snapshot_uncloneable"); } +} + +/** + * Duplicate attribute '%name%' across multiple `` blocks, the latest value will be used. + * @param {string} name + */ +export function svelte_html_duplicate_attribute(name) { + if (DEV) { + console.warn(`%c[svelte] svelte_html_duplicate_attribute\n%cDuplicate attribute '${name}' across multiple \`\` blocks, the latest value will be used.`, bold, normal); + } else { + // TODO print a link to the documentation + console.warn("svelte_html_duplicate_attribute"); + } } \ No newline at end of file