@projectwallace/css-parser 0.1.0 → 0.3.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 +18 -12
- package/dist/arena.d.ts +3 -0
- package/dist/{at-rule-prelude-parser-DlqYQAYH.js → at-rule-prelude-parser-pzWB6S0z.js} +5 -2
- package/dist/at-rule-prelude-parser.d.ts +1 -1
- package/dist/css-node.d.ts +1 -0
- package/dist/index.js +1 -1
- package/dist/parse-atrule-prelude.js +2 -2
- package/dist/parse-selector.js +2 -2
- package/dist/parse.js +17 -5
- package/dist/{selector-parser-2b3tGyri.js → selector-parser-CwNLN7Us.js} +20 -2
- package/dist/selector-parser.d.ts +1 -1
- package/dist/{string-utils-B-rJII-E.js → string-utils-5j619JDp.js} +31 -5
- package/dist/string-utils.d.ts +23 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# CSS Parser
|
|
2
2
|
|
|
3
|
+
> [!WARNING]
|
|
4
|
+
> This is a very experimental CSS parser. Expect several bugs and inconveniences!
|
|
5
|
+
|
|
3
6
|
**High-performance CSS parser optimized for static analysis and formatting**
|
|
4
7
|
|
|
5
8
|
Built for speed and efficiency, this parser handles large CSS files with minimal memory overhead and blazing-fast parse times. Designed with a data-oriented architecture using a single contiguous memory arena for zero allocations during parsing.
|
|
@@ -10,18 +13,7 @@ Built for speed and efficiency, this parser handles large CSS files with minimal
|
|
|
10
13
|
- **Error recovery** - Continues parsing on malformed CSS
|
|
11
14
|
- **Comment preservation** - Comments stored as first-class AST nodes
|
|
12
15
|
- **Location tracking** - Line, column, offset, and length for all nodes
|
|
13
|
-
- **
|
|
14
|
-
- **Structured parsing** - Deep parsing of selectors, values, and at-rule preludes
|
|
15
|
-
|
|
16
|
-
## Performance
|
|
17
|
-
|
|
18
|
-
- **Tiny install size**
|
|
19
|
-
- **Zero allocations during parsing** - all memory allocated upfront based on real world heuristics, which also helps prevent garbage collection running often
|
|
20
|
-
- **Cache-friendly data layout** - contiguous memory for sequential access
|
|
21
|
-
- **First-class comment and location support** - while still being performant because analysis requires constant access to lines and columns
|
|
22
|
-
- **No syntax validation** - focusing only on the raw data we can skip expensive syntax files and MDN data syncs
|
|
23
|
-
|
|
24
|
-
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
|
|
25
17
|
|
|
26
18
|
## Installation
|
|
27
19
|
|
|
@@ -63,10 +55,24 @@ for (const rule of ast) {
|
|
|
63
55
|
}
|
|
64
56
|
```
|
|
65
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
|
+
|
|
66
68
|
## Documentation
|
|
67
69
|
|
|
68
70
|
See [API.md](./API.md) for complete documentation of all parser functions and options.
|
|
69
71
|
|
|
72
|
+
## Non-goals
|
|
73
|
+
|
|
74
|
+
- **No syntax validation** - this parser does not try to validate your CSS structure. Everything can be anything
|
|
75
|
+
|
|
70
76
|
## License
|
|
71
77
|
|
|
72
78
|
MIT
|
package/dist/arena.d.ts
CHANGED
|
@@ -36,6 +36,7 @@ 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;
|
|
39
40
|
export declare class CSSDataArena {
|
|
40
41
|
private buffer;
|
|
41
42
|
private view;
|
|
@@ -59,6 +60,7 @@ export declare class CSSDataArena {
|
|
|
59
60
|
get_last_child(node_index: number): number;
|
|
60
61
|
get_next_sibling(node_index: number): number;
|
|
61
62
|
get_start_line(node_index: number): number;
|
|
63
|
+
get_start_column(node_index: number): number;
|
|
62
64
|
get_value_start(node_index: number): number;
|
|
63
65
|
get_value_length(node_index: number): number;
|
|
64
66
|
set_type(node_index: number, type: number): void;
|
|
@@ -71,6 +73,7 @@ export declare class CSSDataArena {
|
|
|
71
73
|
set_last_child(node_index: number, childIndex: number): void;
|
|
72
74
|
set_next_sibling(node_index: number, siblingIndex: number): void;
|
|
73
75
|
set_start_line(node_index: number, line: number): void;
|
|
76
|
+
set_start_column(node_index: number, column: number): void;
|
|
74
77
|
set_value_start(node_index: number, offset: number): void;
|
|
75
78
|
set_value_length(node_index: number, length: number): void;
|
|
76
79
|
private grow;
|
|
@@ -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 {
|
|
2
|
+
import { Q 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, P 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, R as CHAR_SPACE, S as CHAR_TAB, T as CHAR_NEWLINE, U as CHAR_CARRIAGE_RETURN, V as CHAR_FORM_FEED } from './string-utils-5j619JDp.js';
|
|
3
3
|
|
|
4
4
|
class AtRulePreludeParser {
|
|
5
5
|
lexer;
|
|
@@ -13,10 +13,11 @@ class AtRulePreludeParser {
|
|
|
13
13
|
this.prelude_end = 0;
|
|
14
14
|
}
|
|
15
15
|
// Parse an at-rule prelude into nodes based on the at-rule type
|
|
16
|
-
parse_prelude(at_rule_name, start, end, line = 1) {
|
|
16
|
+
parse_prelude(at_rule_name, start, end, line = 1, column = 1) {
|
|
17
17
|
this.prelude_end = end;
|
|
18
18
|
this.lexer.pos = start;
|
|
19
19
|
this.lexer.line = line;
|
|
20
|
+
this.lexer.column = column;
|
|
20
21
|
if (str_equals("media", at_rule_name)) {
|
|
21
22
|
return this.parse_media_query_list();
|
|
22
23
|
} else if (str_equals("container", at_rule_name)) {
|
|
@@ -59,6 +60,7 @@ class AtRulePreludeParser {
|
|
|
59
60
|
parse_single_media_query() {
|
|
60
61
|
let query_start = this.lexer.pos;
|
|
61
62
|
let query_line = this.lexer.line;
|
|
63
|
+
this.lexer.column;
|
|
62
64
|
this.skip_whitespace();
|
|
63
65
|
if (this.lexer.pos >= this.prelude_end) return null;
|
|
64
66
|
let token_start = this.lexer.pos;
|
|
@@ -98,6 +100,7 @@ class AtRulePreludeParser {
|
|
|
98
100
|
this.arena.set_start_offset(media_type, this.lexer.token_start);
|
|
99
101
|
this.arena.set_length(media_type, this.lexer.token_end - this.lexer.token_start);
|
|
100
102
|
this.arena.set_start_line(media_type, this.lexer.token_line);
|
|
103
|
+
this.arena.set_start_column(media_type, this.lexer.token_column);
|
|
101
104
|
components.push(media_type);
|
|
102
105
|
}
|
|
103
106
|
} else {
|
|
@@ -5,7 +5,7 @@ export declare class AtRulePreludeParser {
|
|
|
5
5
|
private source;
|
|
6
6
|
private prelude_end;
|
|
7
7
|
constructor(arena: CSSDataArena, source: string);
|
|
8
|
-
parse_prelude(at_rule_name: string, start: number, end: number, line?: number): number[];
|
|
8
|
+
parse_prelude(at_rule_name: string, start: number, end: number, line?: number, column?: number): number[];
|
|
9
9
|
private parse_media_query_list;
|
|
10
10
|
private is_and_or_not;
|
|
11
11
|
private parse_single_media_query;
|
package/dist/css-node.d.ts
CHANGED
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-
|
|
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-5j619JDp.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-
|
|
2
|
-
import { A as AtRulePreludeParser } from './at-rule-prelude-parser-
|
|
1
|
+
import { K as CSSDataArena, C as CSSNode } from './string-utils-5j619JDp.js';
|
|
2
|
+
import { A as AtRulePreludeParser } from './at-rule-prelude-parser-pzWB6S0z.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));
|
package/dist/parse-selector.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { K as CSSDataArena, d as NODE_SELECTOR, C as CSSNode } from './string-utils-
|
|
2
|
-
import { S as SelectorParser } from './selector-parser-
|
|
1
|
+
import { K as CSSDataArena, d as NODE_SELECTOR, C as CSSNode } from './string-utils-5j619JDp.js';
|
|
2
|
+
import { S as SelectorParser } from './selector-parser-CwNLN7Us.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-
|
|
3
|
-
import { S as SelectorParser } from './selector-parser-
|
|
4
|
-
import { A as AtRulePreludeParser } from './at-rule-prelude-parser-
|
|
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 is_vendor_prefixed, O as FLAG_VENDOR_PREFIXED, P as trim_boundaries, I as FLAG_IMPORTANT, a as NODE_AT_RULE } from './string-utils-5j619JDp.js';
|
|
3
|
+
import { S as SelectorParser } from './selector-parser-CwNLN7Us.js';
|
|
4
|
+
import { A as AtRulePreludeParser } from './at-rule-prelude-parser-pzWB6S0z.js';
|
|
5
5
|
|
|
6
6
|
const CH_PLUS = 43;
|
|
7
7
|
const CH_MINUS = 45;
|
|
@@ -240,6 +240,7 @@ class Parser {
|
|
|
240
240
|
this.arena.set_start_offset(stylesheet, 0);
|
|
241
241
|
this.arena.set_length(stylesheet, this.source.length);
|
|
242
242
|
this.arena.set_start_line(stylesheet, 1);
|
|
243
|
+
this.arena.set_start_column(stylesheet, 1);
|
|
243
244
|
while (!this.is_eof()) {
|
|
244
245
|
let rule = this.parse_rule();
|
|
245
246
|
if (rule !== null) {
|
|
@@ -265,9 +266,11 @@ class Parser {
|
|
|
265
266
|
if (this.is_eof()) return null;
|
|
266
267
|
let rule_start = this.lexer.token_start;
|
|
267
268
|
let rule_line = this.lexer.token_line;
|
|
269
|
+
let rule_column = this.lexer.token_column;
|
|
268
270
|
let style_rule = this.arena.create_node();
|
|
269
271
|
this.arena.set_type(style_rule, NODE_STYLE_RULE);
|
|
270
272
|
this.arena.set_start_line(style_rule, rule_line);
|
|
273
|
+
this.arena.set_start_column(style_rule, rule_column);
|
|
271
274
|
let selector = this.parse_selector();
|
|
272
275
|
if (selector !== null) {
|
|
273
276
|
this.arena.append_child(style_rule, selector);
|
|
@@ -313,13 +316,14 @@ class Parser {
|
|
|
313
316
|
if (this.is_eof()) return null;
|
|
314
317
|
let selector_start = this.lexer.token_start;
|
|
315
318
|
let selector_line = this.lexer.token_line;
|
|
319
|
+
let selector_column = this.lexer.token_column;
|
|
316
320
|
let last_end = this.lexer.token_end;
|
|
317
321
|
while (!this.is_eof() && this.peek_type() !== TOKEN_LEFT_BRACE) {
|
|
318
322
|
last_end = this.lexer.token_end;
|
|
319
323
|
this.next_token();
|
|
320
324
|
}
|
|
321
325
|
if (this.parse_selectors_enabled && this.selector_parser) {
|
|
322
|
-
let selectorNode = this.selector_parser.parse_selector(selector_start, last_end, selector_line);
|
|
326
|
+
let selectorNode = this.selector_parser.parse_selector(selector_start, last_end, selector_line, selector_column);
|
|
323
327
|
if (selectorNode !== null) {
|
|
324
328
|
return selectorNode;
|
|
325
329
|
}
|
|
@@ -327,6 +331,7 @@ class Parser {
|
|
|
327
331
|
let selector = this.arena.create_node();
|
|
328
332
|
this.arena.set_type(selector, NODE_SELECTOR);
|
|
329
333
|
this.arena.set_start_line(selector, selector_line);
|
|
334
|
+
this.arena.set_start_column(selector, selector_column);
|
|
330
335
|
this.arena.set_start_offset(selector, selector_start);
|
|
331
336
|
this.arena.set_length(selector, last_end - selector_start);
|
|
332
337
|
return selector;
|
|
@@ -339,6 +344,7 @@ class Parser {
|
|
|
339
344
|
let prop_start = this.lexer.token_start;
|
|
340
345
|
let prop_end = this.lexer.token_end;
|
|
341
346
|
let decl_line = this.lexer.token_line;
|
|
347
|
+
let decl_column = this.lexer.token_column;
|
|
342
348
|
this.next_token();
|
|
343
349
|
if (this.peek_type() !== TOKEN_COLON) {
|
|
344
350
|
return null;
|
|
@@ -347,9 +353,13 @@ class Parser {
|
|
|
347
353
|
let declaration = this.arena.create_node();
|
|
348
354
|
this.arena.set_type(declaration, NODE_DECLARATION);
|
|
349
355
|
this.arena.set_start_line(declaration, decl_line);
|
|
356
|
+
this.arena.set_start_column(declaration, decl_column);
|
|
350
357
|
this.arena.set_start_offset(declaration, prop_start);
|
|
351
358
|
this.arena.set_content_start(declaration, prop_start);
|
|
352
359
|
this.arena.set_content_length(declaration, prop_end - prop_start);
|
|
360
|
+
if (is_vendor_prefixed(this.source, prop_start, prop_end)) {
|
|
361
|
+
this.arena.set_flag(declaration, FLAG_VENDOR_PREFIXED);
|
|
362
|
+
}
|
|
353
363
|
let value_start = this.lexer.token_start;
|
|
354
364
|
let value_end = value_start;
|
|
355
365
|
let has_important = false;
|
|
@@ -402,6 +412,7 @@ class Parser {
|
|
|
402
412
|
}
|
|
403
413
|
let at_rule_start = this.lexer.token_start;
|
|
404
414
|
let at_rule_line = this.lexer.token_line;
|
|
415
|
+
let at_rule_column = this.lexer.token_column;
|
|
405
416
|
let at_rule_name = this.source.substring(this.lexer.token_start + 1, this.lexer.token_end);
|
|
406
417
|
let name_start = this.lexer.token_start + 1;
|
|
407
418
|
let name_length = at_rule_name.length;
|
|
@@ -409,6 +420,7 @@ class Parser {
|
|
|
409
420
|
let at_rule = this.arena.create_node();
|
|
410
421
|
this.arena.set_type(at_rule, NODE_AT_RULE);
|
|
411
422
|
this.arena.set_start_line(at_rule, at_rule_line);
|
|
423
|
+
this.arena.set_start_column(at_rule, at_rule_column);
|
|
412
424
|
this.arena.set_start_offset(at_rule, at_rule_start);
|
|
413
425
|
this.arena.set_content_start(at_rule, name_start);
|
|
414
426
|
this.arena.set_content_length(at_rule, name_length);
|
|
@@ -423,7 +435,7 @@ class Parser {
|
|
|
423
435
|
this.arena.set_value_start(at_rule, trimmed[0]);
|
|
424
436
|
this.arena.set_value_length(at_rule, trimmed[1] - trimmed[0]);
|
|
425
437
|
if (this.prelude_parser) {
|
|
426
|
-
let prelude_nodes = this.prelude_parser.parse_prelude(at_rule_name, trimmed[0], trimmed[1], at_rule_line);
|
|
438
|
+
let prelude_nodes = this.prelude_parser.parse_prelude(at_rule_name, trimmed[0], trimmed[1], at_rule_line, at_rule_column);
|
|
427
439
|
for (let prelude_node of prelude_nodes) {
|
|
428
440
|
this.arena.append_child(at_rule, prelude_node);
|
|
429
441
|
}
|
|
@@ -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,
|
|
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, P as trim_boundaries, s as NODE_SELECTOR_PSEUDO_ELEMENT, r as NODE_SELECTOR_PSEUDO_CLASS, M as is_vendor_prefixed, O 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-5j619JDp.js';
|
|
3
3
|
|
|
4
4
|
class SelectorParser {
|
|
5
5
|
lexer;
|
|
@@ -14,10 +14,11 @@ class SelectorParser {
|
|
|
14
14
|
}
|
|
15
15
|
// Parse a selector range into selector nodes
|
|
16
16
|
// Always returns a NODE_SELECTOR wrapper with detailed selector nodes as children
|
|
17
|
-
parse_selector(start, end, line = 1) {
|
|
17
|
+
parse_selector(start, end, line = 1, column = 1) {
|
|
18
18
|
this.selector_end = end;
|
|
19
19
|
this.lexer.pos = start;
|
|
20
20
|
this.lexer.line = line;
|
|
21
|
+
this.lexer.column = column;
|
|
21
22
|
let innerSelector = this.parse_selector_list();
|
|
22
23
|
if (innerSelector === null) {
|
|
23
24
|
return null;
|
|
@@ -27,6 +28,7 @@ class SelectorParser {
|
|
|
27
28
|
this.arena.set_start_offset(selectorWrapper, start);
|
|
28
29
|
this.arena.set_length(selectorWrapper, end - start);
|
|
29
30
|
this.arena.set_start_line(selectorWrapper, line);
|
|
31
|
+
this.arena.set_start_column(selectorWrapper, column);
|
|
30
32
|
this.arena.set_first_child(selectorWrapper, innerSelector);
|
|
31
33
|
this.arena.set_last_child(selectorWrapper, innerSelector);
|
|
32
34
|
return selectorWrapper;
|
|
@@ -60,6 +62,7 @@ class SelectorParser {
|
|
|
60
62
|
this.arena.set_start_offset(list_node, list_start);
|
|
61
63
|
this.arena.set_length(list_node, this.lexer.pos - list_start);
|
|
62
64
|
this.arena.set_start_line(list_node, this.lexer.line);
|
|
65
|
+
this.arena.set_start_column(list_node, this.lexer.column);
|
|
63
66
|
this.arena.set_first_child(list_node, selectors[0]);
|
|
64
67
|
this.arena.set_last_child(list_node, selectors[selectors.length - 1]);
|
|
65
68
|
for (let i = 0; i < selectors.length - 1; i++) {
|
|
@@ -210,6 +213,7 @@ class SelectorParser {
|
|
|
210
213
|
this.arena.set_start_offset(node, dot_pos);
|
|
211
214
|
this.arena.set_length(node, this.lexer.token_end - dot_pos);
|
|
212
215
|
this.arena.set_start_line(node, this.lexer.line);
|
|
216
|
+
this.arena.set_start_column(node, this.lexer.column);
|
|
213
217
|
this.arena.set_content_start(node, this.lexer.token_start);
|
|
214
218
|
this.arena.set_content_length(node, this.lexer.token_end - this.lexer.token_start);
|
|
215
219
|
return node;
|
|
@@ -236,6 +240,7 @@ class SelectorParser {
|
|
|
236
240
|
this.arena.set_start_offset(node, start);
|
|
237
241
|
this.arena.set_length(node, end - start);
|
|
238
242
|
this.arena.set_start_line(node, this.lexer.line);
|
|
243
|
+
this.arena.set_start_column(node, this.lexer.column);
|
|
239
244
|
let trimmed = trim_boundaries(this.source, start + 1, end - 1);
|
|
240
245
|
if (trimmed) {
|
|
241
246
|
this.arena.set_content_start(node, trimmed[0]);
|
|
@@ -258,8 +263,12 @@ class SelectorParser {
|
|
|
258
263
|
this.arena.set_start_offset(node, start);
|
|
259
264
|
this.arena.set_length(node, this.lexer.token_end - start);
|
|
260
265
|
this.arena.set_start_line(node, this.lexer.line);
|
|
266
|
+
this.arena.set_start_column(node, this.lexer.column);
|
|
261
267
|
this.arena.set_content_start(node, this.lexer.token_start);
|
|
262
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
|
+
}
|
|
263
272
|
return node;
|
|
264
273
|
} else if (token_type === TOKEN_FUNCTION) {
|
|
265
274
|
return this.parse_pseudo_function_after_colon(start, is_pseudo_element);
|
|
@@ -294,8 +303,12 @@ class SelectorParser {
|
|
|
294
303
|
this.arena.set_start_offset(node, start);
|
|
295
304
|
this.arena.set_length(node, end - start);
|
|
296
305
|
this.arena.set_start_line(node, this.lexer.line);
|
|
306
|
+
this.arena.set_start_column(node, this.lexer.column);
|
|
297
307
|
this.arena.set_content_start(node, func_name_start);
|
|
298
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
|
+
}
|
|
299
312
|
return node;
|
|
300
313
|
}
|
|
301
314
|
// Create simple selector nodes
|
|
@@ -305,6 +318,7 @@ class SelectorParser {
|
|
|
305
318
|
this.arena.set_start_offset(node, start);
|
|
306
319
|
this.arena.set_length(node, end - start);
|
|
307
320
|
this.arena.set_start_line(node, this.lexer.line);
|
|
321
|
+
this.arena.set_start_column(node, this.lexer.column);
|
|
308
322
|
this.arena.set_content_start(node, start);
|
|
309
323
|
this.arena.set_content_length(node, end - start);
|
|
310
324
|
return node;
|
|
@@ -315,6 +329,7 @@ class SelectorParser {
|
|
|
315
329
|
this.arena.set_start_offset(node, start);
|
|
316
330
|
this.arena.set_length(node, end - start);
|
|
317
331
|
this.arena.set_start_line(node, this.lexer.line);
|
|
332
|
+
this.arena.set_start_column(node, this.lexer.column);
|
|
318
333
|
this.arena.set_content_start(node, start + 1);
|
|
319
334
|
this.arena.set_content_length(node, end - start - 1);
|
|
320
335
|
return node;
|
|
@@ -325,6 +340,7 @@ class SelectorParser {
|
|
|
325
340
|
this.arena.set_start_offset(node, start);
|
|
326
341
|
this.arena.set_length(node, end - start);
|
|
327
342
|
this.arena.set_start_line(node, this.lexer.line);
|
|
343
|
+
this.arena.set_start_column(node, this.lexer.column);
|
|
328
344
|
this.arena.set_content_start(node, start);
|
|
329
345
|
this.arena.set_content_length(node, end - start);
|
|
330
346
|
return node;
|
|
@@ -335,6 +351,7 @@ class SelectorParser {
|
|
|
335
351
|
this.arena.set_start_offset(node, start);
|
|
336
352
|
this.arena.set_length(node, end - start);
|
|
337
353
|
this.arena.set_start_line(node, this.lexer.line);
|
|
354
|
+
this.arena.set_start_column(node, this.lexer.column);
|
|
338
355
|
this.arena.set_content_start(node, start);
|
|
339
356
|
this.arena.set_content_length(node, end - start);
|
|
340
357
|
return node;
|
|
@@ -345,6 +362,7 @@ class SelectorParser {
|
|
|
345
362
|
this.arena.set_start_offset(node, start);
|
|
346
363
|
this.arena.set_length(node, end - start);
|
|
347
364
|
this.arena.set_start_line(node, this.lexer.line);
|
|
365
|
+
this.arena.set_start_column(node, this.lexer.column);
|
|
348
366
|
this.arena.set_content_start(node, start);
|
|
349
367
|
this.arena.set_content_length(node, end - start);
|
|
350
368
|
return node;
|
|
@@ -5,7 +5,7 @@ export declare class SelectorParser {
|
|
|
5
5
|
private source;
|
|
6
6
|
private selector_end;
|
|
7
7
|
constructor(arena: CSSDataArena, source: string);
|
|
8
|
-
parse_selector(start: number, end: number, line?: number): number | null;
|
|
8
|
+
parse_selector(start: number, end: number, line?: number, column?: number): number | null;
|
|
9
9
|
private parse_selector_list;
|
|
10
10
|
private parse_complex_selector;
|
|
11
11
|
private parse_compound_selector;
|
|
@@ -36,6 +36,7 @@ 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;
|
|
39
40
|
class CSSDataArena {
|
|
40
41
|
buffer;
|
|
41
42
|
view;
|
|
@@ -114,6 +115,10 @@ class CSSDataArena {
|
|
|
114
115
|
get_start_line(node_index) {
|
|
115
116
|
return this.view.getUint32(this.node_offset(node_index) + 32, true);
|
|
116
117
|
}
|
|
118
|
+
// Read start column
|
|
119
|
+
get_start_column(node_index) {
|
|
120
|
+
return this.view.getUint16(this.node_offset(node_index) + 42, true);
|
|
121
|
+
}
|
|
117
122
|
// Read value start offset (declaration value / at-rule prelude)
|
|
118
123
|
get_value_start(node_index) {
|
|
119
124
|
return this.view.getUint32(this.node_offset(node_index) + 36, true);
|
|
@@ -163,6 +168,10 @@ class CSSDataArena {
|
|
|
163
168
|
set_start_line(node_index, line) {
|
|
164
169
|
this.view.setUint32(this.node_offset(node_index) + 32, line, true);
|
|
165
170
|
}
|
|
171
|
+
// Write start column
|
|
172
|
+
set_start_column(node_index, column) {
|
|
173
|
+
this.view.setUint16(this.node_offset(node_index) + 42, column, true);
|
|
174
|
+
}
|
|
166
175
|
// Write value start offset (declaration value / at-rule prelude)
|
|
167
176
|
set_value_start(node_index, offset) {
|
|
168
177
|
this.view.setUint32(this.node_offset(node_index) + 36, offset, true);
|
|
@@ -282,11 +291,9 @@ class CSSNode {
|
|
|
282
291
|
get is_important() {
|
|
283
292
|
return this.arena.has_flag(this.index, FLAG_IMPORTANT);
|
|
284
293
|
}
|
|
285
|
-
// Check if this has a vendor prefix (
|
|
294
|
+
// Check if this has a vendor prefix (flag-based for performance)
|
|
286
295
|
get is_vendor_prefixed() {
|
|
287
|
-
|
|
288
|
-
if (!name) return false;
|
|
289
|
-
return name.startsWith("-webkit-") || name.startsWith("-moz-") || name.startsWith("-ms-") || name.startsWith("-o-");
|
|
296
|
+
return this.arena.has_flag(this.index, FLAG_VENDOR_PREFIXED);
|
|
290
297
|
}
|
|
291
298
|
// Check if this node has an error
|
|
292
299
|
get has_error() {
|
|
@@ -325,6 +332,10 @@ class CSSNode {
|
|
|
325
332
|
get line() {
|
|
326
333
|
return this.arena.get_start_line(this.index);
|
|
327
334
|
}
|
|
335
|
+
// Get start column number
|
|
336
|
+
get column() {
|
|
337
|
+
return this.arena.get_start_column(this.index);
|
|
338
|
+
}
|
|
328
339
|
// Get start offset in source
|
|
329
340
|
get offset() {
|
|
330
341
|
return this.arena.get_start_offset(this.index);
|
|
@@ -377,6 +388,7 @@ const CHAR_CARRIAGE_RETURN = 13;
|
|
|
377
388
|
const CHAR_FORM_FEED = 12;
|
|
378
389
|
const CHAR_FORWARD_SLASH = 47;
|
|
379
390
|
const CHAR_ASTERISK = 42;
|
|
391
|
+
const CHAR_MINUS_HYPHEN = 45;
|
|
380
392
|
function is_whitespace(ch) {
|
|
381
393
|
return ch === CHAR_SPACE || ch === CHAR_TAB || ch === CHAR_NEWLINE || ch === CHAR_CARRIAGE_RETURN || ch === CHAR_FORM_FEED;
|
|
382
394
|
}
|
|
@@ -436,5 +448,19 @@ function str_equals(a, b) {
|
|
|
436
448
|
}
|
|
437
449
|
return true;
|
|
438
450
|
}
|
|
451
|
+
function is_vendor_prefixed(source, start, end) {
|
|
452
|
+
if (source.charCodeAt(start) !== CHAR_MINUS_HYPHEN) {
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
if (source.charCodeAt(start + 1) === CHAR_MINUS_HYPHEN) {
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
458
|
+
let length = end - start;
|
|
459
|
+
if (length < 3) {
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
let secondHyphenPos = source.indexOf("-", start + 2);
|
|
463
|
+
return secondHyphenPos !== -1 && secondHyphenPos < end;
|
|
464
|
+
}
|
|
439
465
|
|
|
440
|
-
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,
|
|
466
|
+
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, is_vendor_prefixed as M, NODE_STYLE_RULE as N, FLAG_VENDOR_PREFIXED as O, trim_boundaries as P, str_equals as Q, CHAR_SPACE as R, CHAR_TAB as S, CHAR_NEWLINE as T, CHAR_CARRIAGE_RETURN as U, CHAR_FORM_FEED as V, 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 };
|
package/dist/string-utils.d.ts
CHANGED
|
@@ -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