Skip to content
Merged
5 changes: 5 additions & 0 deletions .changeset/seven-hornets-smile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"svelte": patch
---

fix: correctly parse at-rules containing special characters in strings
9 changes: 4 additions & 5 deletions packages/svelte/src/compiler/phases/1-parse/read/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ const REGEX_PERCENTAGE = /^\d+(\.\d+)?%/;
const REGEX_NTH_OF =
/^(even|odd|\+?(\d+|\d*n(\s*[+-]\s*\d+)?)|-\d*n(\s*\+\s*\d+))((?=\s*[,)])|\s+of\s+)/;
const REGEX_WHITESPACE_OR_COLON = /[\s:]/;
const REGEX_BRACE_OR_SEMICOLON = /[{;]/;
const REGEX_LEADING_HYPHEN_OR_DIGIT = /-?\d/;
const REGEX_VALID_IDENTIFIER_CHAR = /[a-zA-Z0-9_-]/;
const REGEX_COMMENT_CLOSE = /\*\//;
Expand Down Expand Up @@ -79,7 +78,7 @@ function read_at_rule(parser) {

const name = read_identifier(parser);

const prelude = parser.read_until(REGEX_BRACE_OR_SEMICOLON).trim();
const prelude = read_value(parser);

/** @type {import('#compiler').Css.Block | null} */
let block = null;
Expand Down Expand Up @@ -398,7 +397,7 @@ function read_declaration(parser) {
parser.eat(':');
parser.allow_whitespace();

const value = read_declaration_value(parser);
const value = read_value(parser);

const end = parser.index;

Expand All @@ -419,7 +418,7 @@ function read_declaration(parser) {
* @param {import('../index.js').Parser} parser
* @returns {string}
*/
function read_declaration_value(parser) {
function read_value(parser) {
let value = '';
let escaped = false;
let in_url = false;
Expand All @@ -443,7 +442,7 @@ function read_declaration_value(parser) {
quote_mark = char;
} else if (char === '(' && value.slice(-3) === 'url') {
in_url = true;
} else if ((char === ';' || char === '}') && !in_url && !quote_mark) {
} else if ((char === ';' || char === '{' || char === '}') && !in_url && !quote_mark) {
return value.trim();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<h1>
Semicolon inside quotes
</h1>
<style>
@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap");
h1 {
font-weight: bold;
background: url("whatever");
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
{
"css": {
"type": "Style",
"start": 36,
"end": 205,
"attributes": [],
"children": [
{
"type": "Atrule",
"start": 45,
"end": 135,
"name": "import",
"prelude": "url(\"https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap\")",
"block": null
},
{
"type": "Rule",
"prelude": {
"type": "SelectorList",
"start": 137,
"end": 139,
"children": [
{
"type": "Selector",
"start": 137,
"end": 139,
"children": [
{
"type": "TypeSelector",
"name": "h1",
"start": 137,
"end": 139
}
]
}
]
},
"block": {
"type": "Block",
"start": 140,
"end": 196,
"children": [
{
"type": "Declaration",
"start": 144,
"end": 161,
"property": "font-weight",
"value": "bold"
},
{
"type": "Declaration",
"start": 165,
"end": 192,
"property": "background",
"value": "url(\"whatever\")"
}
]
},
"start": 137,
"end": 196
}
],
"content": {
"start": 43,
"end": 197,
"styles": "\n\t@import url(\"https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap\");\n\th1 {\n\t\tfont-weight: bold;\n\t\tbackground: url(\"whatever\");\n\t}\n"
}
},
"js": [],
"start": 0,
"end": 35,
"type": "Root",
"fragment": {
"type": "Fragment",
"nodes": [
{
"type": "RegularElement",
"start": 0,
"end": 35,
"name": "h1",
"attributes": [],
"fragment": {
"type": "Fragment",
"nodes": [
{
"type": "Text",
"start": 4,
"end": 30,
"raw": "\n\tSemicolon inside quotes\n",
"data": "\n\tSemicolon inside quotes\n"
}
],
"transparent": true
}
},
{
"type": "Text",
"start": 35,
"end": 36,
"raw": "\n",
"data": "\n"
}
],
"transparent": false
},
"options": null
}