Skip to content

fix(attributes): use CSS whitespace for token matching - #1911

Merged
fb55 merged 4 commits into
fb55:masterfrom
xiehuanyi:fix/ascii-attribute-token-whitespace
Nov 6, 2026
Merged

fb55 merged 4 commits into
fb55:masterfrom
xiehuanyi:fix/ascii-attribute-token-whitespace

Conversation

@xiehuanyi

@xiehuanyi xiehuanyi commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

[class~=foo] currently matches class="foo\u00a0bar", while [class~="foo\u00a0bar"] cannot match the complete token. JavaScript \s includes NBSP, EM SPACE and vertical tab; CSS uses only TAB, LF, FF, CR and SPACE as whitespace (Selectors §3.7, attribute selectors).

Use those five characters for both the ~= selector-value check and attribute token boundaries. The tests cover non-CSS whitespace as token content, case-insensitive matching, and all five CSS separators.

Validation: the new suite fails in three cases before the change; npm test passes with 225 tests and one existing skipped test, including ESLint, TypeScript and Biome checks. npm run build and git diff --check pass.

Prepared and tested with OpenAI Codex assistance. This covers token whitespace; the separate empty-value issue already has PR #1810.

View guided diff

Summary by CodeRabbit

  • Bug Fixes
    • Attribute selectors now split tokens only on CSS whitespace characters: tabs, line feeds, form feeds, carriage returns, and spaces. Other whitespace-like characters, including non-breaking spaces, remain part of the token rather than creating a boundary. Selectors match the complete token as expected, including when case-insensitive matching is used; the text on either side of a non-breaking space does not match as separate tokens.

Copilot AI balanced review requested due to automatic review settings October 12, 2026 18:42

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-06T17:17:46.684856Z 4f04e83 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Explain Change →

No actionable comments were generated in the recent review.

Walkthrough

Attribute selector tokenization now treats only tab, line feed, form feed, carriage return, and space as separators. A test checks that NBSP remains within one token.

Changes: Attribute Tokenization

Layer / File(s) Summary
Whitespace matching and token boundaries
src/attributes.ts, test/attributes.ts
The whitespace and token-boundary regexes now recognize five CSS whitespace characters. The test verifies that NBSP does not split a ~= attribute value, with and without the i flag.

Estimated code review effort: 2 (Simple) | ~8 minutes

Change: Bug fix

Possibly related PRs

  • fb55/css-select#1916: Also changes [attr~=value] matching, by rejecting empty values before boundary matching.

Suggested reviewers: fb55

Merge Risk: Minimal · up to 4f04e

The change preserves NBSP within attribute tokens. No merge-blocking risk was established.

Pre-merge checks: Passed 5

Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly describes the change to use CSS whitespace for attribute token matching.
Docstring Coverage Passed Docstring coverage is inconclusive: files from earlier reviews still need to be rechecked within the check's analysis limits. Not blocking.
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.
Finishing Touches

Generate unit tests (beta)

  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

A rabbit checks the tokens tight
Five spaces mark the boundary right
NBSP stays with its token mate
Case-insensitive matches wait
The tests confirm the parsing state

Recent review info

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 9c8a6bfc-9c66-4dea-ac64-9596d7a67f58
Commits

Reviewing files that changed from the base of the PR and between 66e3e2f and 4f04e83.

Files selected for processing: 1

  • test/attributes.ts

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

@cubic-dev-ai cubic-dev-ai 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.

No issues found across 2 files

View guided diff | Re-trigger cubic

@greptile-apps

greptile-apps Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge.

Summary

Changes ~= matching to use CSS whitespace instead of JavaScript whitespace.

  • Attribute token selectors split values only at CSS whitespace.

Reviews (2) · Last reviewed commit: "test(attributes): test one non-CSS white..." · Reviewed by Greptile

fb55 and others added 3 commits November 5, 2026 15:42
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
All five cases pass on master, where `\s` already splits on them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NBSP, EM SPACE and VT all reach the same character class, so one case
covers the path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="test/attributes.ts">

<violation number="1" location="test/attributes.ts:14">
P2: This test now exercises only NBSP, so a regression that treats TAB, LF, FF, CR, or SPACE as token content would pass the suite. Retain a parameterized assertion for all five CSS separators.</violation>
</file>

Reply with feedback, questions, or to request a fix.

View guided diff | Re-trigger cubic

Comment thread test/attributes.ts
Comment on lines +14 to +32
it("should keep non-CSS whitespace inside a token", () => {
const token = "foo\u{A0}bar";
const document = parseDocument(`<i class="${token}"></i>`);
expect(CSSselect.selectAll("[class~=foo]", document)).toHaveLength(
0,
);
expect(CSSselect.selectAll("[class~=bar]", document)).toHaveLength(
0,
);
expect(
CSSselect.selectAll(`[class~="${token}"]`, document),
).toStrictEqual(document.children);
expect(
CSSselect.selectAll(
`[class~="${token.toUpperCase()}" i]`,
document,
),
).toStrictEqual(document.children);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: This test now exercises only NBSP, so a regression that treats TAB, LF, FF, CR, or SPACE as token content would pass the suite. Retain a parameterized assertion for all five CSS separators.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At test/attributes.ts, line 14:

<comment>This test now exercises only NBSP, so a regression that treats TAB, LF, FF, CR, or SPACE as token content would pass the suite. Retain a parameterized assertion for all five CSS separators.</comment>

<file context>
@@ -11,46 +11,25 @@ const domChilds = dom.children as Element[];
-                ).toBe(boolbase.falseFunc);
-            },
-        );
+        it("should keep non-CSS whitespace inside a token", () => {
+            const token = "foo\u{A0}bar";
+            const document = parseDocument(`<i class="${token}"></i>`);
</file context>
Suggested change
it("should keep non-CSS whitespace inside a token", () => {
const token = "foo\u{A0}bar";
const document = parseDocument(`<i class="${token}"></i>`);
expect(CSSselect.selectAll("[class~=foo]", document)).toHaveLength(
0,
);
expect(CSSselect.selectAll("[class~=bar]", document)).toHaveLength(
0,
);
expect(
CSSselect.selectAll(`[class~="${token}"]`, document),
).toStrictEqual(document.children);
expect(
CSSselect.selectAll(
`[class~="${token.toUpperCase()}" i]`,
document,
),
).toStrictEqual(document.children);
});
it("should keep non-CSS whitespace inside a token", () => {
const token = "foo\u{A0}bar";
const document = parseDocument(`<i class="${token}"></i>`);
expect(CSSselect.selectAll("[class~=foo]", document)).toHaveLength(
0,
);
expect(CSSselect.selectAll("[class~=bar]", document)).toHaveLength(
0,
);
expect(
CSSselect.selectAll(`[class~="${token}"]`, document),
).toStrictEqual(document.children);
expect(
CSSselect.selectAll(
`[class~="${token.toUpperCase()}" i]`,
document,
),
).toStrictEqual(document.children);
});
it.each(["\t", "\n", "\f", "\r", " "])(
"should split tokens on CSS whitespace %j",
(space) => {
const document = parseDocument(
`<i class="foo${space}bar"></i>`,
);
expect(
CSSselect.selectAll("[class~=foo]", document),
).toStrictEqual(document.children);
expect(
CSSselect.selectAll("[class~=bar]", document),
).toStrictEqual(document.children);
expect(
CSSselect._compileUnsafe(`[class~="foo${space}bar"]`),
).toBe(boolbase.falseFunc);
},
);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Intentional: the five-separator rows all pass on master (\s already splits on them), so they don't test this change; splitting on spaces stays covered by the existing ~= tests ([data-foo~=IT i], the nwmatcher [class~=…] cases). The remaining NBSP row is the one that fails without the fix.

🤖 Generated with Claude Code, using Opus 5.5.

@fb55
fb55 merged commit 3a34d99 into fb55:master Nov 6, 2026
12 checks passed
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.

3 participants