@projectwallace/css-parser 0.13.2 → 0.13.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/arena.d.ts CHANGED
@@ -37,6 +37,7 @@ export declare const LAYER_NAME = 37;
37
37
  export declare const PRELUDE_OPERATOR = 38;
38
38
  export declare const FEATURE_RANGE = 39;
39
39
  export declare const AT_RULE_PRELUDE = 40;
40
+ export declare const PRELUDE_SELECTORLIST = 41;
40
41
  export declare const VALUE = 50;
41
42
  export declare const FLAG_IMPORTANT: number;
42
43
  export declare const FLAG_HAS_ERROR: number;
package/dist/arena.js CHANGED
@@ -38,6 +38,7 @@ const LAYER_NAME = 37;
38
38
  const PRELUDE_OPERATOR = 38;
39
39
  const FEATURE_RANGE = 39;
40
40
  const AT_RULE_PRELUDE = 40;
41
+ const PRELUDE_SELECTORLIST = 41;
41
42
  const VALUE = 50;
42
43
  const FLAG_IMPORTANT = 1 << 0;
43
44
  const FLAG_HAS_ERROR = 1 << 1;
@@ -286,4 +287,4 @@ class CSSDataArena {
286
287
  }
287
288
  }
288
289
 
289
- export { ATTRIBUTE_SELECTOR, ATTR_FLAG_CASE_INSENSITIVE, ATTR_FLAG_CASE_SENSITIVE, ATTR_FLAG_NONE, ATTR_OPERATOR_CARET_EQUAL, ATTR_OPERATOR_DOLLAR_EQUAL, ATTR_OPERATOR_EQUAL, ATTR_OPERATOR_NONE, ATTR_OPERATOR_PIPE_EQUAL, ATTR_OPERATOR_STAR_EQUAL, ATTR_OPERATOR_TILDE_EQUAL, AT_RULE, AT_RULE_PRELUDE, BLOCK, CLASS_SELECTOR, COMBINATOR, COMMENT, CONTAINER_QUERY, CSSDataArena, DECLARATION, DIMENSION, FEATURE_RANGE, FLAG_BROWSERHACK, FLAG_HAS_BLOCK, FLAG_HAS_DECLARATIONS, FLAG_HAS_ERROR, FLAG_HAS_PARENS, FLAG_IMPORTANT, FLAG_LENGTH_OVERFLOW, FUNCTION, HASH, IDENTIFIER, ID_SELECTOR, LANG_SELECTOR, LAYER_NAME, MEDIA_FEATURE, MEDIA_QUERY, MEDIA_TYPE, NESTING_SELECTOR, NTH_OF_SELECTOR, NTH_SELECTOR, NUMBER, OPERATOR, PARENTHESIS, PRELUDE_OPERATOR, PSEUDO_CLASS_SELECTOR, PSEUDO_ELEMENT_SELECTOR, SELECTOR, SELECTOR_LIST, STRING, STYLESHEET, STYLE_RULE, SUPPORTS_QUERY, TYPE_SELECTOR, UNICODE_RANGE, UNIVERSAL_SELECTOR, URL, VALUE };
290
+ export { ATTRIBUTE_SELECTOR, ATTR_FLAG_CASE_INSENSITIVE, ATTR_FLAG_CASE_SENSITIVE, ATTR_FLAG_NONE, ATTR_OPERATOR_CARET_EQUAL, ATTR_OPERATOR_DOLLAR_EQUAL, ATTR_OPERATOR_EQUAL, ATTR_OPERATOR_NONE, ATTR_OPERATOR_PIPE_EQUAL, ATTR_OPERATOR_STAR_EQUAL, ATTR_OPERATOR_TILDE_EQUAL, AT_RULE, AT_RULE_PRELUDE, BLOCK, CLASS_SELECTOR, COMBINATOR, COMMENT, CONTAINER_QUERY, CSSDataArena, DECLARATION, DIMENSION, FEATURE_RANGE, FLAG_BROWSERHACK, FLAG_HAS_BLOCK, FLAG_HAS_DECLARATIONS, FLAG_HAS_ERROR, FLAG_HAS_PARENS, FLAG_IMPORTANT, FLAG_LENGTH_OVERFLOW, FUNCTION, HASH, IDENTIFIER, ID_SELECTOR, LANG_SELECTOR, LAYER_NAME, MEDIA_FEATURE, MEDIA_QUERY, MEDIA_TYPE, NESTING_SELECTOR, NTH_OF_SELECTOR, NTH_SELECTOR, NUMBER, OPERATOR, PARENTHESIS, PRELUDE_OPERATOR, PRELUDE_SELECTORLIST, PSEUDO_CLASS_SELECTOR, PSEUDO_ELEMENT_SELECTOR, SELECTOR, SELECTOR_LIST, STRING, STYLESHEET, STYLE_RULE, SUPPORTS_QUERY, TYPE_SELECTOR, UNICODE_RANGE, UNIVERSAL_SELECTOR, URL, VALUE };
@@ -3,6 +3,7 @@ export declare let CHAR_DIGIT: number;
3
3
  export declare let CHAR_HEX: number;
4
4
  export declare let CHAR_WHITESPACE: number;
5
5
  export declare let CHAR_NEWLINE: number;
6
+ export declare let CHAR_IDENT: number;
6
7
  export declare let char_types: Uint8Array<ArrayBuffer>;
7
8
  export declare function is_digit(ch: number): boolean;
8
9
  export declare function is_hex_digit(ch: number): boolean;
@@ -3,6 +3,7 @@ let CHAR_DIGIT = 1 << 1;
3
3
  let CHAR_HEX = 1 << 2;
4
4
  let CHAR_WHITESPACE = 1 << 3;
5
5
  let CHAR_NEWLINE = 1 << 4;
6
+ let CHAR_IDENT = 1 << 5;
6
7
  let char_types = new Uint8Array(128);
7
8
  for (let i = 48; i <= 57; i++) {
8
9
  char_types[i] = CHAR_DIGIT;
@@ -27,9 +28,13 @@ char_types[9] = CHAR_WHITESPACE;
27
28
  char_types[10] = CHAR_NEWLINE;
28
29
  char_types[13] = CHAR_NEWLINE;
29
30
  char_types[12] = CHAR_NEWLINE;
30
- function is_digit(ch) {
31
- return ch < 128 && (char_types[ch] & CHAR_DIGIT) !== 0;
31
+ for (let i = 0; i < 128; i++) {
32
+ if (char_types[i] & (CHAR_ALPHA | CHAR_DIGIT)) {
33
+ char_types[i] |= CHAR_IDENT;
34
+ }
32
35
  }
36
+ char_types[45] |= CHAR_IDENT;
37
+ char_types[95] |= CHAR_IDENT;
33
38
  function is_hex_digit(ch) {
34
39
  return ch < 128 && (char_types[ch] & CHAR_HEX) !== 0;
35
40
  }
@@ -39,17 +44,10 @@ function is_alpha(ch) {
39
44
  function is_whitespace(ch) {
40
45
  return ch < 128 && (char_types[ch] & CHAR_WHITESPACE) !== 0;
41
46
  }
42
- function is_newline(ch) {
43
- return ch < 128 && (char_types[ch] & CHAR_NEWLINE) !== 0;
44
- }
45
47
  function is_ident_start(ch) {
46
48
  if (ch >= 128) return true;
47
49
  if (ch === 95) return true;
48
50
  return is_alpha(ch);
49
51
  }
50
- function is_ident_char(ch) {
51
- if (ch === 45) return true;
52
- return is_ident_start(ch) || is_digit(ch);
53
- }
54
52
 
55
- export { CHAR_ALPHA, CHAR_DIGIT, CHAR_HEX, CHAR_NEWLINE, CHAR_WHITESPACE, char_types, is_alpha, is_digit, is_hex_digit, is_ident_char, is_ident_start, is_newline, is_whitespace };
53
+ export { CHAR_ALPHA, CHAR_DIGIT, CHAR_HEX, CHAR_IDENT, CHAR_NEWLINE, CHAR_WHITESPACE, char_types, is_alpha, is_hex_digit, is_ident_start, is_whitespace };
@@ -2,7 +2,6 @@ import { Lexer } from './tokenize.js';
2
2
  import { NTH_SELECTOR, CSSDataArena } from './arena.js';
3
3
  import { TOKEN_IDENT, TOKEN_DELIM, TOKEN_DIMENSION, TOKEN_NUMBER } from './token-types.js';
4
4
  import { str_equals, CHAR_MINUS_HYPHEN, CHAR_PLUS, str_index_of } from './string-utils.js';
5
- import { skip_whitespace_and_comments_forward } from './parse-utils.js';
6
5
  import { CSSNode } from './css-node.js';
7
6
 
8
7
  class ANplusBParser {
@@ -22,8 +21,7 @@ class ANplusBParser {
22
21
  */
23
22
  parse_anplusb(start, end, line = 1) {
24
23
  this.expr_end = end;
25
- this.lexer.pos = start;
26
- this.lexer.line = line;
24
+ this.lexer.seek(start, line);
27
25
  let b = null;
28
26
  let a_start = start;
29
27
  let a_end = start;
@@ -186,7 +184,7 @@ class ANplusBParser {
186
184
  return null;
187
185
  }
188
186
  skip_whitespace() {
189
- this.lexer.pos = skip_whitespace_and_comments_forward(this.source, this.lexer.pos, this.expr_end);
187
+ this.lexer.skip_whitespace_in_range(this.expr_end);
190
188
  }
191
189
  create_anplusb_node(start, a_start, a_end, b_start, b_end) {
192
190
  const node = this.arena.create_node(NTH_SELECTOR, start, this.lexer.pos - start, this.lexer.line, 1);
@@ -1,5 +1,5 @@
1
1
  import { Lexer } from './tokenize.js';
2
- import { CSSDataArena, PRELUDE_OPERATOR, MEDIA_TYPE, MEDIA_QUERY, MEDIA_FEATURE, FUNCTION, IDENTIFIER, CONTAINER_QUERY, SUPPORTS_QUERY, LAYER_NAME, STRING, URL, DIMENSION, NUMBER, FEATURE_RANGE } from './arena.js';
2
+ import { CSSDataArena, PRELUDE_OPERATOR, MEDIA_TYPE, MEDIA_QUERY, MEDIA_FEATURE, FUNCTION, IDENTIFIER, CONTAINER_QUERY, SUPPORTS_QUERY, LAYER_NAME, STRING, URL, DIMENSION, NUMBER, PRELUDE_SELECTORLIST, FEATURE_RANGE } from './arena.js';
3
3
  import { TOKEN_COMMA, TOKEN_IDENT, TOKEN_LEFT_PAREN, TOKEN_RIGHT_PAREN, TOKEN_FUNCTION, TOKEN_EOF, TOKEN_WHITESPACE, TOKEN_STRING, TOKEN_URL, TOKEN_DIMENSION, TOKEN_PERCENTAGE, TOKEN_NUMBER } from './token-types.js';
4
4
  import { strip_vendor_prefix, str_equals, CHAR_LESS_THAN, CHAR_GREATER_THAN, CHAR_EQUALS, CHAR_COLON, is_whitespace } from './string-utils.js';
5
5
  import { skip_whitespace_and_comments_forward, trim_boundaries } from './parse-utils.js';
@@ -19,30 +19,40 @@ class AtRulePreludeParser {
19
19
  // Parse an at-rule prelude into nodes (standalone use)
20
20
  parse_prelude(at_rule_name, start, end, line = 1, column = 1) {
21
21
  this.prelude_end = end;
22
- this.lexer.pos = start;
23
- this.lexer.line = line;
24
- this.lexer.column = column;
22
+ this.lexer.seek(start, line, column);
25
23
  return this.parse_prelude_dispatch(at_rule_name);
26
24
  }
27
25
  // Dispatch to appropriate parser based on at-rule type
28
26
  parse_prelude_dispatch(at_rule_name) {
29
- let normalized_name = strip_vendor_prefix(at_rule_name);
30
- if (str_equals("media", normalized_name)) {
31
- return this.parse_media_query_list();
32
- } else if (str_equals("container", normalized_name)) {
33
- return this.parse_container_query();
34
- } else if (str_equals("supports", normalized_name)) {
35
- return this.parse_supports_query();
36
- } else if (str_equals("layer", normalized_name)) {
37
- return this.parse_layer_names();
38
- } else if (str_equals("keyframes", normalized_name)) {
39
- return this.parse_identifier();
40
- } else if (str_equals("property", normalized_name)) {
41
- return this.parse_identifier();
42
- } else if (str_equals("import", normalized_name)) {
43
- return this.parse_import_prelude();
44
- } else if (str_equals("charset", normalized_name)) {
45
- return this.parse_charset_prelude();
27
+ let normalized_name = strip_vendor_prefix(at_rule_name).toLowerCase();
28
+ switch (normalized_name) {
29
+ case "media":
30
+ return this.parse_media_query_list();
31
+ case "container":
32
+ return this.parse_container_query();
33
+ case "supports":
34
+ return this.parse_supports_query();
35
+ case "layer":
36
+ return this.parse_layer_names();
37
+ case "keyframes":
38
+ case "property":
39
+ case "counter-style":
40
+ case "color-profile":
41
+ case "font-palette-values":
42
+ case "position-try":
43
+ case "font-feature-values":
44
+ case "page":
45
+ return this.parse_identifier();
46
+ case "import":
47
+ return this.parse_import_prelude();
48
+ case "charset":
49
+ return this.parse_charset_prelude();
50
+ case "namespace":
51
+ return this.parse_namespace_prelude();
52
+ case "scope":
53
+ return this.parse_scope_prelude();
54
+ case "custom-media":
55
+ return this.parse_custom_media_prelude();
46
56
  }
47
57
  return [];
48
58
  }
@@ -76,15 +86,15 @@ class AtRulePreludeParser {
76
86
  let query_start = this.lexer.pos;
77
87
  this.skip_whitespace();
78
88
  if (this.lexer.pos >= this.prelude_end) return null;
79
- let token_start = this.lexer.pos;
89
+ const saved_token_start = this.lexer.save_position();
80
90
  this.next_token();
81
91
  if (this.lexer.token_type === TOKEN_IDENT) {
82
92
  let text = this.source.substring(this.lexer.token_start, this.lexer.token_end);
83
93
  if (!str_equals("only", text) && !str_equals("not", text)) {
84
- this.lexer.pos = token_start;
94
+ this.lexer.restore_position(saved_token_start);
85
95
  }
86
96
  } else {
87
- this.lexer.pos = token_start;
97
+ this.lexer.restore_position(saved_token_start);
88
98
  }
89
99
  let components = [];
90
100
  while (this.lexer.pos < this.prelude_end) {
@@ -468,7 +478,7 @@ class AtRulePreludeParser {
468
478
  }
469
479
  // Helper: Skip whitespace and comments
470
480
  skip_whitespace() {
471
- this.lexer.pos = skip_whitespace_and_comments_forward(this.source, this.lexer.pos, this.prelude_end);
481
+ this.lexer.skip_whitespace_in_range(this.prelude_end);
472
482
  }
473
483
  // Helper: Peek at next token type without consuming
474
484
  peek_token_type() {
@@ -504,9 +514,11 @@ class AtRulePreludeParser {
504
514
  }
505
515
  // Helper: Parse feature value portion into typed nodes
506
516
  parse_feature_value(start, end) {
507
- let saved_pos = this.lexer.save_position();
508
- this.lexer.pos = start;
517
+ let temp_lexer = new Lexer(this.source);
518
+ temp_lexer.seek(start, this.lexer.line, this.lexer.column);
509
519
  let nodes = [];
520
+ let saved_lexer = this.lexer;
521
+ this.lexer = temp_lexer;
510
522
  while (this.lexer.pos < end) {
511
523
  this.lexer.next_token_fast(false);
512
524
  if (this.lexer.token_start >= end) break;
@@ -521,7 +533,77 @@ class AtRulePreludeParser {
521
533
  let node = this.parse_value_token();
522
534
  if (node !== null) nodes.push(node);
523
535
  }
524
- this.lexer.restore_position(saved_pos);
536
+ this.lexer = saved_lexer;
537
+ return nodes;
538
+ }
539
+ // Parse @namespace prelude: [prefix] url("...") | "..."
540
+ // e.g. @namespace url("http://www.w3.org/1999/xhtml");
541
+ // e.g. @namespace svg url("http://www.w3.org/2000/svg");
542
+ parse_namespace_prelude() {
543
+ let nodes = [];
544
+ this.skip_whitespace();
545
+ if (this.lexer.pos >= this.prelude_end) return [];
546
+ const saved = this.lexer.save_position();
547
+ this.next_token();
548
+ if (this.lexer.token_type === TOKEN_IDENT) {
549
+ nodes.push(this.create_node(IDENTIFIER, this.lexer.token_start, this.lexer.token_end));
550
+ this.skip_whitespace();
551
+ } else {
552
+ this.lexer.restore_position(saved);
553
+ }
554
+ const url_node = this.parse_import_url();
555
+ if (url_node !== null) nodes.push(url_node);
556
+ return nodes;
557
+ }
558
+ // Parse @scope prelude: [(<scope-start>)] [to (<scope-end>)]
559
+ // e.g. @scope (.parent) to (.child) { }
560
+ parse_scope_prelude() {
561
+ let nodes = [];
562
+ while (this.lexer.pos < this.prelude_end) {
563
+ this.skip_whitespace();
564
+ if (this.lexer.pos >= this.prelude_end) break;
565
+ const token_type = this.peek_token_type();
566
+ if (token_type === TOKEN_LEFT_PAREN) {
567
+ this.next_token();
568
+ let paren_start = this.lexer.token_start;
569
+ let content_start = this.lexer.pos;
570
+ let depth = 1;
571
+ while (this.lexer.pos < this.prelude_end && depth > 0) {
572
+ this.next_token();
573
+ if (this.lexer.token_type === TOKEN_LEFT_PAREN) depth++;
574
+ else if (this.lexer.token_type === TOKEN_RIGHT_PAREN) depth--;
575
+ }
576
+ let content_end = this.lexer.token_start;
577
+ let paren_end = this.lexer.token_end;
578
+ let scope_node = this.create_node(PRELUDE_SELECTORLIST, paren_start, paren_end);
579
+ let trimmed = trim_boundaries(this.source, content_start, content_end);
580
+ if (trimmed) {
581
+ this.arena.set_value_start_delta(scope_node, trimmed[0] - paren_start);
582
+ this.arena.set_value_length(scope_node, trimmed[1] - trimmed[0]);
583
+ }
584
+ nodes.push(scope_node);
585
+ } else if (token_type === TOKEN_IDENT) {
586
+ this.next_token();
587
+ let text = this.source.substring(this.lexer.token_start, this.lexer.token_end);
588
+ if (str_equals("to", text)) {
589
+ nodes.push(this.create_node(PRELUDE_OPERATOR, this.lexer.token_start, this.lexer.token_end));
590
+ }
591
+ } else {
592
+ this.next_token();
593
+ }
594
+ }
595
+ return nodes;
596
+ }
597
+ // Parse @custom-media prelude: --name <media-condition>
598
+ // e.g. @custom-media --small (max-width: 30em);
599
+ parse_custom_media_prelude() {
600
+ let nodes = [];
601
+ this.skip_whitespace();
602
+ if (this.lexer.pos >= this.prelude_end) return [];
603
+ this.next_token();
604
+ if (this.lexer.token_type !== TOKEN_IDENT) return [];
605
+ nodes.push(this.create_node(IDENTIFIER, this.lexer.token_start, this.lexer.token_end));
606
+ nodes.push(...this.parse_media_query_list());
525
607
  return nodes;
526
608
  }
527
609
  // Parse media feature range syntax: (50px <= width <= 100px)
@@ -18,9 +18,7 @@ class DeclarationParser {
18
18
  // Parse a declaration range into a declaration node (standalone use)
19
19
  parse_declaration(start, end, line = 1, column = 1) {
20
20
  const lexer = new Lexer(this.source);
21
- lexer.pos = start;
22
- lexer.line = line;
23
- lexer.column = column;
21
+ lexer.seek(start, line, column);
24
22
  lexer.next_token_fast(true);
25
23
  return this.parse_declaration_with_lexer(lexer, end);
26
24
  }
@@ -21,9 +21,7 @@ class SelectorParser {
21
21
  // Always returns a NODE_SELECTOR_LIST with selector components as children
22
22
  parse_selector(start, end, line = 1, column = 1, allow_relative = true) {
23
23
  this.selector_end = end;
24
- this.lexer.pos = start;
25
- this.lexer.line = line;
26
- this.lexer.column = column;
24
+ this.lexer.seek(start, line, column);
27
25
  return this.parse_selector_list(allow_relative);
28
26
  }
29
27
  // Parse comma-separated selectors
@@ -230,7 +228,7 @@ class SelectorParser {
230
228
  this.lexer.pos++;
231
229
  let node = this.parse_namespace_local_part(start, start, end - start);
232
230
  if (node !== null) return node;
233
- this.lexer.pos = end;
231
+ this.lexer.restore_position(saved);
234
232
  } else {
235
233
  this.lexer.restore_position(saved);
236
234
  }
@@ -245,7 +243,7 @@ class SelectorParser {
245
243
  this.lexer.pos++;
246
244
  let node = this.parse_namespace_local_part(start, start, end - start);
247
245
  if (node !== null) return node;
248
- this.lexer.pos = end;
246
+ this.lexer.restore_position(saved);
249
247
  } else {
250
248
  this.lexer.restore_position(saved);
251
249
  }
@@ -258,16 +256,12 @@ class SelectorParser {
258
256
  }
259
257
  // Parse combinator (>, +, ~, or descendant space)
260
258
  try_parse_combinator() {
261
- let whitespace_start = this.lexer.pos;
262
- let whitespace_start_line = this.lexer.line;
263
- let whitespace_start_column = this.lexer.column;
259
+ const saved_whitespace_start = this.lexer.save_position();
264
260
  let has_whitespace = this.lexer.pos < this.selector_end;
265
261
  this.skip_whitespace();
266
- has_whitespace = has_whitespace && this.lexer.pos > whitespace_start;
262
+ has_whitespace = has_whitespace && this.lexer.pos > saved_whitespace_start.pos;
267
263
  if (this.lexer.pos >= this.selector_end) {
268
- this.lexer.pos = whitespace_start;
269
- this.lexer.line = whitespace_start_line;
270
- this.lexer.column = whitespace_start_column;
264
+ this.lexer.restore_position(saved_whitespace_start);
271
265
  return null;
272
266
  }
273
267
  this.lexer.next_token_fast(false);
@@ -278,15 +272,11 @@ class SelectorParser {
278
272
  }
279
273
  }
280
274
  if (has_whitespace) {
281
- this.lexer.pos = whitespace_start;
282
- this.lexer.line = whitespace_start_line;
283
- this.lexer.column = whitespace_start_column;
275
+ this.lexer.restore_position(saved_whitespace_start);
284
276
  this.skip_whitespace();
285
- return this.create_node_at(COMBINATOR, whitespace_start, this.lexer.pos, whitespace_start_line, whitespace_start_column);
277
+ return this.create_node_at(COMBINATOR, saved_whitespace_start.pos, this.lexer.pos, saved_whitespace_start.line, saved_whitespace_start.column);
286
278
  }
287
- this.lexer.pos = whitespace_start;
288
- this.lexer.line = whitespace_start_line;
289
- this.lexer.column = whitespace_start_column;
279
+ this.lexer.restore_position(saved_whitespace_start);
290
280
  return null;
291
281
  }
292
282
  // Parse class selector (.classname)
@@ -512,9 +502,11 @@ class SelectorParser {
512
502
  // Parse :lang() content - comma-separated language identifiers
513
503
  // Accepts both quoted strings: :lang("en", "fr") and unquoted: :lang(en, fr)
514
504
  parse_lang_identifiers(start, end, parent_node) {
505
+ let temp_lexer = new Lexer(this.source);
506
+ temp_lexer.seek(start, this.lexer.line, this.lexer.column);
515
507
  let saved_selector_end = this.selector_end;
516
- const saved = this.lexer.save_position();
517
- this.lexer.pos = start;
508
+ let saved_lexer = this.lexer;
509
+ this.lexer = temp_lexer;
518
510
  this.selector_end = end;
519
511
  let first_child = null;
520
512
  let last_child = null;
@@ -547,7 +539,7 @@ class SelectorParser {
547
539
  this.arena.set_first_child(parent_node, first_child);
548
540
  }
549
541
  this.selector_end = saved_selector_end;
550
- this.lexer.restore_position(saved);
542
+ this.lexer = saved_lexer;
551
543
  }
552
544
  // Parse An+B expression for nth-* pseudo-classes
553
545
  // Handles both simple An+B and "An+B of S" syntax
@@ -19,9 +19,7 @@ class ValueParser {
19
19
  // Returns single VALUE node index
20
20
  parse_value(start, end, start_line, start_column) {
21
21
  this.value_end = end;
22
- this.lexer.pos = start;
23
- this.lexer.line = start_line;
24
- this.lexer.column = start_column;
22
+ this.lexer.seek(start, start_line, start_column);
25
23
  let value_nodes = this.parse_value_tokens();
26
24
  if (value_nodes.length === 0) {
27
25
  let value_node2 = this.arena.create_node(VALUE, start, 0, start_line, start_column);
package/dist/parse.js CHANGED
@@ -8,8 +8,8 @@ import { TOKEN_EOF, TOKEN_AT_KEYWORD, TOKEN_LEFT_BRACE, TOKEN_RIGHT_BRACE, TOKEN
8
8
  import { trim_boundaries } from './parse-utils.js';
9
9
  import { CHAR_PERIOD, CHAR_GREATER_THAN, CHAR_PLUS, CHAR_TILDE, CHAR_AMPERSAND } from './string-utils.js';
10
10
 
11
- let DECLARATION_AT_RULES = /* @__PURE__ */ new Set(["font-face", "font-feature-values", "page", "property", "counter-style"]);
12
- let CONDITIONAL_AT_RULES = /* @__PURE__ */ new Set(["media", "supports", "container", "layer", "nest"]);
11
+ let DECLARATION_AT_RULES = /* @__PURE__ */ new Set(["font-face", "font-feature-values", "page", "property", "counter-style", "color-profile", "font-palette-values", "position-try", "view-transition"]);
12
+ let CONDITIONAL_AT_RULES = /* @__PURE__ */ new Set(["media", "supports", "container", "layer", "nest", "scope", "starting-style"]);
13
13
  class Parser {
14
14
  source;
15
15
  lexer;
@@ -3,6 +3,7 @@ export interface LexerPosition {
3
3
  pos: number;
4
4
  line: number;
5
5
  column: number;
6
+ _line_offset: number;
6
7
  token_type: TokenType;
7
8
  token_start: number;
8
9
  token_end: number;
package/dist/tokenize.js CHANGED
@@ -1,6 +1,9 @@
1
- import { char_types, CHAR_WHITESPACE, CHAR_NEWLINE, CHAR_DIGIT, is_ident_start, is_newline, is_hex_digit, is_whitespace, is_ident_char } from './char-types.js';
1
+ import { char_types, CHAR_WHITESPACE, CHAR_NEWLINE, CHAR_DIGIT, is_ident_start, is_hex_digit, is_whitespace, CHAR_IDENT } from './char-types.js';
2
2
  import { TOKEN_EOF, TOKEN_RIGHT_PAREN, TOKEN_LEFT_PAREN, TOKEN_RIGHT_BRACKET, TOKEN_LEFT_BRACKET, TOKEN_COMMA, TOKEN_SEMICOLON, TOKEN_COLON, TOKEN_RIGHT_BRACE, TOKEN_LEFT_BRACE, TOKEN_CDO, TOKEN_CDC, TOKEN_DELIM, TOKEN_WHITESPACE, TOKEN_STRING, TOKEN_BAD_STRING, TOKEN_PERCENTAGE, TOKEN_DIMENSION, TOKEN_NUMBER, TOKEN_FUNCTION, TOKEN_IDENT, TOKEN_UNICODE_RANGE, TOKEN_AT_KEYWORD, TOKEN_HASH } from './token-types.js';
3
3
 
4
+ function is_newline(ch) {
5
+ return ch < 128 && (char_types[ch] & CHAR_NEWLINE) !== 0;
6
+ }
4
7
  const CHAR_LEFT_BRACE = 123;
5
8
  const CHAR_RIGHT_BRACE = 125;
6
9
  const CHAR_COLON = 58;
@@ -34,8 +37,8 @@ const CHAR_LINE_FEED = 10;
34
37
  class Lexer {
35
38
  source;
36
39
  pos;
37
- line;
38
- column;
40
+ _line;
41
+ _line_offset;
39
42
  on_comment;
40
43
  // Current token properties (avoiding object allocation)
41
44
  token_type;
@@ -46,8 +49,8 @@ class Lexer {
46
49
  constructor(source, on_comment) {
47
50
  this.source = source;
48
51
  this.pos = 0;
49
- this.line = 1;
50
- this.column = 1;
52
+ this._line = 1;
53
+ this._line_offset = 0;
51
54
  this.on_comment = on_comment;
52
55
  this.token_type = TOKEN_EOF;
53
56
  this.token_start = 0;
@@ -55,6 +58,17 @@ class Lexer {
55
58
  this.token_line = 1;
56
59
  this.token_column = 1;
57
60
  }
61
+ get line() {
62
+ return this._line;
63
+ }
64
+ get column() {
65
+ return this.pos - this._line_offset + 1;
66
+ }
67
+ seek(pos, line, column = 1) {
68
+ this.pos = pos;
69
+ this._line = line;
70
+ this._line_offset = pos - column + 1;
71
+ }
58
72
  // Fast token advancing without object allocation (for internal parser use)
59
73
  next_token_fast(skip_whitespace = false) {
60
74
  if (skip_whitespace) {
@@ -295,7 +309,9 @@ class Lexer {
295
309
  return this.make_token(TOKEN_PERCENTAGE, start, this.pos, start_line, start_column);
296
310
  }
297
311
  if (is_ident_start(ch2) || ch2 === CHAR_HYPHEN && is_ident_start(this.peek())) {
298
- while (this.pos < this.source.length && is_ident_char(this.source.charCodeAt(this.pos))) {
312
+ while (this.pos < this.source.length) {
313
+ let ch3 = this.source.charCodeAt(this.pos);
314
+ if (ch3 < 128 && (char_types[ch3] & CHAR_IDENT) === 0) break;
299
315
  this.advance();
300
316
  }
301
317
  return this.make_token(TOKEN_DIMENSION, start, this.pos, start_line, start_column);
@@ -327,7 +343,7 @@ class Lexer {
327
343
  } else {
328
344
  this.advance();
329
345
  }
330
- } else if (is_ident_char(ch)) {
346
+ } else if (ch >= 128 || (char_types[ch] & CHAR_IDENT) !== 0) {
331
347
  this.advance();
332
348
  } else {
333
349
  break;
@@ -388,7 +404,9 @@ class Lexer {
388
404
  consume_at_keyword(start_line, start_column) {
389
405
  let start = this.pos;
390
406
  this.advance();
391
- while (this.pos < this.source.length && is_ident_char(this.source.charCodeAt(this.pos))) {
407
+ while (this.pos < this.source.length) {
408
+ let ch = this.source.charCodeAt(this.pos);
409
+ if (ch < 128 && (char_types[ch] & CHAR_IDENT) === 0) break;
392
410
  this.advance();
393
411
  }
394
412
  return this.make_token(TOKEN_AT_KEYWORD, start, this.pos, start_line, start_column);
@@ -396,7 +414,9 @@ class Lexer {
396
414
  consume_hash(start_line, start_column) {
397
415
  let start = this.pos;
398
416
  this.advance();
399
- while (this.pos < this.source.length && is_ident_char(this.source.charCodeAt(this.pos))) {
417
+ while (this.pos < this.source.length) {
418
+ let ch = this.source.charCodeAt(this.pos);
419
+ if (ch < 128 && (char_types[ch] & CHAR_IDENT) === 0) break;
400
420
  this.advance();
401
421
  }
402
422
  return this.make_token(TOKEN_HASH, start, this.pos, start_line, start_column);
@@ -406,14 +426,12 @@ class Lexer {
406
426
  if (this.pos >= this.source.length) return;
407
427
  let ch = this.source.charCodeAt(this.pos);
408
428
  this.pos++;
409
- if (is_newline(ch)) {
429
+ if (ch < 128 && (char_types[ch] & CHAR_NEWLINE) !== 0) {
410
430
  if (ch === CHAR_CARRIAGE_RETURN && this.pos < this.source.length && this.source.charCodeAt(this.pos) === CHAR_LINE_FEED) {
411
431
  this.pos++;
412
432
  }
413
- this.line++;
414
- this.column = 1;
415
- } else {
416
- this.column++;
433
+ this._line++;
434
+ this._line_offset = this.pos;
417
435
  }
418
436
  return;
419
437
  }
@@ -421,15 +439,13 @@ class Lexer {
421
439
  if (this.pos >= this.source.length) break;
422
440
  let ch = this.source.charCodeAt(this.pos);
423
441
  this.pos++;
424
- if (is_newline(ch)) {
442
+ if (ch < 128 && (char_types[ch] & CHAR_NEWLINE) !== 0) {
425
443
  if (ch === CHAR_CARRIAGE_RETURN && this.pos < this.source.length && this.source.charCodeAt(this.pos) === CHAR_LINE_FEED) {
426
444
  this.pos++;
427
445
  i++;
428
446
  }
429
- this.line++;
430
- this.column = 1;
431
- } else {
432
- this.column++;
447
+ this._line++;
448
+ this._line_offset = this.pos;
433
449
  }
434
450
  }
435
451
  }
@@ -464,8 +480,9 @@ class Lexer {
464
480
  save_position() {
465
481
  return {
466
482
  pos: this.pos,
467
- line: this.line,
483
+ line: this._line,
468
484
  column: this.column,
485
+ _line_offset: this._line_offset,
469
486
  token_type: this.token_type,
470
487
  token_start: this.token_start,
471
488
  token_end: this.token_end,
@@ -479,14 +496,41 @@ class Lexer {
479
496
  */
480
497
  restore_position(saved) {
481
498
  this.pos = saved.pos;
482
- this.line = saved.line;
483
- this.column = saved.column;
499
+ this._line = saved.line;
500
+ this._line_offset = saved._line_offset;
484
501
  this.token_type = saved.token_type;
485
502
  this.token_start = saved.token_start;
486
503
  this.token_end = saved.token_end;
487
504
  this.token_line = saved.token_line;
488
505
  this.token_column = saved.token_column;
489
506
  }
507
+ /**
508
+ * Skip whitespace and comments within a range, maintaining line/column tracking
509
+ * @param end The end boundary (exclusive)
510
+ */
511
+ skip_whitespace_in_range(end) {
512
+ while (this.pos < end) {
513
+ let ch = this.source.charCodeAt(this.pos);
514
+ if (is_whitespace(ch)) {
515
+ this.advance();
516
+ continue;
517
+ }
518
+ if (ch === CHAR_FORWARD_SLASH && this.pos + 1 < end && this.source.charCodeAt(this.pos + 1) === CHAR_ASTERISK) {
519
+ this.advance();
520
+ this.advance();
521
+ while (this.pos < end) {
522
+ if (this.source.charCodeAt(this.pos) === CHAR_ASTERISK && this.pos + 1 < end && this.source.charCodeAt(this.pos + 1) === CHAR_FORWARD_SLASH) {
523
+ this.advance();
524
+ this.advance();
525
+ break;
526
+ }
527
+ this.advance();
528
+ }
529
+ continue;
530
+ }
531
+ break;
532
+ }
533
+ }
490
534
  }
491
535
  function* tokenize(source, on_comment) {
492
536
  const lexer = new Lexer(source, on_comment);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@projectwallace/css-parser",
3
- "version": "0.13.2",
3
+ "version": "0.13.4",
4
4
  "description": "High-performance CSS lexer and parser, optimized for CSS inspection and analysis",
5
5
  "author": "Bart Veneman <bart@projectwallace.com>",
6
6
  "license": "MIT",