@projectwallace/css-parser 0.12.0 → 0.12.2

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) {
@@ -2,7 +2,7 @@ import { Lexer } from './tokenize.js';
2
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
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';
4
4
  import { skip_whitespace_and_comments_forward, skip_whitespace_and_comments_backward, skip_whitespace_forward } from './parse-utils.js';
5
- import { CHAR_GREATER_THAN, CHAR_PLUS, CHAR_TILDE, CHAR_PERIOD, CHAR_ASTERISK, CHAR_AMPERSAND, CHAR_PIPE, CHAR_SPACE, CHAR_TAB, CHAR_NEWLINE, CHAR_CARRIAGE_RETURN, CHAR_FORM_FEED, 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';
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';
7
7
  import { CSSNode } from './css-node.js';
8
8
 
@@ -38,7 +38,13 @@ class SelectorParser {
38
38
  let selector_column = this.lexer.column;
39
39
  let complex_selector = this.parse_complex_selector(allow_relative);
40
40
  if (complex_selector !== null) {
41
- let selector_wrapper = this.arena.create_node(SELECTOR, selector_start, this.lexer.pos - selector_start, selector_line, selector_column);
41
+ let selector_wrapper = this.arena.create_node(
42
+ SELECTOR,
43
+ selector_start,
44
+ this.lexer.pos - selector_start,
45
+ selector_line,
46
+ selector_column
47
+ );
42
48
  this.arena.set_content_start_delta(selector_wrapper, 0);
43
49
  this.arena.set_content_length(selector_wrapper, this.lexer.pos - selector_start);
44
50
  let last_component = complex_selector;
@@ -81,7 +87,13 @@ class SelectorParser {
81
87
  if (token_type === TOKEN_DELIM) {
82
88
  let ch = this.source.charCodeAt(this.lexer.token_start);
83
89
  if (ch === CHAR_GREATER_THAN || ch === CHAR_PLUS || ch === CHAR_TILDE) {
84
- let combinator = this.create_node_at(COMBINATOR, this.lexer.token_start, this.lexer.token_end, this.lexer.token_line, this.lexer.token_column);
90
+ let combinator = this.create_node_at(
91
+ COMBINATOR,
92
+ this.lexer.token_start,
93
+ this.lexer.token_end,
94
+ this.lexer.token_line,
95
+ this.lexer.token_column
96
+ );
85
97
  components.push(combinator);
86
98
  this.skip_whitespace();
87
99
  } else {
@@ -210,22 +222,30 @@ class SelectorParser {
210
222
  // Parse type selector or namespace selector (ns|E or ns|*)
211
223
  // Called when we've seen an IDENT token
212
224
  parse_type_or_namespace_selector(start, end) {
225
+ const saved = this.lexer.save_position();
226
+ this.skip_whitespace();
213
227
  if (this.lexer.pos < this.selector_end && this.source.charCodeAt(this.lexer.pos) === CHAR_PIPE) {
214
228
  this.lexer.pos++;
215
229
  let node = this.parse_namespace_local_part(start, start, end - start);
216
230
  if (node !== null) return node;
217
231
  this.lexer.pos = end;
232
+ } else {
233
+ this.lexer.restore_position(saved);
218
234
  }
219
235
  return this.create_node(TYPE_SELECTOR, start, end);
220
236
  }
221
237
  // Parse universal selector or namespace selector (*|E or *|*)
222
238
  // Called when we've seen a * DELIM token
223
239
  parse_universal_or_namespace_selector(start, end) {
240
+ const saved = this.lexer.save_position();
241
+ this.skip_whitespace();
224
242
  if (this.lexer.pos < this.selector_end && this.source.charCodeAt(this.lexer.pos) === CHAR_PIPE) {
225
243
  this.lexer.pos++;
226
244
  let node = this.parse_namespace_local_part(start, start, end - start);
227
245
  if (node !== null) return node;
228
246
  this.lexer.pos = end;
247
+ } else {
248
+ this.lexer.restore_position(saved);
229
249
  }
230
250
  return this.create_node(UNIVERSAL_SELECTOR, start, end);
231
251
  }
@@ -239,16 +259,9 @@ class SelectorParser {
239
259
  let whitespace_start = this.lexer.pos;
240
260
  let whitespace_start_line = this.lexer.line;
241
261
  let whitespace_start_column = this.lexer.column;
242
- let has_whitespace = false;
243
- while (this.lexer.pos < this.selector_end) {
244
- let ch = this.source.charCodeAt(this.lexer.pos);
245
- if (ch === CHAR_SPACE || ch === CHAR_TAB || ch === CHAR_NEWLINE || ch === CHAR_CARRIAGE_RETURN || ch === CHAR_FORM_FEED) {
246
- has_whitespace = true;
247
- this.lexer.advance();
248
- } else {
249
- break;
250
- }
251
- }
262
+ let has_whitespace = this.lexer.pos < this.selector_end;
263
+ this.skip_whitespace();
264
+ has_whitespace = has_whitespace && this.lexer.pos > whitespace_start;
252
265
  if (this.lexer.pos >= this.selector_end) {
253
266
  this.lexer.pos = whitespace_start;
254
267
  this.lexer.line = whitespace_start_line;
@@ -266,14 +279,7 @@ class SelectorParser {
266
279
  this.lexer.pos = whitespace_start;
267
280
  this.lexer.line = whitespace_start_line;
268
281
  this.lexer.column = whitespace_start_column;
269
- while (this.lexer.pos < this.selector_end) {
270
- let ch = this.source.charCodeAt(this.lexer.pos);
271
- if (ch === CHAR_SPACE || ch === CHAR_TAB || ch === CHAR_NEWLINE || ch === CHAR_CARRIAGE_RETURN || ch === CHAR_FORM_FEED) {
272
- this.lexer.advance();
273
- } else {
274
- break;
275
- }
276
- }
282
+ this.skip_whitespace();
277
283
  return this.create_node_at(COMBINATOR, whitespace_start, this.lexer.pos, whitespace_start_line, whitespace_start_column);
278
284
  }
279
285
  this.lexer.pos = whitespace_start;
@@ -422,10 +428,14 @@ class SelectorParser {
422
428
  // Parse pseudo-class or pseudo-element (:hover, ::before)
423
429
  parse_pseudo(start) {
424
430
  const saved = this.lexer.save_position();
431
+ const saved_ws = this.lexer.save_position();
432
+ this.skip_whitespace();
425
433
  let is_pseudo_element = false;
426
434
  if (this.lexer.pos < this.selector_end && this.source.charCodeAt(this.lexer.pos) === CHAR_COLON) {
427
435
  is_pseudo_element = true;
428
436
  this.lexer.pos++;
437
+ } else {
438
+ this.lexer.restore_position(saved_ws);
429
439
  }
430
440
  this.lexer.next_token_fast(false);
431
441
  let token_type = this.lexer.token_type;
@@ -566,16 +576,22 @@ class SelectorParser {
566
576
  return anplusb_parser.parse_anplusb(start, end, this.lexer.line);
567
577
  }
568
578
  }
569
- // Find the position of standalone "of" keyword
579
+ // Find the position of standalone "of" keyword (case-insensitive)
570
580
  find_of_keyword(start, end) {
571
- for (let i = start; i < end - 1; i++) {
572
- if (this.source.charCodeAt(i) === 111 && this.source.charCodeAt(i + 1) === 102) {
581
+ let i = start;
582
+ while (i < end - 1) {
583
+ i = skip_whitespace_and_comments_forward(this.source, i, end);
584
+ if (i >= end - 1) break;
585
+ let ch1 = this.source.charCodeAt(i);
586
+ let ch2 = this.source.charCodeAt(i + 1);
587
+ if ((ch1 === 111 || ch1 === 79) && (ch2 === 102 || ch2 === 70)) {
573
588
  let before_ok = i === start || is_whitespace(this.source.charCodeAt(i - 1));
574
589
  let after_ok = i + 2 >= end || is_whitespace(this.source.charCodeAt(i + 2));
575
590
  if (before_ok && after_ok) {
576
591
  return i;
577
592
  }
578
593
  }
594
+ i++;
579
595
  }
580
596
  return -1;
581
597
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@projectwallace/css-parser",
3
- "version": "0.12.0",
3
+ "version": "0.12.2",
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",