11import * as csstree from 'css-tree'
2+ import { TreeNode } from './TreeNode.js'
23
34/**
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
710 */
811
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-
5912/**
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 }
6315 */
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+ }
7625}
7726
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'
8430}
8531
8632/**
87- *
8833 * @param {import('css-tree').CssNode } ast
89- * @param {import('css-tree').Atrule } atrule
90- * @returns {string[] }
9134 */
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 } __`
10045 }
10146
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+ }
11258
11359 csstree . walk ( ast , {
11460 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+ }
11871
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+ }
12384 }
124-
125- return this . skip
12685 }
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
12789
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 ) {
13290 // @import url("foo.css") layer(test);
91+ // OR
92+ // @import url("foo.css") layer(test.nested);
13393 // @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' )
13595 if ( layer ) {
13696 // @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+ }
138101 return this . skip
139102 }
140103
141104 // @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' )
147106 if ( layer_fn ) {
148- list . push ( [ '<anonymous>' ] )
107+ root . add_child ( [ ] , get_anonymous_id ( ) , location )
149108 return this . skip
150109 }
151110
152111 // @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' )
158113 if ( layer_keyword ) {
159- list . push ( [ '<anonymous>' ] )
114+ root . add_child ( [ ] , get_anonymous_id ( ) , location )
160115 return this . skip
161116 }
162117 }
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+ } ,
165135 } )
166136
167- return list
137+ return root . to_plain_object ( ) . children
168138}
169139
170140/**
171141 * @param {string } css
172- * @returns {LayerTree[] }
173142 */
174143export function get_tree ( css ) {
175144 let ast = csstree . parse ( css , {
176145 positions : true ,
177146 parseAtrulePrelude : true ,
178- parseRulePrelude : false ,
179147 parseValue : false ,
148+ parseRulePrelude : false ,
180149 parseCustomProperty : false ,
181150 } )
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- }
202151
203- return known . children . map ( ( child ) => child . serialize ( ) )
152+ return get_tree_from_ast ( ast )
204153}
0 commit comments