Add support for CSS if() inline conditional function parsing - #253
Merged
Conversation
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
bartveneman
force-pushed
the
claude/great-tesla-9us65p
branch
from
August 15, 2026 12:25
69f376b to
689df0c
Compare
Contributor
|
| 📦 Package | 📏 Base Size | 📏 Source Size | 📈 Size Change |
|---|---|---|---|
| @projectwallace/css-parser | 42.1 kB | 44.5 kB | +2.4 kB |
Member
Author
|
Rebased onto latest Generated by Claude Code |
4 tasks
bartveneman
force-pushed
the
claude/great-tesla-9us65p
branch
from
August 16, 2026 09:06
2e77351 to
62186e0
Compare
commit: |
bartveneman
added a commit
that referenced
this pull request
Aug 16, 2026
## Summary Prep refactor split out of #253, so that PR stays focused on the `if()`-parsing feature itself. Pulls media-feature (incl. range syntax), supports-condition (incl. compound `and`/`or`/`not`), and `style()`/`selector()`/`font-tech()` function-condition parsing out of `AtRulePreludeParser` into a new `ConditionParser` class (`src/parse-condition.ts`), so this logic has a single implementation instead of being duplicated by an upcoming change to `ValueNodeParser` (needed so `if()`'s `media()`/`supports()`/`style()` condition functions produce real `MediaFeature`/`FeatureRange`/`SupportsQuery`/`SupportsDeclaration` nodes instead of ad-hoc ones). ## Design notes - `ConditionParser` takes an already-constructed `ValueNodeParser` rather than importing and instantiating its own, since `parse-atrule-prelude.ts` already imports `value-node-parser.ts` — a runtime import the other way would be circular. The constructor parameter is typed via `import type`, which is erased at build time and creates no runtime dependency in either direction. - `ConditionParser` owns its own `Lexer`, separate from whichever class composes it — matching this codebase's existing sub-parser convention (e.g. how `AtRulePreludeParser` already composes `ValueNodeParser`). Where `AtRulePreludeParser` calls into it mid-scan on its own lexer, it now explicitly reseeks to `ConditionParser.end_position` afterward, since the delegated work no longer advances `AtRulePreludeParser`'s lexer as a side effect the way an in-class method call would have. ## Test plan - [x] Pure refactor: no behavior change intended for `@media`/`@supports`/`@container`/`@import` parsing. - [x] Full existing test suite passes unchanged (1391 tests, no assertion changes). - [x] `tsc --noEmit`, `oxlint`, `oxfmt --check` all clean. - [x] `pnpm run build` succeeds with no circular-import issues; `publint` clean. --- _Generated by [Claude Code](https://claude.ai/code/session_015D2xHEuZHwna87DA8VeYjT)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
The CSS inline if() function uses colons and semicolons as structural delimiters (condition: value; condition: value; else: fallback). These were previously silently dropped inside function argument parsing. Now TOKEN_COLON and TOKEN_SEMICOLON produce OPERATOR nodes in value contexts, preserving the full structure of if(), style(), supports(), and media() condition functions in the AST.
Implements the CSS Values Level 5 grammar for inline if():
if( <if-branch>+ )
<if-branch> = <if-condition> : <declaration-value>? ;?
<if-condition> = style(…) | media(…) | supports(…) | else
Key changes:
- New IF_BRANCH (58) node type in the arena. Each condition/value pair
inside if() becomes an IfBranch node — a first-class AST node rather
than a flat list of tokens.
- IfBranch exposes:
.condition — the condition text ("style(--x: 1)", "else", …)
.value — the value text ("green", "red", …), or null if absent
.is_else — true on the else branch
.first_child — parsed condition node (Function or Identifier)
.children — condition node followed by parsed value nodes
- FUNCTION("if") children are exclusively IfBranch nodes; colons and
semicolons are structural separators and are not emitted as OPERATOR
nodes at the if() level.
- Condition functions (style(), supports(), media()) are generic FUNCTION
nodes. Their `:` separators are preserved as OPERATOR children (the
TOKEN_COLON → OPERATOR change from the previous commit), which is
correct for all three condition types.
- Nested if() functions are parsed recursively via the same dispatch in
parse_function_node().
- New is_if_branch() type predicate and IfBranch TypeScript type
exported from the public API.
style() and supports() conditions now produce a DECLARATION child (property + VALUE), media() conditions produce a MEDIA_FEATURE child (property + value children), and each IF_BRANCH value is wrapped in a VALUE node so branch.value returns a Value node instead of a raw string.
Rebasing onto main (which added RATIO = 58) collided with this branch's IF_BRANCH = 58, so IF_BRANCH moves to 59. Also widens Function's children type to include Declaration/MediaFeature, which if()'s style()/supports()/media() condition parsing produces but the type didn't account for.
- css-node.ts: use braces/newline for guard-clause return in IfBranch.condition getter - IfBranch.condition now returns the parsed condition node (Function | Identifier) instead of its raw text, matching first_child; text is still available via condition.text - parse-value.test.ts: rename abbreviated `idx` param to `index`, update condition assertions to .condition.text
Dedupes three pieces of logic the if()-parsing code had reimplemented privately, bringing it in line with patterns already established elsewhere in this file and in parse-atrule-prelude.ts: - trim_range -> reuse parse-utils.ts's trim_boundaries (also fixes a minor gap: comments inside if() condition values now trim correctly) - find_colon_at_depth_zero -> extracted to parse-utils.ts, shared with parse-atrule-prelude.ts's identical private copy - the unquoted url()/src() scan and the if()-condition-function extent scan -> unified into one scan_matching_paren(bounded) helper, mirroring AtRulePreludeParser's existing scan_matching_paren Also condenses the two JSDoc blocks that (unlike .d.ts comments, which are stripped) ship verbatim in the compiled JS, to keep only the non-obvious spec-grammar reference. Net: ~740 lines from ~764 in value-node-parser.ts, plus removes a duplicate function from parse-atrule-prelude.ts; the packed npm tarball drops from ~43.8kB back to ~42.7kB. Added a regression test for the scan_matching_paren bounded path (unterminated nested condition function inside if()).
…ery nodes style()/supports()/media() inside if() now delegate to ConditionParser (introduced in the sub-parser extraction PR) instead of producing plain Function/Identifier children. This fixes range-syntax media features like media(400px <= width) hanging/mis-parsing, and adds support for the full compound and/or/not supports() grammar, matching @supports's own prelude shape. supports()/style() still accept the bare single-declaration shorthand as before. Function's children type is widened to include MediaFeature, SupportsDeclaration, SupportsQuery, FeatureRange and PreludeOperator to reflect these new shapes.
Use the shared parse-utils.ts export instead of a private copy — the private one was flagged by knip as making the shared export unused.
bartveneman
force-pushed
the
claude/great-tesla-9us65p
branch
from
August 16, 2026 10:35
9dd530b to
b7c3d5c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds comprehensive support for parsing CSS
if()inline conditional functions (CSS Values Level 5 spec). The parser now recognizesif()functions and creates a dedicatedIF_BRANCHnode type to represent each condition-value pair within the function.Key Changes
New
IF_BRANCHnode type: Introduced a new AST node type to represent individual branches within anif()function, with properties for:condition: The condition text (e.g.,"style(--active: 1)"or"else")value: The value text between the colon and semicolon (ornullif empty)is_else: Boolean flag indicating if this is theelsebranchchildren: Parsed condition node followed by parsed value nodesDedicated
if()parser: Implementedparse_if_function_node()inValueParserthat:if()functions and dispatches to specialized parsing logicstyle(),supports(),media(), orelseidentifier)if()functions recursivelyToken handling: Extended operator parsing to include colons and semicolons as structural separators within
if()branchesType definitions: Added
IfBranchtype to the public API with proper TypeScript support and type guardsComprehensive test coverage: Added 30+ test cases covering:
style(),supports(),media(),else)if()functionsImplementation Details
The parser treats
if()as a special function that creates aFUNCTIONnode withIF_BRANCHchildren instead of generic value nodes. Each branch's condition and value are tracked separately through arena fields (contentStartDelta/contentLengthfor condition,valueStartDelta/valueLengthfor value), allowing efficient text extraction without reparsing. The implementation properly handles whitespace, malformed input, and maintains accurate source location information for all nodes.https://claude.ai/code/session_01UmLv7na8e3eUyPAZbjMf3U