From 076fc7812a01c074191f12c8ad924dd58ee48657 Mon Sep 17 00:00:00 2001 From: Ryan Atkinson Date: Fri, 7 Nov 2025 16:09:01 -0500 Subject: [PATCH 1/4] fix: set Root start/end to null when fragment contains only whitespace --- .changeset/fruity-knives-ring.md | 5 + .../src/compiler/phases/1-parse/index.js | 5 +- .../script-style-no-markup/input.svelte | 6 + .../script-style-no-markup/output.json | 112 ++++++++++++++++++ 4 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 .changeset/fruity-knives-ring.md create mode 100644 packages/svelte/tests/parser-modern/samples/script-style-no-markup/input.svelte create mode 100644 packages/svelte/tests/parser-modern/samples/script-style-no-markup/output.json diff --git a/.changeset/fruity-knives-ring.md b/.changeset/fruity-knives-ring.md new file mode 100644 index 000000000000..169945077674 --- /dev/null +++ b/.changeset/fruity-knives-ring.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: set Root start/end to null when fragment contains only whitespace diff --git a/packages/svelte/src/compiler/phases/1-parse/index.js b/packages/svelte/src/compiler/phases/1-parse/index.js index 8f7ef76be567..04ac9f12f92b 100644 --- a/packages/svelte/src/compiler/phases/1-parse/index.js +++ b/packages/svelte/src/compiler/phases/1-parse/index.js @@ -115,7 +115,10 @@ export class Parser { e.unexpected_eof(this.index); } - if (this.root.fragment.nodes.length) { + const has_content = this.root.fragment.nodes.some( + (node) => node.type !== 'Text' || node.data.trim().length > 0 + ); + if (has_content) { let start = /** @type {number} */ (this.root.fragment.nodes[0].start); while (regex_whitespace.test(template[start])) start += 1; diff --git a/packages/svelte/tests/parser-modern/samples/script-style-no-markup/input.svelte b/packages/svelte/tests/parser-modern/samples/script-style-no-markup/input.svelte new file mode 100644 index 000000000000..c4dc4d5271af --- /dev/null +++ b/packages/svelte/tests/parser-modern/samples/script-style-no-markup/input.svelte @@ -0,0 +1,6 @@ + + diff --git a/packages/svelte/tests/parser-modern/samples/script-style-no-markup/output.json b/packages/svelte/tests/parser-modern/samples/script-style-no-markup/output.json new file mode 100644 index 000000000000..d12d1da159f5 --- /dev/null +++ b/packages/svelte/tests/parser-modern/samples/script-style-no-markup/output.json @@ -0,0 +1,112 @@ +{ + "css": { + "type": "StyleSheet", + "start": 54, + "end": 91, + "attributes": [], + "children": [ + { + "type": "Rule", + "prelude": { + "type": "SelectorList", + "start": 63, + "end": 66, + "children": [ + { + "type": "ComplexSelector", + "start": 63, + "end": 66, + "children": [ + { + "type": "RelativeSelector", + "combinator": null, + "selectors": [ + { + "type": "TypeSelector", + "name": "div", + "start": 63, + "end": 66 + } + ], + "start": 63, + "end": 66 + } + ] + } + ] + }, + "block": { + "type": "Block", + "start": 67, + "end": 82, + "children": [ + { + "type": "Declaration", + "start": 69, + "end": 79, + "property": "color", + "value": "red" + } + ] + }, + "start": 63, + "end": 82 + } + ], + "content": { + "start": 61, + "end": 83, + "styles": "\n\tdiv { color: red; }\n", + "comment": null + } + }, + "js": [], + "start": null, + "end": null, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 53, + "end": 54, + "raw": "\n", + "data": "\n" + } + ] + }, + "options": null, + "instance": { + "type": "Script", + "start": 0, + "end": 53, + "context": "default", + "content": { + "type": "Program", + "start": 8, + "end": 44, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 0 + } + }, + "body": [], + "sourceType": "module", + "trailingComments": [ + { + "type": "Line", + "value": " script and style but no markup", + "start": 10, + "end": 43 + } + ] + }, + "attributes": [] + } +} \ No newline at end of file From a81eabcb8a15748c762f2cf8f2dd4d6cbf764de6 Mon Sep 17 00:00:00 2001 From: Ryan Atkinson Date: Fri, 7 Nov 2025 16:24:00 -0500 Subject: [PATCH 2/4] format --- .../parser-modern/samples/script-style-no-markup/output.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/tests/parser-modern/samples/script-style-no-markup/output.json b/packages/svelte/tests/parser-modern/samples/script-style-no-markup/output.json index d12d1da159f5..33674de5041f 100644 --- a/packages/svelte/tests/parser-modern/samples/script-style-no-markup/output.json +++ b/packages/svelte/tests/parser-modern/samples/script-style-no-markup/output.json @@ -109,4 +109,4 @@ }, "attributes": [] } -} \ No newline at end of file +} From a2b2a255fcc62a459eabe3aa80f6a3c2b2088cd1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 8 Dec 2025 14:02:17 -0800 Subject: [PATCH 3/4] always set root.start to 0 and root.end to template.length --- .../src/compiler/phases/1-parse/index.js | 20 ++----------------- .../output.json | 2 +- .../samples/comment-before-script/output.json | 2 +- .../samples/css-nth-syntax/output.json | 2 +- .../samples/css-pseudo-classes/output.json | 4 ++-- .../samples/generic-snippets/output.json | 2 +- .../samples/loose-valid-each-as/output.json | 2 +- .../parser-modern/samples/options/output.json | 2 +- .../script-style-no-markup/output.json | 4 ++-- .../semicolon-inside-quotes/output.json | 2 +- .../samples/snippets/output.json | 2 +- .../typescript-in-event-handler/output.json | 2 +- 12 files changed, 15 insertions(+), 31 deletions(-) diff --git a/packages/svelte/src/compiler/phases/1-parse/index.js b/packages/svelte/src/compiler/phases/1-parse/index.js index 04ac9f12f92b..5fbd1bddf282 100644 --- a/packages/svelte/src/compiler/phases/1-parse/index.js +++ b/packages/svelte/src/compiler/phases/1-parse/index.js @@ -115,24 +115,8 @@ export class Parser { e.unexpected_eof(this.index); } - const has_content = this.root.fragment.nodes.some( - (node) => node.type !== 'Text' || node.data.trim().length > 0 - ); - if (has_content) { - let start = /** @type {number} */ (this.root.fragment.nodes[0].start); - while (regex_whitespace.test(template[start])) start += 1; - - let end = /** @type {number} */ ( - this.root.fragment.nodes[this.root.fragment.nodes.length - 1].end - ); - while (regex_whitespace.test(template[end - 1])) end -= 1; - - this.root.start = start; - this.root.end = end; - } else { - // @ts-ignore - this.root.start = this.root.end = null; - } + this.root.start = 0; + this.root.end = template.length; const options_index = this.root.fragment.nodes.findIndex( /** @param {any} thing */ diff --git a/packages/svelte/tests/parser-modern/samples/comment-before-function-binding/output.json b/packages/svelte/tests/parser-modern/samples/comment-before-function-binding/output.json index dba258a6b164..daa1804bcc8c 100644 --- a/packages/svelte/tests/parser-modern/samples/comment-before-function-binding/output.json +++ b/packages/svelte/tests/parser-modern/samples/comment-before-function-binding/output.json @@ -1,7 +1,7 @@ { "css": null, "js": [], - "start": 37, + "start": 0, "end": 117, "type": "Root", "fragment": { diff --git a/packages/svelte/tests/parser-modern/samples/comment-before-script/output.json b/packages/svelte/tests/parser-modern/samples/comment-before-script/output.json index 1aca0ce03607..5912afd3c15a 100644 --- a/packages/svelte/tests/parser-modern/samples/comment-before-script/output.json +++ b/packages/svelte/tests/parser-modern/samples/comment-before-script/output.json @@ -2,7 +2,7 @@ "css": null, "js": [], "start": 0, - "end": 27, + "end": 76, "type": "Root", "fragment": { "type": "Fragment", diff --git a/packages/svelte/tests/parser-modern/samples/css-nth-syntax/output.json b/packages/svelte/tests/parser-modern/samples/css-nth-syntax/output.json index a126acb4c3f8..418be8135d2b 100644 --- a/packages/svelte/tests/parser-modern/samples/css-nth-syntax/output.json +++ b/packages/svelte/tests/parser-modern/samples/css-nth-syntax/output.json @@ -1079,7 +1079,7 @@ } }, "js": [], - "start": 808, + "start": 0, "end": 820, "type": "Root", "fragment": { diff --git a/packages/svelte/tests/parser-modern/samples/css-pseudo-classes/output.json b/packages/svelte/tests/parser-modern/samples/css-pseudo-classes/output.json index e410cf2a801b..d052affe46a2 100644 --- a/packages/svelte/tests/parser-modern/samples/css-pseudo-classes/output.json +++ b/packages/svelte/tests/parser-modern/samples/css-pseudo-classes/output.json @@ -398,8 +398,8 @@ } }, "js": [], - "start": null, - "end": null, + "start": 0, + "end": 386, "type": "Root", "fragment": { "type": "Fragment", diff --git a/packages/svelte/tests/parser-modern/samples/generic-snippets/output.json b/packages/svelte/tests/parser-modern/samples/generic-snippets/output.json index b66ee7288f2e..05c6ea3d1dbf 100644 --- a/packages/svelte/tests/parser-modern/samples/generic-snippets/output.json +++ b/packages/svelte/tests/parser-modern/samples/generic-snippets/output.json @@ -1,7 +1,7 @@ { "css": null, "js": [], - "start": 30, + "start": 0, "end": 192, "type": "Root", "fragment": { diff --git a/packages/svelte/tests/parser-modern/samples/loose-valid-each-as/output.json b/packages/svelte/tests/parser-modern/samples/loose-valid-each-as/output.json index 181f1ba25000..ddcdb86225f0 100644 --- a/packages/svelte/tests/parser-modern/samples/loose-valid-each-as/output.json +++ b/packages/svelte/tests/parser-modern/samples/loose-valid-each-as/output.json @@ -1,7 +1,7 @@ { "css": null, "js": [], - "start": 45, + "start": 0, "end": 119, "type": "Root", "fragment": { diff --git a/packages/svelte/tests/parser-modern/samples/options/output.json b/packages/svelte/tests/parser-modern/samples/options/output.json index 6feee2d4f506..080ecd8d4fa3 100644 --- a/packages/svelte/tests/parser-modern/samples/options/output.json +++ b/packages/svelte/tests/parser-modern/samples/options/output.json @@ -2,7 +2,7 @@ "css": null, "js": [], "start": 0, - "end": 102, + "end": 169, "type": "Root", "fragment": { "type": "Fragment", diff --git a/packages/svelte/tests/parser-modern/samples/script-style-no-markup/output.json b/packages/svelte/tests/parser-modern/samples/script-style-no-markup/output.json index 33674de5041f..b5e42e92170d 100644 --- a/packages/svelte/tests/parser-modern/samples/script-style-no-markup/output.json +++ b/packages/svelte/tests/parser-modern/samples/script-style-no-markup/output.json @@ -61,8 +61,8 @@ } }, "js": [], - "start": null, - "end": null, + "start": 0, + "end": 91, "type": "Root", "fragment": { "type": "Fragment", diff --git a/packages/svelte/tests/parser-modern/samples/semicolon-inside-quotes/output.json b/packages/svelte/tests/parser-modern/samples/semicolon-inside-quotes/output.json index 33dd78879cff..9563f410ed91 100644 --- a/packages/svelte/tests/parser-modern/samples/semicolon-inside-quotes/output.json +++ b/packages/svelte/tests/parser-modern/samples/semicolon-inside-quotes/output.json @@ -77,7 +77,7 @@ }, "js": [], "start": 0, - "end": 35, + "end": 205, "type": "Root", "fragment": { "type": "Fragment", diff --git a/packages/svelte/tests/parser-modern/samples/snippets/output.json b/packages/svelte/tests/parser-modern/samples/snippets/output.json index acf484d8ae16..d465af36ce76 100644 --- a/packages/svelte/tests/parser-modern/samples/snippets/output.json +++ b/packages/svelte/tests/parser-modern/samples/snippets/output.json @@ -1,7 +1,7 @@ { "css": null, "js": [], - "start": 29, + "start": 0, "end": 101, "type": "Root", "fragment": { diff --git a/packages/svelte/tests/parser-modern/samples/typescript-in-event-handler/output.json b/packages/svelte/tests/parser-modern/samples/typescript-in-event-handler/output.json index 9c515ad9058f..02dbb740872e 100644 --- a/packages/svelte/tests/parser-modern/samples/typescript-in-event-handler/output.json +++ b/packages/svelte/tests/parser-modern/samples/typescript-in-event-handler/output.json @@ -1,7 +1,7 @@ { "css": null, "js": [], - "start": 54, + "start": 0, "end": 173, "type": "Root", "fragment": { From 6d77bd44c423f7f5d9c9c709e63063ab94f4341e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 8 Dec 2025 14:07:37 -0800 Subject: [PATCH 4/4] Update .changeset/fruity-knives-ring.md --- .changeset/fruity-knives-ring.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/fruity-knives-ring.md b/.changeset/fruity-knives-ring.md index 169945077674..059b2df1c7a2 100644 --- a/.changeset/fruity-knives-ring.md +++ b/.changeset/fruity-knives-ring.md @@ -2,4 +2,4 @@ 'svelte': patch --- -fix: set Root start/end to null when fragment contains only whitespace +fix: set AST `root.start` to `0` and `root.end` to `template.length`