diff --git a/.changeset/afraid-moose-matter.md b/.changeset/afraid-moose-matter.md new file mode 100644 index 000000000..d6be9c854 --- /dev/null +++ b/.changeset/afraid-moose-matter.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: tweak script/style tag parsing/preprocessing logic 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 d193aeaeb..c649d5f02 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/element.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/element.js @@ -191,11 +191,24 @@ export default function tag(parser) { }; } - /** @type {Set} */ - const unique_names = new Set(); + /** @type {string[]} */ + const unique_names = []; + + const current = parser.current(); + const is_top_level_script_or_style = + (name === 'script' || name === 'style') && current.type === 'Root'; + + const read = is_top_level_script_or_style ? read_static_attribute : read_attribute; let attribute; - while ((attribute = read_attribute(parser, unique_names))) { + while ((attribute = read(parser))) { + if ( + (attribute.type === 'Attribute' || attribute.type === 'BindDirective') && + unique_names.includes(attribute.name) + ) { + error(attribute.start, 'duplicate-attribute'); + } + element.attributes.push(attribute); parser.allow_whitespace(); } @@ -245,10 +258,7 @@ export default function tag(parser) { : chunk.expression; } - const current = parser.current(); - - // special cases – top-level + +

Hello {name}!

\ No newline at end of file diff --git a/packages/svelte/tests/parser-legacy/samples/script-attribute-with-curly-braces/output.json b/packages/svelte/tests/parser-legacy/samples/script-attribute-with-curly-braces/output.json new file mode 100644 index 000000000..af4ec56a6 --- /dev/null +++ b/packages/svelte/tests/parser-legacy/samples/script-attribute-with-curly-braces/output.json @@ -0,0 +1,150 @@ +{ + "html": { + "start": 79, + "end": 101, + "type": "Fragment", + "children": [ + { + "start": 77, + "end": 79, + "type": "Text", + "raw": "\n\n", + "data": "\n\n" + }, + { + "start": 79, + "end": 101, + "type": "Element", + "name": "h1", + "attributes": [], + "children": [ + { + "start": 83, + "end": 89, + "type": "Text", + "raw": "Hello ", + "data": "Hello " + }, + { + "start": 89, + "end": 95, + "type": "MustacheTag", + "expression": { + "type": "Identifier", + "start": 90, + "end": 94, + "loc": { + "start": { + "line": 5, + "column": 11 + }, + "end": { + "line": 5, + "column": 15 + } + }, + "name": "name" + } + }, + { + "start": 95, + "end": 96, + "type": "Text", + "raw": "!", + "data": "!" + } + ] + } + ] + }, + "instance": { + "type": "Script", + "start": 0, + "end": 77, + "context": "default", + "content": { + "type": "Program", + "start": 46, + "end": 68, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 0 + } + }, + "body": [ + { + "type": "VariableDeclaration", + "start": 48, + "end": 67, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 20 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 52, + "end": 66, + "loc": { + "start": { + "line": 2, + "column": 5 + }, + "end": { + "line": 2, + "column": 19 + } + }, + "id": { + "type": "Identifier", + "start": 52, + "end": 56, + "loc": { + "start": { + "line": 2, + "column": 5 + }, + "end": { + "line": 2, + "column": 9 + } + }, + "name": "name" + }, + "init": { + "type": "Literal", + "start": 59, + "end": 66, + "loc": { + "start": { + "line": 2, + "column": 12 + }, + "end": { + "line": 2, + "column": 19 + } + }, + "value": "world", + "raw": "'world'" + } + } + ], + "kind": "let" + } + ], + "sourceType": "module" + } + } +} diff --git a/packages/svelte/tests/preprocess/samples/attributes-with-closing-tag/_config.js b/packages/svelte/tests/preprocess/samples/attributes-with-closing-tag/_config.js new file mode 100644 index 000000000..ddb4fb4df --- /dev/null +++ b/packages/svelte/tests/preprocess/samples/attributes-with-closing-tag/_config.js @@ -0,0 +1,10 @@ +import { test } from '../../test'; + +export default test({ + preprocess: { + script: ({ attributes }) => + typeof attributes.generics === 'string' && attributes.generics.includes('>') + ? { code: '' } + : undefined + } +}); diff --git a/packages/svelte/tests/preprocess/samples/attributes-with-closing-tag/input.svelte b/packages/svelte/tests/preprocess/samples/attributes-with-closing-tag/input.svelte new file mode 100644 index 000000000..aa1a533ac --- /dev/null +++ b/packages/svelte/tests/preprocess/samples/attributes-with-closing-tag/input.svelte @@ -0,0 +1,3 @@ + diff --git a/packages/svelte/tests/preprocess/samples/attributes-with-closing-tag/output.svelte b/packages/svelte/tests/preprocess/samples/attributes-with-closing-tag/output.svelte new file mode 100644 index 000000000..d4ade07a7 --- /dev/null +++ b/packages/svelte/tests/preprocess/samples/attributes-with-closing-tag/output.svelte @@ -0,0 +1 @@ +