@projectwallace/css-parser 0.2.0 → 0.4.0

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/README.md CHANGED
@@ -13,18 +13,7 @@ Built for speed and efficiency, this parser handles large CSS files with minimal
13
13
  - **Error recovery** - Continues parsing on malformed CSS
14
14
  - **Comment preservation** - Comments stored as first-class AST nodes
15
15
  - **Location tracking** - Line, column, offset, and length for all nodes
16
- - **Vendor prefix detection** - Automatic detection of `-webkit-`, `-moz-`, etc.
17
- - **Structured parsing** - Deep parsing of selectors, values, and at-rule preludes
18
-
19
- ## Performance
20
-
21
- - **Tiny install size**
22
- - **Zero allocations during parsing** - all memory allocated upfront based on real world heuristics, which also helps prevent garbage collection running often
23
- - **Cache-friendly data layout** - contiguous memory for sequential access
24
- - **First-class comment and location support** - while still being performant because analysis requires constant access to lines and columns
25
- - **No syntax validation** - focusing only on the raw data we can skip expensive syntax files and MDN data syncs
26
-
27
- This parser was heavily influenced by [CSSTree](https://github.com/csstree/csstree), one of the most robust CSS parsers available.
16
+ - **Built-in vendor prefix detection** - Automatic detection of `-webkit-`, `-moz-`, etc. for selectors, values, properties and more
28
17
 
29
18
  ## Installation
30
19
 
@@ -66,10 +55,24 @@ for (const rule of ast) {
66
55
  }
67
56
  ```
68
57
 
58
+ ## Performance
59
+
60
+ - **Tiny install size**
61
+ - **Zero allocations during parsing** - all memory allocated upfront based on real world heuristics, which also helps prevent garbage collection running often
62
+ - **Cache-friendly data layout** - contiguous memory for sequential access powered by concepts or Data Oriented Design
63
+ - **First-class comment and location support** - while still being performant because analysis requires constant access to lines and columns
64
+ - **No syntax validation** - focusing only on the raw data we can skip expensive syntax files and MDN data syncs
65
+
66
+ This parser was heavily influenced by [CSSTree](https://github.com/csstree/csstree), one of the most robust CSS parsers available.
67
+
69
68
  ## Documentation
70
69
 
71
70
  See [API.md](./API.md) for complete documentation of all parser functions and options.
72
71
 
72
+ ## Non-goals
73
+
74
+ - **No syntax validation** - this parser does not try to validate your CSS structure. Everything can be anything
75
+
73
76
  ## License
74
77
 
75
78
  MIT
package/dist/arena.d.ts CHANGED
@@ -36,6 +36,8 @@ export declare const FLAG_IMPORTANT: number;
36
36
  export declare const FLAG_HAS_ERROR: number;
37
37
  export declare const FLAG_LENGTH_OVERFLOW: number;
38
38
  export declare const FLAG_HAS_BLOCK: number;
39
+ export declare const FLAG_VENDOR_PREFIXED: number;
40
+ export declare const FLAG_HAS_DECLARATIONS: number;
39
41
  export declare class CSSDataArena {
40
42
  private buffer;
41
43
  private view;
@@ -1,5 +1,5 @@
1
1
  import { L as Lexer, q as TOKEN_COMMA, T as TOKEN_IDENT, t as TOKEN_LEFT_PAREN, u as TOKEN_RIGHT_PAREN, l as TOKEN_WHITESPACE, f as TOKEN_URL, a as TOKEN_FUNCTION, d as TOKEN_STRING, y as TOKEN_EOF } from './lexer-DXablYMZ.js';
2
- import { O as str_equals, E as NODE_PRELUDE_OPERATOR, y as NODE_PRELUDE_MEDIA_TYPE, w as NODE_PRELUDE_MEDIA_QUERY, x as NODE_PRELUDE_MEDIA_FEATURE, M as trim_boundaries, D as NODE_PRELUDE_IDENTIFIER, z as NODE_PRELUDE_CONTAINER_QUERY, A as NODE_PRELUDE_SUPPORTS_QUERY, B as NODE_PRELUDE_LAYER_NAME, F as NODE_PRELUDE_IMPORT_URL, G as NODE_PRELUDE_IMPORT_LAYER, H as NODE_PRELUDE_IMPORT_SUPPORTS, P as CHAR_SPACE, Q as CHAR_TAB, R as CHAR_NEWLINE, S as CHAR_CARRIAGE_RETURN, T as CHAR_FORM_FEED } from './string-utils-Bm2l6p1L.js';
2
+ import { R as str_equals, E as NODE_PRELUDE_OPERATOR, y as NODE_PRELUDE_MEDIA_TYPE, w as NODE_PRELUDE_MEDIA_QUERY, x as NODE_PRELUDE_MEDIA_FEATURE, Q as trim_boundaries, D as NODE_PRELUDE_IDENTIFIER, z as NODE_PRELUDE_CONTAINER_QUERY, A as NODE_PRELUDE_SUPPORTS_QUERY, B as NODE_PRELUDE_LAYER_NAME, F as NODE_PRELUDE_IMPORT_URL, G as NODE_PRELUDE_IMPORT_LAYER, H as NODE_PRELUDE_IMPORT_SUPPORTS, S as CHAR_SPACE, T as CHAR_TAB, U as CHAR_NEWLINE, V as CHAR_CARRIAGE_RETURN, W as CHAR_FORM_FEED } from './string-utils-tMt2O9RW.js';
3
3
 
4
4
  class AtRulePreludeParser {
5
5
  lexer;
@@ -18,6 +18,7 @@ export declare class CSSNode {
18
18
  get has_error(): boolean;
19
19
  get has_prelude(): boolean;
20
20
  get has_block(): boolean;
21
+ get has_declarations(): boolean;
21
22
  get values(): CSSNode[];
22
23
  get value_count(): number;
23
24
  get line(): number;
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ export { parse } from './parse.js';
2
2
  export { parse_selector } from './parse-selector.js';
3
3
  export { parse_atrule_prelude } from './parse-atrule-prelude.js';
4
4
  export { tokenize } from './tokenize.js';
5
- export { C as CSSNode, I as FLAG_IMPORTANT, a as NODE_AT_RULE, b as NODE_COMMENT, c as NODE_DECLARATION, z as NODE_PRELUDE_CONTAINER_QUERY, D as NODE_PRELUDE_IDENTIFIER, G as NODE_PRELUDE_IMPORT_LAYER, H as NODE_PRELUDE_IMPORT_SUPPORTS, F as NODE_PRELUDE_IMPORT_URL, B as NODE_PRELUDE_LAYER_NAME, x as NODE_PRELUDE_MEDIA_FEATURE, w as NODE_PRELUDE_MEDIA_QUERY, y as NODE_PRELUDE_MEDIA_TYPE, E as NODE_PRELUDE_OPERATOR, A as NODE_PRELUDE_SUPPORTS_QUERY, d as NODE_SELECTOR, q as NODE_SELECTOR_ATTRIBUTE, o as NODE_SELECTOR_CLASS, t as NODE_SELECTOR_COMBINATOR, p as NODE_SELECTOR_ID, m as NODE_SELECTOR_LIST, v as NODE_SELECTOR_NESTING, r as NODE_SELECTOR_PSEUDO_CLASS, s as NODE_SELECTOR_PSEUDO_ELEMENT, n as NODE_SELECTOR_TYPE, u as NODE_SELECTOR_UNIVERSAL, e as NODE_STYLESHEET, N as NODE_STYLE_RULE, j as NODE_VALUE_COLOR, h as NODE_VALUE_DIMENSION, k as NODE_VALUE_FUNCTION, f as NODE_VALUE_KEYWORD, g as NODE_VALUE_NUMBER, l as NODE_VALUE_OPERATOR, i as NODE_VALUE_STRING } from './string-utils-Bm2l6p1L.js';
5
+ export { C as CSSNode, I as FLAG_IMPORTANT, a as NODE_AT_RULE, b as NODE_COMMENT, c as NODE_DECLARATION, z as NODE_PRELUDE_CONTAINER_QUERY, D as NODE_PRELUDE_IDENTIFIER, G as NODE_PRELUDE_IMPORT_LAYER, H as NODE_PRELUDE_IMPORT_SUPPORTS, F as NODE_PRELUDE_IMPORT_URL, B as NODE_PRELUDE_LAYER_NAME, x as NODE_PRELUDE_MEDIA_FEATURE, w as NODE_PRELUDE_MEDIA_QUERY, y as NODE_PRELUDE_MEDIA_TYPE, E as NODE_PRELUDE_OPERATOR, A as NODE_PRELUDE_SUPPORTS_QUERY, d as NODE_SELECTOR, q as NODE_SELECTOR_ATTRIBUTE, o as NODE_SELECTOR_CLASS, t as NODE_SELECTOR_COMBINATOR, p as NODE_SELECTOR_ID, m as NODE_SELECTOR_LIST, v as NODE_SELECTOR_NESTING, r as NODE_SELECTOR_PSEUDO_CLASS, s as NODE_SELECTOR_PSEUDO_ELEMENT, n as NODE_SELECTOR_TYPE, u as NODE_SELECTOR_UNIVERSAL, e as NODE_STYLESHEET, N as NODE_STYLE_RULE, j as NODE_VALUE_COLOR, h as NODE_VALUE_DIMENSION, k as NODE_VALUE_FUNCTION, f as NODE_VALUE_KEYWORD, g as NODE_VALUE_NUMBER, l as NODE_VALUE_OPERATOR, i as NODE_VALUE_STRING } from './string-utils-tMt2O9RW.js';
6
6
  export { b as TOKEN_AT_KEYWORD, e as TOKEN_BAD_STRING, g as TOKEN_BAD_URL, n as TOKEN_CDC, m as TOKEN_CDO, o as TOKEN_COLON, q as TOKEN_COMMA, x as TOKEN_COMMENT, h as TOKEN_DELIM, k as TOKEN_DIMENSION, y as TOKEN_EOF, a as TOKEN_FUNCTION, c as TOKEN_HASH, T as TOKEN_IDENT, v as TOKEN_LEFT_BRACE, r as TOKEN_LEFT_BRACKET, t as TOKEN_LEFT_PAREN, i as TOKEN_NUMBER, j as TOKEN_PERCENTAGE, w as TOKEN_RIGHT_BRACE, s as TOKEN_RIGHT_BRACKET, u as TOKEN_RIGHT_PAREN, p as TOKEN_SEMICOLON, d as TOKEN_STRING, f as TOKEN_URL, l as TOKEN_WHITESPACE } from './lexer-DXablYMZ.js';
7
7
 
8
8
  function walk(node, callback, depth = 0) {
@@ -1,5 +1,5 @@
1
- import { K as CSSDataArena, C as CSSNode } from './string-utils-Bm2l6p1L.js';
2
- import { A as AtRulePreludeParser } from './at-rule-prelude-parser-7_qZ1z7t.js';
1
+ import { K as CSSDataArena, C as CSSNode } from './string-utils-tMt2O9RW.js';
2
+ import { A as AtRulePreludeParser } from './at-rule-prelude-parser-Cj8ecgQp.js';
3
3
 
4
4
  function parse_atrule_prelude(at_rule_name, prelude) {
5
5
  const arena = new CSSDataArena(CSSDataArena.capacity_for_source(prelude.length));
@@ -1,5 +1,5 @@
1
- import { K as CSSDataArena, d as NODE_SELECTOR, C as CSSNode } from './string-utils-Bm2l6p1L.js';
2
- import { S as SelectorParser } from './selector-parser-BjdQlGvW.js';
1
+ import { K as CSSDataArena, d as NODE_SELECTOR, C as CSSNode } from './string-utils-tMt2O9RW.js';
2
+ import { S as SelectorParser } from './selector-parser-Wo-1tJbU.js';
3
3
 
4
4
  function parse_selector(source) {
5
5
  const arena = new CSSDataArena(CSSDataArena.capacity_for_source(source.length));
package/dist/parse.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { L as Lexer, y as TOKEN_EOF, q as TOKEN_COMMA, h as TOKEN_DELIM, a as TOKEN_FUNCTION, c as TOKEN_HASH, d as TOKEN_STRING, k as TOKEN_DIMENSION, j as TOKEN_PERCENTAGE, i as TOKEN_NUMBER, T as TOKEN_IDENT, t as TOKEN_LEFT_PAREN, u as TOKEN_RIGHT_PAREN, b as TOKEN_AT_KEYWORD, v as TOKEN_LEFT_BRACE, w as TOKEN_RIGHT_BRACE, o as TOKEN_COLON, p as TOKEN_SEMICOLON } from './lexer-DXablYMZ.js';
2
- import { J as is_whitespace, f as NODE_VALUE_KEYWORD, g as NODE_VALUE_NUMBER, h as NODE_VALUE_DIMENSION, i as NODE_VALUE_STRING, j as NODE_VALUE_COLOR, l as NODE_VALUE_OPERATOR, k as NODE_VALUE_FUNCTION, K as CSSDataArena, e as NODE_STYLESHEET, C as CSSNode, N as NODE_STYLE_RULE, L as FLAG_HAS_BLOCK, d as NODE_SELECTOR, c as NODE_DECLARATION, M as trim_boundaries, I as FLAG_IMPORTANT, a as NODE_AT_RULE } from './string-utils-Bm2l6p1L.js';
3
- import { S as SelectorParser } from './selector-parser-BjdQlGvW.js';
4
- import { A as AtRulePreludeParser } from './at-rule-prelude-parser-7_qZ1z7t.js';
2
+ import { J as is_whitespace, f as NODE_VALUE_KEYWORD, g as NODE_VALUE_NUMBER, h as NODE_VALUE_DIMENSION, i as NODE_VALUE_STRING, j as NODE_VALUE_COLOR, l as NODE_VALUE_OPERATOR, k as NODE_VALUE_FUNCTION, K as CSSDataArena, e as NODE_STYLESHEET, C as CSSNode, N as NODE_STYLE_RULE, L as FLAG_HAS_BLOCK, M as FLAG_HAS_DECLARATIONS, d as NODE_SELECTOR, c as NODE_DECLARATION, O as is_vendor_prefixed, P as FLAG_VENDOR_PREFIXED, Q as trim_boundaries, I as FLAG_IMPORTANT, a as NODE_AT_RULE } from './string-utils-tMt2O9RW.js';
3
+ import { S as SelectorParser } from './selector-parser-Wo-1tJbU.js';
4
+ import { A as AtRulePreludeParser } from './at-rule-prelude-parser-Cj8ecgQp.js';
5
5
 
6
6
  const CH_PLUS = 43;
7
7
  const CH_MINUS = 45;
@@ -195,12 +195,7 @@ class Parser {
195
195
  parse_atrule_preludes_enabled;
196
196
  constructor(source, options) {
197
197
  this.source = source;
198
- let opts;
199
- if (typeof options === "boolean") {
200
- opts = { skip_comments: options };
201
- } else {
202
- opts = options || {};
203
- }
198
+ let opts = options || {};
204
199
  let skip_comments = opts.skip_comments ?? true;
205
200
  this.parse_values_enabled = opts.parse_values ?? true;
206
201
  this.parse_selectors_enabled = opts.parse_selectors ?? true;
@@ -294,6 +289,7 @@ class Parser {
294
289
  }
295
290
  let declaration = this.parse_declaration();
296
291
  if (declaration !== null) {
292
+ this.arena.set_flag(style_rule, FLAG_HAS_DECLARATIONS);
297
293
  this.arena.append_child(style_rule, declaration);
298
294
  continue;
299
295
  }
@@ -357,6 +353,9 @@ class Parser {
357
353
  this.arena.set_start_offset(declaration, prop_start);
358
354
  this.arena.set_content_start(declaration, prop_start);
359
355
  this.arena.set_content_length(declaration, prop_end - prop_start);
356
+ if (is_vendor_prefixed(this.source, prop_start, prop_end)) {
357
+ this.arena.set_flag(declaration, FLAG_VENDOR_PREFIXED);
358
+ }
360
359
  let value_start = this.lexer.token_start;
361
360
  let value_end = value_start;
362
361
  let has_important = false;
package/dist/parser.d.ts CHANGED
@@ -16,7 +16,7 @@ export declare class Parser {
16
16
  private parse_values_enabled;
17
17
  private parse_selectors_enabled;
18
18
  private parse_atrule_preludes_enabled;
19
- constructor(source: string, options?: ParserOptions | boolean);
19
+ constructor(source: string, options?: ParserOptions);
20
20
  get_arena(): CSSDataArena;
21
21
  get_source(): string;
22
22
  private next_token;
@@ -1,5 +1,5 @@
1
1
  import { L as Lexer, q as TOKEN_COMMA, y as TOKEN_EOF, l as TOKEN_WHITESPACE, a as TOKEN_FUNCTION, o as TOKEN_COLON, r as TOKEN_LEFT_BRACKET, h as TOKEN_DELIM, c as TOKEN_HASH, T as TOKEN_IDENT, s as TOKEN_RIGHT_BRACKET, t as TOKEN_LEFT_PAREN, u as TOKEN_RIGHT_PAREN } from './lexer-DXablYMZ.js';
2
- import { d as NODE_SELECTOR, m as NODE_SELECTOR_LIST, J as is_whitespace, o as NODE_SELECTOR_CLASS, q as NODE_SELECTOR_ATTRIBUTE, M as trim_boundaries, s as NODE_SELECTOR_PSEUDO_ELEMENT, r as NODE_SELECTOR_PSEUDO_CLASS, n as NODE_SELECTOR_TYPE, p as NODE_SELECTOR_ID, u as NODE_SELECTOR_UNIVERSAL, v as NODE_SELECTOR_NESTING, t as NODE_SELECTOR_COMBINATOR } from './string-utils-Bm2l6p1L.js';
2
+ import { d as NODE_SELECTOR, m as NODE_SELECTOR_LIST, J as is_whitespace, o as NODE_SELECTOR_CLASS, q as NODE_SELECTOR_ATTRIBUTE, Q as trim_boundaries, s as NODE_SELECTOR_PSEUDO_ELEMENT, r as NODE_SELECTOR_PSEUDO_CLASS, O as is_vendor_prefixed, P as FLAG_VENDOR_PREFIXED, n as NODE_SELECTOR_TYPE, p as NODE_SELECTOR_ID, u as NODE_SELECTOR_UNIVERSAL, v as NODE_SELECTOR_NESTING, t as NODE_SELECTOR_COMBINATOR } from './string-utils-tMt2O9RW.js';
3
3
 
4
4
  class SelectorParser {
5
5
  lexer;
@@ -266,6 +266,9 @@ class SelectorParser {
266
266
  this.arena.set_start_column(node, this.lexer.column);
267
267
  this.arena.set_content_start(node, this.lexer.token_start);
268
268
  this.arena.set_content_length(node, this.lexer.token_end - this.lexer.token_start);
269
+ if (is_vendor_prefixed(this.source, this.lexer.token_start, this.lexer.token_end)) {
270
+ this.arena.set_flag(node, FLAG_VENDOR_PREFIXED);
271
+ }
269
272
  return node;
270
273
  } else if (token_type === TOKEN_FUNCTION) {
271
274
  return this.parse_pseudo_function_after_colon(start, is_pseudo_element);
@@ -303,6 +306,9 @@ class SelectorParser {
303
306
  this.arena.set_start_column(node, this.lexer.column);
304
307
  this.arena.set_content_start(node, func_name_start);
305
308
  this.arena.set_content_length(node, func_name_end - func_name_start);
309
+ if (is_vendor_prefixed(this.source, func_name_start, func_name_end)) {
310
+ this.arena.set_flag(node, FLAG_VENDOR_PREFIXED);
311
+ }
306
312
  return node;
307
313
  }
308
314
  // Create simple selector nodes
@@ -36,6 +36,8 @@ const NODE_PRELUDE_IMPORT_SUPPORTS = 40;
36
36
  const FLAG_IMPORTANT = 1 << 0;
37
37
  const FLAG_HAS_ERROR = 1 << 1;
38
38
  const FLAG_HAS_BLOCK = 1 << 3;
39
+ const FLAG_VENDOR_PREFIXED = 1 << 4;
40
+ const FLAG_HAS_DECLARATIONS = 1 << 5;
39
41
  class CSSDataArena {
40
42
  buffer;
41
43
  view;
@@ -290,11 +292,9 @@ class CSSNode {
290
292
  get is_important() {
291
293
  return this.arena.has_flag(this.index, FLAG_IMPORTANT);
292
294
  }
293
- // Check if this has a vendor prefix (lazy computation for performance)
295
+ // Check if this has a vendor prefix (flag-based for performance)
294
296
  get is_vendor_prefixed() {
295
- const name = this.name;
296
- if (!name) return false;
297
- return name.startsWith("-webkit-") || name.startsWith("-moz-") || name.startsWith("-ms-") || name.startsWith("-o-");
297
+ return this.arena.has_flag(this.index, FLAG_VENDOR_PREFIXED);
298
298
  }
299
299
  // Check if this node has an error
300
300
  get has_error() {
@@ -308,6 +308,10 @@ class CSSNode {
308
308
  get has_block() {
309
309
  return this.arena.has_flag(this.index, FLAG_HAS_BLOCK);
310
310
  }
311
+ // Check if this style rule has declarations
312
+ get has_declarations() {
313
+ return this.arena.has_flag(this.index, FLAG_HAS_DECLARATIONS);
314
+ }
311
315
  // --- Value Node Access (for declarations) ---
312
316
  // Get array of parsed value nodes (for declarations only)
313
317
  get values() {
@@ -389,6 +393,7 @@ const CHAR_CARRIAGE_RETURN = 13;
389
393
  const CHAR_FORM_FEED = 12;
390
394
  const CHAR_FORWARD_SLASH = 47;
391
395
  const CHAR_ASTERISK = 42;
396
+ const CHAR_MINUS_HYPHEN = 45;
392
397
  function is_whitespace(ch) {
393
398
  return ch === CHAR_SPACE || ch === CHAR_TAB || ch === CHAR_NEWLINE || ch === CHAR_CARRIAGE_RETURN || ch === CHAR_FORM_FEED;
394
399
  }
@@ -448,5 +453,19 @@ function str_equals(a, b) {
448
453
  }
449
454
  return true;
450
455
  }
456
+ function is_vendor_prefixed(source, start, end) {
457
+ if (source.charCodeAt(start) !== CHAR_MINUS_HYPHEN) {
458
+ return false;
459
+ }
460
+ if (source.charCodeAt(start + 1) === CHAR_MINUS_HYPHEN) {
461
+ return false;
462
+ }
463
+ let length = end - start;
464
+ if (length < 3) {
465
+ return false;
466
+ }
467
+ let secondHyphenPos = source.indexOf("-", start + 2);
468
+ return secondHyphenPos !== -1 && secondHyphenPos < end;
469
+ }
451
470
 
452
- export { NODE_PRELUDE_SUPPORTS_QUERY as A, NODE_PRELUDE_LAYER_NAME as B, CSSNode as C, NODE_PRELUDE_IDENTIFIER as D, NODE_PRELUDE_OPERATOR as E, NODE_PRELUDE_IMPORT_URL as F, NODE_PRELUDE_IMPORT_LAYER as G, NODE_PRELUDE_IMPORT_SUPPORTS as H, FLAG_IMPORTANT as I, is_whitespace as J, CSSDataArena as K, FLAG_HAS_BLOCK as L, trim_boundaries as M, NODE_STYLE_RULE as N, str_equals as O, CHAR_SPACE as P, CHAR_TAB as Q, CHAR_NEWLINE as R, CHAR_CARRIAGE_RETURN as S, CHAR_FORM_FEED as T, NODE_AT_RULE as a, NODE_COMMENT as b, NODE_DECLARATION as c, NODE_SELECTOR as d, NODE_STYLESHEET as e, NODE_VALUE_KEYWORD as f, NODE_VALUE_NUMBER as g, NODE_VALUE_DIMENSION as h, NODE_VALUE_STRING as i, NODE_VALUE_COLOR as j, NODE_VALUE_FUNCTION as k, NODE_VALUE_OPERATOR as l, NODE_SELECTOR_LIST as m, NODE_SELECTOR_TYPE as n, NODE_SELECTOR_CLASS as o, NODE_SELECTOR_ID as p, NODE_SELECTOR_ATTRIBUTE as q, NODE_SELECTOR_PSEUDO_CLASS as r, NODE_SELECTOR_PSEUDO_ELEMENT as s, NODE_SELECTOR_COMBINATOR as t, NODE_SELECTOR_UNIVERSAL as u, NODE_SELECTOR_NESTING as v, NODE_PRELUDE_MEDIA_QUERY as w, NODE_PRELUDE_MEDIA_FEATURE as x, NODE_PRELUDE_MEDIA_TYPE as y, NODE_PRELUDE_CONTAINER_QUERY as z };
471
+ export { NODE_PRELUDE_SUPPORTS_QUERY as A, NODE_PRELUDE_LAYER_NAME as B, CSSNode as C, NODE_PRELUDE_IDENTIFIER as D, NODE_PRELUDE_OPERATOR as E, NODE_PRELUDE_IMPORT_URL as F, NODE_PRELUDE_IMPORT_LAYER as G, NODE_PRELUDE_IMPORT_SUPPORTS as H, FLAG_IMPORTANT as I, is_whitespace as J, CSSDataArena as K, FLAG_HAS_BLOCK as L, FLAG_HAS_DECLARATIONS as M, NODE_STYLE_RULE as N, is_vendor_prefixed as O, FLAG_VENDOR_PREFIXED as P, trim_boundaries as Q, str_equals as R, CHAR_SPACE as S, CHAR_TAB as T, CHAR_NEWLINE as U, CHAR_CARRIAGE_RETURN as V, CHAR_FORM_FEED as W, NODE_AT_RULE as a, NODE_COMMENT as b, NODE_DECLARATION as c, NODE_SELECTOR as d, NODE_STYLESHEET as e, NODE_VALUE_KEYWORD as f, NODE_VALUE_NUMBER as g, NODE_VALUE_DIMENSION as h, NODE_VALUE_STRING as i, NODE_VALUE_COLOR as j, NODE_VALUE_FUNCTION as k, NODE_VALUE_OPERATOR as l, NODE_SELECTOR_LIST as m, NODE_SELECTOR_TYPE as n, NODE_SELECTOR_CLASS as o, NODE_SELECTOR_ID as p, NODE_SELECTOR_ATTRIBUTE as q, NODE_SELECTOR_PSEUDO_CLASS as r, NODE_SELECTOR_PSEUDO_ELEMENT as s, NODE_SELECTOR_COMBINATOR as t, NODE_SELECTOR_UNIVERSAL as u, NODE_SELECTOR_NESTING as v, NODE_PRELUDE_MEDIA_QUERY as w, NODE_PRELUDE_MEDIA_FEATURE as x, NODE_PRELUDE_MEDIA_TYPE as y, NODE_PRELUDE_CONTAINER_QUERY as z };
@@ -5,6 +5,7 @@ export declare const CHAR_CARRIAGE_RETURN = 13;
5
5
  export declare const CHAR_FORM_FEED = 12;
6
6
  export declare const CHAR_FORWARD_SLASH = 47;
7
7
  export declare const CHAR_ASTERISK = 42;
8
+ export declare const CHAR_MINUS_HYPHEN = 45;
8
9
  /**
9
10
  * Check if a character code is whitespace (space, tab, newline, CR, or FF)
10
11
  */
@@ -27,3 +28,25 @@ export declare function trim_boundaries(source: string, start: number, end: numb
27
28
  * @param b Compare string
28
29
  */
29
30
  export declare function str_equals(a: string, b: string): boolean;
31
+ /**
32
+ * Check if a string range has a vendor prefix
33
+ *
34
+ * @param source - The source string
35
+ * @param start - Start offset in source
36
+ * @param end - End offset in source
37
+ * @returns true if the range starts with a vendor prefix (-webkit-, -moz-, -ms-, -o-)
38
+ *
39
+ * Detects vendor prefixes by checking:
40
+ * 1. Starts with a single hyphen (not --)
41
+ * 2. Contains at least 3 characters (shortest is -o-)
42
+ * 3. Has a second hyphen after the vendor name
43
+ *
44
+ * Examples:
45
+ * - `-webkit-transform` → true
46
+ * - `-moz-appearance` → true
47
+ * - `-ms-filter` → true
48
+ * - `-o-border-image` → true
49
+ * - `--custom-property` → false (CSS custom property)
50
+ * - `border-radius` → false (doesn't start with hyphen)
51
+ */
52
+ export declare function is_vendor_prefixed(source: string, start: number, end: number): boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@projectwallace/css-parser",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "High-performance CSS lexer and parser, optimized for CSS inspection and analysis",
5
5
  "author": "Bart Veneman <bat@projectwallace.com>",
6
6
  "license": "MIT",