Skip to content

Commit

Permalink
fix: tweak script/style tag parsing/preprocessing logic
Browse files Browse the repository at this point in the history
Related to sveltejs/language-tools#2204 / sveltejs/language-tools#2039
The Svelte 5 version of #9486 and #9498
  • Loading branch information
dummdidumm committed Nov 16, 2023
1 parent 37f2493 commit 1807e88
Show file tree
Hide file tree
Showing 8 changed files with 217 additions and 13 deletions.
5 changes: 5 additions & 0 deletions .changeset/afraid-moose-matter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: tweak script/style tag parsing/preprocessing logic
50 changes: 39 additions & 11 deletions packages/svelte/src/compiler/phases/1-parse/state/element.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,12 @@ export default function tag(parser) {
/** @type {Set<string>} */
const unique_names = new Set();

const current = parser.current();
const is_top_level_script_or_style =
(name === 'script' || name === 'style') && current.type === 'Root';

let attribute;
while ((attribute = read_attribute(parser, unique_names))) {
while ((attribute = read_attribute(parser, unique_names, is_top_level_script_or_style))) {
element.attributes.push(attribute);
parser.allow_whitespace();
}
Expand Down Expand Up @@ -245,10 +249,7 @@ export default function tag(parser) {
: chunk.expression;
}

const current = parser.current();

// special cases – top-level <script> and <style>
if ((name === 'script' || name === 'style') && current.type === 'Root') {
if (is_top_level_script_or_style) {
parser.eat('>', true);
if (name === 'script') {
const content = read_script(parser, start, element.attributes);
Expand Down Expand Up @@ -376,9 +377,10 @@ const regex_starts_with_quote_characters = /^["']/;
/**
* @param {import('../index.js').Parser} parser
* @param {Set<string>} unique_names
* @param {boolean} is_static If `true`, `{` and `}` are not treated as delimiters for expressions
* @returns {any}
*/
function read_attribute(parser, unique_names) {
function read_attribute(parser, unique_names, is_static) {
const start = parser.index;

/** @param {string} name */
Expand All @@ -389,7 +391,7 @@ function read_attribute(parser, unique_names) {
unique_names.add(name);
}

if (parser.eat('{')) {
if (!is_static && parser.eat('{')) {
parser.allow_whitespace();

if (parser.eat('...')) {
Expand Down Expand Up @@ -460,13 +462,13 @@ function read_attribute(parser, unique_names) {
let value = true;
if (parser.eat('=')) {
parser.allow_whitespace();
value = read_attribute_value(parser);
value = read_attribute_value(parser, is_static);
end = parser.index;
} else if (parser.match_regex(regex_starts_with_quote_characters)) {
error(parser.index, 'expected-token', '=');
}

if (type) {
if (!is_static && type) {
const [directive_name, ...modifiers] = name.slice(colon_index + 1).split('|');

if (directive_name === '') {
Expand Down Expand Up @@ -567,11 +569,37 @@ function get_directive_type(name) {
return false;
}

const regex_attribute_value = /^(?:"([^"]*)"|'([^'])*'|([^>\s]))/;

/**
* @param {import('../index.js').Parser} parser
* @returns {any[]}
* @param {boolean} is_static If `true`, `{` and `}` are not treated as delimiters for expressions
*/
function read_attribute_value(parser) {
function read_attribute_value(parser, is_static) {
if (is_static) {
let value = parser.match_regex(regex_attribute_value);
if (!value) {
error(parser.index, 'missing-attribute-value');
}

parser.index += value.length;

const quoted = value[0] === '"' || value[0] === "'";
if (quoted) {
value = value.slice(1, -1);
}

return [
{
start: parser.index - value.length - (quoted ? 1 : 0),
end: quoted ? parser.index - 1 : parser.index,
type: 'Text',
raw: value,
data: decode_character_references(value, true)
}
];
}

const quote_mark = parser.eat("'") ? "'" : parser.eat('"') ? '"' : null;
if (quote_mark && parser.eat(quote_mark)) {
return [
Expand Down
6 changes: 4 additions & 2 deletions packages/svelte/src/compiler/preprocess/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,10 @@ function stringify_tag_attributes(attributes) {
return value;
}

const regex_style_tags = /<!--[^]*?-->|<style(\s[^]*?)?(?:>([^]*?)<\/style>|\/>)/gi;
const regex_script_tags = /<!--[^]*?-->|<script(\s[^]*?)?(?:>([^]*?)<\/script>|\/>)/gi;
const regex_style_tags =
/<!--[^]*?-->|<style((?:\s+[^=>'"\/]+=(?:"[^"]*"|'[^']*'|[^>\s])|\s+[^=>'"\/]+)*\s*)(?:\/>|>([\S\s]*?)<\/style>)/g;

Check failure on line 257 in packages/svelte/src/compiler/preprocess/index.js

View workflow job for this annotation

GitHub Actions / Lint

Unnecessary escape character: \/

Check failure on line 257 in packages/svelte/src/compiler/preprocess/index.js

View workflow job for this annotation

GitHub Actions / Lint

Unnecessary escape character: \/
const regex_script_tags =
/<!--[^]*?-->|<script((?:\s+[^=>'"\/]+=(?:"[^"]*"|'[^']*'|[^>\s])|\s+[^=>'"\/]+)*\s*)(?:\/>|>([\S\s]*?)<\/script>)/g;

Check failure on line 259 in packages/svelte/src/compiler/preprocess/index.js

View workflow job for this annotation

GitHub Actions / Lint

Unnecessary escape character: \/

Check failure on line 259 in packages/svelte/src/compiler/preprocess/index.js

View workflow job for this annotation

GitHub Actions / Lint

Unnecessary escape character: \/

/**
* Calculate the updates required to process all instances of the specified tag.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<script generics="T extends { yes: boolean }">
let name = 'world';
</script>

<h1>Hello {name}!</h1>
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { test } from '../../test';

export default test({
preprocess: {
script: ({ attributes }) =>
typeof attributes.generics === 'string' && attributes.generics.includes('>')
? { code: '' }
: undefined
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<script generics="T extends Record<string, string>">
foo {}
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<script generics="T extends Record<string, string>"></script>

0 comments on commit 1807e88

Please sign in to comment.