@projectwallace/css-parser 0.11.0 → 0.11.1

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() {
@@ -81,7 +81,7 @@ class SelectorParser {
81
81
  if (token_type === TOKEN_DELIM) {
82
82
  let ch = this.source.charCodeAt(this.lexer.token_start);
83
83
  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);
84
+ let combinator = this.create_node_at(COMBINATOR, this.lexer.token_start, this.lexer.token_end, this.lexer.token_line, this.lexer.token_column);
85
85
  components.push(combinator);
86
86
  this.skip_whitespace();
87
87
  } else {
@@ -237,40 +237,48 @@ class SelectorParser {
237
237
  // Parse combinator (>, +, ~, or descendant space)
238
238
  try_parse_combinator() {
239
239
  let whitespace_start = this.lexer.pos;
240
+ let whitespace_start_line = this.lexer.line;
241
+ let whitespace_start_column = this.lexer.column;
240
242
  let has_whitespace = false;
241
243
  while (this.lexer.pos < this.selector_end) {
242
244
  let ch = this.source.charCodeAt(this.lexer.pos);
243
245
  if (ch === CHAR_SPACE || ch === CHAR_TAB || ch === CHAR_NEWLINE || ch === CHAR_CARRIAGE_RETURN || ch === CHAR_FORM_FEED) {
244
246
  has_whitespace = true;
245
- this.lexer.pos++;
247
+ this.lexer.advance();
246
248
  } else {
247
249
  break;
248
250
  }
249
251
  }
250
252
  if (this.lexer.pos >= this.selector_end) {
251
253
  this.lexer.pos = whitespace_start;
254
+ this.lexer.line = whitespace_start_line;
255
+ this.lexer.column = whitespace_start_column;
252
256
  return null;
253
257
  }
254
258
  this.lexer.next_token_fast(false);
255
259
  if (this.lexer.token_type === TOKEN_DELIM) {
256
260
  let ch = this.source.charCodeAt(this.lexer.token_start);
257
261
  if (is_combinator(ch)) {
258
- return this.create_node(COMBINATOR, this.lexer.token_start, this.lexer.token_end);
262
+ return this.create_node_at(COMBINATOR, this.lexer.token_start, this.lexer.token_end, this.lexer.token_line, this.lexer.token_column);
259
263
  }
260
264
  }
261
265
  if (has_whitespace) {
262
266
  this.lexer.pos = whitespace_start;
267
+ this.lexer.line = whitespace_start_line;
268
+ this.lexer.column = whitespace_start_column;
263
269
  while (this.lexer.pos < this.selector_end) {
264
270
  let ch = this.source.charCodeAt(this.lexer.pos);
265
271
  if (ch === CHAR_SPACE || ch === CHAR_TAB || ch === CHAR_NEWLINE || ch === CHAR_CARRIAGE_RETURN || ch === CHAR_FORM_FEED) {
266
- this.lexer.pos++;
272
+ this.lexer.advance();
267
273
  } else {
268
274
  break;
269
275
  }
270
276
  }
271
- return this.create_node(COMBINATOR, whitespace_start, this.lexer.pos);
277
+ return this.create_node_at(COMBINATOR, whitespace_start, this.lexer.pos, whitespace_start_line, whitespace_start_column);
272
278
  }
273
279
  this.lexer.pos = whitespace_start;
280
+ this.lexer.line = whitespace_start_line;
281
+ this.lexer.column = whitespace_start_column;
274
282
  return null;
275
283
  }
276
284
  // Parse class selector (.classname)
@@ -572,7 +580,13 @@ class SelectorParser {
572
580
  return -1;
573
581
  }
574
582
  create_node(type, start, end) {
575
- let node = this.arena.create_node(type, start, end - start, this.lexer.line, this.lexer.column);
583
+ let node = this.arena.create_node(type, start, end - start, this.lexer.token_line, this.lexer.token_column);
584
+ this.arena.set_content_start_delta(node, 0);
585
+ this.arena.set_content_length(node, end - start);
586
+ return node;
587
+ }
588
+ create_node_at(type, start, end, line, column) {
589
+ let node = this.arena.create_node(type, start, end - start, line, column);
576
590
  this.arena.set_content_start_delta(node, 0);
577
591
  this.arena.set_content_length(node, end - start);
578
592
  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.1",
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",