diff --git a/package-lock.json b/package-lock.json index 0270100..53e4e49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -552,6 +552,7 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.0.tgz", "integrity": "sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==", "dev": true, + "peer": true, "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -1988,6 +1989,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz", "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==", "dev": true, + "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", diff --git a/src/TreeNode.js b/src/TreeNode.js index df57fd7..c9abe54 100644 --- a/src/TreeNode.js +++ b/src/TreeNode.js @@ -28,12 +28,16 @@ export class TreeNode { // If the item already exists, add the location to its metadata if (current.children.has(name)) { - // @ts-expect-error Apparently, TypeScript doesn't know that current is a TreeNode - current.children.get(name).locations.push(location) + if (location !== undefined) { + // @ts-expect-error Apparently, TypeScript doesn't know that current is a TreeNode + current.children.get(name).locations.push(location) + } } else { // Otherwise, create the item and add the location const new_node = new TreeNode(name) - new_node.locations.push(location) + if (location !== undefined) { + new_node.locations.push(location) + } new_node.is_anonymous = name.startsWith('__anonymous') current.children.set(name, new_node) } @@ -56,8 +60,7 @@ export class TreeNode { name: this.name, is_anonymous: this.is_anonymous, locations: this.locations, - children: Array - .from(this.children.values(), (child) => child.to_plain_object()) + children: Array.from(this.children.values(), (child) => child.to_plain_object()), } } -} \ No newline at end of file +} diff --git a/src/index.js b/src/index.js index d3b124e..30bd728 100644 --- a/src/index.js +++ b/src/index.js @@ -71,7 +71,18 @@ export function layer_tree_from_ast(ast) { // @ts-expect-error CSSTree types are not updated yet in @types/css-tree let prelude = csstree.findAll(node.prelude, n => n.type === 'Layer').map(n => n.name) for (let name of prelude) { - root.add_child(current_stack, name, location) + // Split the layer name by dots to handle nested layers + let parts = name.split('.').map((/** @type {string} */ s) => s.trim()) + + // Ensure all parent layers exist and add them to the tree + for (let i = 0; i < parts.length; i++) { + let path = parts.slice(0, i) + let layerName = parts[i] + // Only add location to the final layer in dotted notation + // Create a new copy to avoid sharing references + let loc = i === parts.length - 1 ? {...location} : undefined + root.add_child(path, layerName, loc) + } } } else { for (let layer_name of get_layer_names(node.prelude)) { diff --git a/test/global.spec.js b/test/global.spec.js index 4df610a..0f08623 100644 --- a/test/global.spec.js +++ b/test/global.spec.js @@ -27,13 +27,13 @@ test('mixed imports and layers', () => { name: '__anonymous-1__', is_anonymous: true, locations: [{ line: 2, column: 3, start: 3, end: 33 }], - children: [] + children: [], }, { name: 'test', is_anonymous: false, locations: [{ line: 3, column: 3, start: 36, end: 72 }], - children: [] + children: [], }, { name: 'anotherTest', @@ -49,18 +49,99 @@ test('mixed imports and layers', () => { name: 'deepTest', is_anonymous: false, locations: [{ line: 6, column: 5, start: 121, end: 139 }], - children: [] - } - ] - } - ] + children: [], + }, + ], + }, + ], }, { name: '__anonymous-2__', is_anonymous: true, locations: [{ line: 10, column: 3, start: 176, end: 185 }], - children: [] - } + children: [], + }, + ] + assert.equal(actual, expected) +}) + +test('the fokus.dev boilerplate', () => { + let actual = layer_tree(` + @layer core, third-party, components, utility; + @layer core.reset, core.tokens, core.base; + @layer third-party.imports, third-party.overrides; + @layer components.base, components.variations; + `) + let expected = [ + { + name: 'core', + is_anonymous: false, + locations: [{ line: 2, column: 3, start: 3, end: 49 }], + children: [ + { + name: 'reset', + is_anonymous: false, + locations: [{ line: 3, column: 3, start: 52, end: 94 }], + children: [], + }, + { + name: 'tokens', + is_anonymous: false, + locations: [{ line: 3, column: 3, start: 52, end: 94 }], + children: [], + }, + { + name: 'base', + is_anonymous: false, + locations: [{ line: 3, column: 3, start: 52, end: 94 }], + children: [], + }, + ], + }, + { + name: 'third-party', + is_anonymous: false, + locations: [{ line: 2, column: 3, start: 3, end: 49 }], + children: [ + { + name: 'imports', + is_anonymous: false, + locations: [{ line: 4, column: 3, start: 97, end: 147 }], + children: [], + }, + { + name: 'overrides', + is_anonymous: false, + locations: [{ line: 4, column: 3, start: 97, end: 147 }], + children: [], + }, + ], + }, + { + name: 'components', + is_anonymous: false, + locations: [{ line: 2, column: 3, start: 3, end: 49 }], + children: [ + { + name: 'base', + is_anonymous: false, + locations: [{ line: 5, column: 3, start: 150, end: 196 }], + children: [], + }, + { + name: 'variations', + is_anonymous: false, + locations: [{ line: 5, column: 3, start: 150, end: 196 }], + children: [], + }, + ], + }, + { + name: 'utility', + is_anonymous: false, + locations: [{ line: 2, column: 3, start: 3, end: 49 }], + children: [], + }, ] assert.equal(actual, expected) }) diff --git a/test/layer.spec.js b/test/layer.spec.js index 1930bab..aa30478 100644 --- a/test/layer.spec.js +++ b/test/layer.spec.js @@ -9,7 +9,7 @@ test('single anonymous layer without body', () => { name: '__anonymous-1__', is_anonymous: true, children: [], - locations: [{ line: 1, column: 1, start: 0, end: 7 }] + locations: [{ line: 1, column: 1, start: 0, end: 7 }], }, ] assert.equal(actual, expected) @@ -22,7 +22,7 @@ test('single anonymous layer with body', () => { name: '__anonymous-1__', is_anonymous: true, children: [], - locations: [{ line: 1, column: 1, start: 0, end: 9 }] + locations: [{ line: 1, column: 1, start: 0, end: 9 }], }, ] assert.equal(actual, expected) @@ -35,7 +35,7 @@ test('single named layer without body', () => { name: 'first', is_anonymous: false, children: [], - locations: [{ line: 1, column: 1, start: 0, end: 13 }] + locations: [{ line: 1, column: 1, start: 0, end: 13 }], }, ] assert.equal(actual, expected) @@ -48,7 +48,7 @@ test('single named layer with body', () => { name: 'first', is_anonymous: false, children: [], - locations: [{ line: 1, column: 1, start: 0, end: 15 }] + locations: [{ line: 1, column: 1, start: 0, end: 15 }], }, ] assert.equal(actual, expected) @@ -61,13 +61,13 @@ test('multiple named layers in one line', () => { name: 'first', is_anonymous: false, children: [], - locations: [{ line: 1, column: 1, start: 0, end: 21 }] + locations: [{ line: 1, column: 1, start: 0, end: 21 }], }, { name: 'second', is_anonymous: false, children: [], - locations: [{ line: 1, column: 1, start: 0, end: 21 }] + locations: [{ line: 1, column: 1, start: 0, end: 21 }], }, ] assert.equal(actual, expected) @@ -85,8 +85,8 @@ test('repeated use of the same layer name', () => { children: [], locations: [ { line: 2, column: 3, start: 3, end: 18 }, - { line: 3, column: 3, start: 21, end: 36 } - ] + { line: 3, column: 3, start: 21, end: 36 }, + ], }, ] assert.equal(actual, expected) @@ -199,7 +199,7 @@ test('nested layers with anonymous layers and duplicate names', () => { children: [], locations: [{ line: 3, column: 4, start: 15, end: 30 }], }, - ] + ], }, { name: 'first',