Skip to content

fix: escape all special characters in identifiers when stringifying#1802

Open
spokodev wants to merge 2 commits into
fb55:masterfrom
spokodev:fix/stringify-escape-special-chars
Open

fix: escape all special characters in identifiers when stringifying#1802
spokodev wants to merge 2 commits into
fb55:masterfrom
spokodev:fix/stringify-escape-special-chars

Conversation

@spokodev

@spokodev spokodev commented Jun 17, 2026

Copy link
Copy Markdown

Bug

charsToEscapeInName is missing several characters that are special in selector syntax, so stringify() produces selectors that re-parse to a different AST (or fail to parse). parse(stringify(x)) is no longer equal to x:

import { parse, stringify } from "css-what";

stringify(parse(String.raw`.x\&y`));         // ".x&y"        -> parses as 2 selectors (`&` nesting)
stringify(parse(String.raw`[data-foo\=bar]`)); // "[data-foo=bar]" -> name `data-foo` + operator `=`
stringify(parse(String.raw`.a\{b\}c`));      // ".a{b}c"      -> unparseable

This breaks round-tripping of modern class names that contain these characters — e.g. Tailwind arbitrary values and CSS-nesting fragments (grid-cols-[&], data-[x=y], …).

Fix

Add =, &, ?, @, `, { and } to charsToEscapeInName, so every special ASCII character in an identifier is escaped — completing the set alongside the # > < / , ; etc. that are already handled. Escaping is transparent to parse, so existing round-trips are unaffected; only previously-corrupted ones are fixed.

Tests

Added cases to the "Stringify CSS spec compliance" block in stringify.spec.ts (a=ba\=b, &, {}, ?, @). They fail on main (the characters are emitted unescaped) and pass with this change. Full suite: 860 passing, no regressions.

Same class as #1056 (which added %); distinct from #1791, which fixes escape consumption on the parse side — this is escape production on the stringify side.

Summary by CodeRabbit

  • Bug Fixes

    • Improved CSS selector escaping so tag/identifier names containing =, &, ?, @, backticks, {, and } are handled correctly.
  • Tests

    • Expanded escape expectation coverage for these special-character identifiers.
    • Added round-trip verification to ensure selectors with escaped special characters consistently parse and stringify back to the original form.

`charsToEscapeInName` was missing several characters that are special in
selector syntax, so `stringify()` emitted selectors that re-parse to a
different AST or fail to parse. This breaks round-tripping of modern
class names (arbitrary values, CSS nesting), e.g.:

  stringify(parse(".x\\&y"))        // => ".x&y"      (two selectors)
  stringify(parse("[data-foo\\=x]")) // => "[data-foo=x]" (name+operator)
  stringify(parse(".a\\{b\\}c"))    // => ".a{b}c"

Add `=`, `&`, `?`, `@`, `` ` ``, `{` and `}` so every special ASCII
character in an identifier is escaped, matching the existing handling of
`# > < / , ;` etc.
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f75892da-7a13-4bdb-9b69-ef2826974498

📥 Commits

Reviewing files that changed from the base of the PR and between af3afa9 and c24aff0.

📒 Files selected for processing (1)
  • src/stringify.spec.ts

📝 Walkthrough

Walkthrough

charsToEscapeInName in stringify.ts is extended with seven additional special characters (=, &, ?, @, `, {, }), causing escapeName to hex/char-escape them during selector stringification. Corresponding test cases are added to the escapeCases table, and a new round-trip test suite validates that parsed selectors remain stable through stringify/parse cycles.

Changes

Escape character expansion

Layer / File(s) Summary
Expand escape character set and test outputs
src/stringify.ts, src/stringify.spec.ts
Adds =, &, ?, @, `, {, } to the escape character set in escapeName, and extends the escapeCases test table with five new entries asserting the correct escaped CSS output for those characters in a tag name.
Round-trip validation tests
src/stringify.spec.ts
Adds a new test suite that validates stringify + parse round-tripping for selectors containing the newly escaped special characters, ensuring that parse(stringify(parse(selector))) equals parse(selector).

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Poem

🐇 Hop hop, the curly brace must flee,
The @ and ? can no longer roam free,
A backtick, ampersand, equals sign too—
All hex-escaped now, tidy and true.
The rabbit has tidied the CSS zoo! 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main change: extending the escape character set in identifiers when stringifying. This directly reflects the core modification in both stringify.ts and the corresponding test coverage.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/stringify.spec.ts`:
- Around line 31-35: The test data array starting at line 31 is missing coverage
for the backtick character and specific round-trip selector contexts. Add test
cases to this array for the backtick escape (backtick paired with its escaped
equivalent string), and add three additional test cases for the exact round-trip
selector contexts mentioned in the PR: the class selector with escaped ampersand
(`.x\&y`), the data attribute selector with escaped equals (` [data-foo\=bar]`),
and the class selector with escaped braces (`.a\{b\}c`). Each test case should
follow the existing pattern of providing the input string, the expected escaped
output using String.raw, and a descriptive label.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 902c7b00-7785-47ad-87c4-7401897ac6e0

📥 Commits

Reviewing files that changed from the base of the PR and between 2d552b9 and af3afa9.

📒 Files selected for processing (2)
  • src/stringify.spec.ts
  • src/stringify.ts

Comment thread src/stringify.spec.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant