Skip to content

Commit c7937b9

Browse files
bartvenemanclaude
andauthored
Track defined and used CSS names for custom properties and at-rules (#611)
## Summary This PR introduces a new `DefinedUsed` class to track which CSS names (custom properties, animation names, container names, layer names, and anchor names) are defined versus used in a stylesheet. This enables detection of unused declarations and references to undefined names. ## Key Changes - **New `DefinedUsed` class** (`src/defined-used.ts`): A utility class that tracks defined and used names, computing four states: - `defined`: Names that are declared - `used`: Names that are referenced - `unused`: Defined but never used - `unknown`: Used but never defined - **Custom properties tracking**: Monitors `--custom-property` declarations and `var()` function usage, including nested fallbacks - **Animation names tracking**: Tracks `@keyframes` definitions and `animation-name`/`animation` property references - **Container names tracking**: Monitors `container-name` property declarations and `@container` query references - **Layer names tracking**: Distinguishes between `@layer` ordering statements (define) and `@layer` blocks/`@import layer()` (use) - **Anchor names tracking**: Tracks `anchor-name` declarations and usage in `position-anchor`, `anchor()`, and `anchor-size()` functions - **Integration with analysis results**: Each tracked category now includes `defined`, `used`, `unused`, and `unknown` arrays in the analysis output - **Comprehensive test coverage** (`src/defined-used.test.ts`): 50+ tests covering the `DefinedUsed` class and all tracked CSS name categories ## Implementation Details - The `DefinedUsed` class uses `Set` internally for deduplication and efficient lookups - Integration points throughout the CSS parser capture definitions and usages at appropriate AST nodes - Existing tests updated to use `toMatchObject()` where new fields are added to maintain backward compatibility - Anchor names specifically track the `--` prefix to distinguish from other identifiers closes #553 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 50fac71 commit c7937b9

5 files changed

Lines changed: 611 additions & 21 deletions

File tree

src/atrules/atrules.test.ts

Lines changed: 100 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,68 @@ test('finds @layer', () => {
188188
'<anonymous>': 2,
189189
},
190190
uniquenessRatio: 28 / 50,
191+
defined: [
192+
'reset',
193+
'defaults',
194+
'patterns',
195+
'components',
196+
'utilities',
197+
'overrides',
198+
'framework',
199+
'two',
200+
'three',
201+
'one.two',
202+
'one.three',
203+
'reset.type',
204+
'default.type',
205+
'reset.media',
206+
'default.media',
207+
'default',
208+
'themes',
209+
'layouts',
210+
'structures',
211+
],
212+
used: [
213+
'test',
214+
'test.abc',
215+
'utilities',
216+
'defaults',
217+
'layer-1',
218+
'layer-2',
219+
'layer-3',
220+
'sub-layer-1',
221+
'sub-layer-2',
222+
'one',
223+
'three',
224+
'two',
225+
'one.three',
226+
'one.two',
227+
'components',
228+
],
229+
unused: [
230+
'reset',
231+
'patterns',
232+
'overrides',
233+
'framework',
234+
'reset.type',
235+
'default.type',
236+
'reset.media',
237+
'default.media',
238+
'default',
239+
'themes',
240+
'layouts',
241+
'structures',
242+
],
243+
unknown: [
244+
'test',
245+
'test.abc',
246+
'layer-1',
247+
'layer-2',
248+
'layer-3',
249+
'sub-layer-1',
250+
'sub-layer-2',
251+
'one',
252+
],
191253
}
192254

193255
expect(actual).toEqual(expected)
@@ -481,6 +543,10 @@ test('finds @imports', () => {
481543
'reset.remedy': 1,
482544
},
483545
uniquenessRatio: 1,
546+
defined: [],
547+
used: ['named-layer', 'reset.remedy'],
548+
unused: [],
549+
unknown: ['named-layer', 'reset.remedy'],
484550
}
485551
expect(actual.layer).toEqual(expected_layers)
486552
})
@@ -716,6 +782,10 @@ test('analyzes @keyframes', () => {
716782
animation: 4,
717783
},
718784
uniquenessRatio: 4 / 8,
785+
defined: ['one', 'TWO', 'three', 'animation'],
786+
used: [],
787+
unused: ['one', 'TWO', 'three', 'animation'],
788+
unknown: [],
719789
prefixed: {
720790
total: 4,
721791
totalUnique: 3,
@@ -740,6 +810,10 @@ test('counts ratio correctly when no @keyframes present', () => {
740810
totalUnique: 0,
741811
unique: {},
742812
uniquenessRatio: 0,
813+
defined: [],
814+
used: [],
815+
unused: [],
816+
unknown: [],
743817
prefixed: {
744818
total: 0,
745819
totalUnique: 0,
@@ -824,7 +898,11 @@ test('analyzes container queries', () => {
824898
'page-layout': 1,
825899
'component-library': 1,
826900
},
827-
uniquenessRatio: 1 / 1,
901+
uniquenessRatio: 1,
902+
defined: [],
903+
used: ['page-layout', 'component-library', 'card'],
904+
unused: [],
905+
unknown: ['page-layout', 'component-library', 'card'],
828906
},
829907
}
830908

@@ -838,16 +916,18 @@ test('finds named containers in @container', () => {
838916
@container style(--responsive: true) {}
839917
`
840918
const actual = analyze(fixture).atrules.container.names
841-
const TOTAL = 2
842-
const UNIQUE = 2
843919
const expected = {
844-
total: TOTAL,
845-
totalUnique: TOTAL,
920+
total: 2,
921+
totalUnique: 2,
846922
unique: {
847923
test1: 1,
848924
test2: 1,
849925
},
850-
uniquenessRatio: UNIQUE / TOTAL,
926+
uniquenessRatio: 1,
927+
defined: [],
928+
used: ['test1', 'test2'],
929+
unused: [],
930+
unknown: ['test1', 'test2'],
851931
}
852932

853933
expect(actual).toEqual(expected)
@@ -861,16 +941,18 @@ test('finds named containers in the `container-name` property', () => {
861941
}
862942
`
863943
const actual = analyze(fixture).atrules.container.names
864-
const TOTAL = 2
865-
const UNIQUE = 2
866944
const expected = {
867-
total: TOTAL,
868-
totalUnique: TOTAL,
945+
total: 2,
946+
totalUnique: 2,
869947
unique: {
870948
'my-layout': 1,
871949
'my-component': 1,
872950
},
873-
uniquenessRatio: UNIQUE / TOTAL,
951+
uniquenessRatio: 1,
952+
defined: ['my-layout', 'my-component'],
953+
used: [],
954+
unused: ['my-layout', 'my-component'],
955+
unknown: [],
874956
}
875957

876958
expect(actual).toEqual(expected)
@@ -884,16 +966,18 @@ test('finds named containers in the `container` shorthand', () => {
884966
}
885967
`
886968
const actual = analyze(fixture).atrules.container.names
887-
const TOTAL = 2
888-
const UNIQUE = 2
889969
const expected = {
890-
total: TOTAL,
891-
totalUnique: TOTAL,
970+
total: 2,
971+
totalUnique: 2,
892972
unique: {
893973
'my-layout': 1,
894974
'my-component': 1,
895975
},
896-
uniquenessRatio: UNIQUE / TOTAL,
976+
uniquenessRatio: 1,
977+
defined: ['my-layout', 'my-component'],
978+
used: [],
979+
unused: ['my-layout', 'my-component'],
980+
unknown: [],
897981
}
898982

899983
expect(actual).toEqual(expected)

0 commit comments

Comments
 (0)