diff --git a/.changeset/fix-css-unicode-escape.md b/.changeset/fix-css-unicode-escape.md new file mode 100644 index 000000000000..f054687cc2c5 --- /dev/null +++ b/.changeset/fix-css-unicode-escape.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [`#9385`](https://github.com/biomejs/biome/issues/9385): the [`noUselessEscapeInString`](https://biomejs.dev/linter/rules/no-useless-escape-in-string/) rule no longer strips valid CSS unicode escapes from string literals. This fixes broken iconfont `content` values caused by the autofix. diff --git a/crates/biome_css_analyze/src/lint/suspicious/no_useless_escape_in_string.rs b/crates/biome_css_analyze/src/lint/suspicious/no_useless_escape_in_string.rs index 4630051d94c4..b26a8efb0666 100644 --- a/crates/biome_css_analyze/src/lint/suspicious/no_useless_escape_in_string.rs +++ b/crates/biome_css_analyze/src/lint/suspicious/no_useless_escape_in_string.rs @@ -19,7 +19,7 @@ declare_lint_rule! { /// /// ```css,expect_diagnostic /// a::after { - /// content: "\a" + /// content: "\g" /// } /// ``` /// @@ -43,6 +43,12 @@ declare_lint_rule! { /// } /// ``` /// + /// ```css + /// a::after { + /// content: "\e7bb" + /// } + /// ``` + /// pub NoUselessEscapeInString { version: "2.0.0", name: "noUselessEscapeInString", @@ -109,16 +115,17 @@ fn next_useless_escape(str: &str, quote: u8) -> Option { b'^' | b'\r' | b'\n' - | b'0'..=b'7' | b'\\' - | b'b' - | b'f' | b'n' | b'r' | b't' | b'u' | b'v' - | b'x' => {} + | b'x' + // CSS unicode escape: \ followed by 1-6 hex digits + | b'0'..=b'9' + | b'a'..=b'f' + | b'A'..=b'F' => {} // Preserve escaping of Unicode characters U+2028 and U+2029 0xE2 => { if !(matches!(it.next(), Some((_, 0x80))) diff --git a/crates/biome_css_analyze/tests/specs/suspicious/noUselessEscapeInString/invalid.css b/crates/biome_css_analyze/tests/specs/suspicious/noUselessEscapeInString/invalid.css index 5dd28f1b4954..2b8a03f19567 100644 --- a/crates/biome_css_analyze/tests/specs/suspicious/noUselessEscapeInString/invalid.css +++ b/crates/biome_css_analyze/tests/specs/suspicious/noUselessEscapeInString/invalid.css @@ -1,5 +1,5 @@ .a::after { - content: /*before*/ "useless \a" /*after*/ + content: /*before*/ "useless \g" /*after*/ } .b::after { content: "useless \'" diff --git a/crates/biome_css_analyze/tests/specs/suspicious/noUselessEscapeInString/invalid.css.snap b/crates/biome_css_analyze/tests/specs/suspicious/noUselessEscapeInString/invalid.css.snap index 1ecee2537601..624197d64ce7 100644 --- a/crates/biome_css_analyze/tests/specs/suspicious/noUselessEscapeInString/invalid.css.snap +++ b/crates/biome_css_analyze/tests/specs/suspicious/noUselessEscapeInString/invalid.css.snap @@ -5,7 +5,7 @@ expression: invalid.css # Input ```css .a::after { - content: /*before*/ "useless \a" /*after*/ + content: /*before*/ "useless \g" /*after*/ } .b::after { content: "useless \'" @@ -19,7 +19,7 @@ invalid.css:2:35 lint/suspicious/noUselessEscapeInString FIXABLE ━━━━ ! The character doesn't need to be escaped. 1 │ .a::after { - > 2 │ content: /*before*/ "useless \a" /*after*/ + > 2 │ content: /*before*/ "useless \g" /*after*/ │ ^ 3 │ } 4 │ .b::after { @@ -28,7 +28,7 @@ invalid.css:2:35 lint/suspicious/noUselessEscapeInString FIXABLE ━━━━ i Safe fix: Unescape the character. - 2 │ ····content:·/*before*/·"useless·\a"·/*after*/ + 2 │ ····content:·/*before*/·"useless·\g"·/*after*/ │ - ``` diff --git a/crates/biome_css_analyze/tests/specs/suspicious/noUselessEscapeInString/valid.css b/crates/biome_css_analyze/tests/specs/suspicious/noUselessEscapeInString/valid.css index 7d9694f15fd3..87f0fae9c296 100644 --- a/crates/biome_css_analyze/tests/specs/suspicious/noUselessEscapeInString/valid.css +++ b/crates/biome_css_analyze/tests/specs/suspicious/noUselessEscapeInString/valid.css @@ -4,4 +4,31 @@ } .b::after { content: "\t" +} +/* CSS unicode escapes: \ followed by 1-6 hex digits */ +.c::after { + content: "\e7bb" +} +.d::after { + content: "\e644" +} +.e::after { + content: "\a" +} +.f::after { + content: "\0a" +} +.g::after { + content: "\AB12CD" +} +/* hex digit 8-9 (not just a-f) */ +.h::after { + content: "\89" +} +/* hex prefix + non-hex suffix: \ee is valid 2-digit escape, zaf is literal */ +.i::after { + content: "\eezaf" +} +.j::after { + content: "\edk" } \ No newline at end of file diff --git a/crates/biome_css_analyze/tests/specs/suspicious/noUselessEscapeInString/valid.css.snap b/crates/biome_css_analyze/tests/specs/suspicious/noUselessEscapeInString/valid.css.snap index eedda05477d6..d391d983aa8f 100644 --- a/crates/biome_css_analyze/tests/specs/suspicious/noUselessEscapeInString/valid.css.snap +++ b/crates/biome_css_analyze/tests/specs/suspicious/noUselessEscapeInString/valid.css.snap @@ -11,4 +11,33 @@ expression: valid.css .b::after { content: "\t" } +/* CSS unicode escapes: \ followed by 1-6 hex digits */ +.c::after { + content: "\e7bb" +} +.d::after { + content: "\e644" +} +.e::after { + content: "\a" +} +.f::after { + content: "\0a" +} +.g::after { + content: "\AB12CD" +} +/* hex digit 8-9 (not just a-f) */ +.h::after { + content: "\89" +} +/* hex prefix + non-hex suffix: \ee is valid 2-digit escape, zaf is literal */ +.i::after { + content: "\eezaf" +} +.j::after { + content: "\edk" +} ``` + +_Note: The parser emitted 2 diagnostics which are not shown here._