From f0fffad4f6b622cb290e3942c6108bf435afe1bb Mon Sep 17 00:00:00 2001 From: Bart Veneman Date: Mon, 20 Jul 2026 00:02:58 +0200 Subject: [PATCH] fix node type mismatch between parser and type defs --- src/node-types.test.ts | 49 ++++++++++++++++++++++++++++++++++++++++++ src/node-types.ts | 8 +++++-- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/node-types.test.ts b/src/node-types.test.ts index 1824f47..44f34fb 100644 --- a/src/node-types.test.ts +++ b/src/node-types.test.ts @@ -45,6 +45,9 @@ import type { Identifier, Operator, PreludeOperator, + ContainerQuery, + FeatureRange, + String, } from './node-types' // --------------------------------------------------------------------------- @@ -328,6 +331,52 @@ describe('type narrowing — compile-time', () => { } }) + test('FeatureRange children are typed as Dimension | PreludeOperator, not Operator', () => { + const atrule = parse('@media (width >= 400px) {}').first_child! as Atrule + const range = atrule.prelude!.first_child!.first_child! as FeatureRange + expectTypeOf(range.children).toEqualTypeOf<(Dimension | PreludeOperator)[]>() + + const [op, dim] = range.children + expect(op.type_name).toBe('Operator') + expect(dim.type_name).toBe('Dimension') + }) + + test('ContainerQuery children include PreludeOperator for and/or/not, without casting', () => { + const atrule = parse('@container style(a: 1) and style(b: 2) {}').first_child! as Atrule + const containerQuery = atrule.prelude!.first_child! as ContainerQuery + expectTypeOf(containerQuery.children).toEqualTypeOf< + (Identifier | MediaFeature | Function | PreludeOperator)[] + >() + + const [, op] = containerQuery.children + expect(op.type_name).toBe('Operator') + expect((op as PreludeOperator).text).toBe('and') + }) + + test('AtrulePrelude children include Identifier for @keyframes/@page/@property preludes', () => { + const atrule = parse('@keyframes spin {}').first_child! as Atrule + const prelude = atrule.prelude! as AtrulePrelude + const [ident] = prelude.children + + expect(ident.type_name).toBe('Identifier') + if (ident.type_name === 'Identifier') { + expectTypeOf(ident).toExtend() + expect(ident.text).toBe('spin') + } + }) + + test('AtrulePrelude children include String for @charset', () => { + const root = parse('@charset "UTF-8";') + const atrule = root.first_child! as Atrule + const prelude = atrule.prelude! as AtrulePrelude + const [str] = prelude.children + + expect(str.type_name).toBe('String') + if (str.type_name === 'String') { + expectTypeOf(str).toExtend() + } + }) + test('AnyCss enables switch narrowing', () => { // This test verifies the discriminated union works for switch narrowing. // The function must compile without type errors. diff --git a/src/node-types.ts b/src/node-types.ts index bfab44b..af0b077 100644 --- a/src/node-types.ts +++ b/src/node-types.ts @@ -456,6 +456,10 @@ export type AtrulePrelude = WithClone< | Url // `@supports style(...)` / `selector(...)` function conditions | Function + // `@page`, `@keyframes`, `@property`, etc. (parse_identifier()) + | Identifier + // `@charset` (parse_charset_prelude()) + | String > & { readonly type: typeof AT_RULE_PRELUDE; readonly type_name: 'AtrulePrelude' } > @@ -489,7 +493,7 @@ export type MediaType = Leaf< export type ContainerQuery = WithClone< CSSNode & - WithChildren & { + WithChildren & { readonly type: typeof CONTAINER_QUERY readonly type_name: 'ContainerQuery' } @@ -543,7 +547,7 @@ export type PreludeOperator = Leaf export type FeatureRange = WithClone< CSSNode & - WithChildren & { + WithChildren & { readonly type: typeof FEATURE_RANGE readonly type_name: 'MediaFeatureRange' /** The feature name in a range comparison, e.g. "width" from "(width >= 400px)" */