Skip to content

Narrow next_sibling type on children to match parent's child union#214

Merged
bartveneman merged 6 commits intomainfrom
claude/improve-block-child-types-b2r77
Apr 12, 2026
Merged

Narrow next_sibling type on children to match parent's child union#214
bartveneman merged 6 commits intomainfrom
claude/improve-block-child-types-b2r77

Conversation

@bartveneman
Copy link
Copy Markdown
Member

Summary

This PR improves type safety for CSS node trees by narrowing the next_sibling type on child nodes to match the specific child union of their parent, rather than the generic CSSNode type.

Key Changes

  • Added _ChildOf<U, S> and ChildOf<T> utility types that narrow next_sibling to a specific sibling union while preserving the node's type discriminant
  • Updated WithChildren<T> interface to use ChildOf<T> for children, first_child, and the iterator return type
  • Added comprehensive type tests verifying that next_sibling is properly narrowed:
    • Block children have next_sibling narrowed to Raw | Declaration | Atrule | Rule
    • SelectorList children have next_sibling narrowed to Selector

Implementation Details

The _ChildOf type uses a conditional distribution pattern to preserve each union member's type discriminant while replacing the next_sibling field with a narrowed version. This ensures that when traversing sibling nodes, TypeScript knows the sibling must be one of the valid child types for that parent, enabling better type narrowing and preventing invalid node type assumptions.

https://claude.ai/code/session_01WGPQA9UMdtp8hd1VoBsw6h

claude added 2 commits April 8, 2026 14:17
Introduce ChildOf<T> helper type so that next_sibling on any node
obtained from a WithChildren<T> parent (first_child, children[], or
for-of iteration) is narrowed to T instead of the generic CSSNode.

Block children now report `next_sibling: Raw | Declaration | Atrule | Rule`;
SelectorList children report `next_sibling: Selector`; etc.

https://claude.ai/code/session_01WGPQA9UMdtp8hd1VoBsw6h
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 8, 2026

Bundle Report

Changes will increase total bundle size by 753 bytes (0.42%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
@projectwallace/css-parser-esm 181.7kB 753 bytes (0.42%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: @projectwallace/css-parser-esm

Assets Changed:

Asset Name Size Change Total Size Change (%)
node-types-DGzZURyL.d.ts (New) 22.83kB 22.83kB 100.0% 🚀
node-types-CfWcbFWR.d.ts (Deleted) -22.08kB 0 bytes -100.0% 🗑️

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 8, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.60%. Comparing base (39b55da) to head (f58a8f7).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #214      +/-   ##
==========================================
+ Coverage   93.49%   93.60%   +0.10%     
==========================================
  Files          17       17              
  Lines        2938     2938              
  Branches      809      809              
==========================================
+ Hits         2747     2750       +3     
+ Misses        191      188       -3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

claude added 4 commits April 8, 2026 14:32
Omit<T, keys> forces TypeScript to enumerate every key of T, which
recursively expands the whole node graph (Selector -> WithChildren<SelectorNode>
-> PseudoClassSelector -> WithChildren<Selector|NthOfSelector> -> ...) and
triggers "type instantiation is excessively deep" (TS2589).

Plain intersection `U & (sibling union)` is stored lazily: TypeScript
never needs to enumerate T's keys to form it, so the recursive
expansion never happens.  The T extends CSSNode constraint on the
ChildOf<T> wrapper (not on _ChildOf) preserves type safety at the
call site without triggering the recursive check.

https://claude.ai/code/session_01WGPQA9UMdtp8hd1VoBsw6h
The CSS type graph has genuine cycles (Selector -> WithChildren<SelectorNode>
-> PseudoClassSelector -> WithChildren<Selector|NthOfSelector> -> Selector),
so any generic helper placed inside WithChildren<T> hits TypeScript's
instantiation depth limit (TS2589).

Instead, keep WithChildren<T> unchanged and override first_child, children,
and [Symbol.iterator] only on Block, whose child union
(Raw | Declaration | Atrule | Rule) has no WithChildren of its own, making
the type graph acyclic there.  The new exported BlockChild type carries the
narrowed next_sibling: Raw | Declaration | Atrule | Rule discriminant.

https://claude.ai/code/session_01WGPQA9UMdtp8hd1VoBsw6h
@bartveneman bartveneman merged commit fb74488 into main Apr 12, 2026
4 of 5 checks passed
@bartveneman bartveneman deleted the claude/improve-block-child-types-b2r77 branch April 12, 2026 08:46
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