Skip to content

css: recognize :target-current, :target-before and :target-after - #41139

Open
oddharsh wants to merge 4 commits into
oven-sh:mainfrom
oddharsh:css-target-current-pseudo-classes
Open

oddharsh wants to merge 4 commits into
oven-sh:mainfrom
oddharsh:css-target-current-pseudo-classes

Conversation

@oddharsh

@oddharsh oddharsh commented Sep 2, 2026 •

Copy link
Copy Markdown

Fixes #42480.

Problem

:target-current, :target-before and :target-after are css-overflow-5 pseudo-classes, and lightningcss carries all three. Bun's table does not, so they parse as PseudoClass::Custom and are emitted verbatim while the :target-within sitting next to them in the same table is normalized. Measured on 1.4.0:

in:  .a:TARGET-CURRENT{color:red} .b:TARGET-WITHIN{color:red} .c:TARGET-BEFORE{color:red}
out: .a:TARGET-CURRENT{color:red}.b:target-within{color:red}.c:TARGET-BEFORE{color:red}

A pseudo-class name is ASCII case-insensitive, so those are two spellings of one selector and only one of them is canonical today.

This is also the row that regresses when #38572 lands. That PR corrects the bare-pseudo-class fallback from warning on a _ prefix to the upstream - rule, which is right, and the moment it does, all three of these start printing Invalid selector. Unsupported pseudo-class or pseudo-element on valid CSS. Following on from #41120 and #41122.

Fix

Three variants, three table entries, three serialization arms. lookup_non_ts_pseudo_class already matches case-insensitively, so recognition is what buys the canonical spelling.

Hard-enabled rather than flag-gated, which was the open question on #41122. lightningcss puts these behind ParserFlags::SCROLL_NAVIGATION_CONTROLS and defaults it off, so porting the gate faithfully was the alternative. It does not work here: ParserFlags in src/css/css_parser.rs:2989 carries three bits, the one ParserOptions construction in the crate uses ParserFlags::default(), and repo-wide the type appears only in src/css/css_parser.rs, src/css/lib.rs, src/css/selectors/parser.rs, src/css_jsc/css_internals.rs and two test files. Nothing on the bundler path sets a bit, so a fourth one would be unreachable from bun build and the fix would never actually apply. A flag defaulted on is the same as no flag, with more surface to keep.

No compat entries, and nothing about the emitted CSS changes except the casing. I told you on #41122 that recognizing these would turn on :is() lowering under old targets, and that was wrong, so here is the correction. In is_compatible, Component::NonTsPseudoClass sends PseudoClass::Custom { .. } => {} and _ => {} to the same trailing return false, so an unmapped known variant and an unknown custom one are already treated identically. Adding Feature::TargetCurrent / TargetBeforeAfter would be the behavior change rather than the status quo, and src/css/compat.rs is generated and stamped DO NOT EDIT. #41122 added no compat entries for its four pseudo-elements either, so this matches it.

Testing

test/bundler/css/target-current-pseudo-classes-41120.test.ts, one test asserting canonical serialization and an empty log, with :TARGET-WITHIN in the same fixture as the in-file control for what an already-known name does.

Verified failing, not verified passing, and here is exactly why. Under USE_SYSTEM_BUN=1 bun test it fails on released 1.4.0 with the difference this PR is about:

Expected: ".a:target-current{color:red}.b:target-before{color:green}..."
Received: ".a:TARGET-CURRENT{color:red}.b:Target-Before{color:green}..."

I could not run the other half, because bun bd does not complete on this machine (macOS 27, Homebrew llvm@21, the compiler scripts/build.ts requires). The Rust side builds clean, bun_css and every downstream crate, so the change compiles and no exhaustive match elsewhere is broken by the three new variants. The C++ side fails on two things unrelated to this diff, both traced to Homebrew clang against the macOS 27 SDK rather than to Bun's source: NAN is undefined because the SDK routes it through <float.h> under __has_feature(modules) and libc++'s float.h does not implement that protocol, and -Werror rejects Apple's stack_protector_ignore attribute as unknown. Filed separately as #41141 with the measurements.

So CI builds this on your toolchain, and the failing-side control above is the half that proves the test is not vacuous.

An earlier draft had a second test on the plain lowercase spelling. It passed with USE_SYSTEM_BUN=1, because a custom pseudo-class already round-trips unchanged, so it asserted nothing and came back out. Case is the only externally observable difference this change makes, which is why the whole test rests on it.

These are css-overflow-5 pseudo-classes and lightningcss carries all
three. Bun's table did not, so they parsed as PseudoClass::Custom and
were emitted verbatim while the :target-within beside them in the same
table was normalized. A pseudo-class name is ASCII case-insensitive, so
:TARGET-CURRENT and :target-current are one selector and only one of the
two spellings was canonical.

Hard-enabled rather than gated behind a port of lightningcss's
SCROLL_NAVIGATION_CONTROLS flag: nothing on the bundler path sets a
ParserFlags bit, so a gated entry would be unreachable from bun build.

No compat entries, so is_compatible is unchanged: it already sends both
PseudoClass::Custom and unmapped known variants to the same return false.

@claude claude Bot left a comment

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.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@coderabbitai

coderabbitai Bot commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 60dc8771-8e05-4e68-8244-57b61f9015e0

📥 Commits

Reviewing files that changed from the base of the PR and between 85e9ddc and 25fb45f.

📒 Files selected for processing (3)
  • src/css/selectors/parser.rs
  • src/css/selectors/selector.rs
  • test/bundler/css/target-current-pseudo-classes-41120.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


Walkthrough

The CSS selector parser now recognizes three CSS Overflow 5 scroll-marker pseudo-classes. The serializer emits their canonical names. A bundler test verifies mixed-case parsing and lowercase minified output.

Changes

CSS scroll-marker pseudo-classes

Layer / File(s) Summary
Typed pseudo-class parsing
src/css/selectors/parser.rs
Adds TargetCurrent, TargetBefore, and TargetAfter variants and maps their names case-insensitively during parsing.
Serialization and minification validation
src/css/selectors/selector.rs, test/bundler/css/target-current-pseudo-classes-41120.test.ts
Serializes the new variants with canonical names. The test validates mixed-case input and lowercase minified output.

Suggested reviewers: robobun, jarred-sumner

Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to 25fb4

This change adds support for the three CSS Overflow 5 target pseudo-classes and canonical lowercase output, with regression coverage for mixed-case input and minified serialization. No merge-blocking risk is identified.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title clearly and concisely identifies the main change: recognition of the three CSS pseudo-classes.
Description check ✅ Passed The description explains the problem, implementation, flag decision, compatibility impact, and verification results. It uses Problem, Fix, and Testing sections instead of the template headings, but it…

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

@coderabbitai

coderabbitai Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@robobun

robobun commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the patch. Fork PRs from a first-time contributor cannot start CI here, so #42484 carries the same three-file change on a branch where CI runs, with you as co-author on the commit. The test moved into test/js/bun/css/css.test.ts and cites #42480.

This branch has not been deployed

No deployments
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.

css: :target-current, :target-before and :target-after are unknown to the pseudo-class table, so they are emitted verbatim and never merge

2 participants