@@ -53,12 +53,9 @@ export class AtRulePreludeParser {
5353 private arena : CSSDataArena
5454 private source : string
5555 private prelude_end : number
56- // Shared with declaration-value parsing so feature values (calc(), env(), var(), ...)
57- // get the same structured Number/Operator/Function tree, not just an opaque text span.
58- // Runs on its own lexer instance, independent of this.lexer.
56+ // Shared with declaration values so calc()/env()/var() get structured children, not raw text
5957 private value_node_parser : ValueNodeParser
60- // Used to deep-parse `selector()`'s argument (e.g. `@supports selector(:has(a))`) into a
61- // real SelectorList instead of leaving it as opaque text. Own lexer instance, like above.
58+ // Deep-parses selector()'s argument into a real SelectorList
6259 private selector_parser : SelectorParser
6360
6461 constructor ( arena : CSSDataArena , source : string ) {
@@ -169,10 +166,7 @@ export class AtRulePreludeParser {
169166 return str_equals ( 'and' , str ) || str_equals ( 'or' , str ) || str_equals ( 'not' , str )
170167 }
171168
172- // Parse a bare function condition: style(--custom: 1), selector([popover]:open),
173- // font-tech(color-COLRv1), font-format(woff2), ... The lexer's current token must
174- // already be the TOKEN_FUNCTION. Content isn't a CSS value (it may be a selector or
175- // an arbitrary declaration), so it's captured as raw text rather than deep-parsed.
169+ // Parse a bare function condition: style(...), selector(...), font-tech(...). Current token must be TOKEN_FUNCTION.
176170 private parse_function_condition ( ) : number {
177171 let func_name = this . source . substring ( this . lexer . token_start , this . lexer . token_end - 1 ) // -1 to exclude '('
178172 let func_start = this . lexer . token_start
@@ -208,8 +202,7 @@ export class AtRulePreludeParser {
208202 this . arena . set_value_start_delta ( func_node , content_start - func_start )
209203 this . arena . set_value_length ( func_node , content_end - content_start )
210204
211- // `selector()`'s argument is a <complex-selector>, e.g. `selector(:has(a))` — parse it
212- // with the selector parser so consumers get a real SelectorList instead of raw text.
205+ // selector()'s argument is a <complex-selector> — parse it into a real SelectorList
213206 if ( str_equals ( 'selector' , func_name ) ) {
214207 let selector_list = this . selector_parser . parse_selector (
215208 content_start ,
@@ -221,8 +214,7 @@ export class AtRulePreludeParser {
221214 this . arena . set_first_child ( func_node , selector_list )
222215 }
223216 }
224- // `style()`'s argument is a <declaration>, e.g. `style(--custom: 1)` — parse it into the
225- // same SupportsDeclaration → Declaration → Value tree as a plain `(property: value)` query.
217+ // style()'s argument is a <declaration> — parse it into the same tree as (property: value)
226218 else if ( str_equals ( 'style' , func_name ) ) {
227219 let colon_pos = this . find_colon_at_depth_zero ( content_start , content_end )
228220 if ( colon_pos !== - 1 ) {
@@ -242,27 +234,34 @@ export class AtRulePreludeParser {
242234 this . skip_whitespace ( )
243235 if ( this . lexer . pos >= this . prelude_end ) return null
244236
245- // Check for modifier (only, not)
246- // let has_modifier = false
237+ // Parse components (media type, features, operators), chained as siblings without an
238+ // intermediate array — most media queries have exactly one component (a single media
239+ // type or a single feature), so this avoids an allocation for the common case.
240+ let first_component = 0
241+ let last_component = 0
242+
243+ // Leading modifier (only/not) — emit as PRELUDE_OPERATOR like the and/or/not combinators below
247244 const saved_token_start = this . lexer . save_position ( )
248245 this . next_token ( )
249246
250247 if ( this . lexer . token_type === TOKEN_IDENT ) {
251248 let text = this . source . substring ( this . lexer . token_start , this . lexer . token_end )
252- if ( ! str_equals ( 'only' , text ) && ! str_equals ( 'not' , text ) ) {
249+ if ( str_equals ( 'only' , text ) || str_equals ( 'not' , text ) ) {
250+ let modifier = this . create_node (
251+ PRELUDE_OPERATOR ,
252+ this . lexer . token_start ,
253+ this . lexer . token_end ,
254+ )
255+ first_component = modifier
256+ last_component = modifier
257+ } else {
253258 // Reset - this is a media type
254259 this . lexer . restore_position ( saved_token_start )
255260 }
256261 } else {
257262 this . lexer . restore_position ( saved_token_start )
258263 }
259264
260- // Parse components (media type, features, operators), chained as siblings without an
261- // intermediate array — most media queries have exactly one component (a single media
262- // type or a single feature), so this avoids an allocation for the common case.
263- let first_component = 0
264- let last_component = 0
265-
266265 while ( this . lexer . pos < this . prelude_end ) {
267266 this . skip_whitespace ( )
268267 if ( this . lexer . pos >= this . prelude_end ) break
@@ -607,8 +606,7 @@ export class AtRulePreludeParser {
607606 }
608607
609608 let supports_decl = this . create_node ( SUPPORTS_DECLARATION , content_start , content_end )
610- // Mirror the property name onto the wrapper too, so `.property` works without
611- // having to reach into the inner Declaration.
609+ // Mirror the property name onto the wrapper so .property doesn't need the inner Declaration
612610 this . arena . set_content_start_delta ( supports_decl , prop_trimmed [ 0 ] - content_start )
613611 this . arena . set_content_length ( supports_decl , prop_trimmed [ 1 ] - prop_trimmed [ 0 ] )
614612 this . arena . set_first_child ( supports_decl , decl )
@@ -957,11 +955,8 @@ export class AtRulePreludeParser {
957955 return this . lexer . next_token_fast ( false )
958956 }
959957
960- // Parse feature value portion into typed nodes (Number, Dimension, Function, Operator, ...),
961- // chained as siblings without an intermediate array. Delegates to the shared ValueNodeParser
962- // (also used for declaration values) so calc(), env(), var(), etc. get full structured
963- // children instead of being treated as opaque text. Runs on its own lexer instance, so it
964- // doesn't disturb this.lexer's position — no save/restore needed around the call.
958+ // Parse feature value via the shared ValueNodeParser, so calc()/env()/var() get full children.
959+ // Own lexer instance, so it doesn't disturb this.lexer's position — no save/restore needed.
965960 private parse_feature_value ( start : number , end : number ) : number {
966961 return this . value_node_parser . parse_chain ( start , end , this . lexer . line , this . lexer . column )
967962 }
0 commit comments