Skip to content

Commit 752d9e0

Browse files
authored
fix: handle nested layers with dot notation (#10)
1 parent e6bb74c commit 752d9e0

File tree

5 files changed

+122
-25
lines changed

5 files changed

+122
-25
lines changed

package-lock.json

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/TreeNode.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,16 @@ export class TreeNode {
2828

2929
// If the item already exists, add the location to its metadata
3030
if (current.children.has(name)) {
31-
// @ts-expect-error Apparently, TypeScript doesn't know that current is a TreeNode
32-
current.children.get(name).locations.push(location)
31+
if (location !== undefined) {
32+
// @ts-expect-error Apparently, TypeScript doesn't know that current is a TreeNode
33+
current.children.get(name).locations.push(location)
34+
}
3335
} else {
3436
// Otherwise, create the item and add the location
3537
const new_node = new TreeNode(name)
36-
new_node.locations.push(location)
38+
if (location !== undefined) {
39+
new_node.locations.push(location)
40+
}
3741
new_node.is_anonymous = name.startsWith('__anonymous')
3842
current.children.set(name, new_node)
3943
}
@@ -56,8 +60,7 @@ export class TreeNode {
5660
name: this.name,
5761
is_anonymous: this.is_anonymous,
5862
locations: this.locations,
59-
children: Array
60-
.from(this.children.values(), (child) => child.to_plain_object())
63+
children: Array.from(this.children.values(), (child) => child.to_plain_object()),
6164
}
6265
}
63-
}
66+
}

src/index.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,18 @@ export function layer_tree_from_ast(ast) {
7171
// @ts-expect-error CSSTree types are not updated yet in @types/css-tree
7272
let prelude = csstree.findAll(node.prelude, n => n.type === 'Layer').map(n => n.name)
7373
for (let name of prelude) {
74-
root.add_child(current_stack, name, location)
74+
// Split the layer name by dots to handle nested layers
75+
let parts = name.split('.').map((/** @type {string} */ s) => s.trim())
76+
77+
// Ensure all parent layers exist and add them to the tree
78+
for (let i = 0; i < parts.length; i++) {
79+
let path = parts.slice(0, i)
80+
let layerName = parts[i]
81+
// Only add location to the final layer in dotted notation
82+
// Create a new copy to avoid sharing references
83+
let loc = i === parts.length - 1 ? {...location} : undefined
84+
root.add_child(path, layerName, loc)
85+
}
7586
}
7687
} else {
7788
for (let layer_name of get_layer_names(node.prelude)) {

test/global.spec.js

Lines changed: 90 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ test('mixed imports and layers', () => {
2727
name: '__anonymous-1__',
2828
is_anonymous: true,
2929
locations: [{ line: 2, column: 3, start: 3, end: 33 }],
30-
children: []
30+
children: [],
3131
},
3232
{
3333
name: 'test',
3434
is_anonymous: false,
3535
locations: [{ line: 3, column: 3, start: 36, end: 72 }],
36-
children: []
36+
children: [],
3737
},
3838
{
3939
name: 'anotherTest',
@@ -49,18 +49,99 @@ test('mixed imports and layers', () => {
4949
name: 'deepTest',
5050
is_anonymous: false,
5151
locations: [{ line: 6, column: 5, start: 121, end: 139 }],
52-
children: []
53-
}
54-
]
55-
}
56-
]
52+
children: [],
53+
},
54+
],
55+
},
56+
],
5757
},
5858
{
5959
name: '__anonymous-2__',
6060
is_anonymous: true,
6161
locations: [{ line: 10, column: 3, start: 176, end: 185 }],
62-
children: []
63-
}
62+
children: [],
63+
},
64+
]
65+
assert.equal(actual, expected)
66+
})
67+
68+
test('the fokus.dev boilerplate', () => {
69+
let actual = layer_tree(`
70+
@layer core, third-party, components, utility;
71+
@layer core.reset, core.tokens, core.base;
72+
@layer third-party.imports, third-party.overrides;
73+
@layer components.base, components.variations;
74+
`)
75+
let expected = [
76+
{
77+
name: 'core',
78+
is_anonymous: false,
79+
locations: [{ line: 2, column: 3, start: 3, end: 49 }],
80+
children: [
81+
{
82+
name: 'reset',
83+
is_anonymous: false,
84+
locations: [{ line: 3, column: 3, start: 52, end: 94 }],
85+
children: [],
86+
},
87+
{
88+
name: 'tokens',
89+
is_anonymous: false,
90+
locations: [{ line: 3, column: 3, start: 52, end: 94 }],
91+
children: [],
92+
},
93+
{
94+
name: 'base',
95+
is_anonymous: false,
96+
locations: [{ line: 3, column: 3, start: 52, end: 94 }],
97+
children: [],
98+
},
99+
],
100+
},
101+
{
102+
name: 'third-party',
103+
is_anonymous: false,
104+
locations: [{ line: 2, column: 3, start: 3, end: 49 }],
105+
children: [
106+
{
107+
name: 'imports',
108+
is_anonymous: false,
109+
locations: [{ line: 4, column: 3, start: 97, end: 147 }],
110+
children: [],
111+
},
112+
{
113+
name: 'overrides',
114+
is_anonymous: false,
115+
locations: [{ line: 4, column: 3, start: 97, end: 147 }],
116+
children: [],
117+
},
118+
],
119+
},
120+
{
121+
name: 'components',
122+
is_anonymous: false,
123+
locations: [{ line: 2, column: 3, start: 3, end: 49 }],
124+
children: [
125+
{
126+
name: 'base',
127+
is_anonymous: false,
128+
locations: [{ line: 5, column: 3, start: 150, end: 196 }],
129+
children: [],
130+
},
131+
{
132+
name: 'variations',
133+
is_anonymous: false,
134+
locations: [{ line: 5, column: 3, start: 150, end: 196 }],
135+
children: [],
136+
},
137+
],
138+
},
139+
{
140+
name: 'utility',
141+
is_anonymous: false,
142+
locations: [{ line: 2, column: 3, start: 3, end: 49 }],
143+
children: [],
144+
},
64145
]
65146
assert.equal(actual, expected)
66147
})

test/layer.spec.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ test('single anonymous layer without body', () => {
99
name: '__anonymous-1__',
1010
is_anonymous: true,
1111
children: [],
12-
locations: [{ line: 1, column: 1, start: 0, end: 7 }]
12+
locations: [{ line: 1, column: 1, start: 0, end: 7 }],
1313
},
1414
]
1515
assert.equal(actual, expected)
@@ -22,7 +22,7 @@ test('single anonymous layer with body', () => {
2222
name: '__anonymous-1__',
2323
is_anonymous: true,
2424
children: [],
25-
locations: [{ line: 1, column: 1, start: 0, end: 9 }]
25+
locations: [{ line: 1, column: 1, start: 0, end: 9 }],
2626
},
2727
]
2828
assert.equal(actual, expected)
@@ -35,7 +35,7 @@ test('single named layer without body', () => {
3535
name: 'first',
3636
is_anonymous: false,
3737
children: [],
38-
locations: [{ line: 1, column: 1, start: 0, end: 13 }]
38+
locations: [{ line: 1, column: 1, start: 0, end: 13 }],
3939
},
4040
]
4141
assert.equal(actual, expected)
@@ -48,7 +48,7 @@ test('single named layer with body', () => {
4848
name: 'first',
4949
is_anonymous: false,
5050
children: [],
51-
locations: [{ line: 1, column: 1, start: 0, end: 15 }]
51+
locations: [{ line: 1, column: 1, start: 0, end: 15 }],
5252
},
5353
]
5454
assert.equal(actual, expected)
@@ -61,13 +61,13 @@ test('multiple named layers in one line', () => {
6161
name: 'first',
6262
is_anonymous: false,
6363
children: [],
64-
locations: [{ line: 1, column: 1, start: 0, end: 21 }]
64+
locations: [{ line: 1, column: 1, start: 0, end: 21 }],
6565
},
6666
{
6767
name: 'second',
6868
is_anonymous: false,
6969
children: [],
70-
locations: [{ line: 1, column: 1, start: 0, end: 21 }]
70+
locations: [{ line: 1, column: 1, start: 0, end: 21 }],
7171
},
7272
]
7373
assert.equal(actual, expected)
@@ -85,8 +85,8 @@ test('repeated use of the same layer name', () => {
8585
children: [],
8686
locations: [
8787
{ line: 2, column: 3, start: 3, end: 18 },
88-
{ line: 3, column: 3, start: 21, end: 36 }
89-
]
88+
{ line: 3, column: 3, start: 21, end: 36 },
89+
],
9090
},
9191
]
9292
assert.equal(actual, expected)
@@ -199,7 +199,7 @@ test('nested layers with anonymous layers and duplicate names', () => {
199199
children: [],
200200
locations: [{ line: 3, column: 4, start: 15, end: 30 }],
201201
},
202-
]
202+
],
203203
},
204204
{
205205
name: 'first',

0 commit comments

Comments
 (0)