@@ -16,8 +16,6 @@ import {
1616 PRELUDE_SELECTORLIST ,
1717 URL ,
1818 FUNCTION ,
19- NUMBER ,
20- DIMENSION ,
2119 STRING ,
2220 FEATURE_RANGE ,
2321} from './arena'
@@ -31,15 +29,11 @@ import {
3129 TOKEN_STRING ,
3230 TOKEN_URL ,
3331 TOKEN_FUNCTION ,
34- TOKEN_NUMBER ,
35- TOKEN_PERCENTAGE ,
36- TOKEN_DIMENSION ,
3732 TOKEN_DELIM ,
3833 type TokenType ,
3934} from './token-types'
4035import {
4136 str_equals ,
42- is_whitespace ,
4337 strip_vendor_prefix ,
4438 CHAR_COLON ,
4539 CHAR_LESS_THAN ,
@@ -50,20 +44,26 @@ import {
5044import { trim_boundaries , skip_whitespace_and_comments_forward } from './parse-utils'
5145import { CSSNode } from './css-node'
5246import type { AnyNode } from './node-types'
47+ import { ValueNodeParser } from './value-node-parser'
5348
5449/** @internal */
5550export class AtRulePreludeParser {
5651 private lexer : Lexer
5752 private arena : CSSDataArena
5853 private source : string
5954 private prelude_end : number
55+ // Shared with declaration-value parsing so feature values (calc(), env(), var(), ...)
56+ // get the same structured Number/Operator/Function tree, not just an opaque text span.
57+ // Runs on its own lexer instance, independent of this.lexer.
58+ private value_node_parser : ValueNodeParser
6059
6160 constructor ( arena : CSSDataArena , source : string ) {
6261 this . arena = arena
6362 this . source = source
6463 // Create a lexer instance for prelude parsing
6564 this . lexer = new Lexer ( source )
6665 this . prelude_end = 0
66+ this . value_node_parser = new ValueNodeParser ( arena , source )
6767 }
6868
6969 // Parse an at-rule prelude into nodes (standalone use)
@@ -261,7 +261,7 @@ export class AtRulePreludeParser {
261261 while ( this . lexer . pos < this . prelude_end && depth > 0 ) {
262262 this . next_token ( )
263263 let token_type = this . lexer . token_type
264- if ( token_type === TOKEN_LEFT_PAREN ) {
264+ if ( token_type === TOKEN_LEFT_PAREN || token_type === TOKEN_FUNCTION ) {
265265 depth ++
266266 } else if ( token_type === TOKEN_RIGHT_PAREN ) {
267267 depth --
@@ -461,7 +461,7 @@ export class AtRulePreludeParser {
461461 while ( this . lexer . pos < this . prelude_end && depth > 0 ) {
462462 this . next_token ( )
463463 let inner_token_type = this . lexer . token_type
464- if ( inner_token_type === TOKEN_LEFT_PAREN ) {
464+ if ( inner_token_type === TOKEN_LEFT_PAREN || inner_token_type === TOKEN_FUNCTION ) {
465465 depth ++
466466 } else if ( inner_token_type === TOKEN_RIGHT_PAREN ) {
467467 depth --
@@ -908,61 +908,13 @@ export class AtRulePreludeParser {
908908 return this . lexer . next_token_fast ( false )
909909 }
910910
911- // Helper: Parse a single value token into a node
912- private parse_value_token ( ) : number | null {
913- switch ( this . lexer . token_type ) {
914- case TOKEN_IDENT :
915- return this . create_node ( IDENTIFIER , this . lexer . token_start , this . lexer . token_end )
916- case TOKEN_NUMBER :
917- return this . create_node ( NUMBER , this . lexer . token_start , this . lexer . token_end )
918- case TOKEN_PERCENTAGE :
919- case TOKEN_DIMENSION :
920- return this . create_node ( DIMENSION , this . lexer . token_start , this . lexer . token_end )
921- case TOKEN_STRING :
922- return this . create_node ( STRING , this . lexer . token_start , this . lexer . token_end )
923- default :
924- return null
925- }
926- }
927-
928- // Helper: Parse feature value portion into typed nodes, chained as siblings without an
929- // intermediate array. Returns the first node in the chain (0 if none) — the common case is
930- // a single value (e.g. `min-width: 768px`), so callers get that node directly.
911+ // Parse feature value portion into typed nodes (Number, Dimension, Function, Operator, ...),
912+ // chained as siblings without an intermediate array. Delegates to the shared ValueNodeParser
913+ // (also used for declaration values) so calc(), env(), var(), etc. get full structured
914+ // children instead of being treated as opaque text. Runs on its own lexer instance, so it
915+ // doesn't disturb this.lexer's position — no save/restore needed around the call.
931916 private parse_feature_value ( start : number , end : number ) : number {
932- const saved_position = this . lexer . save_position ( )
933- this . lexer . seek ( start , this . lexer . line , this . lexer . column )
934-
935- let first_node = 0
936- let last_node = 0
937-
938- while ( this . lexer . pos < end ) {
939- this . lexer . next_token_fast ( false )
940- if ( this . lexer . token_start >= end ) break
941-
942- // Skip whitespace tokens
943- let all_whitespace = true
944- for ( let i = this . lexer . token_start ; i < this . lexer . token_end && i < end ; i ++ ) {
945- if ( ! is_whitespace ( this . source . charCodeAt ( i ) ) ) {
946- all_whitespace = false
947- break
948- }
949- }
950- if ( all_whitespace ) continue
951-
952- // Create node based on token type
953- let node = this . parse_value_token ( )
954- if ( node !== null ) {
955- if ( first_node === 0 ) {
956- first_node = node
957- } else {
958- this . arena . set_next_sibling ( last_node , node )
959- }
960- last_node = node
961- }
962- }
963-
964- this . lexer . restore_position ( saved_position )
965- return first_node
917+ return this . value_node_parser . parse_chain ( start , end , this . lexer . line , this . lexer . column )
966918 }
967919
968920 // Parse @namespace prelude: [prefix] url("...") | "..."
@@ -1009,7 +961,11 @@ export class AtRulePreludeParser {
1009961
1010962 while ( this . lexer . pos < this . prelude_end && depth > 0 ) {
1011963 this . next_token ( )
1012- if ( this . lexer . token_type === TOKEN_LEFT_PAREN ) depth ++
964+ if (
965+ this . lexer . token_type === TOKEN_LEFT_PAREN ||
966+ this . lexer . token_type === TOKEN_FUNCTION
967+ )
968+ depth ++
1013969 else if ( this . lexer . token_type === TOKEN_RIGHT_PAREN ) depth --
1014970 }
1015971
0 commit comments