@projectwallace/css-analyzer 5.0.0-alpha.1 → 5.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/analyzer.cjs +1 -1
  2. package/dist/analyzer.cjs.map +1 -1
  3. package/dist/analyzer.modern.js +2 -0
  4. package/dist/analyzer.modern.js.map +1 -0
  5. package/dist/analyzer.module.js +1 -1
  6. package/dist/analyzer.module.js.map +1 -1
  7. package/dist/analyzer.umd.js +1 -1
  8. package/dist/analyzer.umd.js.map +1 -1
  9. package/package.json +10 -10
  10. package/dist/analyzer.js +0 -2
  11. package/dist/analyzer.js.map +0 -1
  12. package/src/aggregate-collection.js +0 -113
  13. package/src/aggregate-collection.test.js +0 -47
  14. package/src/atrules/atrules.js +0 -76
  15. package/src/atrules/atrules.test.js +0 -289
  16. package/src/context-collection.js +0 -36
  17. package/src/countable-collection.js +0 -46
  18. package/src/declarations/declarations.js +0 -46
  19. package/src/declarations/declarations.test.js +0 -113
  20. package/src/index.js +0 -259
  21. package/src/index.test.js +0 -60
  22. package/src/properties/properties.js +0 -48
  23. package/src/properties/properties.test.js +0 -138
  24. package/src/rules/rules.js +0 -57
  25. package/src/rules/rules.test.js +0 -247
  26. package/src/selectors/complexity.test.js +0 -123
  27. package/src/selectors/selectors.js +0 -122
  28. package/src/selectors/selectors.test.js +0 -189
  29. package/src/selectors/specificity.js +0 -201
  30. package/src/selectors/specificity.test.js +0 -247
  31. package/src/smoke.test.js +0 -39
  32. package/src/stylesheet/stylesheet.test.js +0 -88
  33. package/src/values/animations.js +0 -53
  34. package/src/values/animations.test.js +0 -154
  35. package/src/values/box-shadows.test.js +0 -82
  36. package/src/values/colors.js +0 -192
  37. package/src/values/colors.test.js +0 -804
  38. package/src/values/font-families.js +0 -98
  39. package/src/values/font-families.test.js +0 -119
  40. package/src/values/font-sizes.js +0 -92
  41. package/src/values/font-sizes.test.js +0 -120
  42. package/src/values/text-shadows.test.js +0 -93
  43. package/src/values/units.test.js +0 -72
  44. package/src/values/values.js +0 -30
  45. package/src/values/vendor-prefix.js +0 -45
  46. package/src/values/vendor-prefix.test.js +0 -64
  47. package/src/values/z-index.test.js +0 -54
  48. package/src/vendor-prefix.js +0 -16
@@ -1,201 +0,0 @@
1
- import * as csstree from 'css-tree'
2
-
3
- /**
4
- * @typedef {[number, number, number]} Specificity
5
- *
6
- * @typedef {import('css-tree').Selector} Selector
7
- */
8
-
9
- /**
10
- * Compare specificity A to Specificity B
11
- * @param {Specificity} a - Specificity A
12
- * @param {Specificity} b - Specificity B
13
- * @returns {number} sortIndex - 0 when a==b, 1 when a<b, -1 when a>b
14
- */
15
- function compareSpecificity(a, b) {
16
- if (a[0] === b[0]) {
17
- if (a[1] === b[1]) {
18
- return b[2] - a[2]
19
- }
20
-
21
- return b[1] - a[1]
22
- }
23
-
24
- return b[0] - a[0]
25
- }
26
-
27
- /**
28
- *
29
- * @param {import('css-tree').SelectorList} selectorListAst
30
- * @returns {Selector} topSpecificitySelector
31
- */
32
- function selectorListSpecificities(selectorListAst) {
33
- const childSelectors = []
34
- csstree.walk(selectorListAst, {
35
- visit: 'Selector',
36
- enter(node) {
37
- childSelectors.push(analyzeSpecificity(node))
38
- }
39
- })
40
-
41
- return childSelectors.sort((a, b) => compareSpecificity(a.specificity, b.specificity))
42
- }
43
-
44
- /**
45
- * Get the Specificity for the AST of a Selector Node
46
- * @param {import('css-tree').Node} ast - AST Node for a Selector
47
- * @return {Object}
48
- * @property {Specificity} specificity
49
- * @property {number} complexity
50
- */
51
- const analyzeSpecificity = (ast) => {
52
- let A = 0
53
- let B = 0
54
- let C = 0
55
- let complexity = 0
56
- let isA11y = false
57
-
58
- csstree.walk(ast, {
59
- enter: function (selector) {
60
- switch (selector.type) {
61
- case 'IdSelector': {
62
- A++
63
- complexity++
64
- break
65
- }
66
- case 'ClassSelector': {
67
- B++
68
- complexity++
69
- break
70
- }
71
- case 'AttributeSelector': {
72
- B++
73
- complexity++
74
-
75
- if (Boolean(selector.value)) {
76
- complexity++
77
- }
78
- isA11y = selector.name.name === 'role' || selector.name.name.startsWith('aria-')
79
- break
80
- }
81
- case 'PseudoElementSelector':
82
- case 'TypeSelector': {
83
- if (selector.name !== '*') {
84
- C++
85
- }
86
- complexity++
87
- break
88
- }
89
- case 'PseudoClassSelector': {
90
- if (['before', 'after', 'first-letter', 'first-line'].includes(selector.name)) {
91
- C++
92
- complexity++
93
- return this.skip
94
- }
95
- // The specificity of an :is(), :not(), or :has() pseudo-class is
96
- // replaced by the specificity of the most specific complex
97
- // selector in its selector list argument.
98
-
99
- // CSSTree doesn't parse the arguments of :is, :has and :matches,
100
- // so we need to create an AST out of them ourselves
101
- if (['is', 'has', 'matches'].includes(selector.name)) {
102
- const rawSelectorList = csstree.find(selector, ({ type }) => type === 'Raw')
103
- const childAst = csstree.parse(rawSelectorList.value, { context: 'selectorList' })
104
- const selectorList = selectorListSpecificities(childAst)
105
- const [topA, topB, topC] = selectorList[0].specificity
106
- A += topA
107
- B += topB
108
- C += topC
109
-
110
- for (let i = 0; i < selectorList.length; i++) {
111
- complexity += selectorList[i].complexity
112
- }
113
- complexity++
114
- return
115
- }
116
-
117
- // CSSTree *does* parse the arguments of the :not() pseudo-class,
118
- // so we have direct access to the AST, instead of having to parse
119
- // the arguments ourselves.
120
- if (selector.name === 'not') {
121
- const selectorList = selectorListSpecificities(selector)
122
- const [topA, topB, topC] = selectorList[0].specificity
123
- A += topA
124
- B += topB
125
- C += topC
126
-
127
- for (let i = 0; i < selectorList.length; i++) {
128
- complexity += selectorList[i].complexity
129
- }
130
- complexity++
131
- return this.skip
132
- }
133
-
134
- // The specificity of an :nth-child() or :nth-last-child() selector
135
- // is the specificity of the pseudo class itself (counting as one
136
- // pseudo-class selector) plus the specificity of the most
137
- // specific complex selector in its selector list argument (if any).
138
- if (['nth-child', 'nth-last-child'].includes(selector.name)) {
139
- // +1 for the pseudo class itself
140
- B++
141
-
142
- const childSelectors = selectorListSpecificities(selector)
143
-
144
- if (childSelectors.length === 0) {
145
- return
146
- }
147
-
148
- const [topA, topB, topC] = childSelectors[0].specificity
149
- A += topA
150
- B += topB
151
- C += topC
152
-
153
- for (let i = 0; i < childSelectors.length; i++) {
154
- complexity += childSelectors[i].complexity;
155
- }
156
-
157
- complexity++
158
- return
159
- }
160
-
161
- // The specificity of a :where() pseudo-class is replaced by zero,
162
- // but it does count towards complexity.
163
- if (selector.name === 'where') {
164
- const rawSelectorList = csstree.find(selector, ({ type }) => type === 'Raw')
165
- const childAst = csstree.parse(rawSelectorList.value, { context: 'selectorList' })
166
- const childSelectors = selectorListSpecificities(childAst)
167
-
168
- for (let i = 0; i < childSelectors.length; i++) {
169
- complexity += childSelectors[i].complexity;
170
- }
171
-
172
- complexity++
173
- return
174
- }
175
-
176
- // Regular pseudo classes have specificity [0,1,0]
177
- complexity++
178
- B++
179
- break
180
- }
181
- case 'Combinator': {
182
- complexity++
183
- break
184
- }
185
- }
186
- }
187
- })
188
-
189
- return {
190
- /** @type Specificity */
191
- specificity: [A, B, C],
192
- complexity,
193
- isId: A > 0,
194
- isA11y
195
- }
196
- }
197
-
198
- export {
199
- analyzeSpecificity,
200
- compareSpecificity,
201
- }
@@ -1,247 +0,0 @@
1
- import { suite } from 'uvu'
2
- import * as assert from 'uvu/assert'
3
- import { analyze } from '../index.js'
4
-
5
- const Specificity = suite('Specificity')
6
-
7
- Specificity('handles the universal selector', () => {
8
- const fixture = `
9
- * {}
10
- `
11
- const actual = analyze(fixture).selectors.specificity.items
12
- const expected = [
13
- [0, 0, 0],
14
- ]
15
- assert.equal(actual, expected)
16
- })
17
-
18
- Specificity('handles ID selectors', () => {
19
- const fixture = `
20
- #id,
21
- .Foo > .Bar ~ .Baz [type="text"] + span::before #bazz #fizz #buzz #brick #house,
22
-
23
- /* https://drafts.csswg.org/selectors-4/#example-d97bd125 */
24
- :not(span,strong#foo), /* a=1 b=0 c=1 */
25
- #x34y, /* a=1 b=0 c=0 */
26
- #s12:not(FOO) /* a=1 b=0 c=1 */
27
- {}
28
- `
29
- const actual = analyze(fixture).selectors.specificity.items
30
- const expected = [
31
- [1, 0, 0],
32
- [5, 4, 2],
33
- [1, 0, 1],
34
- [1, 0, 0],
35
- [1, 0, 1],
36
- ]
37
- assert.equal(actual, expected)
38
- })
39
-
40
- Specificity('handles class selectors', () => {
41
- const fixture = `
42
- .class,
43
- .class.class
44
- {}
45
- `
46
- const actual = analyze(fixture).selectors.specificity.items
47
- const expected = [
48
- [0, 1, 0],
49
- [0, 2, 0],
50
- ]
51
- assert.equal(actual, expected)
52
- })
53
-
54
- Specificity('handles element selectors', () => {
55
- const fixture = `
56
- element,
57
- element element
58
- {}
59
- `
60
- const actual = analyze(fixture).selectors.specificity.items
61
- const expected = [
62
- [0, 0, 1],
63
- [0, 0, 2],
64
- ]
65
- assert.equal(actual, expected)
66
- })
67
-
68
- Specificity('handles the :not, :is and :has pseudo classes', () => {
69
- const fixture = `
70
- /* https://drafts.csswg.org/selectors-4/#example-bd54871c */
71
- :is(em, #foo), /* [1,0,0] like an ID selector (#foo)—when matched against any of <em>, <p id=foo>, or <em id=foo>. */
72
- :not(span, strong#foo), /* [1,0,1] like a tag selector (strong) combined with an ID selector (#foo)—when matched against any element. */
73
-
74
- /* https://drafts.csswg.org/selectors-4/#example-d97bd125 */
75
- #s12:not(FOO), /* a=1 b=0 c=1 */
76
- .foo :is(.bar, #baz) /* a=1 b=1 c=0 */
77
- {}
78
- `
79
- const actual = analyze(fixture).selectors.specificity.items
80
- const expected = [
81
- [1, 0, 0],
82
- [1, 0, 1],
83
- [1, 0, 1],
84
- [1, 1, 0],
85
- ]
86
- assert.equal(actual, expected)
87
- })
88
-
89
- Specificity('handles attribute selectors', () => {
90
- const fixture = `
91
- [attribute],
92
- .Foo > .Bar ~ .Baz [type="text"] + span::before #bazz #fizz #buzz #brick #house,
93
- H1 + *[REL=up],
94
- /* https://drafts.csswg.org/selectors-4/#attribute-representation */
95
- a[rel~="copyright"],
96
- a[hreflang=fr],
97
- a[hreflang|="en"],
98
- /* https://drafts.csswg.org/selectors-4/#attribute-substrings */
99
- object[type^="image"],
100
- a[href$=".html"],
101
- p[title*="hello"],
102
- /* https://drafts.csswg.org/selectors-4/#attribute-case */
103
- [frame=hsides i],
104
- [type="a" s],
105
- [type="A" s],
106
- /* https://drafts.csswg.org/selectors-4/#attrnmsp */
107
- [foo|att=val],
108
- [*|att],
109
- [|att]
110
- {}
111
- `
112
- const actual = analyze(fixture).selectors.specificity.items
113
- const expected = [
114
- [0, 1, 0],
115
- [5, 4, 2],
116
- [0, 1, 1],
117
-
118
- [0, 1, 1],
119
- [0, 1, 1],
120
- [0, 1, 1],
121
-
122
- [0, 1, 1],
123
- [0, 1, 1],
124
- [0, 1, 1],
125
-
126
- [0, 1, 0],
127
- [0, 1, 0],
128
- [0, 1, 0],
129
-
130
- [0, 1, 0],
131
- [0, 1, 0],
132
- [0, 1, 0],
133
- ]
134
- assert.equal(actual, expected)
135
- })
136
-
137
- Specificity('handles the :where pseudo class', () => {
138
- const fixture = `
139
- .qux:where(em, #foo#bar#baz) /* [0,1,0] only the .qux outside the :where() contributes to selector specificity. */
140
- {}
141
- `
142
- const actual = analyze(fixture).selectors.specificity.items
143
- const expected = [
144
- [0, 1, 0]
145
- ]
146
- assert.equal(actual, expected)
147
- })
148
-
149
- Specificity('handles pseudo element selectors', () => {
150
- const fixture = `
151
- element::before,
152
- element:before,
153
- element::first-letter,
154
- element:first-letter,
155
- element::after,
156
- element:after,
157
- element::first-line,
158
- element:first-line,
159
- :nth-child(2n+1)
160
- {}
161
- `
162
- const actual = analyze(fixture).selectors.specificity.items
163
- const expected = [
164
- [0, 0, 2],
165
- [0, 0, 2],
166
- [0, 0, 2],
167
- [0, 0, 2],
168
- [0, 0, 2],
169
- [0, 0, 2],
170
- [0, 0, 2],
171
- [0, 0, 2],
172
- [0, 1, 0]
173
- ]
174
- assert.equal(actual, expected)
175
- })
176
-
177
- // TODO: test this whenever CSSTree contains 'native' specificity analysis
178
- // https://twitter.com/csstree/status/1386799196355825664
179
- Specificity.skip('handles multiple :where or :is parts')
180
-
181
- Specificity('calculates the lowest value', () => {
182
- const fixture = `
183
- #test,
184
- .me,
185
- now,
186
- [crazy] ~ .selector > for [no|="good"] {}
187
- `
188
- const actual = analyze(fixture).selectors.specificity.min
189
- assert.equal(actual, [0, 0, 1])
190
- })
191
-
192
- Specificity('calculates the highest value', () => {
193
- const fixture = `
194
- #test,
195
- .me,
196
- now,
197
- [crazy] ~ .selector > for [no|="good"] {}
198
- `
199
- const actual = analyze(fixture).selectors.specificity.max
200
- assert.equal(actual, [1, 0, 0])
201
- })
202
-
203
- Specificity('calculates the mean value', () => {
204
- const fixture = `
205
- #test,
206
- .me,
207
- now,
208
- [crazy] ~ .selector > for [no|="good"] {}
209
- `
210
- const actual = analyze(fixture).selectors.specificity.mean
211
- assert.equal(actual, [.25, 1, 0.5])
212
- })
213
-
214
- Specificity('calculates the mode value', () => {
215
- const fixture = `
216
- #test,
217
- .me,
218
- now,
219
- [crazy] ~ .selector > for [no|="good"] {}
220
- `
221
- const actual = analyze(fixture).selectors.specificity.mode
222
- assert.equal(actual, [0, 0, 0.5])
223
- })
224
-
225
- Specificity('calculates the median value', () => {
226
- const fixture = `
227
- #test,
228
- .me,
229
- now,
230
- [crazy] ~ .selector > check {}
231
- `
232
- const actual = analyze(fixture).selectors.specificity.median
233
- assert.equal(actual, [0, 0.5, 0.5])
234
- })
235
-
236
- Specificity('calculates total specificity', () => {
237
- const fixture = `
238
- #test,
239
- .me,
240
- now,
241
- [crazy] ~ .selector > for [no|="good"] {}
242
- `
243
- const actual = analyze(fixture).selectors.specificity.sum
244
- assert.equal(actual, [1, 4, 2])
245
- })
246
-
247
- Specificity.run()
package/src/smoke.test.js DELETED
@@ -1,39 +0,0 @@
1
- import * as fs from 'fs'
2
- import { suite } from 'uvu';
3
- import * as assert from 'uvu/assert';
4
- import { analyze } from './index.js'
5
-
6
- const Smoke = suite('Smoke testing')
7
-
8
- Object.entries({
9
- 'Bol.com': 'bol-com-20190617',
10
- 'Bootstrap v5.0.0': 'bootstrap-5.0.0',
11
- 'CSS Tricks': 'css-tricks-20190319',
12
- 'Facebook': 'facebook-20190319',
13
- 'GitHub': 'github-20210501',
14
- 'Lego.com': 'lego-20190617',
15
- 'Trello': 'trello-20190617',
16
- 'Gazelle': 'gazelle-20210905',
17
- 'Smashing Magazine': 'smashing-magazine-20190319',
18
- }).map(([name, fileName]) => {
19
- const css = fs.readFileSync(`./src/__fixtures__/${fileName}.css`, 'utf-8')
20
- const json = fs.readFileSync(`./src/__fixtures__/${fileName}.json`, 'utf-8')
21
- return {
22
- name,
23
- fileName,
24
- json,
25
- css,
26
- }
27
- }).forEach(({ name, fileName, css, json }) => {
28
- // const result = analyze(css)
29
- // delete result.__meta__
30
- // fs.writeFileSync(`./src/__fixtures__/${fileName}.json`, JSON.stringify(result, null, 2))
31
- Smoke(name, () => {
32
- assert.not.throws(() => analyze(css))
33
- const result = analyze(css)
34
- delete result.__meta__
35
- assert.fixture(JSON.stringify(result, null, 2), json)
36
- })
37
- })
38
-
39
- Smoke.run()
@@ -1,88 +0,0 @@
1
- import { suite } from 'uvu'
2
- import * as assert from 'uvu/assert'
3
- import { analyze } from '../index.js'
4
-
5
- const Stylesheet = suite('Stylesheet')
6
-
7
- Stylesheet('counts Lines of Code', () => {
8
- const fixture = `
9
- /* doc */
10
-
11
- html {
12
- nothing: here;
13
- }
14
-
15
- @done {
16
-
17
- }
18
- `
19
- const actual = analyze(fixture).stylesheet.linesOfCode
20
- assert.is(actual, 11)
21
- })
22
-
23
- Stylesheet('counts Source Lines of Code', () => {
24
- const fixture = `
25
- rule {
26
- color: green;
27
- color: orange !important;
28
- }
29
-
30
- @media print {
31
- @media (min-width: 1000px) {
32
- @supports (display: grid) {
33
- @keyframes test {
34
- from {
35
- opacity: 1;
36
- }
37
- to {
38
- opacity: 0;
39
- }
40
- }
41
-
42
- another-rule {
43
- color: purple;
44
- }
45
- }
46
- }
47
- }
48
- `
49
- const actual = analyze(fixture).stylesheet.sourceLinesOfCode
50
- assert.is(actual, 13)
51
- })
52
-
53
- Stylesheet('calculates filesize', () => {
54
- const fixture = `test {}`
55
- const actual = analyze(fixture).stylesheet.size
56
- assert.is(actual, 7)
57
- })
58
-
59
- Stylesheet('counts comments', () => {
60
- const fixture = `
61
- /* comment 1 */
62
- test1,
63
- /* comment 2 */
64
- test2 {
65
- /* comment 3 */
66
- color: /* comment 4 */ green;
67
- background:
68
- red,
69
- /* comment 5 */
70
- yellow
71
- ;
72
- }
73
-
74
- @media all {
75
- /* comment 6 */
76
- }
77
- `
78
- const result = analyze(fixture)
79
- const actual = result.stylesheet.comments
80
- const expected = {
81
- total: 6,
82
- size: 66,
83
- }
84
-
85
- assert.equal(actual, expected)
86
- })
87
-
88
- Stylesheet.run()
@@ -1,53 +0,0 @@
1
- import { CountableCollection } from '../countable-collection.js'
2
-
3
- const timingKeywords = {
4
- 'linear': 1,
5
- 'ease': 1,
6
- 'ease-in': 1,
7
- 'ease-out': 1,
8
- 'ease-in-out': 1,
9
- 'step-start': 1,
10
- 'step-end': 1,
11
- }
12
-
13
- const analyzeAnimations = ({ animations, durations, timingFunctions, stringifyNode }) => {
14
- const allDurations = new CountableCollection(durations.map(stringifyNode))
15
- const allTimingFunctions = new CountableCollection(timingFunctions.map(stringifyNode))
16
-
17
- for (let index = 0; index < animations.length; index++) {
18
- const node = animations[index]
19
- // Flag to know if we've grabbed the first Duration
20
- // yet (the first Dimension in a shorthand)
21
- let durationFound = false
22
-
23
- node.value.children.forEach(child => {
24
- // Right after a ',' we start over again
25
- if (child.type === 'Operator') {
26
- return durationFound = false
27
- }
28
- if (child.type === 'Dimension' && durationFound === false) {
29
- durationFound = true
30
- return allDurations.push(stringifyNode(child))
31
- }
32
- if (child.type === 'Identifier' && timingKeywords[child.name]) {
33
- return allTimingFunctions.push(stringifyNode(child))
34
- }
35
- if (child.type === 'Function'
36
- && (
37
- child.name === 'cubic-bezier' || child.name === 'steps'
38
- )
39
- ) {
40
- return allTimingFunctions.push(stringifyNode(child))
41
- }
42
- })
43
- }
44
-
45
- return {
46
- durations: allDurations.count(),
47
- timingFunctions: allTimingFunctions.count(),
48
- }
49
- }
50
-
51
- export {
52
- analyzeAnimations
53
- }