Skip to content

Commit d22ef26

Browse files
authored
fix: expose Location, Specificity, UniqueWithLocations types (#483)
1 parent 7b0ac82 commit d22ef26

File tree

3 files changed

+107
-66
lines changed

3 files changed

+107
-66
lines changed

src/index.test.ts

Lines changed: 87 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { test, expect } from 'vitest'
1+
import { test, expect, describe } from 'vitest'
22
import {
33
analyze,
44
compareSpecificity,
@@ -17,72 +17,109 @@ import {
1717
systemColors,
1818
colorFunctions,
1919
colorKeywords,
20+
type UniqueWithLocations,
21+
type Location,
22+
type Specificity,
2023
} from './index.js'
2124

22-
test("exposes the 'analyze' method", () => {
23-
expect(typeof analyze).toBe('function')
24-
})
25+
describe('Public API', () => {
26+
test("exposes the 'analyze' method", () => {
27+
expect(typeof analyze).toBe('function')
28+
})
2529

26-
test('exposes the "compareSpecificity" method', () => {
27-
expect(typeof compareSpecificity).toBe('function')
28-
})
30+
test('exposes the "compareSpecificity" method', () => {
31+
expect(typeof compareSpecificity).toBe('function')
32+
})
2933

30-
test('exposes the "selectorComplexity" method', () => {
31-
expect(typeof selectorComplexity).toBe('function')
32-
})
34+
test('exposes the "selectorComplexity" method', () => {
35+
expect(typeof selectorComplexity).toBe('function')
36+
})
3337

34-
test('exposes the "isSelectorPrefixed" method', () => {
35-
expect(typeof isSelectorPrefixed).toBe('function')
36-
})
38+
test('exposes the "isSelectorPrefixed" method', () => {
39+
expect(typeof isSelectorPrefixed).toBe('function')
40+
})
3741

38-
test('exposes the "isAccessibilitySelector" method', () => {
39-
expect(typeof isAccessibilitySelector).toBe('function')
40-
})
42+
test('exposes the "isAccessibilitySelector" method', () => {
43+
expect(typeof isAccessibilitySelector).toBe('function')
44+
})
4145

42-
test('exposes the "isMediaBrowserhack" method', () => {
43-
expect(typeof isMediaBrowserhack).toBe('function')
44-
})
46+
test('exposes the "isMediaBrowserhack" method', () => {
47+
expect(typeof isMediaBrowserhack).toBe('function')
48+
})
4549

46-
test('exposes the "isSupportsBrowserhack" method', () => {
47-
expect(typeof isSupportsBrowserhack).toBe('function')
48-
})
50+
test('exposes the "isSupportsBrowserhack" method', () => {
51+
expect(typeof isSupportsBrowserhack).toBe('function')
52+
})
4953

50-
test('exposes the "isPropertyHack" method', () => {
51-
expect(typeof isPropertyHack).toBe('function')
52-
})
54+
test('exposes the "isPropertyHack" method', () => {
55+
expect(typeof isPropertyHack).toBe('function')
56+
})
5357

54-
test('exposes the "isValuePrefixed" method', () => {
55-
expect(typeof isValuePrefixed).toBe('function')
56-
})
58+
test('exposes the "isValuePrefixed" method', () => {
59+
expect(typeof isValuePrefixed).toBe('function')
60+
})
5761

58-
test('exposes the "hasVendorPrefix" method', () => {
59-
expect(typeof hasVendorPrefix).toBe('function')
60-
})
62+
test('exposes the "hasVendorPrefix" method', () => {
63+
expect(typeof hasVendorPrefix).toBe('function')
64+
})
6165

62-
test('exposes the namedColors KeywordSet', () => {
63-
expect(namedColors.has('Red')).toBeTruthy()
64-
})
66+
test('exposes the "compareSpecificity" method', () => {
67+
expect(typeof compareSpecificity).toBe('function')
68+
})
6569

66-
test('exposes the systemColors KeywordSet', () => {
67-
expect(systemColors.has('LinkText')).toBeTruthy()
68-
})
70+
test('exposes the namedColors KeywordSet', () => {
71+
expect(namedColors.has('Red')).toBeTruthy()
72+
})
6973

70-
test('exposes the colorFunctions KeywordSet', () => {
71-
expect(colorFunctions.has('okLAB')).toBeTruthy()
72-
})
74+
test('exposes the systemColors KeywordSet', () => {
75+
expect(systemColors.has('LinkText')).toBeTruthy()
76+
})
7377

74-
test('exposes the colorKeywords KeywordSet', () => {
75-
expect(colorKeywords.has('TRANSPARENT')).toBeTruthy()
76-
})
78+
test('exposes the colorFunctions KeywordSet', () => {
79+
expect(colorFunctions.has('okLAB')).toBeTruthy()
80+
})
7781

78-
test('exposes CSS keywords KeywordSet', () => {
79-
expect(cssKeywords.has('Auto')).toBeTruthy()
80-
expect(cssKeywords.has('inherit')).toBeTruthy()
81-
})
82+
test('exposes the colorKeywords KeywordSet', () => {
83+
expect(colorKeywords.has('TRANSPARENT')).toBeTruthy()
84+
})
85+
86+
test('exposes CSS keywords KeywordSet', () => {
87+
expect(cssKeywords.has('Auto')).toBeTruthy()
88+
expect(cssKeywords.has('inherit')).toBeTruthy()
89+
})
90+
91+
test('exposes the KeywordSet class', () => {
92+
expect(typeof KeywordSet).toBe('function')
93+
expect(new KeywordSet([]).constructor.name).toBe('KeywordSet')
94+
})
95+
96+
test('exposes Location type', () => {
97+
let location: Location = {
98+
offset: 0,
99+
line: 0,
100+
length: 0,
101+
column: 0,
102+
}
103+
expect(location).toHaveProperty('line')
104+
})
105+
106+
test('exposes UniqueWithLocations type', () => {
107+
let location: Location = {
108+
offset: 0,
109+
line: 0,
110+
length: 0,
111+
column: 0,
112+
}
113+
let uniqueWithLocations: UniqueWithLocations = {
114+
'my-item': [location],
115+
}
116+
expect(uniqueWithLocations).toHaveProperty('my-item')
117+
})
82118

83-
test('exposes the KeywordSet class', () => {
84-
expect(typeof KeywordSet).toBe('function')
85-
expect(new KeywordSet([]).constructor.name).toBe('KeywordSet')
119+
test('exposes Specificity type', () => {
120+
let specificity: Specificity = [1, 1, 1]
121+
expect(specificity).toHaveLength(3)
122+
})
86123
})
87124

88125
test('does not break on CSS Syntax Errors', () => {

src/index.ts

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { isValueKeyword, keywords, isValueReset } from './values/values.js'
1212
import { analyzeAnimation } from './values/animations.js'
1313
import { isValuePrefixed } from './values/vendor-prefix.js'
1414
import { ContextCollection } from './context-collection.js'
15-
import { Collection } from './collection.js'
15+
import { Collection, type Location } from './collection.js'
1616
import { AggregateCollection } from './aggregate-collection.js'
1717
import { strEquals, startsWith, endsWith } from './string-utils.js'
1818
import { hasVendorPrefix } from './vendor-prefix.js'
@@ -24,7 +24,7 @@ import { Atrule, Selector, Dimension, Url, Value, Hash, Rule, Identifier, Func,
2424
import { KeywordSet } from './keyword-set.js'
2525
import type { CssNode, Declaration, SelectorList } from 'css-tree'
2626

27-
type Specificity = [number, number, number]
27+
export type Specificity = [number, number, number]
2828

2929
let border_radius_properties = new KeywordSet([
3030
'border-radius',
@@ -63,14 +63,14 @@ function analyzeInternal<T extends boolean>(css: string, options: Options, useLo
6363

6464
/**
6565
* Recreate the authored CSS from a CSSTree node
66-
* @param {import('css-tree').CssNode} node - Node from CSSTree AST to stringify
67-
* @returns {string} str - The stringified node
66+
* @param node - Node from CSSTree AST to stringify
67+
* @returns The stringified node
6868
*/
69-
function stringifyNode(node: CssNode) {
69+
function stringifyNode(node: CssNode): string {
7070
return stringifyNodePlain(node).trim()
7171
}
7272

73-
function stringifyNodePlain(node: CssNode) {
73+
function stringifyNodePlain(node: CssNode): string {
7474
let loc = node.loc!
7575
return css.substring(loc.start.offset, loc.end.offset)
7676
}
@@ -86,7 +86,7 @@ function analyzeInternal<T extends boolean>(css: string, options: Options, useLo
8686
{
8787
size: number
8888
count: number
89-
uniqueWithLocations?: { offset: number; line: number; column: number; length: number }[]
89+
uniqueWithLocations?: Location[]
9090
}
9191
>,
9292
}
@@ -337,40 +337,41 @@ function analyzeInternal<T extends boolean>(css: string, options: Options, useLo
337337
}
338338
case Selector: {
339339
let selector = stringifyNode(node)
340+
let loc = node.loc!
340341

341342
if (this.atrule && endsWith('keyframes', this.atrule.name)) {
342-
keyframeSelectors.p(selector, node.loc!)
343+
keyframeSelectors.p(selector, loc)
343344
return this.skip
344345
}
345346

346347
if (isAccessibility(node)) {
347-
a11y.p(selector, node.loc!)
348+
a11y.p(selector, loc)
348349
}
349350

350351
let pseudos = hasPseudoClass(node)
351352
if (pseudos !== false) {
352353
for (let pseudo of pseudos) {
353-
pseudoClasses.p(pseudo, node.loc!)
354+
pseudoClasses.p(pseudo, loc)
354355
}
355356
}
356357

357358
let complexity = getComplexity(node)
358359

359360
if (isPrefixed(node)) {
360-
prefixedSelectors.p(selector, node.loc!)
361+
prefixedSelectors.p(selector, loc)
361362
}
362363

363364
uniqueSelectors.add(selector)
364365
selectorComplexities.push(complexity)
365-
uniqueSelectorComplexities.p(complexity, node.loc!)
366+
uniqueSelectorComplexities.p(complexity, loc)
366367
selectorNesting.push(nestingDepth - 1)
367-
uniqueSelectorNesting.p(nestingDepth - 1, node.loc!)
368+
uniqueSelectorNesting.p(nestingDepth - 1, loc)
368369

369370
// #region specificity
370-
let specificity = calculateForAST(node).toArray()
371+
let specificity: Specificity = calculateForAST(node).toArray()
371372
let [sa, sb, sc] = specificity
372373

373-
uniqueSpecificities.p(specificity.toString(), node.loc!)
374+
uniqueSpecificities.p(specificity.toString(), loc)
374375

375376
specificityA.push(sa)
376377
specificityB.push(sb)
@@ -396,7 +397,7 @@ function analyzeInternal<T extends boolean>(css: string, options: Options, useLo
396397
// #endregion
397398

398399
if (sa > 0) {
399-
ids.p(selector, node.loc!)
400+
ids.p(selector, loc)
400401
}
401402

402403
getCombinators(node, function onCombinator(combinator) {
@@ -1032,3 +1033,5 @@ export { keywords as cssKeywords } from './values/values.js'
10321033
export { hasVendorPrefix } from './vendor-prefix.js'
10331034

10341035
export { KeywordSet } from './keyword-set.js'
1036+
1037+
export type { Location, UniqueWithLocations } from './collection.js'

tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
// Strictness
1212
"strict": true,
1313
"noUncheckedIndexedAccess": true,
14+
"noImplicitAny": true,
1415

1516
// Type checking, not transpiling
1617
"module": "ESNext",

0 commit comments

Comments
 (0)