From 837497e939f00a9452934a58380b29a9609e2b15 Mon Sep 17 00:00:00 2001 From: thecalamiity Date: Mon, 7 Jul 2025 20:14:55 +0300 Subject: [PATCH 1/7] fix: enforce strict syntax for `@charset` in no-invalid-at-rules --- docs/rules/no-invalid-at-rules.md | 27 +++- src/rules/no-invalid-at-rules.js | 72 +++++++++ tests/rules/no-invalid-at-rules.test.js | 200 +++++++++++++++++++++++- 3 files changed, 293 insertions(+), 6 deletions(-) diff --git a/docs/rules/no-invalid-at-rules.md b/docs/rules/no-invalid-at-rules.md index 5df1d663..52c3bbb3 100644 --- a/docs/rules/no-invalid-at-rules.md +++ b/docs/rules/no-invalid-at-rules.md @@ -13,16 +13,15 @@ CSS contains a number of at-rules, each beginning with a `@`, that perform vario - `@supports` - `@namespace` - `@page` -- `@charset` It's important to use a known at-rule because unknown at-rules cause the browser to ignore the entire block, including any rules contained within. For example: ```css /* typo */ -@charse "UTF-8"; +@impor "foo.css"; ``` -Here, the `@charset` at-rule is incorrectly spelled as `@charse`, which means that it will be ignored. +Here, the `@import` at-rule is incorrectly spelled as `@impor`, which means that it will be ignored. Each at-rule also has a defined prelude (which may be empty) and potentially one or more descriptors. For example: @@ -73,6 +72,28 @@ Examples of **incorrect** code: } ``` +Note on `@charset`: Although it begins with an `@` symbol, it is not an at-rule. It is a specific byte sequence of the following form: + +```css +@charset ""; +``` + +where `charset` is a [``](https://developer.mozilla.org/en-US/docs/Web/CSS/string) denoting the character encoding to be used. It must be the name of a web-safe character encoding defined in the [IANA-registry](https://www.iana.org/assignments/character-sets/character-sets.xhtml), and must be double-quoted, following exactly one space character (U+0020), and immediately terminated with a semicolon. + +Examples of **incorrect** code: + +```css +@charset 'iso-8859-15'; /* Wrong quotes used */ +@charset "UTF-8"; /* More than one space */ +@charset UTF-8; /* The charset is a CSS and requires double-quotes */ +``` + +Examples of **correct** code: + +```css +@charset "UTF-8"; +``` + ## When Not to Use It If you are purposely using at-rules that aren't part of the CSS specification, then you can safely disable this rule. diff --git a/src/rules/no-invalid-at-rules.js b/src/rules/no-invalid-at-rules.js index adcf5142..b93e059e 100644 --- a/src/rules/no-invalid-at-rules.js +++ b/src/rules/no-invalid-at-rules.js @@ -24,6 +24,14 @@ import { isSyntaxMatchError } from "../util.js"; // Helpers //----------------------------------------------------------------------------- +/** + * A valid `@charset` rule must: + * - Enclose the encoding name in double quotes + * - Include exactly one space character after `@charset` + * - End immediately with a semicolon + */ +const charsetPattern = /^@charset "[^"]+";$/u; + /** * Extracts metadata from an error object. * @param {SyntaxError} error The error object to extract metadata from. @@ -81,8 +89,72 @@ export default { const { sourceCode } = context; const lexer = sourceCode.lexer; + /** + * Validates a `@charset` rule for correct syntax: + * - Verifies the rule name is exactly "charset" (case-sensitive) + * - Ensures the rule has a prelude + * - Validates the prelude matches the expected pattern + * @param {AtrulePlain} node The node representing the rule. + */ + function validateCharsetRule(node) { + const { name, prelude, loc } = node; + + const charsetNameLoc = { + start: loc.start, + end: { + line: loc.start.line, + column: loc.start.column + name.length + 1, + }, + }; + + if (name !== "charset") { + context.report({ + loc: charsetNameLoc, + messageId: "unknownAtRule", + data: { + name, + }, + }); + return; + } + + if (!prelude) { + context.report({ + loc: charsetNameLoc, + messageId: "missingPrelude", + data: { + name, + }, + }); + return; + } + + const nodeText = sourceCode.getText(node); + if (!charsetPattern.test(nodeText)) { + const preludeText = nodeText.slice( + prelude.loc.start.offset, + prelude.loc.end.offset, + ); + + context.report({ + loc: prelude.loc, + messageId: "invalidPrelude", + data: { + name, + prelude: preludeText, + expected: "", + }, + }); + } + } + return { Atrule(node) { + if (node.name.toLowerCase() === "charset") { + validateCharsetRule(node); + return; + } + // checks both name and prelude const { error } = lexer.matchAtrulePrelude( node.name, diff --git a/tests/rules/no-invalid-at-rules.test.js b/tests/rules/no-invalid-at-rules.test.js index fd6ec434..3ab250ff 100644 --- a/tests/rules/no-invalid-at-rules.test.js +++ b/tests/rules/no-invalid-at-rules.test.js @@ -30,6 +30,8 @@ ruleTester.run("no-invalid-at-rules", rule, { "@keyframes slidein { from { transform: translateX(0%); } to { transform: translateX(100%); } }", "@supports (display: grid) { .grid-container { display: grid; } }", "@namespace url(http://www.w3.org/1999/xhtml);", + '@charset "UTF-8";', + '@charset "UTF-8"; @import url("foo.css");', "@media screen and (max-width: 600px) { body { font-size: 12px; } }", { code: "@foobar url(foo.css) { body { font-size: 12px } }", @@ -96,15 +98,15 @@ ruleTester.run("no-invalid-at-rules", rule, { ], }, { - code: '@charse "test";', + code: '@impor "foo.css";', errors: [ { messageId: "unknownAtRule", - data: { name: "charse" }, + data: { name: "impor" }, line: 1, column: 1, endLine: 1, - endColumn: 8, + endColumn: 7, }, ], }, @@ -293,5 +295,197 @@ ruleTester.run("no-invalid-at-rules", rule, { }, ], }, + { + code: '@CHARSET "UTF-8";', + errors: [ + { + messageId: "unknownAtRule", + data: { name: "CHARSET" }, + line: 1, + column: 1, + endLine: 1, + endColumn: 9, + }, + ], + }, + { + code: '@CharSet "UTF-8";', + errors: [ + { + messageId: "unknownAtRule", + data: { name: "CharSet" }, + line: 1, + column: 1, + endLine: 1, + endColumn: 9, + }, + ], + }, + { + code: "@charset", + errors: [ + { + messageId: "missingPrelude", + data: { name: "charset" }, + line: 1, + column: 1, + endLine: 1, + endColumn: 9, + }, + ], + }, + { + code: "@charset;", + errors: [ + { + messageId: "missingPrelude", + data: { name: "charset" }, + line: 1, + column: 1, + endLine: 1, + endColumn: 9, + }, + ], + }, + { + code: "@charset ;", + errors: [ + { + messageId: "missingPrelude", + data: { name: "charset" }, + line: 1, + column: 1, + endLine: 1, + endColumn: 9, + }, + ], + }, + { + code: "@charset 'UTF-8';", + errors: [ + { + messageId: "invalidPrelude", + data: { + name: "charset", + prelude: "'UTF-8'", + expected: "", + }, + line: 1, + column: 10, + endLine: 1, + endColumn: 17, + }, + ], + }, + { + code: "@charset UTF-8;", + errors: [ + { + messageId: "invalidPrelude", + data: { + name: "charset", + prelude: "UTF-8", + expected: "", + }, + line: 1, + column: 10, + endLine: 1, + endColumn: 15, + }, + ], + }, + { + code: '@charset"UTF-8";', + errors: [ + { + messageId: "invalidPrelude", + data: { + name: "charset", + prelude: '"UTF-8"', + expected: "", + }, + line: 1, + column: 9, + endLine: 1, + endColumn: 16, + }, + ], + }, + { + code: '@charset "UTF-8";', + errors: [ + { + messageId: "invalidPrelude", + data: { + name: "charset", + prelude: '"UTF-8"', + expected: "", + }, + line: 1, + column: 11, + endLine: 1, + endColumn: 18, + }, + ], + }, + { + code: '@charset "UTF-8"', + errors: [ + { + messageId: "invalidPrelude", + data: { + name: "charset", + prelude: '"UTF-8"', + expected: "", + }, + line: 1, + column: 10, + endLine: 1, + endColumn: 17, + }, + ], + }, + { + code: '@charset "UTF-8" ;', + errors: [ + { + messageId: "invalidPrelude", + data: { + name: "charset", + prelude: '"UTF-8"', + expected: "", + }, + line: 1, + column: 10, + endLine: 1, + endColumn: 17, + }, + ], + }, + { + code: '@charset "UTF-8";\n@impor "foo.css";', + errors: [ + { + messageId: "invalidPrelude", + data: { + name: "charset", + prelude: '"UTF-8"', + expected: "", + }, + line: 1, + column: 11, + endLine: 1, + endColumn: 18, + }, + { + messageId: "unknownAtRule", + data: { name: "impor" }, + line: 2, + column: 1, + endLine: 2, + endColumn: 7, + }, + ], + }, ], }); From c8f7753b6aa2773c51e584d4a27bafd5aba8647d Mon Sep 17 00:00:00 2001 From: thecalamiity Date: Mon, 7 Jul 2025 20:54:56 +0300 Subject: [PATCH 2/7] fix incorrect code example --- docs/rules/no-invalid-at-rules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rules/no-invalid-at-rules.md b/docs/rules/no-invalid-at-rules.md index 52c3bbb3..33723fa9 100644 --- a/docs/rules/no-invalid-at-rules.md +++ b/docs/rules/no-invalid-at-rules.md @@ -84,7 +84,7 @@ Examples of **incorrect** code: ```css @charset 'iso-8859-15'; /* Wrong quotes used */ -@charset "UTF-8"; /* More than one space */ +@charset "UTF-8"; /* More than one space */ @charset UTF-8; /* The charset is a CSS and requires double-quotes */ ``` From 1045d304f5e54c0df7b23cf152ae7c4c14a2cb5f Mon Sep 17 00:00:00 2001 From: thecalamiity Date: Mon, 7 Jul 2025 21:06:06 +0300 Subject: [PATCH 3/7] fix CI failure --- docs/rules/no-invalid-at-rules.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/rules/no-invalid-at-rules.md b/docs/rules/no-invalid-at-rules.md index 33723fa9..7a5f562b 100644 --- a/docs/rules/no-invalid-at-rules.md +++ b/docs/rules/no-invalid-at-rules.md @@ -84,6 +84,7 @@ Examples of **incorrect** code: ```css @charset 'iso-8859-15'; /* Wrong quotes used */ + @charset "UTF-8"; /* More than one space */ @charset UTF-8; /* The charset is a CSS and requires double-quotes */ ``` From 1d43501bf8b781b78ccb60ff439d0c6275699253 Mon Sep 17 00:00:00 2001 From: thecalamiity Date: Mon, 7 Jul 2025 21:19:02 +0300 Subject: [PATCH 4/7] fix CI failure --- docs/rules/no-invalid-at-rules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rules/no-invalid-at-rules.md b/docs/rules/no-invalid-at-rules.md index 7a5f562b..beec7d75 100644 --- a/docs/rules/no-invalid-at-rules.md +++ b/docs/rules/no-invalid-at-rules.md @@ -82,9 +82,9 @@ where `charset` is a [``](https://developer.mozilla.org/en-US/docs/Web/C Examples of **incorrect** code: + ```css @charset 'iso-8859-15'; /* Wrong quotes used */ - @charset "UTF-8"; /* More than one space */ @charset UTF-8; /* The charset is a CSS and requires double-quotes */ ``` From cc2287080a517f1ac558dd816f733d1b9db6acc0 Mon Sep 17 00:00:00 2001 From: thecalamiity Date: Fri, 18 Jul 2025 05:59:14 +0300 Subject: [PATCH 5/7] add autofix for @charset formatting issues --- src/rules/no-invalid-at-rules.js | 24 ++++++++++++++++++++++++ tests/rules/no-invalid-at-rules.test.js | 9 +++++++++ 2 files changed, 33 insertions(+) diff --git a/src/rules/no-invalid-at-rules.js b/src/rules/no-invalid-at-rules.js index b93e059e..8114ee47 100644 --- a/src/rules/no-invalid-at-rules.js +++ b/src/rules/no-invalid-at-rules.js @@ -31,6 +31,7 @@ import { isSyntaxMatchError } from "../util.js"; * - End immediately with a semicolon */ const charsetPattern = /^@charset "[^"]+";$/u; +const charsetEncodingPattern = /^['"]?([^"';]+)['"]?/u; /** * Extracts metadata from an error object. @@ -65,6 +66,8 @@ export default { meta: { type: "problem", + fixable: "code", + docs: { description: "Disallow invalid at-rules", recommended: true, @@ -114,6 +117,15 @@ export default { data: { name, }, + fix(fixer) { + return fixer.replaceTextRange( + [ + loc.start.offset, + loc.start.offset + name.length + 1, + ], + "@charset", + ); + }, }); return; } @@ -136,6 +148,8 @@ export default { prelude.loc.end.offset, ); + const encoding = preludeText.match(charsetEncodingPattern)?.[1]; + context.report({ loc: prelude.loc, messageId: "invalidPrelude", @@ -144,6 +158,16 @@ export default { prelude: preludeText, expected: "", }, + fix(fixer) { + if (!encoding) { + return null; + } + + return fixer.replaceText( + node, + `@charset "${encoding}";`, + ); + }, }); } } diff --git a/tests/rules/no-invalid-at-rules.test.js b/tests/rules/no-invalid-at-rules.test.js index 3ab250ff..40fc2fdd 100644 --- a/tests/rules/no-invalid-at-rules.test.js +++ b/tests/rules/no-invalid-at-rules.test.js @@ -297,6 +297,7 @@ ruleTester.run("no-invalid-at-rules", rule, { }, { code: '@CHARSET "UTF-8";', + output: '@charset "UTF-8";', errors: [ { messageId: "unknownAtRule", @@ -310,6 +311,7 @@ ruleTester.run("no-invalid-at-rules", rule, { }, { code: '@CharSet "UTF-8";', + output: '@charset "UTF-8";', errors: [ { messageId: "unknownAtRule", @@ -362,6 +364,7 @@ ruleTester.run("no-invalid-at-rules", rule, { }, { code: "@charset 'UTF-8';", + output: '@charset "UTF-8";', errors: [ { messageId: "invalidPrelude", @@ -379,6 +382,7 @@ ruleTester.run("no-invalid-at-rules", rule, { }, { code: "@charset UTF-8;", + output: '@charset "UTF-8";', errors: [ { messageId: "invalidPrelude", @@ -396,6 +400,7 @@ ruleTester.run("no-invalid-at-rules", rule, { }, { code: '@charset"UTF-8";', + output: '@charset "UTF-8";', errors: [ { messageId: "invalidPrelude", @@ -413,6 +418,7 @@ ruleTester.run("no-invalid-at-rules", rule, { }, { code: '@charset "UTF-8";', + output: '@charset "UTF-8";', errors: [ { messageId: "invalidPrelude", @@ -430,6 +436,7 @@ ruleTester.run("no-invalid-at-rules", rule, { }, { code: '@charset "UTF-8"', + output: '@charset "UTF-8";', errors: [ { messageId: "invalidPrelude", @@ -447,6 +454,7 @@ ruleTester.run("no-invalid-at-rules", rule, { }, { code: '@charset "UTF-8" ;', + output: '@charset "UTF-8";', errors: [ { messageId: "invalidPrelude", @@ -464,6 +472,7 @@ ruleTester.run("no-invalid-at-rules", rule, { }, { code: '@charset "UTF-8";\n@impor "foo.css";', + output: '@charset "UTF-8";\n@impor "foo.css";', errors: [ { messageId: "invalidPrelude", From 00a2a3eb0ab5f04f1ecb10322bcf0ac998678062 Mon Sep 17 00:00:00 2001 From: thecalamiity Date: Fri, 8 Aug 2025 00:29:35 +0300 Subject: [PATCH 6/7] improve @charset error messaging --- docs/rules/no-invalid-at-rules.md | 2 +- src/rules/no-invalid-at-rules.js | 25 +++++---------- tests/rules/no-invalid-at-rules.test.js | 42 +++++++++---------------- 3 files changed, 23 insertions(+), 46 deletions(-) diff --git a/docs/rules/no-invalid-at-rules.md b/docs/rules/no-invalid-at-rules.md index beec7d75..f2e7fd7a 100644 --- a/docs/rules/no-invalid-at-rules.md +++ b/docs/rules/no-invalid-at-rules.md @@ -78,7 +78,7 @@ Note on `@charset`: Although it begins with an `@` symbol, it is not an at-rule. @charset ""; ``` -where `charset` is a [``](https://developer.mozilla.org/en-US/docs/Web/CSS/string) denoting the character encoding to be used. It must be the name of a web-safe character encoding defined in the [IANA-registry](https://www.iana.org/assignments/character-sets/character-sets.xhtml), and must be double-quoted, following exactly one space character (U+0020), and immediately terminated with a semicolon. +where `` is a [``](https://developer.mozilla.org/en-US/docs/Web/CSS/string) denoting the character encoding to be used. It must be the name of a web-safe character encoding defined in the [IANA-registry](https://www.iana.org/assignments/character-sets/character-sets.xhtml), and must be double-quoted, following exactly one space character (U+0020) after `@charset`, and immediately terminated with a semicolon. Examples of **incorrect** code: diff --git a/src/rules/no-invalid-at-rules.js b/src/rules/no-invalid-at-rules.js index 8114ee47..5152361e 100644 --- a/src/rules/no-invalid-at-rules.js +++ b/src/rules/no-invalid-at-rules.js @@ -16,7 +16,7 @@ import { isSyntaxMatchError } from "../util.js"; /** * @import { AtrulePlain } from "@eslint/css-tree" * @import { CSSRuleDefinition } from "../types.js" - * @typedef {"unknownAtRule" | "invalidPrelude" | "unknownDescriptor" | "invalidDescriptor" | "invalidExtraPrelude" | "missingPrelude"} NoInvalidAtRulesMessageIds + * @typedef {"unknownAtRule" | "invalidPrelude" | "unknownDescriptor" | "invalidDescriptor" | "invalidExtraPrelude" | "missingPrelude" | "invalidCharsetSyntax"} NoInvalidAtRulesMessageIds * @typedef {CSSRuleDefinition<{ RuleOptions: [], MessageIds: NoInvalidAtRulesMessageIds }>} NoInvalidAtRulesRuleDefinition */ @@ -85,6 +85,8 @@ export default { invalidExtraPrelude: "At-rule '@{{name}}' should not contain a prelude.", missingPrelude: "At-rule '@{{name}}' should contain a prelude.", + invalidCharsetSyntax: + "Invalid @charset syntax. Expected '@charset \"{{encoding}}\";'.", }, }, @@ -143,26 +145,15 @@ export default { const nodeText = sourceCode.getText(node); if (!charsetPattern.test(nodeText)) { - const preludeText = nodeText.slice( - prelude.loc.start.offset, - prelude.loc.end.offset, - ); - - const encoding = preludeText.match(charsetEncodingPattern)?.[1]; + const preludeText = sourceCode.getText(prelude); + const encoding = + preludeText.match(charsetEncodingPattern)?.[1] ?? "UTF-8"; context.report({ loc: prelude.loc, - messageId: "invalidPrelude", - data: { - name, - prelude: preludeText, - expected: "", - }, + messageId: "invalidCharsetSyntax", + data: { encoding }, fix(fixer) { - if (!encoding) { - return null; - } - return fixer.replaceText( node, `@charset "${encoding}";`, diff --git a/tests/rules/no-invalid-at-rules.test.js b/tests/rules/no-invalid-at-rules.test.js index 40fc2fdd..6ba000cb 100644 --- a/tests/rules/no-invalid-at-rules.test.js +++ b/tests/rules/no-invalid-at-rules.test.js @@ -367,11 +367,9 @@ ruleTester.run("no-invalid-at-rules", rule, { output: '@charset "UTF-8";', errors: [ { - messageId: "invalidPrelude", + messageId: "invalidCharsetSyntax", data: { - name: "charset", - prelude: "'UTF-8'", - expected: "", + encoding: "UTF-8", }, line: 1, column: 10, @@ -385,11 +383,9 @@ ruleTester.run("no-invalid-at-rules", rule, { output: '@charset "UTF-8";', errors: [ { - messageId: "invalidPrelude", + messageId: "invalidCharsetSyntax", data: { - name: "charset", - prelude: "UTF-8", - expected: "", + encoding: "UTF-8", }, line: 1, column: 10, @@ -403,11 +399,9 @@ ruleTester.run("no-invalid-at-rules", rule, { output: '@charset "UTF-8";', errors: [ { - messageId: "invalidPrelude", + messageId: "invalidCharsetSyntax", data: { - name: "charset", - prelude: '"UTF-8"', - expected: "", + encoding: "UTF-8", }, line: 1, column: 9, @@ -421,11 +415,9 @@ ruleTester.run("no-invalid-at-rules", rule, { output: '@charset "UTF-8";', errors: [ { - messageId: "invalidPrelude", + messageId: "invalidCharsetSyntax", data: { - name: "charset", - prelude: '"UTF-8"', - expected: "", + encoding: "UTF-8", }, line: 1, column: 11, @@ -439,11 +431,9 @@ ruleTester.run("no-invalid-at-rules", rule, { output: '@charset "UTF-8";', errors: [ { - messageId: "invalidPrelude", + messageId: "invalidCharsetSyntax", data: { - name: "charset", - prelude: '"UTF-8"', - expected: "", + encoding: "UTF-8", }, line: 1, column: 10, @@ -457,11 +447,9 @@ ruleTester.run("no-invalid-at-rules", rule, { output: '@charset "UTF-8";', errors: [ { - messageId: "invalidPrelude", + messageId: "invalidCharsetSyntax", data: { - name: "charset", - prelude: '"UTF-8"', - expected: "", + encoding: "UTF-8", }, line: 1, column: 10, @@ -475,11 +463,9 @@ ruleTester.run("no-invalid-at-rules", rule, { output: '@charset "UTF-8";\n@impor "foo.css";', errors: [ { - messageId: "invalidPrelude", + messageId: "invalidCharsetSyntax", data: { - name: "charset", - prelude: '"UTF-8"', - expected: "", + encoding: "UTF-8", }, line: 1, column: 11, From d8d45fbf3abb024e8f50c54d0da4c91d4028577d Mon Sep 17 00:00:00 2001 From: thecalamiity Date: Wed, 13 Aug 2025 02:58:27 +0300 Subject: [PATCH 7/7] avoid autofixing missing `@charset` encoding --- src/rules/no-invalid-at-rules.js | 18 +++++++++++++---- tests/rules/no-invalid-at-rules.test.js | 26 +++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/rules/no-invalid-at-rules.js b/src/rules/no-invalid-at-rules.js index 5152361e..c10843bb 100644 --- a/src/rules/no-invalid-at-rules.js +++ b/src/rules/no-invalid-at-rules.js @@ -144,11 +144,21 @@ export default { } const nodeText = sourceCode.getText(node); - if (!charsetPattern.test(nodeText)) { - const preludeText = sourceCode.getText(prelude); - const encoding = - preludeText.match(charsetEncodingPattern)?.[1] ?? "UTF-8"; + const preludeText = sourceCode.getText(prelude); + const encoding = preludeText + .match(charsetEncodingPattern)?.[1] + ?.trim(); + if (!encoding) { + context.report({ + loc: prelude.loc, + messageId: "invalidCharsetSyntax", + data: { encoding: "" }, + }); + return; + } + + if (!charsetPattern.test(nodeText)) { context.report({ loc: prelude.loc, messageId: "invalidCharsetSyntax", diff --git a/tests/rules/no-invalid-at-rules.test.js b/tests/rules/no-invalid-at-rules.test.js index 6ba000cb..68a2f0d5 100644 --- a/tests/rules/no-invalid-at-rules.test.js +++ b/tests/rules/no-invalid-at-rules.test.js @@ -362,6 +362,32 @@ ruleTester.run("no-invalid-at-rules", rule, { }, ], }, + { + code: '@charset "";', + errors: [ + { + messageId: "invalidCharsetSyntax", + data: { encoding: "" }, + line: 1, + column: 10, + endLine: 1, + endColumn: 12, + }, + ], + }, + { + code: '@charset " ";', + errors: [ + { + messageId: "invalidCharsetSyntax", + data: { encoding: "" }, + line: 1, + column: 10, + endLine: 1, + endColumn: 14, + }, + ], + }, { code: "@charset 'UTF-8';", output: '@charset "UTF-8";',