@projectwallace/css-code-quality 0.2.1 → 0.3.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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@projectwallace/css-code-quality",
3
3
  "description": "Calculate the Code Quality score of your CSS based on a range of different quality guards",
4
- "version": "0.2.1",
4
+ "version": "0.3.2",
5
5
  "repository": "git@github.com:projectwallace/css-code-quality.git",
6
6
  "author": {
7
7
  "email": "bart@projectwallace.com",
@@ -17,7 +17,8 @@
17
17
  "maintainability"
18
18
  ],
19
19
  "files": [
20
- "dist"
20
+ "dist",
21
+ "src"
21
22
  ],
22
23
  "type": "module",
23
24
  "source": "src/index.js",
@@ -25,8 +26,13 @@
25
26
  "module": "./dist/css-code-quality.module.js",
26
27
  "unpkg": "./dist/css-code-quality.umd.js",
27
28
  "exports": {
28
- "require": "./dist/css-code-quality.cjs",
29
- "default": "./dist/css-code-quality.modern.js"
29
+ ".": {
30
+ "import": "./dist/css-code-quality.modern.js",
31
+ "require": "./dist/css-code-quality.cjs"
32
+ },
33
+ "./core": {
34
+ "import": "./src/core.js"
35
+ }
30
36
  },
31
37
  "types": "./dist/index.d.ts",
32
38
  "scripts": {
@@ -34,10 +40,10 @@
34
40
  "build": "microbundle"
35
41
  },
36
42
  "dependencies": {
37
- "@projectwallace/css-analyzer": "^5.5.0"
43
+ "@projectwallace/css-analyzer": "^5.7.1"
38
44
  },
39
45
  "devDependencies": {
40
- "microbundle": "^0.15.0",
41
- "uvu": "^0.5.3"
46
+ "microbundle": "^0.15.1",
47
+ "uvu": "^0.5.6"
42
48
  }
43
49
  }
@@ -0,0 +1,126 @@
1
+ import { compareSpecificity } from '@projectwallace/css-analyzer'
2
+
3
+ export const guards = [
4
+
5
+ // Complexity per Selector should not differ too much from the most common Complexity
6
+ result => {
7
+ const mode = result.selectors.complexity.mode
8
+ const selectorsAboveMode = result.selectors.complexity.items
9
+ .filter(c => c > mode)
10
+ .length
11
+
12
+ const outcome = {
13
+ id: 'MoreThanMostCommonSelectorComplexity',
14
+ score: 0,
15
+ value: result.selectors.total === 0 ? 0 : selectorsAboveMode / result.selectors.total,
16
+ actuals: result.selectors.complexity.items,
17
+ }
18
+
19
+ if (selectorsAboveMode > result.selectors.total * 0.1) {
20
+ const score = Math.floor(selectorsAboveMode * 0.01)
21
+ outcome.score = Math.min(10, score)
22
+ }
23
+
24
+ return outcome
25
+ },
26
+
27
+ // Specificity per Selector should not differ too much from the most common Specificity
28
+ result => {
29
+ const mode = result.selectors.specificity.mode
30
+ const selectorsAboveMode = result.selectors.specificity.items
31
+ .filter(c => compareSpecificity(c, mode) < 0)
32
+ .length
33
+
34
+ const outcome = {
35
+ id: 'MoreThanMostCommonSelectorSpecificity',
36
+ score: 0,
37
+ value: result.selectors.total === 0 ? 0 : selectorsAboveMode / result.selectors.total,
38
+ actuals: result.selectors.specificity.items,
39
+ }
40
+
41
+ if (selectorsAboveMode > result.selectors.total * 0.1) {
42
+ const score = Math.floor(selectorsAboveMode * 0.01)
43
+ outcome.score = Math.min(10, score)
44
+ }
45
+
46
+ return outcome
47
+ },
48
+
49
+ // Maximum Selector Complexity should be low
50
+ result => {
51
+ const MAX_SELECTOR_COMPLEXITY = 5
52
+ const actual = result.selectors.complexity.max
53
+
54
+ const outcome = {
55
+ id: 'MaxSelectorComplexity',
56
+ score: 0,
57
+ value: result.selectors.complexity.max,
58
+ actuals: result.selectors.complexity.items,
59
+ }
60
+
61
+ // Deduct 0.5 points per complexity over 5, up to 5 points
62
+ if (actual > MAX_SELECTOR_COMPLEXITY) {
63
+ const score = Math.ceil((actual - MAX_SELECTOR_COMPLEXITY) * 0.5)
64
+ outcome.score = Math.min(5, score)
65
+ }
66
+
67
+ return outcome
68
+ },
69
+
70
+ // Average Selector Complexity should be low
71
+ result => {
72
+ const ALLOWED_COMPLEXITY = 2
73
+ const actual = result.selectors.complexity.mean
74
+
75
+ const outcome = {
76
+ id: 'AverageSelectorComplexity',
77
+ score: 0,
78
+ value: actual,
79
+ actuals: result.selectors.complexity.items,
80
+ }
81
+
82
+ // Deduct 2 points per selector over 2
83
+ if (actual > ALLOWED_COMPLEXITY) {
84
+ const score = Math.ceil((actual - ALLOWED_COMPLEXITY) * 2)
85
+ outcome.score = Math.min(10, score)
86
+ }
87
+
88
+ return outcome
89
+ },
90
+
91
+ result => {
92
+ const ALLOWED = 0.01
93
+ const actual = result.selectors.id.ratio
94
+ const outcome = {
95
+ id: 'IdSelectorRatio',
96
+ score: 0,
97
+ value: actual,
98
+ actuals: Object.keys(result.selectors.id.unique)
99
+ }
100
+
101
+ if (actual > ALLOWED) {
102
+ const score = Math.floor((actual - ALLOWED) * 10)
103
+ outcome.score = Math.min(score, 5)
104
+ }
105
+
106
+ return outcome
107
+ },
108
+
109
+ result => {
110
+ const ALLOWED = 0.01
111
+ const actual = result.declarations.importants.ratio
112
+ const outcome = {
113
+ id: 'ImportantRatio',
114
+ score: 0,
115
+ value: actual,
116
+ actuals: result.declarations.importants.total,
117
+ }
118
+
119
+ if (actual > ALLOWED) {
120
+ const score = Math.floor((actual - ALLOWED) * 10)
121
+ outcome.score = Math.min(score, 5)
122
+ }
123
+
124
+ return outcome
125
+ },
126
+ ]
@@ -0,0 +1,126 @@
1
+ import * as assert from 'uvu/assert'
2
+ import { suite } from 'uvu'
3
+ import { calculate } from './index.js'
4
+
5
+ const Complexity = suite('Complexity')
6
+
7
+ Complexity('should deduct points for a lot of Selectors more complex than most common Complexity', () => {
8
+ const fixture = `
9
+ ${new Array(1000)
10
+ .fill('')
11
+ .map(_ => `selector { }`)
12
+ .join('')
13
+ }
14
+
15
+ ${new Array(500)
16
+ .fill('')
17
+ .map(_ => `:where(selector) { }`)
18
+ .join('')
19
+ }
20
+ `
21
+ const actual = calculate(fixture)
22
+
23
+ assert.equal(actual.complexity.violations, [
24
+ {
25
+ id: 'MoreThanMostCommonSelectorComplexity',
26
+ score: 5,
27
+ value: 1 / 3,
28
+ actuals: (new Array(1000).fill(1)).concat(new Array(500).fill(2)),
29
+ }
30
+ ])
31
+ assert.is(actual.complexity.score, 95)
32
+ })
33
+
34
+ Complexity('should deduct points for a lot of Selectors more complex than most common Specificity', () => {
35
+ const fixture = `
36
+ ${new Array(500)
37
+ .fill('')
38
+ .map(_ => `selector1 { }`)
39
+ .join('')
40
+ }
41
+
42
+ ${new Array(200)
43
+ .fill('')
44
+ .map(_ => `.selector { }`)
45
+ .join('')
46
+ }
47
+ `
48
+ const actual = calculate(fixture)
49
+
50
+ assert.equal(actual.complexity.violations, [
51
+ {
52
+ id: 'MoreThanMostCommonSelectorSpecificity',
53
+ score: 2,
54
+ value: 200 / 700,
55
+ actuals: (new Array(500).fill([0, 0, 1])).concat(new Array(200).fill([0, 1, 0])),
56
+ }
57
+ ])
58
+ assert.is(actual.complexity.score, 98)
59
+ })
60
+
61
+ Complexity('deducts points for selectors with high complexity', () => {
62
+ const fixture = `
63
+ a b c d e f {}
64
+ `
65
+ const actual = calculate(fixture)
66
+
67
+ assert.equal(actual.complexity.violations, [
68
+ {
69
+ id: 'MaxSelectorComplexity',
70
+ score: 3,
71
+ value: 11,
72
+ actuals: [11],
73
+ },
74
+ {
75
+ id: 'AverageSelectorComplexity',
76
+ score: 10,
77
+ value: 11,
78
+ actuals: [11],
79
+ }
80
+ ])
81
+ assert.is(actual.complexity.score, 87)
82
+ })
83
+
84
+ Complexity('deducts points for having a high ratio of ID selectors', () => {
85
+ const fixture = `
86
+ a {}
87
+ b {}
88
+ c {}
89
+ #a {}
90
+ `
91
+ const actual = calculate(fixture)
92
+
93
+ assert.equal(actual.complexity.violations, [
94
+ {
95
+ id: 'IdSelectorRatio',
96
+ score: 2,
97
+ value: 0.25,
98
+ actuals: ['#a'],
99
+ }
100
+ ])
101
+ assert.is(actual.complexity.score, 98)
102
+ })
103
+
104
+ Complexity('deducts points for having a high ratio !importants', () => {
105
+ const fixture = `
106
+ selector {
107
+ a: b;
108
+ c: d;
109
+ e: f;
110
+ g: h !important;
111
+ }
112
+ `
113
+ const actual = calculate(fixture)
114
+
115
+ assert.equal(actual.complexity.violations, [
116
+ {
117
+ id: 'ImportantRatio',
118
+ score: 2,
119
+ value: 0.25,
120
+ actuals: 1,
121
+ }
122
+ ])
123
+ assert.is(actual.complexity.score, 98)
124
+ })
125
+
126
+ Complexity.run()
package/src/core.js ADDED
@@ -0,0 +1,47 @@
1
+ import { guards as performanceGuards } from './performance.js'
2
+ import { guards as maintainabilityGuards } from './maintainability.js'
3
+ import { guards as complexityGuards } from './complexity.js'
4
+
5
+ function calculateScore({ result, guards }) {
6
+ let score = 100
7
+ let violations = []
8
+ let passes = []
9
+
10
+ for (const guard of guards) {
11
+ /** @type {{score: number, value: number, id: string}} */
12
+ const outcome = guard(result)
13
+
14
+ if (outcome.score > 0) {
15
+ score -= outcome.score
16
+ violations.push(outcome)
17
+ } else {
18
+ passes.push(outcome)
19
+ }
20
+ }
21
+
22
+ return {
23
+ score: Math.max(score, 0),
24
+ violations,
25
+ passes,
26
+ }
27
+ }
28
+
29
+ export function calculate(analysis) {
30
+ const performance = calculateScore({ result: analysis, guards: performanceGuards })
31
+ const maintainability = calculateScore({ result: analysis, guards: maintainabilityGuards })
32
+ const complexity = calculateScore({ result: analysis, guards: complexityGuards })
33
+
34
+ return {
35
+ /** @deprecated */
36
+ score: 0,
37
+ violations: performance.violations
38
+ .concat(maintainability.violations)
39
+ .concat(complexity.violations),
40
+ passes: performance.passes
41
+ .concat(maintainability.passes)
42
+ .concat(complexity.passes),
43
+ performance,
44
+ maintainability,
45
+ complexity,
46
+ }
47
+ }
@@ -0,0 +1,11 @@
1
+ import * as assert from 'uvu/assert'
2
+ import { suite } from 'uvu'
3
+ import { calculate } from './core.js'
4
+
5
+ const Core = suite('Core')
6
+
7
+ Core('exports calculate', () => {
8
+ assert.is(typeof calculate, 'function')
9
+ })
10
+
11
+ Core.run()
package/src/index.js ADDED
@@ -0,0 +1,7 @@
1
+ import { analyze } from '@projectwallace/css-analyzer'
2
+ import { calculate as calculateFromAnalysis } from './core.js'
3
+
4
+ export function calculate(css) {
5
+ const analysis = analyze(css)
6
+ return calculateFromAnalysis(analysis)
7
+ }
@@ -0,0 +1,20 @@
1
+ import * as assert from 'uvu/assert'
2
+ import { suite } from 'uvu'
3
+ import { calculate } from './index.js'
4
+ import { calculate as pkgCalculate } from '../dist/css-code-quality.modern.js'
5
+
6
+ const Index = suite('Index')
7
+
8
+ Index('exposes a calculcate function', () => {
9
+ assert.is(typeof calculate, 'function')
10
+ })
11
+
12
+ Index.run()
13
+
14
+ const Package = suite('NPM Package')
15
+
16
+ Package('exposes a calculate function', () => {
17
+ assert.is(typeof pkgCalculate, 'function')
18
+ })
19
+
20
+ Package.run()
@@ -0,0 +1,150 @@
1
+ export const guards = [
2
+
3
+ // Source Lines of Code should be low'
4
+ result => {
5
+ const outcome = {
6
+ id: 'SourceLinesOfCode',
7
+ score: 0,
8
+ value: result.stylesheet.sourceLinesOfCode,
9
+ }
10
+
11
+ if (result.stylesheet.sourceLinesOfCode > 10000) {
12
+ // deduct 1 point per 1000 lines of code over 10,000
13
+ const score = Math.floor((result.stylesheet.sourceLinesOfCode - 10000) / 1000)
14
+ outcome.score = Math.min(15, score)
15
+ }
16
+
17
+ return outcome
18
+ },
19
+
20
+ // Average count of Selectors per RuleSet should be low
21
+ result => {
22
+ const ALLOWED_SELECTORS_PER_RULESET = 2
23
+ const actual = result.rules.selectors.mean
24
+
25
+ const outcome = {
26
+ id: 'AverageSelectorsPerRule',
27
+ score: 0,
28
+ value: actual,
29
+ actuals: result.rules.selectors.items,
30
+ }
31
+
32
+ // Deduct 5 points per selector over 2
33
+ if (actual > ALLOWED_SELECTORS_PER_RULESET) {
34
+ const score = Math.floor((actual - ALLOWED_SELECTORS_PER_RULESET) * 5)
35
+ outcome.score = Math.min(15, score)
36
+ }
37
+
38
+ return outcome
39
+ },
40
+
41
+ // Average count of Declarations per RuleSet should be low
42
+ result => {
43
+ const ALLOWED_DECLARATIONS_PER_RULESET = 5
44
+
45
+ const outcome = {
46
+ id: 'AverageDeclarationsPerRule',
47
+ score: 0,
48
+ value: result.rules.declarations.mean,
49
+ actuals: result.rules.declarations.items,
50
+ }
51
+
52
+ // Deduct 5 points per declaration over 5
53
+ if (result.rules.declarations.mean > ALLOWED_DECLARATIONS_PER_RULESET) {
54
+ const score = Math.floor((result.rules.declarations.mean - ALLOWED_DECLARATIONS_PER_RULESET) * 5)
55
+ outcome.score = Math.min(15, score)
56
+ }
57
+
58
+ return outcome
59
+ },
60
+
61
+ // Max number of Selectors per Rule should be low
62
+ result => {
63
+ const MAX_SELECTORS_PER_RULESET = 10
64
+
65
+ const outcome = {
66
+ id: 'MaxSelectorsPerRule',
67
+ score: 0,
68
+ value: result.rules.selectors.max,
69
+ actuals: result.rules.selectors.items,
70
+ }
71
+
72
+ // Deduct 0.5 points per selectors over 10
73
+ if (result.rules.selectors.max > MAX_SELECTORS_PER_RULESET) {
74
+ const score = Math.ceil((result.rules.selectors.max - MAX_SELECTORS_PER_RULESET) * 0.5)
75
+ outcome.score = Math.min(score, 15)
76
+ }
77
+
78
+ return outcome
79
+ },
80
+
81
+ // Max number of Declarations per Rule should be low
82
+ result => {
83
+ const MAX_DECLARATIONS_PER_RULESET = 10
84
+
85
+ const outcome = {
86
+ id: 'MaxDeclarationsPerRule',
87
+ score: 0,
88
+ value: result.rules.declarations.max,
89
+ actuals: result.rules.declarations.items,
90
+ }
91
+
92
+ // Deduct 0.5 points per declarations over 10
93
+ if (result.rules.declarations.max > MAX_DECLARATIONS_PER_RULESET) {
94
+ const score = Math.ceil((result.rules.declarations.max - MAX_DECLARATIONS_PER_RULESET) * 0.5)
95
+ outcome.score = Math.min(15, score)
96
+ }
97
+
98
+ return outcome
99
+ },
100
+
101
+ // Number of Selectors per RuleSet should not differ too much from the most common amount of
102
+ // Selectors per RuleSet
103
+ result => {
104
+ const mode = result.rules.selectors.mode
105
+ const rulesHavingMoreThanMode = result.rules.selectors.items
106
+ .filter(item => item > mode)
107
+ .length
108
+
109
+ const outcome = {
110
+ id: 'MoreThanMostCommonSelectorsPerRule',
111
+ score: 0,
112
+ value: result.rules.selectors.mode,
113
+ actuals: result.rules.selectors.items,
114
+ }
115
+
116
+ // if more than 10% of RuleSets has more Selectors than most common:
117
+ if (rulesHavingMoreThanMode > result.rules.total * 0.1) {
118
+ // then deduct 0.01 for ever applicable RuleSet
119
+ const score = Math.floor(rulesHavingMoreThanMode * 0.01)
120
+ // with a maximum of 10 points
121
+ outcome.score = Math.min(15, score)
122
+ }
123
+
124
+ return outcome
125
+ },
126
+
127
+ // Number of Declarations per RuleSet should not differ too much from the most common amount of
128
+ // Declarations per RuleSet
129
+ result => {
130
+ const mode = result.rules.selectors.mode
131
+ const rulesHavingMoreThanMode = result.rules.declarations.items.filter(item => item > mode).length
132
+
133
+ const outcome = {
134
+ id: 'MoreThanMostCommonDeclarationsPerRule',
135
+ score: 0,
136
+ value: result.rules.declarations.mode,
137
+ actuals: result.rules.declarations.items,
138
+ }
139
+
140
+ // if more than 10% of RuleSets has more Declarations than most common:
141
+ if (rulesHavingMoreThanMode > result.rules.total * 0.1) {
142
+ // then deduct 0.01 for ever applicable RuleSet
143
+ const score = Math.floor(rulesHavingMoreThanMode * 0.01)
144
+ // with a maximum of 10 points
145
+ outcome.score = Math.min(15, score)
146
+ }
147
+
148
+ return outcome
149
+ },
150
+ ]