@projectwallace/css-parser 0.12.1 → 0.12.3

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.
@@ -2,7 +2,7 @@ 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_forward } from './parse-utils.js';
5
+ import { skip_whitespace_and_comments_forward } from './parse-utils.js';
6
6
  import { CSSNode } from './css-node.js';
7
7
 
8
8
  class ANplusBParser {
@@ -186,7 +186,7 @@ class ANplusBParser {
186
186
  return null;
187
187
  }
188
188
  skip_whitespace() {
189
- this.lexer.pos = skip_whitespace_forward(this.source, this.lexer.pos, this.expr_end);
189
+ this.lexer.pos = skip_whitespace_and_comments_forward(this.source, this.lexer.pos, this.expr_end);
190
190
  }
191
191
  create_anplusb_node(start, a_start, a_end, b_start, b_end) {
192
192
  const node = this.arena.create_node(NTH_SELECTOR, start, this.lexer.pos - start, this.lexer.line, 1);
@@ -2,7 +2,7 @@ import { Lexer } from './tokenize.js';
2
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';
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
- import { trim_boundaries, skip_whitespace_forward } from './parse-utils.js';
5
+ import { skip_whitespace_and_comments_forward, trim_boundaries } from './parse-utils.js';
6
6
  import { CSSNode } from './css-node.js';
7
7
 
8
8
  class AtRulePreludeParser {
@@ -134,23 +134,31 @@ class AtRulePreludeParser {
134
134
  let content_end = this.lexer.token_start;
135
135
  let feature_end = this.lexer.token_end;
136
136
  let has_comparison = false;
137
- for (let i = content_start; i < content_end; i++) {
137
+ let i = content_start;
138
+ while (i < content_end) {
139
+ i = skip_whitespace_and_comments_forward(this.source, i, content_end);
140
+ if (i >= content_end) break;
138
141
  let ch = this.source.charCodeAt(i);
139
142
  if (ch === CHAR_LESS_THAN || ch === CHAR_GREATER_THAN || ch === CHAR_EQUALS) {
140
143
  has_comparison = true;
141
144
  break;
142
145
  }
146
+ i++;
143
147
  }
144
148
  if (has_comparison) {
145
149
  return this.parse_feature_range(feature_start, feature_end, content_start, content_end);
146
150
  }
147
151
  let feature = this.create_node(MEDIA_FEATURE, feature_start, feature_end);
148
152
  let colon_pos = -1;
149
- for (let i = content_start; i < content_end; i++) {
150
- if (this.source.charCodeAt(i) === CHAR_COLON) {
151
- colon_pos = i;
153
+ let j = content_start;
154
+ while (j < content_end) {
155
+ j = skip_whitespace_and_comments_forward(this.source, j, content_end);
156
+ if (j >= content_end) break;
157
+ if (this.source.charCodeAt(j) === CHAR_COLON) {
158
+ colon_pos = j;
152
159
  break;
153
160
  }
161
+ j++;
154
162
  }
155
163
  if (colon_pos !== -1) {
156
164
  let name_trimmed = trim_boundaries(this.source, content_start, colon_pos);
@@ -458,9 +466,9 @@ class AtRulePreludeParser {
458
466
  this.lexer.restore_position(saved);
459
467
  return null;
460
468
  }
461
- // Helper: Skip whitespace
469
+ // Helper: Skip whitespace and comments
462
470
  skip_whitespace() {
463
- this.lexer.pos = skip_whitespace_forward(this.source, this.lexer.pos, this.prelude_end);
471
+ this.lexer.pos = skip_whitespace_and_comments_forward(this.source, this.lexer.pos, this.prelude_end);
464
472
  }
465
473
  // Helper: Peek at next token type without consuming
466
474
  peek_token_type() {
@@ -524,7 +532,7 @@ class AtRulePreludeParser {
524
532
  let feature_name_end = -1;
525
533
  let pos = content_start;
526
534
  while (pos < content_end) {
527
- pos = skip_whitespace_forward(this.source, pos, content_end);
535
+ pos = skip_whitespace_and_comments_forward(this.source, pos, content_end);
528
536
  if (pos >= content_end) break;
529
537
  let ch = this.source.charCodeAt(pos);
530
538
  if (ch === CHAR_LESS_THAN || ch === CHAR_GREATER_THAN || ch === CHAR_EQUALS) {
@@ -1,6 +1,6 @@
1
1
  import { Lexer } from './tokenize.js';
2
- import { CSSDataArena, SELECTOR_LIST, SELECTOR, COMBINATOR, NESTING_SELECTOR, ID_SELECTOR, TYPE_SELECTOR, UNIVERSAL_SELECTOR, CLASS_SELECTOR, ATTRIBUTE_SELECTOR, ATTR_OPERATOR_NONE, ATTR_FLAG_NONE, ATTR_OPERATOR_EQUAL, ATTR_OPERATOR_TILDE_EQUAL, ATTR_OPERATOR_PIPE_EQUAL, ATTR_OPERATOR_CARET_EQUAL, ATTR_OPERATOR_DOLLAR_EQUAL, ATTR_OPERATOR_STAR_EQUAL, ATTR_FLAG_CASE_INSENSITIVE, ATTR_FLAG_CASE_SENSITIVE, PSEUDO_ELEMENT_SELECTOR, PSEUDO_CLASS_SELECTOR, FLAG_HAS_PARENS, LANG_SELECTOR, NTH_OF_SELECTOR } from './arena.js';
3
- import { TOKEN_COMMA, TOKEN_DELIM, TOKEN_EOF, TOKEN_WHITESPACE, TOKEN_FUNCTION, TOKEN_COLON, TOKEN_LEFT_BRACKET, TOKEN_HASH, TOKEN_IDENT, TOKEN_RIGHT_BRACKET, TOKEN_LEFT_PAREN, TOKEN_RIGHT_PAREN, TOKEN_STRING } from './token-types.js';
2
+ import { CSSDataArena, SELECTOR_LIST, SELECTOR, COMBINATOR, DIMENSION, NESTING_SELECTOR, ID_SELECTOR, TYPE_SELECTOR, UNIVERSAL_SELECTOR, CLASS_SELECTOR, ATTRIBUTE_SELECTOR, ATTR_OPERATOR_NONE, ATTR_FLAG_NONE, ATTR_OPERATOR_EQUAL, ATTR_OPERATOR_TILDE_EQUAL, ATTR_OPERATOR_PIPE_EQUAL, ATTR_OPERATOR_CARET_EQUAL, ATTR_OPERATOR_DOLLAR_EQUAL, ATTR_OPERATOR_STAR_EQUAL, ATTR_FLAG_CASE_INSENSITIVE, ATTR_FLAG_CASE_SENSITIVE, PSEUDO_ELEMENT_SELECTOR, PSEUDO_CLASS_SELECTOR, FLAG_HAS_PARENS, LANG_SELECTOR, NTH_OF_SELECTOR } from './arena.js';
3
+ import { TOKEN_COMMA, TOKEN_DELIM, TOKEN_EOF, TOKEN_WHITESPACE, TOKEN_PERCENTAGE, TOKEN_FUNCTION, TOKEN_COLON, TOKEN_LEFT_BRACKET, TOKEN_HASH, TOKEN_IDENT, TOKEN_RIGHT_BRACKET, TOKEN_LEFT_PAREN, TOKEN_RIGHT_PAREN, TOKEN_STRING } from './token-types.js';
4
4
  import { skip_whitespace_and_comments_forward, skip_whitespace_and_comments_backward, skip_whitespace_forward } from './parse-utils.js';
5
5
  import { CHAR_GREATER_THAN, CHAR_PLUS, CHAR_TILDE, CHAR_PERIOD, CHAR_ASTERISK, CHAR_AMPERSAND, CHAR_PIPE, is_combinator, is_whitespace, CHAR_EQUALS, CHAR_CARET, CHAR_DOLLAR, CHAR_SINGLE_QUOTE, CHAR_DOUBLE_QUOTE, CHAR_COLON, str_equals, CHAR_FORWARD_SLASH } from './string-utils.js';
6
6
  import { ANplusBParser } from './parse-anplusb.js';
@@ -193,6 +193,8 @@ class SelectorParser {
193
193
  return this.parse_pseudo(start);
194
194
  case TOKEN_FUNCTION:
195
195
  return this.parse_pseudo_function(start, end);
196
+ case TOKEN_PERCENTAGE:
197
+ return this.create_node(DIMENSION, start, end);
196
198
  case TOKEN_WHITESPACE:
197
199
  case TOKEN_COMMA:
198
200
  return null;
@@ -222,22 +224,30 @@ class SelectorParser {
222
224
  // Parse type selector or namespace selector (ns|E or ns|*)
223
225
  // Called when we've seen an IDENT token
224
226
  parse_type_or_namespace_selector(start, end) {
227
+ const saved = this.lexer.save_position();
228
+ this.skip_whitespace();
225
229
  if (this.lexer.pos < this.selector_end && this.source.charCodeAt(this.lexer.pos) === CHAR_PIPE) {
226
230
  this.lexer.pos++;
227
231
  let node = this.parse_namespace_local_part(start, start, end - start);
228
232
  if (node !== null) return node;
229
233
  this.lexer.pos = end;
234
+ } else {
235
+ this.lexer.restore_position(saved);
230
236
  }
231
237
  return this.create_node(TYPE_SELECTOR, start, end);
232
238
  }
233
239
  // Parse universal selector or namespace selector (*|E or *|*)
234
240
  // Called when we've seen a * DELIM token
235
241
  parse_universal_or_namespace_selector(start, end) {
242
+ const saved = this.lexer.save_position();
243
+ this.skip_whitespace();
236
244
  if (this.lexer.pos < this.selector_end && this.source.charCodeAt(this.lexer.pos) === CHAR_PIPE) {
237
245
  this.lexer.pos++;
238
246
  let node = this.parse_namespace_local_part(start, start, end - start);
239
247
  if (node !== null) return node;
240
248
  this.lexer.pos = end;
249
+ } else {
250
+ this.lexer.restore_position(saved);
241
251
  }
242
252
  return this.create_node(UNIVERSAL_SELECTOR, start, end);
243
253
  }
@@ -420,10 +430,14 @@ class SelectorParser {
420
430
  // Parse pseudo-class or pseudo-element (:hover, ::before)
421
431
  parse_pseudo(start) {
422
432
  const saved = this.lexer.save_position();
433
+ const saved_ws = this.lexer.save_position();
434
+ this.skip_whitespace();
423
435
  let is_pseudo_element = false;
424
436
  if (this.lexer.pos < this.selector_end && this.source.charCodeAt(this.lexer.pos) === CHAR_COLON) {
425
437
  is_pseudo_element = true;
426
438
  this.lexer.pos++;
439
+ } else {
440
+ this.lexer.restore_position(saved_ws);
427
441
  }
428
442
  this.lexer.next_token_fast(false);
429
443
  let token_type = this.lexer.token_type;
@@ -564,16 +578,22 @@ class SelectorParser {
564
578
  return anplusb_parser.parse_anplusb(start, end, this.lexer.line);
565
579
  }
566
580
  }
567
- // Find the position of standalone "of" keyword
581
+ // Find the position of standalone "of" keyword (case-insensitive)
568
582
  find_of_keyword(start, end) {
569
- for (let i = start; i < end - 1; i++) {
570
- if (this.source.charCodeAt(i) === 111 && this.source.charCodeAt(i + 1) === 102) {
583
+ let i = start;
584
+ while (i < end - 1) {
585
+ i = skip_whitespace_and_comments_forward(this.source, i, end);
586
+ if (i >= end - 1) break;
587
+ let ch1 = this.source.charCodeAt(i);
588
+ let ch2 = this.source.charCodeAt(i + 1);
589
+ if ((ch1 === 111 || ch1 === 79) && (ch2 === 102 || ch2 === 70)) {
571
590
  let before_ok = i === start || is_whitespace(this.source.charCodeAt(i - 1));
572
591
  let after_ok = i + 2 >= end || is_whitespace(this.source.charCodeAt(i + 2));
573
592
  if (before_ok && after_ok) {
574
593
  return i;
575
594
  }
576
595
  }
596
+ i++;
577
597
  }
578
598
  return -1;
579
599
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@projectwallace/css-parser",
3
- "version": "0.12.1",
3
+ "version": "0.12.3",
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",