Skip to content

Implement CSS Nesting (& selector and nested style rules)#218

Open
jhaygood86 wants to merge 1 commit into
TylerBrinks:masterfrom
jhaygood86:feature/css-nesting
Open

Implement CSS Nesting (& selector and nested style rules)#218
jhaygood86 wants to merge 1 commit into
TylerBrinks:masterfrom
jhaygood86:feature/css-nesting

Conversation

@jhaygood86

Copy link
Copy Markdown
Contributor

Summary

Implements CSS Nesting 1: the & nesting selector and nested style rules inside a declaration block.

Nested rules are resolved at parse time into ordinary flat rules with an absolute selector (&:is(parent)), so the object model and any downstream matcher/cascade need no nesting awareness. Each parent StyleRule exposes its resolved children via a new IStyleRule.NestedRules.

.card {
  color: red;
  & span { color: blue; }   /* → :is(.card) span */
  > p { color: green; }      /* → :is(.card) > p  */
  .inner { font-weight: bold; } /* → :is(.card) .inner */
}

How it works

  • StylesheetComposer classifies a declaration-block construct as a nested rule vs a declaration by scanning bracket-aware for the first top-level { (vs ;/}), with a -- custom-property guard. Function tokens are opaque (their parens are already balanced), so only bare round/square brackets count toward depth. This resolves the div { } (type selector, first token is an Ident) and a:hover { } (ambiguous with a: hover) cases via {-before-;.
  • On the declaration branch it rewinds the lexer (LexerBase.RewindTo, a faithful raw-source-index restore) and lets the unchanged declaration path re-lex in value mode. This is deliberately not a token pushback buffer, which would corrupt a #rrggbb value (the lexer tokenizes #123456 differently in value vs non-value mode).
  • & substitution is string-aware (a char scanner tracking quote state), so a & inside an attribute-value string ([data-x="a&b"]) is preserved and doesn't flip the rule off the descendant branch.

Tests

CssNestingTests — 11 OM-level cases: implicit descendant, &-compound, type-selector nesting, no-& scoping, leading child combinator, hex-color regression (nested value not corrupted), top-level-unaffected, declaration-after-nested-rule, custom-property guard, multi-level nesting, and &-inside-attribute-string preservation. Full suite green.

Ported from PeachPDF, which vendors ExCSS.

Parse nested style rules and the & nesting selector (CSS Nesting 1),
resolving each nested rule at parse time into an ordinary flat rule with an
absolute selector (& -> :is(parent)), exposed on the object model via
IStyleRule.NestedRules so the parser needs no matcher/cascade awareness.

- StylesheetComposer classifies a declaration-block construct as a nested
  rule vs a declaration by scanning bracket-aware for the first top-level {
  (vs ;/}), with a -- custom-property guard. On the declaration branch it
  rewinds the lexer (LexerBase.RewindTo, a faithful source-index restore --
  not the InsertionPoint setter, whose BackNative loop mis-counts across \r\n)
  and re-lexes cleanly in value mode, avoiding the hex-color value-mode
  tokenization hazard a token buffer would hit.
- Nested selectors resolve against the enclosing rule's SelectorText and parse
  via Parser.ParseSelector; the & substitution is string-aware so a & inside an
  attribute-value string is preserved. StyleRule gains a NestedRules list.

Ported from PeachPDF, which vendors ExCSS.
@jhaygood86
jhaygood86 marked this pull request as ready for review July 24, 2026 20:00
@jhaygood86
jhaygood86 marked this pull request as draft July 24, 2026 20:35
@jhaygood86
jhaygood86 marked this pull request as ready for review July 24, 2026 20:35
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