Skip to content

Commit

Permalink
fix: ignore expressions in top level script/style tag attributes (#9498)
Browse files Browse the repository at this point in the history
  • Loading branch information
dummdidumm authored Nov 17, 2023
1 parent 2ea1764 commit 9a97d5c
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/ten-ducks-jump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: ignore expressions in top level script/style tag attributes
44 changes: 36 additions & 8 deletions packages/svelte/src/compiler/parse/state/tag.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,9 @@ export default function tag(parser) {
* @type {Set<string>}
*/
const unique_names = new Set();
const is_top_level_script_or_style = specials.has(name) && parser.stack.length === 1;
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 @@ -196,8 +197,7 @@ export default function tag(parser) {
}
element.tag = definition.value[0].data || definition.value[0].expression;
}
// special cases – top-level <script> and <style>
if (specials.has(name) && parser.stack.length === 1) {
if (is_top_level_script_or_style) {
const special = specials.get(name);
parser.eat('>', true);
const content = special.read(parser, start, element.attributes);
Expand Down Expand Up @@ -280,8 +280,9 @@ 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
*/
function read_attribute(parser, unique_names) {
function read_attribute(parser, unique_names, is_static) {
const start = parser.index;

/**
Expand All @@ -293,7 +294,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('...')) {
const expression = read_expression(parser);
Expand Down Expand Up @@ -348,12 +349,12 @@ 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)) {
parser.error(parser_errors.unexpected_token('='), parser.index);
}
if (type) {
if (!is_static && type) {
const [directive_name, ...modifiers] = name.slice(colon_index + 1).split('|');
if (directive_name === '') {
parser.error(parser_errors.empty_directive_name(type), start + colon_index + 1);
Expand Down Expand Up @@ -436,10 +437,37 @@ function get_directive_type(name) {
if (name === 'in' || name === 'out' || name === 'transition') return 'Transition';
}

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

/**
* @param {import('../index.js').Parser} parser
* @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) {
parser.error(parser_errors.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
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"
}
}
}

0 comments on commit 9a97d5c

Please sign in to comment.