@projectwallace/css-parser 0.11.0 → 0.11.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.
@@ -1,7 +1,7 @@
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, URL, STRING, DIMENSION, NUMBER, FEATURE_RANGE } from './arena.js';
3
- import { TOKEN_COMMA, TOKEN_IDENT, TOKEN_LEFT_PAREN, TOKEN_RIGHT_PAREN, TOKEN_FUNCTION, TOKEN_EOF, TOKEN_WHITESPACE, TOKEN_URL, TOKEN_STRING, TOKEN_DIMENSION, TOKEN_PERCENTAGE, TOKEN_NUMBER } from './token-types.js';
4
- import { str_equals, CHAR_LESS_THAN, CHAR_GREATER_THAN, CHAR_EQUALS, CHAR_COLON, is_whitespace } from './string-utils.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';
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
+ 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 { trim_boundaries, skip_whitespace_forward } from './parse-utils.js';
6
6
  import { CSSNode } from './css-node.js';
7
7
 
@@ -26,20 +26,23 @@ class AtRulePreludeParser {
26
26
  }
27
27
  // Dispatch to appropriate parser based on at-rule type
28
28
  parse_prelude_dispatch(at_rule_name) {
29
- if (str_equals("media", at_rule_name)) {
29
+ let normalized_name = strip_vendor_prefix(at_rule_name);
30
+ if (str_equals("media", normalized_name)) {
30
31
  return this.parse_media_query_list();
31
- } else if (str_equals("container", at_rule_name)) {
32
+ } else if (str_equals("container", normalized_name)) {
32
33
  return this.parse_container_query();
33
- } else if (str_equals("supports", at_rule_name)) {
34
+ } else if (str_equals("supports", normalized_name)) {
34
35
  return this.parse_supports_query();
35
- } else if (str_equals("layer", at_rule_name)) {
36
+ } else if (str_equals("layer", normalized_name)) {
36
37
  return this.parse_layer_names();
37
- } else if (str_equals("keyframes", at_rule_name)) {
38
+ } else if (str_equals("keyframes", normalized_name)) {
38
39
  return this.parse_identifier();
39
- } else if (str_equals("property", at_rule_name)) {
40
+ } else if (str_equals("property", normalized_name)) {
40
41
  return this.parse_identifier();
41
- } else if (str_equals("import", at_rule_name)) {
42
+ } else if (str_equals("import", normalized_name)) {
42
43
  return this.parse_import_prelude();
44
+ } else if (str_equals("charset", normalized_name)) {
45
+ return this.parse_charset_prelude();
43
46
  }
44
47
  return [];
45
48
  }
@@ -303,6 +306,15 @@ class AtRulePreludeParser {
303
306
  let ident = this.create_node(IDENTIFIER, this.lexer.token_start, this.lexer.token_end);
304
307
  return [ident];
305
308
  }
309
+ // Parse @charset prelude: "UTF-8"
310
+ parse_charset_prelude() {
311
+ this.skip_whitespace();
312
+ if (this.lexer.pos >= this.prelude_end) return [];
313
+ this.next_token();
314
+ if (this.lexer.token_type !== TOKEN_STRING) return [];
315
+ let str = this.create_node(STRING, this.lexer.token_start, this.lexer.token_end);
316
+ return [str];
317
+ }
306
318
  // Parse @import prelude: url() [layer] [supports()] [media-query-list]
307
319
  // @import url("styles.css") layer(base) supports(display: grid) screen and (min-width: 768px);
308
320
  parse_import_prelude() {
@@ -1,6 +1,6 @@
1
1
  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
- 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';
3
+ import { TOKEN_COMMENT, 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
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 } from './string-utils.js';
6
6
  import { ANplusBParser } from './parse-anplusb.js';
@@ -52,10 +52,29 @@ class SelectorParser {
52
52
  }
53
53
  this.skip_whitespace();
54
54
  if (this.lexer.pos >= this.selector_end) break;
55
- this.lexer.next_token_fast(false);
56
- let token_type = this.lexer.token_type;
55
+ let token_type = TOKEN_EOF;
56
+ while (this.lexer.pos < this.selector_end) {
57
+ this.lexer.next_token_fast(false);
58
+ token_type = this.lexer.token_type;
59
+ if (token_type === TOKEN_COMMENT) {
60
+ this.skip_whitespace();
61
+ } else {
62
+ break;
63
+ }
64
+ }
57
65
  if (token_type === TOKEN_COMMA) {
58
66
  this.skip_whitespace();
67
+ while (this.lexer.pos < this.selector_end) {
68
+ const saved = this.lexer.save_position();
69
+ this.lexer.next_token_fast(false);
70
+ token_type = this.lexer.token_type;
71
+ if (token_type === TOKEN_COMMENT) {
72
+ this.skip_whitespace();
73
+ } else {
74
+ this.lexer.restore_position(saved);
75
+ break;
76
+ }
77
+ }
59
78
  continue;
60
79
  } else {
61
80
  break;
@@ -81,7 +100,7 @@ class SelectorParser {
81
100
  if (token_type === TOKEN_DELIM) {
82
101
  let ch = this.source.charCodeAt(this.lexer.token_start);
83
102
  if (ch === CHAR_GREATER_THAN || ch === CHAR_PLUS || ch === CHAR_TILDE) {
84
- let combinator = this.create_node(COMBINATOR, this.lexer.token_start, this.lexer.token_end);
103
+ let combinator = this.create_node_at(COMBINATOR, this.lexer.token_start, this.lexer.token_end, this.lexer.token_line, this.lexer.token_column);
85
104
  components.push(combinator);
86
105
  this.skip_whitespace();
87
106
  } else {
@@ -237,40 +256,48 @@ class SelectorParser {
237
256
  // Parse combinator (>, +, ~, or descendant space)
238
257
  try_parse_combinator() {
239
258
  let whitespace_start = this.lexer.pos;
259
+ let whitespace_start_line = this.lexer.line;
260
+ let whitespace_start_column = this.lexer.column;
240
261
  let has_whitespace = false;
241
262
  while (this.lexer.pos < this.selector_end) {
242
263
  let ch = this.source.charCodeAt(this.lexer.pos);
243
264
  if (ch === CHAR_SPACE || ch === CHAR_TAB || ch === CHAR_NEWLINE || ch === CHAR_CARRIAGE_RETURN || ch === CHAR_FORM_FEED) {
244
265
  has_whitespace = true;
245
- this.lexer.pos++;
266
+ this.lexer.advance();
246
267
  } else {
247
268
  break;
248
269
  }
249
270
  }
250
271
  if (this.lexer.pos >= this.selector_end) {
251
272
  this.lexer.pos = whitespace_start;
273
+ this.lexer.line = whitespace_start_line;
274
+ this.lexer.column = whitespace_start_column;
252
275
  return null;
253
276
  }
254
277
  this.lexer.next_token_fast(false);
255
278
  if (this.lexer.token_type === TOKEN_DELIM) {
256
279
  let ch = this.source.charCodeAt(this.lexer.token_start);
257
280
  if (is_combinator(ch)) {
258
- return this.create_node(COMBINATOR, this.lexer.token_start, this.lexer.token_end);
281
+ return this.create_node_at(COMBINATOR, this.lexer.token_start, this.lexer.token_end, this.lexer.token_line, this.lexer.token_column);
259
282
  }
260
283
  }
261
284
  if (has_whitespace) {
262
285
  this.lexer.pos = whitespace_start;
286
+ this.lexer.line = whitespace_start_line;
287
+ this.lexer.column = whitespace_start_column;
263
288
  while (this.lexer.pos < this.selector_end) {
264
289
  let ch = this.source.charCodeAt(this.lexer.pos);
265
290
  if (ch === CHAR_SPACE || ch === CHAR_TAB || ch === CHAR_NEWLINE || ch === CHAR_CARRIAGE_RETURN || ch === CHAR_FORM_FEED) {
266
- this.lexer.pos++;
291
+ this.lexer.advance();
267
292
  } else {
268
293
  break;
269
294
  }
270
295
  }
271
- return this.create_node(COMBINATOR, whitespace_start, this.lexer.pos);
296
+ return this.create_node_at(COMBINATOR, whitespace_start, this.lexer.pos, whitespace_start_line, whitespace_start_column);
272
297
  }
273
298
  this.lexer.pos = whitespace_start;
299
+ this.lexer.line = whitespace_start_line;
300
+ this.lexer.column = whitespace_start_column;
274
301
  return null;
275
302
  }
276
303
  // Parse class selector (.classname)
@@ -572,7 +599,13 @@ class SelectorParser {
572
599
  return -1;
573
600
  }
574
601
  create_node(type, start, end) {
575
- let node = this.arena.create_node(type, start, end - start, this.lexer.line, this.lexer.column);
602
+ let node = this.arena.create_node(type, start, end - start, this.lexer.token_line, this.lexer.token_column);
603
+ this.arena.set_content_start_delta(node, 0);
604
+ this.arena.set_content_length(node, end - start);
605
+ return node;
606
+ }
607
+ create_node_at(type, start, end, line, column) {
608
+ let node = this.arena.create_node(type, start, end - start, line, column);
576
609
  this.arena.set_content_start_delta(node, 0);
577
610
  this.arena.set_content_length(node, end - start);
578
611
  return node;
package/dist/parse.js CHANGED
@@ -226,9 +226,9 @@ class Parser {
226
226
  this.arena.set_value_start_delta(at_rule, trimmed[0] - at_rule_start);
227
227
  this.arena.set_value_length(at_rule, trimmed[1] - trimmed[0]);
228
228
  if (this.prelude_parser) {
229
+ prelude_wrapper = this.arena.create_node(AT_RULE_PRELUDE, trimmed[0], trimmed[1] - trimmed[0], at_rule_line, at_rule_column);
229
230
  let prelude_nodes = this.prelude_parser.parse_prelude(at_rule_name, trimmed[0], trimmed[1], at_rule_line, at_rule_column);
230
231
  if (prelude_nodes.length > 0) {
231
- prelude_wrapper = this.arena.create_node(AT_RULE_PRELUDE, trimmed[0], trimmed[1] - trimmed[0], at_rule_line, at_rule_column);
232
232
  this.arena.append_children(prelude_wrapper, prelude_nodes);
233
233
  }
234
234
  }
@@ -82,3 +82,18 @@ export declare function is_vendor_prefixed(source: string, start: number, end: n
82
82
  * - `color` → false
83
83
  */
84
84
  export declare function is_custom(str: string): boolean;
85
+ /**
86
+ * Strip vendor prefix from a string
87
+ *
88
+ * @param str - The string to strip vendor prefix from
89
+ * @returns The string without vendor prefix, or original string if no prefix found
90
+ *
91
+ * Examples:
92
+ * - `-webkit-keyframes` → `keyframes`
93
+ * - `-moz-appearance` → `appearance`
94
+ * - `-ms-filter` → `filter`
95
+ * - `-o-border-image` → `border-image`
96
+ * - `keyframes` → `keyframes` (no change)
97
+ * - `--custom-property` → `--custom-property` (custom property, not vendor prefix)
98
+ */
99
+ export declare function strip_vendor_prefix(str: string): string;
@@ -114,5 +114,16 @@ function is_custom(str) {
114
114
  if (str.length < 3) return false;
115
115
  return str.charCodeAt(0) === CHAR_MINUS_HYPHEN && str.charCodeAt(1) === CHAR_MINUS_HYPHEN;
116
116
  }
117
+ function strip_vendor_prefix(str) {
118
+ if (!is_vendor_prefixed(str)) {
119
+ return str;
120
+ }
121
+ for (let i = 2; i < str.length; i++) {
122
+ if (str.charCodeAt(i) === CHAR_MINUS_HYPHEN) {
123
+ return str.substring(i + 1);
124
+ }
125
+ }
126
+ return str;
127
+ }
117
128
 
118
- export { CHAR_AMPERSAND, CHAR_ASTERISK, CHAR_CARET, CHAR_CARRIAGE_RETURN, CHAR_COLON, CHAR_DOLLAR, CHAR_DOUBLE_QUOTE, CHAR_EQUALS, CHAR_FORM_FEED, CHAR_FORWARD_SLASH, CHAR_GREATER_THAN, CHAR_LESS_THAN, CHAR_MINUS_HYPHEN, CHAR_NEWLINE, CHAR_PERIOD, CHAR_PIPE, CHAR_PLUS, CHAR_SINGLE_QUOTE, CHAR_SPACE, CHAR_TAB, CHAR_TILDE, is_combinator, is_custom, is_digit, is_vendor_prefixed, is_whitespace, str_equals, str_index_of, str_starts_with };
129
+ export { CHAR_AMPERSAND, CHAR_ASTERISK, CHAR_CARET, CHAR_CARRIAGE_RETURN, CHAR_COLON, CHAR_DOLLAR, CHAR_DOUBLE_QUOTE, CHAR_EQUALS, CHAR_FORM_FEED, CHAR_FORWARD_SLASH, CHAR_GREATER_THAN, CHAR_LESS_THAN, CHAR_MINUS_HYPHEN, CHAR_NEWLINE, CHAR_PERIOD, CHAR_PIPE, CHAR_PLUS, CHAR_SINGLE_QUOTE, CHAR_SPACE, CHAR_TAB, CHAR_TILDE, is_combinator, is_custom, is_digit, is_vendor_prefixed, is_whitespace, str_equals, str_index_of, str_starts_with, strip_vendor_prefix };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@projectwallace/css-parser",
3
- "version": "0.11.0",
3
+ "version": "0.11.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",