1
1
import * as csstree from 'css-tree'
2
+ import { TreeNode } from './TreeNode.js'
2
3
3
4
/**
4
- * @typedef {Object } LayerTree
5
- * @property {string } name
6
- * @property {LayerTree[] } children
5
+ * @typedef Location
6
+ * @property {number } line
7
+ * @property {number } column
8
+ * @property {number } start
9
+ * @property {number } end
7
10
*/
8
11
9
- class List {
10
- /** @type {string } */
11
- name
12
- /** @type {List[] } */
13
- children
14
-
15
- /**
16
- * @param {string | undefined } name
17
- */
18
- constructor ( name = undefined ) {
19
- this . name = name || 'root'
20
- this . children = [ ]
21
- }
22
-
23
- /** @param {string } name */
24
- has ( name ) {
25
- for ( let child of this . children ) {
26
- if ( child . name === name ) {
27
- return true
28
- }
29
- }
30
- return false
31
- }
32
-
33
- /**
34
- *
35
- * @param {string } name
36
- * @returns
37
- */
38
- push ( name ) {
39
- if ( this . has ( name ) && name !== '<anonymous>' ) {
40
- return this . children . find ( ( child ) => child . name === name )
41
- }
42
-
43
- let new_item = new List ( name )
44
- this . children . push ( new_item )
45
- return new_item
46
- }
47
-
48
- /**
49
- * @returns {LayerTree }
50
- */
51
- serialize ( ) {
52
- return {
53
- name : this . name ,
54
- children : this . children . map ( ( child ) => child . serialize ( ) ) ,
55
- }
56
- }
57
- }
58
-
59
12
/**
60
- * Get the parent Atrule for `childNode`
61
- * @param {import('css-tree').CssNode } ast The AST to search in
62
- * @param {import('css-tree').Atrule } childNode The Atrule we want to get the potential parent Atrule for
13
+ * @param {import('css-tree').CssNode } node
14
+ * @returns {Location | undefined }
63
15
*/
64
- function get_parent_rule ( ast , childNode ) {
65
- let parent
66
- csstree . walk ( ast , {
67
- visit : 'Atrule' ,
68
- enter : function ( /** @type {import('css-tree').Atrule } */ node ) {
69
- if ( node === childNode && this . atrule ) {
70
- parent = this . atrule
71
- return this . break
72
- }
73
- } ,
74
- } )
75
- return parent
16
+ function get_location ( node ) {
17
+ let loc = node . loc
18
+ if ( ! loc ) return
19
+ return {
20
+ line : loc . start . line ,
21
+ column : loc . start . column ,
22
+ start : loc . start . offset ,
23
+ end : loc . end . offset ,
24
+ }
76
25
}
77
26
78
- /**
79
- * @param {import('css-tree').AtrulePrelude | import('css-tree').Raw | null } prelude
80
- * @returns string
81
- */
82
- function get_layer_name ( prelude ) {
83
- return prelude === null ? '<anonymous>' : csstree . generate ( prelude )
27
+ /** @param {import('css-tree').Atrule } node */
28
+ function is_layer ( node ) {
29
+ return node . name . toLowerCase ( ) === 'layer'
84
30
}
85
31
86
32
/**
87
- *
88
33
* @param {import('css-tree').CssNode } ast
89
- * @param {import('css-tree').Atrule } atrule
90
- * @returns {string[] }
91
34
*/
92
- function resolve_parent_tree ( ast , atrule ) {
93
- let stack = [ ]
94
-
95
- // @ts -expect-error Let me just do a while loop plz
96
- while ( ( atrule = get_parent_rule ( ast , atrule ) ) ) {
97
- if ( atrule . name === 'layer' ) {
98
- stack . unshift ( get_layer_name ( atrule . prelude ) )
99
- }
35
+ export function get_tree_from_ast ( ast ) {
36
+ /** @type {string[] } */
37
+ let current_stack = [ ]
38
+ let root = new TreeNode ( 'root' )
39
+ let anonymous_counter = 0
40
+
41
+ /** @returns {string } */
42
+ function get_anonymous_id ( ) {
43
+ anonymous_counter ++
44
+ return `__anonymous-${ anonymous_counter } __`
100
45
}
101
46
102
- return stack
103
- }
104
-
105
- /**
106
- * @param {import('css-tree').CssNode } ast
107
- * @returns {string[][] }
108
- */
109
- export function get_ast_tree ( ast ) {
110
- /** @type {string[][] } */
111
- let list = [ ]
47
+ /**
48
+ * @param {import('css-tree').AtrulePrelude } prelude
49
+ * @returns {string[] }
50
+ */
51
+ function get_layer_names ( prelude ) {
52
+ return csstree
53
+ // @todo : fewer loops plz
54
+ . generate ( prelude )
55
+ . split ( '.' )
56
+ . map ( ( s ) => s . trim ( ) )
57
+ }
112
58
113
59
csstree . walk ( ast , {
114
60
visit : 'Atrule' ,
115
- enter : function ( /** @type {import('css-tree').Atrule } */ node ) {
116
- if ( node . name === 'layer' ) {
117
- let layer_name = get_layer_name ( node . prelude )
61
+ enter ( node ) {
62
+ if ( is_layer ( node ) ) {
63
+ let location = get_location ( node )
64
+
65
+ if ( node . prelude === null ) {
66
+ let layer_name = get_anonymous_id ( )
67
+ root . add_child ( current_stack , layer_name , location )
68
+ current_stack . push ( layer_name )
69
+ return
70
+ }
118
71
119
- // @layer first, second;
120
- if ( node . block === null ) {
121
- for ( let name of layer_name . split ( ',' ) ) {
122
- list . push ( [ ...resolve_parent_tree ( ast , node ) , name . trim ( ) ] )
72
+ if ( node . prelude . type === 'AtrulePrelude' ) {
73
+ if ( node . block === null ) {
74
+ // @ts -expect-error CSSTree types are not updated yet in @types/css-tree
75
+ let prelude = csstree . findAll ( node . prelude , n => n . type === 'Layer' ) . map ( n => n . name )
76
+ for ( let name of prelude ) {
77
+ root . add_child ( current_stack , name , location )
78
+ }
79
+ } else {
80
+ for ( let layer_name of get_layer_names ( node . prelude ) ) {
81
+ root . add_child ( current_stack , layer_name , location )
82
+ current_stack . push ( layer_name )
83
+ }
123
84
}
124
-
125
- return this . skip
126
85
}
86
+ } else if ( node . name . toLowerCase ( ) === 'import' && node . prelude !== null && node . prelude . type === 'AtrulePrelude' ) {
87
+ let location = get_location ( node )
88
+ let prelude = node . prelude
127
89
128
- // @layer first { /* content */ }
129
- list . push ( [ ...resolve_parent_tree ( ast , node ) , layer_name ] )
130
- return this . skip
131
- } else if ( node . name === 'import' && node . prelude !== null ) {
132
90
// @import url("foo.css") layer(test);
91
+ // OR
92
+ // @import url("foo.css") layer(test.nested);
133
93
// @ts -expect-error CSSTree types are not updated to v3 yet
134
- let layer = csstree . find ( node . prelude , ( pr_node ) => pr_node . type === 'Layer' )
94
+ let layer = csstree . find ( prelude , n => n . type === 'Layer' )
135
95
if ( layer ) {
136
96
// @ts -expect-error CSSTree types are not updated to v3 yet
137
- list . push ( [ layer . name ] )
97
+ for ( let layer_name of get_layer_names ( layer ) ) {
98
+ root . add_child ( current_stack , layer_name , location )
99
+ current_stack . push ( layer_name )
100
+ }
138
101
return this . skip
139
102
}
140
103
141
104
// @import url("foo.css") layer();
142
- let layer_fn = csstree . find (
143
- node . prelude ,
144
- ( pr_node ) =>
145
- pr_node . type === 'Function' && pr_node . name . toLowerCase ( ) === 'layer'
146
- )
105
+ let layer_fn = csstree . find ( prelude , n => n . type === 'Function' && n . name . toLowerCase ( ) === 'layer' )
147
106
if ( layer_fn ) {
148
- list . push ( [ '<anonymous>' ] )
107
+ root . add_child ( [ ] , get_anonymous_id ( ) , location )
149
108
return this . skip
150
109
}
151
110
152
111
// @import url("foo.css") layer;
153
- let layer_keyword = csstree . find (
154
- node . prelude ,
155
- ( pre_node ) =>
156
- pre_node . type === 'Identifier' && pre_node . name . toLowerCase ( ) === 'layer'
157
- )
112
+ let layer_keyword = csstree . find ( prelude , n => n . type === 'Identifier' && n . name . toLowerCase ( ) === 'layer' )
158
113
if ( layer_keyword ) {
159
- list . push ( [ '<anonymous>' ] )
114
+ root . add_child ( [ ] , get_anonymous_id ( ) , location )
160
115
return this . skip
161
116
}
162
117
}
163
- return this . skip
164
- }
118
+ } ,
119
+ leave ( node ) {
120
+ if ( is_layer ( node ) ) {
121
+ if ( node . prelude !== null && node . prelude . type === 'AtrulePrelude' ) {
122
+ let layer_names = get_layer_names ( node . prelude )
123
+ for ( let i = 0 ; i < layer_names . length ; i ++ ) {
124
+ current_stack . pop ( )
125
+ }
126
+ } else {
127
+ // pop the anonymous layer
128
+ current_stack . pop ( )
129
+ }
130
+ } else if ( node . name . toLowerCase ( ) === 'import' ) {
131
+ // clear the stack, imports can not be nested
132
+ current_stack . length = 0
133
+ }
134
+ } ,
165
135
} )
166
136
167
- return list
137
+ return root . to_plain_object ( ) . children
168
138
}
169
139
170
140
/**
171
141
* @param {string } css
172
- * @returns {LayerTree[] }
173
142
*/
174
143
export function get_tree ( css ) {
175
144
let ast = csstree . parse ( css , {
176
145
positions : true ,
177
146
parseAtrulePrelude : true ,
178
- parseRulePrelude : false ,
179
147
parseValue : false ,
148
+ parseRulePrelude : false ,
180
149
parseCustomProperty : false ,
181
150
} )
182
- let list_of_layers = get_ast_tree ( ast ) . map ( ( layer ) => layer . join ( '.' ) )
183
-
184
- let known = new List ( )
185
-
186
- for ( let name of list_of_layers ) {
187
- if ( name . includes ( '.' ) ) {
188
- let parts = name . split ( '.' )
189
- // @ts -expect-error Let me just do a while loop plz
190
- let last_item = known . push ( parts . shift ( ) )
191
-
192
- while ( parts . length > 0 && last_item ) {
193
- // @ts -expect-error Let me just do a while loop plz
194
- last_item = last_item . push ( parts . shift ( ) )
195
- }
196
-
197
- continue
198
- }
199
-
200
- known . push ( name )
201
- }
202
151
203
- return known . children . map ( ( child ) => child . serialize ( ) )
152
+ return get_tree_from_ast ( ast )
204
153
}
0 commit comments