@projectwallace/css-parser 0.8.0 → 0.8.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.
- package/dist/constants.d.ts +40 -1
- package/dist/constants.js +47 -0
- package/dist/css-node.d.ts +78 -2
- package/dist/css-node.js +78 -68
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/parse-selector.js +11 -5
- package/dist/walk.d.ts +59 -5
- package/dist/walk.js +33 -9
- package/package.json +1 -1
package/dist/constants.d.ts
CHANGED
|
@@ -1 +1,40 @@
|
|
|
1
|
-
|
|
1
|
+
import { STYLESHEET, STYLE_RULE, AT_RULE, DECLARATION, SELECTOR, COMMENT, BLOCK, IDENTIFIER, NUMBER, DIMENSION, STRING, HASH, FUNCTION, OPERATOR, PARENTHESIS, URL, SELECTOR_LIST, TYPE_SELECTOR, CLASS_SELECTOR, ID_SELECTOR, ATTRIBUTE_SELECTOR, PSEUDO_CLASS_SELECTOR, PSEUDO_ELEMENT_SELECTOR, COMBINATOR, UNIVERSAL_SELECTOR, NESTING_SELECTOR, NTH_SELECTOR, NTH_OF_SELECTOR, LANG_SELECTOR, MEDIA_QUERY, MEDIA_FEATURE, MEDIA_TYPE, CONTAINER_QUERY, SUPPORTS_QUERY, LAYER_NAME, PRELUDE_OPERATOR, FLAG_IMPORTANT, ATTR_OPERATOR_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_NONE, ATTR_FLAG_CASE_INSENSITIVE, ATTR_FLAG_CASE_SENSITIVE } from './arena';
|
|
2
|
+
export { STYLESHEET, STYLE_RULE, AT_RULE, DECLARATION, SELECTOR, COMMENT, BLOCK, IDENTIFIER, NUMBER, DIMENSION, STRING, HASH, FUNCTION, OPERATOR, PARENTHESIS, URL, SELECTOR_LIST, TYPE_SELECTOR, CLASS_SELECTOR, ID_SELECTOR, ATTRIBUTE_SELECTOR, PSEUDO_CLASS_SELECTOR, PSEUDO_ELEMENT_SELECTOR, COMBINATOR, UNIVERSAL_SELECTOR, NESTING_SELECTOR, NTH_SELECTOR, NTH_OF_SELECTOR, LANG_SELECTOR, MEDIA_QUERY, MEDIA_FEATURE, MEDIA_TYPE, CONTAINER_QUERY, SUPPORTS_QUERY, LAYER_NAME, PRELUDE_OPERATOR, FLAG_IMPORTANT, ATTR_OPERATOR_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_NONE, ATTR_FLAG_CASE_INSENSITIVE, ATTR_FLAG_CASE_SENSITIVE, };
|
|
3
|
+
export declare const NODE_TYPES: {
|
|
4
|
+
readonly STYLESHEET: 1;
|
|
5
|
+
readonly STYLE_RULE: 2;
|
|
6
|
+
readonly AT_RULE: 3;
|
|
7
|
+
readonly DECLARATION: 4;
|
|
8
|
+
readonly SELECTOR: 5;
|
|
9
|
+
readonly COMMENT: 6;
|
|
10
|
+
readonly BLOCK: 7;
|
|
11
|
+
readonly IDENTIFIER: 10;
|
|
12
|
+
readonly NUMBER: 11;
|
|
13
|
+
readonly DIMENSION: 12;
|
|
14
|
+
readonly STRING: 13;
|
|
15
|
+
readonly HASH: 14;
|
|
16
|
+
readonly FUNCTION: 15;
|
|
17
|
+
readonly OPERATOR: 16;
|
|
18
|
+
readonly PARENTHESIS: 17;
|
|
19
|
+
readonly URL: 18;
|
|
20
|
+
readonly SELECTOR_LIST: 20;
|
|
21
|
+
readonly TYPE_SELECTOR: 21;
|
|
22
|
+
readonly CLASS_SELECTOR: 22;
|
|
23
|
+
readonly ID_SELECTOR: 23;
|
|
24
|
+
readonly ATTRIBUTE_SELECTOR: 24;
|
|
25
|
+
readonly PSEUDO_CLASS_SELECTOR: 25;
|
|
26
|
+
readonly PSEUDO_ELEMENT_SELECTOR: 26;
|
|
27
|
+
readonly COMBINATOR: 27;
|
|
28
|
+
readonly UNIVERSAL_SELECTOR: 28;
|
|
29
|
+
readonly NESTING_SELECTOR: 29;
|
|
30
|
+
readonly NTH_SELECTOR: 30;
|
|
31
|
+
readonly NTH_OF_SELECTOR: 31;
|
|
32
|
+
readonly LANG_SELECTOR: 56;
|
|
33
|
+
readonly MEDIA_QUERY: 32;
|
|
34
|
+
readonly MEDIA_FEATURE: 33;
|
|
35
|
+
readonly MEDIA_TYPE: 34;
|
|
36
|
+
readonly CONTAINER_QUERY: 35;
|
|
37
|
+
readonly SUPPORTS_QUERY: 36;
|
|
38
|
+
readonly LAYER_NAME: 37;
|
|
39
|
+
readonly PRELUDE_OPERATOR: 38;
|
|
40
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { PRELUDE_OPERATOR, LAYER_NAME, SUPPORTS_QUERY, CONTAINER_QUERY, MEDIA_TYPE, MEDIA_FEATURE, MEDIA_QUERY, LANG_SELECTOR, NTH_OF_SELECTOR, NTH_SELECTOR, NESTING_SELECTOR, UNIVERSAL_SELECTOR, COMBINATOR, PSEUDO_ELEMENT_SELECTOR, PSEUDO_CLASS_SELECTOR, ATTRIBUTE_SELECTOR, ID_SELECTOR, CLASS_SELECTOR, TYPE_SELECTOR, SELECTOR_LIST, URL, PARENTHESIS, OPERATOR, FUNCTION, HASH, STRING, DIMENSION, NUMBER, IDENTIFIER, BLOCK, COMMENT, SELECTOR, DECLARATION, AT_RULE, STYLE_RULE, STYLESHEET } from './arena.js';
|
|
2
|
+
export { ATTR_FLAG_CASE_INSENSITIVE, ATTR_FLAG_CASE_SENSITIVE, ATTR_FLAG_NONE, ATTR_OPERATOR_CARET_EQUAL, ATTR_OPERATOR_DOLLAR_EQUAL, ATTR_OPERATOR_EQUAL, ATTR_OPERATOR_NONE, ATTR_OPERATOR_PIPE_EQUAL, ATTR_OPERATOR_STAR_EQUAL, ATTR_OPERATOR_TILDE_EQUAL, FLAG_IMPORTANT } from './arena.js';
|
|
3
|
+
|
|
4
|
+
const NODE_TYPES = {
|
|
5
|
+
// Core nodes
|
|
6
|
+
STYLESHEET,
|
|
7
|
+
STYLE_RULE,
|
|
8
|
+
AT_RULE,
|
|
9
|
+
DECLARATION,
|
|
10
|
+
SELECTOR,
|
|
11
|
+
COMMENT,
|
|
12
|
+
BLOCK,
|
|
13
|
+
// Value nodes
|
|
14
|
+
IDENTIFIER,
|
|
15
|
+
NUMBER,
|
|
16
|
+
DIMENSION,
|
|
17
|
+
STRING,
|
|
18
|
+
HASH,
|
|
19
|
+
FUNCTION,
|
|
20
|
+
OPERATOR,
|
|
21
|
+
PARENTHESIS,
|
|
22
|
+
URL,
|
|
23
|
+
// Selector nodes
|
|
24
|
+
SELECTOR_LIST,
|
|
25
|
+
TYPE_SELECTOR,
|
|
26
|
+
CLASS_SELECTOR,
|
|
27
|
+
ID_SELECTOR,
|
|
28
|
+
ATTRIBUTE_SELECTOR,
|
|
29
|
+
PSEUDO_CLASS_SELECTOR,
|
|
30
|
+
PSEUDO_ELEMENT_SELECTOR,
|
|
31
|
+
COMBINATOR,
|
|
32
|
+
UNIVERSAL_SELECTOR,
|
|
33
|
+
NESTING_SELECTOR,
|
|
34
|
+
NTH_SELECTOR,
|
|
35
|
+
NTH_OF_SELECTOR,
|
|
36
|
+
LANG_SELECTOR,
|
|
37
|
+
// At-rule prelude nodes
|
|
38
|
+
MEDIA_QUERY,
|
|
39
|
+
MEDIA_FEATURE,
|
|
40
|
+
MEDIA_TYPE,
|
|
41
|
+
CONTAINER_QUERY,
|
|
42
|
+
SUPPORTS_QUERY,
|
|
43
|
+
LAYER_NAME,
|
|
44
|
+
PRELUDE_OPERATOR
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export { ATTRIBUTE_SELECTOR, AT_RULE, BLOCK, CLASS_SELECTOR, COMBINATOR, COMMENT, CONTAINER_QUERY, DECLARATION, DIMENSION, FUNCTION, HASH, IDENTIFIER, ID_SELECTOR, LANG_SELECTOR, LAYER_NAME, MEDIA_FEATURE, MEDIA_QUERY, MEDIA_TYPE, NESTING_SELECTOR, NODE_TYPES, NTH_OF_SELECTOR, NTH_SELECTOR, NUMBER, OPERATOR, PARENTHESIS, PRELUDE_OPERATOR, PSEUDO_CLASS_SELECTOR, PSEUDO_ELEMENT_SELECTOR, SELECTOR, SELECTOR_LIST, STRING, STYLESHEET, STYLE_RULE, SUPPORTS_QUERY, TYPE_SELECTOR, UNIVERSAL_SELECTOR, URL };
|
package/dist/css-node.d.ts
CHANGED
|
@@ -80,48 +80,124 @@ export declare class CSSNode {
|
|
|
80
80
|
private source;
|
|
81
81
|
private index;
|
|
82
82
|
constructor(arena: CSSDataArena, source: string, index: number);
|
|
83
|
-
|
|
83
|
+
/** Get node type as number (for performance) */
|
|
84
84
|
get type(): CSSNodeType;
|
|
85
|
+
/** Get node type as human-readable string */
|
|
85
86
|
get type_name(): TypeName;
|
|
87
|
+
/** Get the full text of this node from source */
|
|
86
88
|
get text(): string;
|
|
89
|
+
/** Get the "content" text (property name for declarations, at-rule name for at-rules, layer name for import layers) */
|
|
87
90
|
get name(): string;
|
|
91
|
+
/**
|
|
92
|
+
* Alias for name (for declarations: "color" in "color: blue")
|
|
93
|
+
* More semantic than `name` for declaration nodes
|
|
94
|
+
*/
|
|
88
95
|
get property(): string;
|
|
96
|
+
/**
|
|
97
|
+
* Get the value text (for declarations: "blue" in "color: blue")
|
|
98
|
+
* For dimension/number nodes: returns the numeric value as a number
|
|
99
|
+
* For string nodes: returns the string content without quotes
|
|
100
|
+
* For URL nodes with quoted string: returns the string with quotes (consistent with STRING node)
|
|
101
|
+
* For URL nodes with unquoted URL: returns the URL content without quotes
|
|
102
|
+
*/
|
|
89
103
|
get value(): string | number | null;
|
|
104
|
+
/** Get the numeric value for NUMBER and DIMENSION nodes, or null for other node types */
|
|
90
105
|
get value_as_number(): number | null;
|
|
106
|
+
/**
|
|
107
|
+
* Get the prelude text (for at-rules: "(min-width: 768px)" in "@media (min-width: 768px)")
|
|
108
|
+
* This is an alias for `value` to make at-rule usage more semantic
|
|
109
|
+
*/
|
|
91
110
|
get prelude(): string | null;
|
|
111
|
+
/**
|
|
112
|
+
* Get the attribute operator (for attribute selectors: =, ~=, |=, ^=, $=, *=)
|
|
113
|
+
* Returns one of the ATTR_OPERATOR_* constants
|
|
114
|
+
*/
|
|
92
115
|
get attr_operator(): number;
|
|
116
|
+
/**
|
|
117
|
+
* Get the attribute flags (for attribute selectors: i, s)
|
|
118
|
+
* Returns one of the ATTR_FLAG_* constants
|
|
119
|
+
*/
|
|
93
120
|
get attr_flags(): number;
|
|
121
|
+
/** Get the unit for dimension nodes (e.g., "px" from "100px", "%" from "50%") */
|
|
94
122
|
get unit(): string | null;
|
|
123
|
+
/** Check if this declaration has !important */
|
|
95
124
|
get is_important(): boolean | null;
|
|
125
|
+
/** Check if this has a vendor prefix (computed on-demand) */
|
|
96
126
|
get is_vendor_prefixed(): boolean;
|
|
127
|
+
/** Check if this node has an error */
|
|
97
128
|
get has_error(): boolean;
|
|
129
|
+
/** Check if this at-rule has a prelude */
|
|
98
130
|
get has_prelude(): boolean;
|
|
131
|
+
/** Check if this rule has a block { } */
|
|
99
132
|
get has_block(): boolean;
|
|
133
|
+
/** Check if this style rule has declarations */
|
|
100
134
|
get has_declarations(): boolean;
|
|
135
|
+
/** Get the block node (for style rules and at-rules with blocks) */
|
|
101
136
|
get block(): CSSNode | null;
|
|
137
|
+
/** Check if this block is empty (no declarations or rules, only comments allowed) */
|
|
102
138
|
get is_empty(): boolean;
|
|
139
|
+
/** Get array of parsed value nodes (for declarations only) */
|
|
103
140
|
get values(): CSSNode[];
|
|
104
|
-
|
|
141
|
+
/** Get start line number */
|
|
105
142
|
get line(): number;
|
|
143
|
+
/** Get start column number */
|
|
106
144
|
get column(): number;
|
|
145
|
+
/** Get start offset in source */
|
|
107
146
|
get start(): number;
|
|
147
|
+
/** Get length in source */
|
|
108
148
|
get length(): number;
|
|
149
|
+
/**
|
|
150
|
+
* Get end offset in source
|
|
151
|
+
* End is not stored, must be calculated
|
|
152
|
+
*/
|
|
109
153
|
get end(): number;
|
|
154
|
+
/** Get first child node */
|
|
110
155
|
get first_child(): CSSNode | null;
|
|
156
|
+
/** Get next sibling node */
|
|
111
157
|
get next_sibling(): CSSNode | null;
|
|
158
|
+
/** Check if this node has a next sibling */
|
|
112
159
|
get has_next(): boolean;
|
|
160
|
+
/**
|
|
161
|
+
* Check if this node has children
|
|
162
|
+
* For pseudo-class/pseudo-element functions, returns true if FLAG_HAS_PARENS is set
|
|
163
|
+
* This allows formatters to distinguish :lang() from :hover
|
|
164
|
+
*/
|
|
113
165
|
get has_children(): boolean;
|
|
166
|
+
/** Get all children as an array */
|
|
114
167
|
get children(): CSSNode[];
|
|
168
|
+
/** Make CSSNode iterable over its children */
|
|
115
169
|
[Symbol.iterator](): Iterator<CSSNode>;
|
|
170
|
+
/** Get the 'a' coefficient from An+B expression (e.g., "2n" from "2n+1", "odd" from "odd") */
|
|
116
171
|
get nth_a(): string | null;
|
|
172
|
+
/** Get the 'b' coefficient from An+B expression (e.g., "+1" from "2n+1") */
|
|
117
173
|
get nth_b(): string | null;
|
|
174
|
+
/** Get the An+B formula node from :nth-child(2n+1 of .foo) */
|
|
118
175
|
get nth(): CSSNode | null;
|
|
176
|
+
/** Get the selector list from :nth-child(2n+1 of .foo) */
|
|
119
177
|
get selector(): CSSNode | null;
|
|
178
|
+
/**
|
|
179
|
+
* Get selector list from pseudo-class functions
|
|
180
|
+
* Works for :is(.a), :not(.b), :has(.c), :where(.d), :nth-child(2n of .e)
|
|
181
|
+
*/
|
|
120
182
|
get selector_list(): CSSNode | null;
|
|
183
|
+
/**
|
|
184
|
+
* Iterator over first compound selector parts (zero allocation)
|
|
185
|
+
* Yields parts before the first combinator
|
|
186
|
+
*/
|
|
121
187
|
compound_parts(): IterableIterator<CSSNode>;
|
|
188
|
+
/**
|
|
189
|
+
* Get first compound selector as array
|
|
190
|
+
* Returns array of parts before first combinator
|
|
191
|
+
*/
|
|
122
192
|
get first_compound(): CSSNode[];
|
|
193
|
+
/**
|
|
194
|
+
* Split selector into compound selectors
|
|
195
|
+
* Returns array of compound arrays split by combinators
|
|
196
|
+
*/
|
|
123
197
|
get all_compounds(): CSSNode[][];
|
|
198
|
+
/** Check if selector is compound (no combinators) */
|
|
124
199
|
get is_compound(): boolean;
|
|
200
|
+
/** Get text of first compound selector (no node allocation) */
|
|
125
201
|
get first_compound_text(): string;
|
|
126
202
|
/**
|
|
127
203
|
* Clone this node as a mutable plain JavaScript object
|
package/dist/css-node.js
CHANGED
|
@@ -49,41 +49,41 @@ class CSSNode {
|
|
|
49
49
|
this.source = source;
|
|
50
50
|
this.index = index;
|
|
51
51
|
}
|
|
52
|
-
|
|
53
|
-
get_index() {
|
|
54
|
-
return this.index;
|
|
55
|
-
}
|
|
56
|
-
// Get node type as number (for performance)
|
|
52
|
+
/** Get node type as number (for performance) */
|
|
57
53
|
get type() {
|
|
58
54
|
return this.arena.get_type(this.index);
|
|
59
55
|
}
|
|
60
|
-
|
|
56
|
+
/** Get node type as human-readable string */
|
|
61
57
|
get type_name() {
|
|
62
58
|
return TYPE_NAMES[this.type] || "unknown";
|
|
63
59
|
}
|
|
64
|
-
|
|
60
|
+
/** Get the full text of this node from source */
|
|
65
61
|
get text() {
|
|
66
62
|
let start = this.arena.get_start_offset(this.index);
|
|
67
63
|
let length = this.arena.get_length(this.index);
|
|
68
64
|
return this.source.substring(start, start + length);
|
|
69
65
|
}
|
|
70
|
-
|
|
66
|
+
/** Get the "content" text (property name for declarations, at-rule name for at-rules, layer name for import layers) */
|
|
71
67
|
get name() {
|
|
72
68
|
let start = this.arena.get_content_start(this.index);
|
|
73
69
|
let length = this.arena.get_content_length(this.index);
|
|
74
70
|
if (length === 0) return "";
|
|
75
71
|
return this.source.substring(start, start + length);
|
|
76
72
|
}
|
|
77
|
-
|
|
78
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Alias for name (for declarations: "color" in "color: blue")
|
|
75
|
+
* More semantic than `name` for declaration nodes
|
|
76
|
+
*/
|
|
79
77
|
get property() {
|
|
80
78
|
return this.name;
|
|
81
79
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Get the value text (for declarations: "blue" in "color: blue")
|
|
82
|
+
* For dimension/number nodes: returns the numeric value as a number
|
|
83
|
+
* For string nodes: returns the string content without quotes
|
|
84
|
+
* For URL nodes with quoted string: returns the string with quotes (consistent with STRING node)
|
|
85
|
+
* For URL nodes with unquoted URL: returns the URL content without quotes
|
|
86
|
+
*/
|
|
87
87
|
get value() {
|
|
88
88
|
let { type, text } = this;
|
|
89
89
|
if (type === DIMENSION) {
|
|
@@ -113,6 +113,7 @@ class CSSNode {
|
|
|
113
113
|
if (length === 0) return null;
|
|
114
114
|
return this.source.substring(start, start + length);
|
|
115
115
|
}
|
|
116
|
+
/** Get the numeric value for NUMBER and DIMENSION nodes, or null for other node types */
|
|
116
117
|
get value_as_number() {
|
|
117
118
|
let text = this.text;
|
|
118
119
|
if (this.type === NUMBER) {
|
|
@@ -123,33 +124,39 @@ class CSSNode {
|
|
|
123
124
|
}
|
|
124
125
|
return null;
|
|
125
126
|
}
|
|
126
|
-
|
|
127
|
-
|
|
127
|
+
/**
|
|
128
|
+
* Get the prelude text (for at-rules: "(min-width: 768px)" in "@media (min-width: 768px)")
|
|
129
|
+
* This is an alias for `value` to make at-rule usage more semantic
|
|
130
|
+
*/
|
|
128
131
|
get prelude() {
|
|
129
132
|
let val = this.value;
|
|
130
133
|
return typeof val === "string" ? val : null;
|
|
131
134
|
}
|
|
132
|
-
|
|
133
|
-
|
|
135
|
+
/**
|
|
136
|
+
* Get the attribute operator (for attribute selectors: =, ~=, |=, ^=, $=, *=)
|
|
137
|
+
* Returns one of the ATTR_OPERATOR_* constants
|
|
138
|
+
*/
|
|
134
139
|
get attr_operator() {
|
|
135
140
|
return this.arena.get_attr_operator(this.index);
|
|
136
141
|
}
|
|
137
|
-
|
|
138
|
-
|
|
142
|
+
/**
|
|
143
|
+
* Get the attribute flags (for attribute selectors: i, s)
|
|
144
|
+
* Returns one of the ATTR_FLAG_* constants
|
|
145
|
+
*/
|
|
139
146
|
get attr_flags() {
|
|
140
147
|
return this.arena.get_attr_flags(this.index);
|
|
141
148
|
}
|
|
142
|
-
|
|
149
|
+
/** Get the unit for dimension nodes (e.g., "px" from "100px", "%" from "50%") */
|
|
143
150
|
get unit() {
|
|
144
151
|
if (this.type !== DIMENSION) return null;
|
|
145
152
|
return parse_dimension(this.text).unit;
|
|
146
153
|
}
|
|
147
|
-
|
|
154
|
+
/** Check if this declaration has !important */
|
|
148
155
|
get is_important() {
|
|
149
156
|
if (this.type !== DECLARATION) return null;
|
|
150
157
|
return this.arena.has_flag(this.index, FLAG_IMPORTANT);
|
|
151
158
|
}
|
|
152
|
-
|
|
159
|
+
/** Check if this has a vendor prefix (computed on-demand) */
|
|
153
160
|
get is_vendor_prefixed() {
|
|
154
161
|
switch (this.type) {
|
|
155
162
|
case DECLARATION:
|
|
@@ -167,23 +174,23 @@ class CSSNode {
|
|
|
167
174
|
return false;
|
|
168
175
|
}
|
|
169
176
|
}
|
|
170
|
-
|
|
177
|
+
/** Check if this node has an error */
|
|
171
178
|
get has_error() {
|
|
172
179
|
return this.arena.has_flag(this.index, FLAG_HAS_ERROR);
|
|
173
180
|
}
|
|
174
|
-
|
|
181
|
+
/** Check if this at-rule has a prelude */
|
|
175
182
|
get has_prelude() {
|
|
176
183
|
return this.arena.get_value_length(this.index) > 0;
|
|
177
184
|
}
|
|
178
|
-
|
|
185
|
+
/** Check if this rule has a block { } */
|
|
179
186
|
get has_block() {
|
|
180
187
|
return this.arena.has_flag(this.index, FLAG_HAS_BLOCK);
|
|
181
188
|
}
|
|
182
|
-
|
|
189
|
+
/** Check if this style rule has declarations */
|
|
183
190
|
get has_declarations() {
|
|
184
191
|
return this.arena.has_flag(this.index, FLAG_HAS_DECLARATIONS);
|
|
185
192
|
}
|
|
186
|
-
|
|
193
|
+
/** Get the block node (for style rules and at-rules with blocks) */
|
|
187
194
|
get block() {
|
|
188
195
|
if (this.type === STYLE_RULE) {
|
|
189
196
|
let first = this.first_child;
|
|
@@ -206,7 +213,7 @@ class CSSNode {
|
|
|
206
213
|
}
|
|
207
214
|
return null;
|
|
208
215
|
}
|
|
209
|
-
|
|
216
|
+
/** Check if this block is empty (no declarations or rules, only comments allowed) */
|
|
210
217
|
get is_empty() {
|
|
211
218
|
if (this.type !== BLOCK) return false;
|
|
212
219
|
let child = this.first_child;
|
|
@@ -219,7 +226,7 @@ class CSSNode {
|
|
|
219
226
|
return true;
|
|
220
227
|
}
|
|
221
228
|
// --- Value Node Access (for declarations) ---
|
|
222
|
-
|
|
229
|
+
/** Get array of parsed value nodes (for declarations only) */
|
|
223
230
|
get values() {
|
|
224
231
|
let result = [];
|
|
225
232
|
let child = this.first_child;
|
|
@@ -229,57 +236,52 @@ class CSSNode {
|
|
|
229
236
|
}
|
|
230
237
|
return result;
|
|
231
238
|
}
|
|
232
|
-
|
|
233
|
-
get value_count() {
|
|
234
|
-
let count = 0;
|
|
235
|
-
let child = this.first_child;
|
|
236
|
-
while (child) {
|
|
237
|
-
count++;
|
|
238
|
-
child = child.next_sibling;
|
|
239
|
-
}
|
|
240
|
-
return count;
|
|
241
|
-
}
|
|
242
|
-
// Get start line number
|
|
239
|
+
/** Get start line number */
|
|
243
240
|
get line() {
|
|
244
241
|
return this.arena.get_start_line(this.index);
|
|
245
242
|
}
|
|
246
|
-
|
|
243
|
+
/** Get start column number */
|
|
247
244
|
get column() {
|
|
248
245
|
return this.arena.get_start_column(this.index);
|
|
249
246
|
}
|
|
250
|
-
|
|
247
|
+
/** Get start offset in source */
|
|
251
248
|
get start() {
|
|
252
249
|
return this.arena.get_start_offset(this.index);
|
|
253
250
|
}
|
|
254
|
-
|
|
251
|
+
/** Get length in source */
|
|
255
252
|
get length() {
|
|
256
253
|
return this.arena.get_length(this.index);
|
|
257
254
|
}
|
|
258
|
-
|
|
259
|
-
|
|
255
|
+
/**
|
|
256
|
+
* Get end offset in source
|
|
257
|
+
* End is not stored, must be calculated
|
|
258
|
+
*/
|
|
260
259
|
get end() {
|
|
261
260
|
return this.start + this.length;
|
|
262
261
|
}
|
|
263
262
|
// --- Tree Traversal ---
|
|
264
|
-
|
|
263
|
+
/** Get first child node */
|
|
265
264
|
get first_child() {
|
|
266
265
|
let child_index = this.arena.get_first_child(this.index);
|
|
267
266
|
if (child_index === 0) return null;
|
|
268
267
|
return new CSSNode(this.arena, this.source, child_index);
|
|
269
268
|
}
|
|
270
|
-
|
|
269
|
+
/** Get next sibling node */
|
|
271
270
|
get next_sibling() {
|
|
272
271
|
let sibling_index = this.arena.get_next_sibling(this.index);
|
|
273
272
|
if (sibling_index === 0) return null;
|
|
274
273
|
return new CSSNode(this.arena, this.source, sibling_index);
|
|
275
274
|
}
|
|
275
|
+
/** Check if this node has a next sibling */
|
|
276
276
|
get has_next() {
|
|
277
277
|
let sibling_index = this.arena.get_next_sibling(this.index);
|
|
278
278
|
return sibling_index !== 0;
|
|
279
279
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
280
|
+
/**
|
|
281
|
+
* Check if this node has children
|
|
282
|
+
* For pseudo-class/pseudo-element functions, returns true if FLAG_HAS_PARENS is set
|
|
283
|
+
* This allows formatters to distinguish :lang() from :hover
|
|
284
|
+
*/
|
|
283
285
|
get has_children() {
|
|
284
286
|
if (this.type === PSEUDO_CLASS_SELECTOR || this.type === PSEUDO_ELEMENT_SELECTOR) {
|
|
285
287
|
if (this.arena.has_flag(this.index, FLAG_HAS_PARENS)) {
|
|
@@ -288,7 +290,7 @@ class CSSNode {
|
|
|
288
290
|
}
|
|
289
291
|
return this.arena.has_children(this.index);
|
|
290
292
|
}
|
|
291
|
-
|
|
293
|
+
/** Get all children as an array */
|
|
292
294
|
get children() {
|
|
293
295
|
let result = [];
|
|
294
296
|
let child = this.first_child;
|
|
@@ -298,7 +300,7 @@ class CSSNode {
|
|
|
298
300
|
}
|
|
299
301
|
return result;
|
|
300
302
|
}
|
|
301
|
-
|
|
303
|
+
/** Make CSSNode iterable over its children */
|
|
302
304
|
*[Symbol.iterator]() {
|
|
303
305
|
let child = this.first_child;
|
|
304
306
|
while (child) {
|
|
@@ -307,7 +309,7 @@ class CSSNode {
|
|
|
307
309
|
}
|
|
308
310
|
}
|
|
309
311
|
// --- An+B Expression Helpers (for NODE_SELECTOR_NTH) ---
|
|
310
|
-
|
|
312
|
+
/** Get the 'a' coefficient from An+B expression (e.g., "2n" from "2n+1", "odd" from "odd") */
|
|
311
313
|
get nth_a() {
|
|
312
314
|
if (this.type !== NTH_SELECTOR) return null;
|
|
313
315
|
let len = this.arena.get_content_length(this.index);
|
|
@@ -315,7 +317,7 @@ class CSSNode {
|
|
|
315
317
|
let start = this.arena.get_content_start(this.index);
|
|
316
318
|
return this.source.substring(start, start + len);
|
|
317
319
|
}
|
|
318
|
-
|
|
320
|
+
/** Get the 'b' coefficient from An+B expression (e.g., "+1" from "2n+1") */
|
|
319
321
|
get nth_b() {
|
|
320
322
|
if (this.type !== NTH_SELECTOR) return null;
|
|
321
323
|
let len = this.arena.get_value_length(this.index);
|
|
@@ -339,20 +341,22 @@ class CSSNode {
|
|
|
339
341
|
return value;
|
|
340
342
|
}
|
|
341
343
|
// --- Pseudo-Class Nth-Of Helpers (for NODE_SELECTOR_NTH_OF) ---
|
|
342
|
-
|
|
344
|
+
/** Get the An+B formula node from :nth-child(2n+1 of .foo) */
|
|
343
345
|
get nth() {
|
|
344
346
|
if (this.type !== NTH_OF_SELECTOR) return null;
|
|
345
347
|
return this.first_child;
|
|
346
348
|
}
|
|
347
|
-
|
|
349
|
+
/** Get the selector list from :nth-child(2n+1 of .foo) */
|
|
348
350
|
get selector() {
|
|
349
351
|
if (this.type !== NTH_OF_SELECTOR) return null;
|
|
350
352
|
let first = this.first_child;
|
|
351
353
|
return first ? first.next_sibling : null;
|
|
352
354
|
}
|
|
353
355
|
// --- Pseudo-Class Selector List Helper ---
|
|
354
|
-
|
|
355
|
-
|
|
356
|
+
/**
|
|
357
|
+
* Get selector list from pseudo-class functions
|
|
358
|
+
* Works for :is(.a), :not(.b), :has(.c), :where(.d), :nth-child(2n of .e)
|
|
359
|
+
*/
|
|
356
360
|
get selector_list() {
|
|
357
361
|
if (this.type !== PSEUDO_CLASS_SELECTOR) return null;
|
|
358
362
|
let child = this.first_child;
|
|
@@ -366,8 +370,10 @@ class CSSNode {
|
|
|
366
370
|
return null;
|
|
367
371
|
}
|
|
368
372
|
// --- Compound Selector Helpers (for NODE_SELECTOR) ---
|
|
369
|
-
|
|
370
|
-
|
|
373
|
+
/**
|
|
374
|
+
* Iterator over first compound selector parts (zero allocation)
|
|
375
|
+
* Yields parts before the first combinator
|
|
376
|
+
*/
|
|
371
377
|
*compound_parts() {
|
|
372
378
|
if (this.type !== SELECTOR) return;
|
|
373
379
|
let child = this.first_child;
|
|
@@ -377,8 +383,10 @@ class CSSNode {
|
|
|
377
383
|
child = child.next_sibling;
|
|
378
384
|
}
|
|
379
385
|
}
|
|
380
|
-
|
|
381
|
-
|
|
386
|
+
/**
|
|
387
|
+
* Get first compound selector as array
|
|
388
|
+
* Returns array of parts before first combinator
|
|
389
|
+
*/
|
|
382
390
|
get first_compound() {
|
|
383
391
|
if (this.type !== SELECTOR) return [];
|
|
384
392
|
let result = [];
|
|
@@ -390,8 +398,10 @@ class CSSNode {
|
|
|
390
398
|
}
|
|
391
399
|
return result;
|
|
392
400
|
}
|
|
393
|
-
|
|
394
|
-
|
|
401
|
+
/**
|
|
402
|
+
* Split selector into compound selectors
|
|
403
|
+
* Returns array of compound arrays split by combinators
|
|
404
|
+
*/
|
|
395
405
|
get all_compounds() {
|
|
396
406
|
if (this.type !== SELECTOR) return [];
|
|
397
407
|
let compounds = [];
|
|
@@ -413,7 +423,7 @@ class CSSNode {
|
|
|
413
423
|
}
|
|
414
424
|
return compounds;
|
|
415
425
|
}
|
|
416
|
-
|
|
426
|
+
/** Check if selector is compound (no combinators) */
|
|
417
427
|
get is_compound() {
|
|
418
428
|
if (this.type !== SELECTOR) return false;
|
|
419
429
|
let child = this.first_child;
|
|
@@ -423,7 +433,7 @@ class CSSNode {
|
|
|
423
433
|
}
|
|
424
434
|
return true;
|
|
425
435
|
}
|
|
426
|
-
|
|
436
|
+
/** Get text of first compound selector (no node allocation) */
|
|
427
437
|
get first_compound_text() {
|
|
428
438
|
if (this.type !== SELECTOR) return "";
|
|
429
439
|
let start = -1;
|
package/dist/index.d.ts
CHANGED
|
@@ -3,10 +3,11 @@ export { parse_selector } from './parse-selector';
|
|
|
3
3
|
export { parse_atrule_prelude } from './parse-atrule-prelude';
|
|
4
4
|
export { parse_value } from './parse-value';
|
|
5
5
|
export { tokenize } from './tokenize';
|
|
6
|
-
export { walk, traverse } from './walk';
|
|
6
|
+
export { walk, traverse, SKIP, BREAK } from './walk';
|
|
7
7
|
export { type ParserOptions } from './parse';
|
|
8
8
|
export { CSSNode, type CSSNodeType, TYPE_NAMES, type CloneOptions, type PlainCSSNode } from './css-node';
|
|
9
9
|
export type { LexerPosition } from './lexer';
|
|
10
10
|
export { ATTR_OPERATOR_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_NONE, ATTR_FLAG_CASE_INSENSITIVE, ATTR_FLAG_CASE_SENSITIVE, } from './arena';
|
|
11
11
|
export * from './constants';
|
|
12
12
|
export * from './token-types';
|
|
13
|
+
export { NODE_TYPES } from './constants';
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,8 @@ export { parse_selector } from './parse-selector.js';
|
|
|
3
3
|
export { parse_atrule_prelude } from './parse-atrule-prelude.js';
|
|
4
4
|
export { parse_value } from './parse-value.js';
|
|
5
5
|
export { tokenize } from './tokenize.js';
|
|
6
|
-
export { traverse, walk } from './walk.js';
|
|
6
|
+
export { BREAK, SKIP, traverse, walk } from './walk.js';
|
|
7
7
|
export { CSSNode, TYPE_NAMES } from './css-node.js';
|
|
8
8
|
export { ATTRIBUTE_SELECTOR, ATTR_FLAG_CASE_INSENSITIVE, ATTR_FLAG_CASE_SENSITIVE, ATTR_FLAG_NONE, ATTR_OPERATOR_CARET_EQUAL, ATTR_OPERATOR_DOLLAR_EQUAL, ATTR_OPERATOR_EQUAL, ATTR_OPERATOR_NONE, ATTR_OPERATOR_PIPE_EQUAL, ATTR_OPERATOR_STAR_EQUAL, ATTR_OPERATOR_TILDE_EQUAL, AT_RULE, BLOCK, CLASS_SELECTOR, COMBINATOR, COMMENT, CONTAINER_QUERY, DECLARATION, DIMENSION, FLAG_IMPORTANT, FUNCTION, HASH, IDENTIFIER, ID_SELECTOR, LANG_SELECTOR, LAYER_NAME, MEDIA_FEATURE, MEDIA_QUERY, MEDIA_TYPE, NESTING_SELECTOR, NTH_OF_SELECTOR, NTH_SELECTOR, NUMBER, OPERATOR, PARENTHESIS, PRELUDE_OPERATOR, PSEUDO_CLASS_SELECTOR, PSEUDO_ELEMENT_SELECTOR, SELECTOR, SELECTOR_LIST, STRING, STYLESHEET, STYLE_RULE, SUPPORTS_QUERY, TYPE_SELECTOR, UNIVERSAL_SELECTOR, URL } from './arena.js';
|
|
9
|
+
export { NODE_TYPES } from './constants.js';
|
|
9
10
|
export { TOKEN_AT_KEYWORD, TOKEN_BAD_STRING, TOKEN_BAD_URL, TOKEN_CDC, TOKEN_CDO, TOKEN_COLON, TOKEN_COMMA, TOKEN_COMMENT, TOKEN_DELIM, TOKEN_DIMENSION, TOKEN_EOF, TOKEN_FUNCTION, TOKEN_HASH, TOKEN_IDENT, TOKEN_LEFT_BRACE, TOKEN_LEFT_BRACKET, TOKEN_LEFT_PAREN, TOKEN_NUMBER, TOKEN_PERCENTAGE, TOKEN_RIGHT_BRACE, TOKEN_RIGHT_BRACKET, TOKEN_RIGHT_PAREN, TOKEN_SEMICOLON, TOKEN_STRING, TOKEN_URL, TOKEN_WHITESPACE } from './token-types.js';
|
package/dist/parse-selector.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Lexer } from './lexer.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_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';
|
|
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';
|
|
7
7
|
import { CSSNode } from './css-node.js';
|
|
8
8
|
|
|
@@ -108,7 +108,10 @@ class SelectorParser {
|
|
|
108
108
|
}
|
|
109
109
|
const saved = this.lexer.save_position();
|
|
110
110
|
this.skip_whitespace();
|
|
111
|
-
if (this.lexer.pos >= this.selector_end)
|
|
111
|
+
if (this.lexer.pos >= this.selector_end) {
|
|
112
|
+
this.lexer.restore_position(saved);
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
112
115
|
this.lexer.next_token_fast(false);
|
|
113
116
|
let token_type = this.lexer.token_type;
|
|
114
117
|
if (token_type === TOKEN_COMMA || this.lexer.pos >= this.selector_end) {
|
|
@@ -239,14 +242,17 @@ class SelectorParser {
|
|
|
239
242
|
let has_whitespace = false;
|
|
240
243
|
while (this.lexer.pos < this.selector_end) {
|
|
241
244
|
let ch = this.source.charCodeAt(this.lexer.pos);
|
|
242
|
-
if (ch === CHAR_SPACE || ch === CHAR_NEWLINE || ch === CHAR_CARRIAGE_RETURN || ch === CHAR_FORM_FEED) {
|
|
245
|
+
if (ch === CHAR_SPACE || ch === CHAR_TAB || ch === CHAR_NEWLINE || ch === CHAR_CARRIAGE_RETURN || ch === CHAR_FORM_FEED) {
|
|
243
246
|
has_whitespace = true;
|
|
244
247
|
this.lexer.pos++;
|
|
245
248
|
} else {
|
|
246
249
|
break;
|
|
247
250
|
}
|
|
248
251
|
}
|
|
249
|
-
if (this.lexer.pos >= this.selector_end)
|
|
252
|
+
if (this.lexer.pos >= this.selector_end) {
|
|
253
|
+
this.lexer.pos = whitespace_start;
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
250
256
|
this.lexer.next_token_fast(false);
|
|
251
257
|
if (this.lexer.token_type === TOKEN_DELIM) {
|
|
252
258
|
let ch = this.source.charCodeAt(this.lexer.token_start);
|
|
@@ -258,7 +264,7 @@ class SelectorParser {
|
|
|
258
264
|
this.lexer.pos = whitespace_start;
|
|
259
265
|
while (this.lexer.pos < this.selector_end) {
|
|
260
266
|
let ch = this.source.charCodeAt(this.lexer.pos);
|
|
261
|
-
if (ch === CHAR_SPACE || ch === CHAR_NEWLINE || ch === CHAR_CARRIAGE_RETURN || ch === CHAR_FORM_FEED) {
|
|
267
|
+
if (ch === CHAR_SPACE || ch === CHAR_TAB || ch === CHAR_NEWLINE || ch === CHAR_CARRIAGE_RETURN || ch === CHAR_FORM_FEED) {
|
|
262
268
|
this.lexer.pos++;
|
|
263
269
|
} else {
|
|
264
270
|
break;
|
package/dist/walk.d.ts
CHANGED
|
@@ -1,20 +1,74 @@
|
|
|
1
1
|
import type { CSSNode } from './css-node';
|
|
2
|
-
|
|
2
|
+
export declare const SKIP: unique symbol;
|
|
3
|
+
export declare const BREAK: unique symbol;
|
|
4
|
+
type WalkCallback = (node: CSSNode, depth: number) => void | typeof SKIP | typeof BREAK;
|
|
3
5
|
/**
|
|
4
6
|
* Walk the AST in depth-first order, calling the callback for each node
|
|
7
|
+
*
|
|
5
8
|
* @param node - The root node to start walking from
|
|
6
|
-
* @param callback - Function to call for each node visited. Receives the node and its depth (0 for root)
|
|
9
|
+
* @param callback - Function to call for each node visited. Receives the node and its depth (0 for root).
|
|
10
|
+
* Return SKIP to skip children of current node, or BREAK to stop traversal entirely.
|
|
11
|
+
* @param depth - Starting depth (default: 0)
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* import { parse, walk, SKIP, BREAK } from '@projectwallace/css-parser'
|
|
15
|
+
*
|
|
16
|
+
* const ast = parse('.a { .b { .c { color: red; } } }')
|
|
17
|
+
*
|
|
18
|
+
* // Skip nested rules
|
|
19
|
+
* walk(ast, (node) => {
|
|
20
|
+
* if (node.type === STYLE_RULE) {
|
|
21
|
+
* console.log(node.text)
|
|
22
|
+
* return SKIP // Don't visit nested rules
|
|
23
|
+
* }
|
|
24
|
+
* })
|
|
25
|
+
* // Output: .a { ... }, but not .b or .c
|
|
26
|
+
*
|
|
27
|
+
* // Stop on first declaration
|
|
28
|
+
* walk(ast, (node) => {
|
|
29
|
+
* if (node.type === DECLARATION) {
|
|
30
|
+
* console.log(node.name)
|
|
31
|
+
* return BREAK // Stop traversal
|
|
32
|
+
* }
|
|
33
|
+
* })
|
|
7
34
|
*/
|
|
8
|
-
export declare function walk(node: CSSNode, callback: WalkCallback, depth?: number):
|
|
9
|
-
type WalkEnterLeaveCallback = (node: CSSNode) => void;
|
|
35
|
+
export declare function walk(node: CSSNode, callback: WalkCallback, depth?: number): boolean;
|
|
36
|
+
type WalkEnterLeaveCallback = (node: CSSNode) => void | typeof SKIP | typeof BREAK;
|
|
10
37
|
interface WalkEnterLeaveOptions {
|
|
11
38
|
enter?: WalkEnterLeaveCallback;
|
|
12
39
|
leave?: WalkEnterLeaveCallback;
|
|
13
40
|
}
|
|
14
41
|
/**
|
|
15
42
|
* Walk the AST in depth-first order, calling enter before visiting children and leave after
|
|
43
|
+
*
|
|
16
44
|
* @param node - The root node to start walking from
|
|
17
45
|
* @param options - Object with optional enter and leave callback functions
|
|
46
|
+
* @param options.enter - Called before visiting children. Return SKIP to skip children (leave still called),
|
|
47
|
+
* or BREAK to stop traversal entirely (leave NOT called).
|
|
48
|
+
* @param options.leave - Called after visiting children. Return BREAK to stop traversal.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* import { parse, traverse, SKIP, BREAK } from '@projectwallace/css-parser'
|
|
52
|
+
*
|
|
53
|
+
* const ast = parse('@media screen { .a { color: red; } }')
|
|
54
|
+
*
|
|
55
|
+
* // Track context with skip
|
|
56
|
+
* let depth = 0
|
|
57
|
+
* traverse(ast, {
|
|
58
|
+
* enter(node) {
|
|
59
|
+
* depth++
|
|
60
|
+
* if (node.type === AT_RULE) {
|
|
61
|
+
* console.log('Entering media query at depth', depth)
|
|
62
|
+
* return SKIP // Skip contents but still call leave
|
|
63
|
+
* }
|
|
64
|
+
* },
|
|
65
|
+
* leave(node) {
|
|
66
|
+
* if (node.type === AT_RULE) {
|
|
67
|
+
* console.log('Leaving media query at depth', depth)
|
|
68
|
+
* }
|
|
69
|
+
* depth--
|
|
70
|
+
* }
|
|
71
|
+
* })
|
|
18
72
|
*/
|
|
19
|
-
export declare function traverse(node: CSSNode, { enter, leave }?: WalkEnterLeaveOptions):
|
|
73
|
+
export declare function traverse(node: CSSNode, { enter, leave }?: WalkEnterLeaveOptions): boolean;
|
|
20
74
|
export {};
|
package/dist/walk.js
CHANGED
|
@@ -1,21 +1,45 @@
|
|
|
1
|
+
const SKIP = Symbol("SKIP");
|
|
2
|
+
const BREAK = Symbol("BREAK");
|
|
1
3
|
function walk(node, callback, depth = 0) {
|
|
2
|
-
callback(node, depth);
|
|
4
|
+
const result = callback(node, depth);
|
|
5
|
+
if (result === BREAK) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
if (result === SKIP) {
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
3
11
|
let child = node.first_child;
|
|
4
12
|
while (child) {
|
|
5
|
-
walk(child, callback, depth + 1);
|
|
13
|
+
const should_continue = walk(child, callback, depth + 1);
|
|
14
|
+
if (!should_continue) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
6
17
|
child = child.next_sibling;
|
|
7
18
|
}
|
|
19
|
+
return true;
|
|
8
20
|
}
|
|
9
21
|
const NOOP = function() {
|
|
10
22
|
};
|
|
11
23
|
function traverse(node, { enter = NOOP, leave = NOOP } = {}) {
|
|
12
|
-
enter(node);
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
24
|
+
const enter_result = enter(node);
|
|
25
|
+
if (enter_result === BREAK) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
if (enter_result !== SKIP) {
|
|
29
|
+
let child = node.first_child;
|
|
30
|
+
while (child) {
|
|
31
|
+
const should_continue = traverse(child, { enter, leave });
|
|
32
|
+
if (!should_continue) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
child = child.next_sibling;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const leave_result = leave(node);
|
|
39
|
+
if (leave_result === BREAK) {
|
|
40
|
+
return false;
|
|
17
41
|
}
|
|
18
|
-
|
|
42
|
+
return true;
|
|
19
43
|
}
|
|
20
44
|
|
|
21
|
-
export { traverse, walk };
|
|
45
|
+
export { BREAK, SKIP, traverse, walk };
|
package/package.json
CHANGED