Skip to content

fix(linter): CSS noUselessEscapeInString now recognizes hex unicode escapes#9418

Closed
CloveSVG wants to merge 1 commit intobiomejs:mainfrom
CloveSVG:fix/css-no-useless-escape-hex-digits
Closed

fix(linter): CSS noUselessEscapeInString now recognizes hex unicode escapes#9418
CloveSVG wants to merge 1 commit intobiomejs:mainfrom
CloveSVG:fix/css-no-useless-escape-hex-digits

Conversation

@CloveSVG
Copy link
Copy Markdown

@CloveSVG CloveSVG commented Mar 9, 2026

Summary

  • Fixed the CSS noUselessEscapeInString rule to correctly handle CSS unicode escapes (\ followed by 1-6 hex digits).
  • The rule was previously using JavaScript escape semantics (only \0-\7 octal escapes), causing valid CSS hex escapes like \e7bb and \e644 to be incorrectly flagged and removed by the auto-fix.
  • This broke icon font rendering when users applied auto-fix, as the unicode code points were stripped.

Root Cause

The next_useless_escape function in the CSS version of the rule was copied from the JavaScript version without adapting it to CSS escape syntax. In CSS:

  • \ followed by 1-6 hex digits (0-9, a-f, A-F) is a valid unicode escape (e.g., \e7bb = U+E7BB)
  • \ followed by a newline is a line continuation
  • \ followed by \\ is an escaped backslash
  • \ followed by the matching quote is an escaped quote
  • Everything else is a useless identity escape

The old code only recognized \0-\7 as valid (JavaScript octal escapes), and included JS-specific sequences like \b, \f, \n (the letter), \t, \u, \v, \x as meaningful - none of which are special CSS escape sequences.

Before/After

Before (broken):

/* Input */
.icon-file:before {
  content: "\e7bb";
}

/* After auto-fix - WRONG, breaks icon font! */
.icon-file:before {
  content: "e7bb";
}

After (fixed):

/* Input - no longer flagged, no auto-fix applied */
.icon-file:before {
  content: "\e7bb";
}

Test plan

  • Updated invalid test case: changed \a to \z since \a is now correctly recognized as a hex escape (U+000A)
  • Added valid test cases for CSS hex escapes: \e7bb, \e644, \a, \abcdef, \0, \\
  • All snapshot tests pass (cargo test -p biome_css_analyze -- no_useless_escape_in_string)
  • Ran just gen-rules for code generation

Closes #9385.

This PR was authored with the assistance of Claude Code.

🤖 Generated with Claude Code

…scapes

The CSS `noUselessEscapeInString` rule was using JavaScript escape rules
instead of CSS-specific ones. In CSS, `\` followed by 1-6 hex digits
(0-9, a-f, A-F) is a valid unicode escape, but the rule only recognized
`\0`-`\7` (JS octal escapes) as meaningful. This caused valid CSS hex
escapes like `\e7bb` or `\e644` (commonly used in icon fonts) to be
incorrectly flagged as useless and removed by the auto-fix, breaking
iconfont rendering.

Fixes #9385.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 9, 2026

🦋 Changeset detected

Latest commit: 1de69ed

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 13 packages
Name Type
@biomejs/biome Patch
@biomejs/cli-win32-x64 Patch
@biomejs/cli-win32-arm64 Patch
@biomejs/cli-darwin-x64 Patch
@biomejs/cli-darwin-arm64 Patch
@biomejs/cli-linux-x64 Patch
@biomejs/cli-linux-arm64 Patch
@biomejs/cli-linux-x64-musl Patch
@biomejs/cli-linux-arm64-musl Patch
@biomejs/wasm-web Patch
@biomejs/wasm-bundler Patch
@biomejs/wasm-nodejs Patch
@biomejs/backend-jsonrpc Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added A-Linter Area: linter L-CSS Language: CSS and super languages labels Mar 9, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 9, 2026

Walkthrough

This PR corrects the CSS linter rule noUselessEscapeInString to properly recognise CSS unicode escapes (a backslash followed by 1–6 hexadecimal digits). Previously, the rule applied JavaScript escape semantics, incorrectly flagging and auto-fixing valid CSS hex escapes like \e7bb and \e644 used in icon fonts. Changes include reworking the escape detection logic to handle CSS-specific rules, adding handling for line continuations and escaped backslashes, and updating test cases to reflect the corrected behaviour.

Suggested labels

A-Linter, L-CSS

Suggested reviewers

  • ematipico
  • Netail
  • denbezrukov
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarises the primary change: fixing the CSS linter rule to recognise hex unicode escapes in the noUselessEscapeInString rule.
Description check ✅ Passed The description is well-detailed and directly related to the changeset, explaining the root cause, impact, and fix for CSS unicode escape handling.
Linked Issues check ✅ Passed The PR fully addresses issue #9385 by correctly recognising and preserving CSS unicode escapes (1-6 hex digits), preventing valid icon font code points from being stripped by auto-fix.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing CSS unicode escape handling in the noUselessEscapeInString rule, with appropriate test updates and changelog documentation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
.changeset/fix-css-unicode-escape.md (1)

5-5: Make the changeset a touch more release-note shaped.

This still leans a bit into root-cause territory. I’d keep the rule name, but swap the JavaScript/CSS implementation detail for the user-visible bit: valid icon-font escapes are preserved again.

Possible wording
-Fixed [`#9385`](https://github.com/biomejs/biome/issues/9385): the CSS [`noUselessEscapeInString`](https://biomejs.dev/linter/rules/no-useless-escape-in-string/) rule now correctly recognizes CSS unicode escapes (`\` followed by 1-6 hex digits). Previously, the rule used JavaScript escape rules and only treated `\0`-`\7` as meaningful, causing valid CSS hex escapes like `\e7bb` or `\e644` (used in icon fonts) to be incorrectly flagged and removed by the auto-fix.
+Fixed [`#9385`](https://github.com/biomejs/biome/issues/9385): the CSS [`noUselessEscapeInString`](https://biomejs.dev/linter/rules/no-useless-escape-in-string/) rule no longer strips valid unicode escapes from string values such as icon-font `content`. Auto-fixes now preserve escapes like `\e7bb` and `\e644` instead of removing the leading backslash.

As per coding guidelines, "Changeset descriptions must be written for end users (explain impact and what changed), not developers (avoid implementation details) in 1-3 sentences".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.changeset/fix-css-unicode-escape.md at line 5, Update the changeset text in
.changeset/fix-css-unicode-escape.md to be a short, user-facing release note:
keep the rule name "noUselessEscapeInString" and state the user-visible outcome
(valid CSS icon-font escapes are preserved again), remove the JS vs CSS
implementation details and root-cause discussion, and keep it to 1–3 concise
sentences that explain the impact (what changed and why users care).
crates/biome_css_analyze/tests/specs/suspicious/noUselessEscapeInString/valid.css (1)

21-23: This fixture promises a newline and only tests \\.

The continuation arm in next_useless_escape still isn’t covered here; .g::after only exercises an escaped backslash. Either add a real backslash-newline case or trim the comment so it says what it does on the tin.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@crates/biome_css_analyze/tests/specs/suspicious/noUselessEscapeInString/valid.css`
around lines 21 - 23, The test fixture comment claims to test a
backslash-newline continuation but the rule `.g::after { content: "\\\" }` only
tests an escaped backslash; update the fixture so the continuation branch in
next_useless_escape is exercised by either adding a real backslash+newline case
in the CSS content (a backslash character immediately followed by a literal
newline inside the content value) or, if you prefer not to add that case, change
the comment to accurately describe that the fixture only tests an escaped
backslash; reference `.g::after` and the parser branch next_useless_escape when
making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/biome_css_analyze/src/lint/suspicious/no_useless_escape_in_string.rs`:
- Around line 131-132: The match arm in no_useless_escape_in_string.rs that
handles line continuations currently only matches b'\r' and b'\n'; add the
form-feed byte (b'\x0C' or b'\f') to that arm so that `\` + form feed is treated
as a valid line continuation rather than falling through to the default
useless-escape case—update the line-continuation match in the function handling
escapes in the suspicious lint to include b'\x0C'.

---

Nitpick comments:
In @.changeset/fix-css-unicode-escape.md:
- Line 5: Update the changeset text in .changeset/fix-css-unicode-escape.md to
be a short, user-facing release note: keep the rule name
"noUselessEscapeInString" and state the user-visible outcome (valid CSS
icon-font escapes are preserved again), remove the JS vs CSS implementation
details and root-cause discussion, and keep it to 1–3 concise sentences that
explain the impact (what changed and why users care).

In
`@crates/biome_css_analyze/tests/specs/suspicious/noUselessEscapeInString/valid.css`:
- Around line 21-23: The test fixture comment claims to test a backslash-newline
continuation but the rule `.g::after { content: "\\\" }` only tests an escaped
backslash; update the fixture so the continuation branch in next_useless_escape
is exercised by either adding a real backslash+newline case in the CSS content
(a backslash character immediately followed by a literal newline inside the
content value) or, if you prefer not to add that case, change the comment to
accurately describe that the fixture only tests an escaped backslash; reference
`.g::after` and the parser branch next_useless_escape when making the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ff356cde-b699-4939-80b4-7e32ae821fe1

📥 Commits

Reviewing files that changed from the base of the PR and between 69e1973 and 1de69ed.

⛔ Files ignored due to path filters (2)
  • crates/biome_css_analyze/tests/specs/suspicious/noUselessEscapeInString/invalid.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/suspicious/noUselessEscapeInString/valid.css.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (4)
  • .changeset/fix-css-unicode-escape.md
  • crates/biome_css_analyze/src/lint/suspicious/no_useless_escape_in_string.rs
  • crates/biome_css_analyze/tests/specs/suspicious/noUselessEscapeInString/invalid.css
  • crates/biome_css_analyze/tests/specs/suspicious/noUselessEscapeInString/valid.css

Comment on lines +131 to +132
// Line continuation: \ followed by newline or carriage return
b'\r' | b'\n' => {}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

In CSS syntax, is a backslash followed by form feed (U+000C) treated as a string continuation/newline escape, alongside LF and CR?

💡 Result:

Yes—effectively.

In CSS Syntax, the input is preprocessed so that U+000C FORM FEED (and U+000D CR, and CRLF) are first converted to a single U+000A LF. [1] The tokenizer then defines newline as U+000A only (explicitly excluding CR and FF because they were converted). [1]

When consuming a string token, if a backslash is followed by a newline, that newline is consumed/ignored (a line-continuation). [2] Since FF becomes LF during preprocessing, a source sequence \ + U+000C behaves the same as \ + U+000A. [1][2]

Sources:
[1] CSS Syntax Module Level 3 — preprocessing + newline definition https://www.w3.org/TR/css-syntax-3/
[2] CSS Syntax Module Level 3 — “Consume a string token” (reverse-solidus + newline) https://www.w3.org/TR/css-syntax-3/


🏁 Script executed:

head -140 crates/biome_css_analyze/src/lint/suspicious/no_useless_escape_in_string.rs | tail -20

Repository: biomejs/biome

Length of output: 819


🏁 Script executed:

wc -l crates/biome_css_analyze/src/lint/suspicious/no_useless_escape_in_string.rs

Repository: biomejs/biome

Length of output: 136


🏁 Script executed:

cat -n crates/biome_css_analyze/src/lint/suspicious/no_useless_escape_in_string.rs

Repository: biomejs/biome

Length of output: 6103


Add form-feed character to line-continuation match.

CSS preprocesses form feed (U+000C) to LF, making \ + form feed a valid string continuation. Lines 131–132 only match CR and LF, so form feed falls through to the default case and gets flagged as a useless escape, which would incorrectly strip a meaningful continuation.

Patch
-                // Line continuation: \ followed by newline or carriage return
-                b'\r' | b'\n' => {}
+                // Line continuation: \ followed by newline, carriage return, or form feed
+                b'\r' | b'\n' | b'\x0C' => {}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_css_analyze/src/lint/suspicious/no_useless_escape_in_string.rs`
around lines 131 - 132, The match arm in no_useless_escape_in_string.rs that
handles line continuations currently only matches b'\r' and b'\n'; add the
form-feed byte (b'\x0C' or b'\f') to that arm so that `\` + form feed is treated
as a valid line continuation rather than falling through to the default
useless-escape case—update the line-continuation match in the function handling
escapes in the suspicious lint to include b'\x0C'.

@ematipico
Copy link
Copy Markdown
Member

No bots

@ematipico ematipico closed this Mar 9, 2026
denbezrukov added a commit that referenced this pull request Mar 9, 2026
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Linter Area: linter L-CSS Language: CSS and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

📝CSS linter removes unicode escape in content property (iconfont broken)

2 participants