@primer/stylelint-config 13.0.0-rc.fe9ab86 → 13.0.1-rc.5358628
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/dist/index.cjs +325 -91
- package/dist/index.mjs +325 -91
- package/package.json +10 -16
- package/plugins/box-shadow.js +97 -21
- package/plugins/lib/utils.js +8 -0
- package/plugins/typography.js +186 -23
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@primer/stylelint-config",
|
|
3
|
-
"version": "13.0.
|
|
3
|
+
"version": "13.0.1-rc.5358628",
|
|
4
4
|
"description": "Sharable stylelint config used by GitHub's CSS",
|
|
5
5
|
"author": "GitHub, Inc.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@github/browserslist-config": "^1.0.0",
|
|
47
47
|
"@primer/css": "^21.0.8",
|
|
48
|
-
"@primer/primitives": "^
|
|
48
|
+
"@primer/primitives": "^9.0.1",
|
|
49
49
|
"anymatch": "^3.1.1",
|
|
50
50
|
"postcss-scss": "^4.0.2",
|
|
51
51
|
"postcss-styled-syntax": "^0.6.4",
|
|
@@ -53,38 +53,32 @@
|
|
|
53
53
|
"string.prototype.matchall": "^4.0.2",
|
|
54
54
|
"stylelint": "^16.3.1",
|
|
55
55
|
"stylelint-config-standard": "^36.0.0",
|
|
56
|
-
"stylelint-css-modules-no-global-scoped-selector": "^1.0.2",
|
|
57
56
|
"stylelint-no-unsupported-browser-features": "^8.0.0",
|
|
58
57
|
"stylelint-order": "^6.0.4",
|
|
59
58
|
"stylelint-scss": "^6.2.0",
|
|
60
59
|
"stylelint-value-no-unknown-custom-properties": "^6.0.1",
|
|
61
60
|
"tap-map": "^1.0.0"
|
|
62
61
|
},
|
|
63
|
-
"overrides": {
|
|
64
|
-
"stylelint-css-modules-no-global-scoped-selector": {
|
|
65
|
-
"stylelint": "$stylelint",
|
|
66
|
-
"postcss-modules-local-by-default": "^4.0.0"
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
62
|
"prettier": "@github/prettier-config",
|
|
70
63
|
"devDependencies": {
|
|
71
64
|
"@changesets/changelog-github": "^0.5.0",
|
|
72
|
-
"@changesets/cli": "2.27.
|
|
65
|
+
"@changesets/cli": "2.27.7",
|
|
73
66
|
"@github/prettier-config": "^0.0.6",
|
|
74
|
-
"@rollup/plugin-commonjs": "^
|
|
67
|
+
"@rollup/plugin-commonjs": "^26.0.1",
|
|
75
68
|
"@rollup/plugin-json": "^6.1.0",
|
|
76
69
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
77
|
-
"@typescript-eslint/parser": "^
|
|
70
|
+
"@typescript-eslint/parser": "^8.0.1",
|
|
78
71
|
"dedent": "^1.5.3",
|
|
79
|
-
"eslint": "^8.0
|
|
80
|
-
"eslint-plugin-github": "^
|
|
72
|
+
"eslint": "^8.57.0",
|
|
73
|
+
"eslint-plugin-github": "^5.0.1",
|
|
74
|
+
"eslint-plugin-import": "^2.29.1",
|
|
81
75
|
"eslint-plugin-jest": "^28.2.0",
|
|
82
76
|
"eslint-plugin-prettier": "^5.1.3",
|
|
83
77
|
"jest": "^29.7.0",
|
|
84
78
|
"jest-preset-stylelint": "^7.0.0",
|
|
85
79
|
"prettier": "^3.2.5",
|
|
86
|
-
"rimraf": "^
|
|
87
|
-
"rollup": "^4.
|
|
80
|
+
"rimraf": "^6.0.1",
|
|
81
|
+
"rollup": "^4.21.1"
|
|
88
82
|
},
|
|
89
83
|
"jest": {
|
|
90
84
|
"transform": {},
|
package/plugins/box-shadow.js
CHANGED
|
@@ -1,22 +1,98 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
singular: true,
|
|
19
|
-
},
|
|
1
|
+
import stylelint from 'stylelint'
|
|
2
|
+
import declarationValueIndex from 'stylelint/lib/utils/declarationValueIndex.cjs'
|
|
3
|
+
import {primitivesVariables} from './lib/utils.js'
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
createPlugin,
|
|
7
|
+
utils: {report, ruleMessages, validateOptions},
|
|
8
|
+
} = stylelint
|
|
9
|
+
|
|
10
|
+
export const ruleName = 'primer/box-shadow'
|
|
11
|
+
export const messages = ruleMessages(ruleName, {
|
|
12
|
+
rejected: (value, replacement) => {
|
|
13
|
+
if (!replacement) {
|
|
14
|
+
return `Please use a Primer box-shadow variable instead of '${value}'. Consult the primer docs for a suitable replacement. https://primer.style/foundations/primitives/color#shadow or https://primer.style/foundations/primitives/size#border-size`
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return `Please replace '${value}' with a Primer box-shadow variable '${replacement['name']}'. https://primer.style/foundations/primitives/color#shadow or https://primer.style/foundations/primitives/size#border-size`
|
|
20
18
|
},
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const variables = primitivesVariables('box-shadow')
|
|
22
|
+
const shadows = []
|
|
23
|
+
|
|
24
|
+
for (const variable of variables) {
|
|
25
|
+
const name = variable['name']
|
|
26
|
+
|
|
27
|
+
// TODO: Decide if this is safe. Someday we might have variables that
|
|
28
|
+
// have 'shadow' in the name but aren't full box-shadows.
|
|
29
|
+
if (name.includes('shadow') || name.includes('boxShadow')) {
|
|
30
|
+
shadows.push(variable)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** @type {import('stylelint').Rule} */
|
|
35
|
+
const ruleFunction = (primary, secondaryOptions, context) => {
|
|
36
|
+
return (root, result) => {
|
|
37
|
+
const validOptions = validateOptions(result, ruleName, {
|
|
38
|
+
actual: primary,
|
|
39
|
+
possible: [true],
|
|
40
|
+
})
|
|
41
|
+
const validValues = shadows
|
|
42
|
+
|
|
43
|
+
if (!validOptions) return
|
|
44
|
+
|
|
45
|
+
root.walkDecls(declNode => {
|
|
46
|
+
const {prop, value} = declNode
|
|
47
|
+
|
|
48
|
+
if (prop !== 'box-shadow') return
|
|
49
|
+
|
|
50
|
+
if (value === 'none') return
|
|
51
|
+
|
|
52
|
+
const problems = []
|
|
53
|
+
|
|
54
|
+
const checkForVariable = (vars, nodeValue) => {
|
|
55
|
+
return vars.some(variable =>
|
|
56
|
+
new RegExp(`${variable['name'].replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(nodeValue),
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (checkForVariable(validValues, value)) {
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const replacement = validValues.find(variable => variable.values.includes(value))
|
|
65
|
+
|
|
66
|
+
if (replacement && context.fix) {
|
|
67
|
+
declNode.value = value.replace(value, `var(${replacement['name']})`)
|
|
68
|
+
} else {
|
|
69
|
+
problems.push({
|
|
70
|
+
index: declarationValueIndex(declNode),
|
|
71
|
+
endIndex: declarationValueIndex(declNode) + value.length,
|
|
72
|
+
message: messages.rejected(value, replacement),
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (problems.length) {
|
|
77
|
+
for (const err of problems) {
|
|
78
|
+
report({
|
|
79
|
+
index: err.index,
|
|
80
|
+
endIndex: err.endIndex,
|
|
81
|
+
message: err.message,
|
|
82
|
+
node: declNode,
|
|
83
|
+
result,
|
|
84
|
+
ruleName,
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
ruleFunction.ruleName = ruleName
|
|
93
|
+
ruleFunction.messages = messages
|
|
94
|
+
ruleFunction.meta = {
|
|
95
|
+
fixable: true,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export default createPlugin(ruleName, ruleFunction)
|
package/plugins/lib/utils.js
CHANGED
|
@@ -13,6 +13,14 @@ export function primitivesVariables(type) {
|
|
|
13
13
|
case 'border':
|
|
14
14
|
files.push('functional/size/border.json')
|
|
15
15
|
break
|
|
16
|
+
case 'typography':
|
|
17
|
+
files.push('base/typography/typography.json')
|
|
18
|
+
files.push('functional/typography/typography.json')
|
|
19
|
+
break
|
|
20
|
+
case 'box-shadow':
|
|
21
|
+
files.push('functional/themes/light.json')
|
|
22
|
+
files.push('functional/size/border.json')
|
|
23
|
+
break
|
|
16
24
|
}
|
|
17
25
|
|
|
18
26
|
for (const file of files) {
|
package/plugins/typography.js
CHANGED
|
@@ -1,24 +1,187 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
1
|
+
import stylelint from 'stylelint'
|
|
2
|
+
import declarationValueIndex from 'stylelint/lib/utils/declarationValueIndex.cjs'
|
|
3
|
+
import {primitivesVariables} from './lib/utils.js'
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
createPlugin,
|
|
7
|
+
utils: {report, ruleMessages, validateOptions},
|
|
8
|
+
} = stylelint
|
|
9
|
+
|
|
10
|
+
export const ruleName = 'primer/typography'
|
|
11
|
+
export const messages = ruleMessages(ruleName, {
|
|
12
|
+
rejected: (value, replacement) => {
|
|
13
|
+
// no possible replacement
|
|
14
|
+
if (!replacement) {
|
|
15
|
+
return `Please use a Primer typography variable instead of '${value}'. Consult the primer docs for a suitable replacement. https://primer.style/foundations/primitives/typography`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// multiple possible replacements
|
|
19
|
+
if (replacement.length) {
|
|
20
|
+
return `Please use one of the following Primer typography variables instead of '${value}': ${replacement.map(replacementObj => `'${replacementObj.name}'`).join(', ')}. https://primer.style/foundations/primitives/typography`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// one possible replacement
|
|
24
|
+
return `Please replace '${value}' with Primer typography variable '${replacement['name']}'. https://primer.style/foundations/primitives/typography`
|
|
22
25
|
},
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const fontWeightKeywordMap = {
|
|
29
|
+
normal: 400,
|
|
30
|
+
bold: 700,
|
|
31
|
+
bolder: 600,
|
|
32
|
+
lighter: 300,
|
|
33
|
+
}
|
|
34
|
+
const getClosestFontWeight = (goalWeightNumber, fontWeightsTokens) => {
|
|
35
|
+
return fontWeightsTokens.reduce((prev, curr) =>
|
|
36
|
+
Math.abs(curr.values - goalWeightNumber) < Math.abs(prev.values - goalWeightNumber) ? curr : prev,
|
|
37
|
+
).values
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const variables = primitivesVariables('typography')
|
|
41
|
+
const fontSizes = []
|
|
42
|
+
const fontWeights = []
|
|
43
|
+
const lineHeights = []
|
|
44
|
+
const fontStacks = []
|
|
45
|
+
const fontShorthands = []
|
|
46
|
+
|
|
47
|
+
// Props that we want to check for typography variables
|
|
48
|
+
const propList = ['font-size', 'font-weight', 'line-height', 'font-family', 'font']
|
|
49
|
+
|
|
50
|
+
for (const variable of variables) {
|
|
51
|
+
const name = variable['name']
|
|
52
|
+
|
|
53
|
+
if (name.includes('size')) {
|
|
54
|
+
fontSizes.push(variable)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (name.includes('weight')) {
|
|
58
|
+
fontWeights.push(variable)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (name.includes('lineHeight')) {
|
|
62
|
+
lineHeights.push(variable)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (name.includes('fontStack')) {
|
|
66
|
+
fontStacks.push(variable)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (name.includes('shorthand')) {
|
|
70
|
+
fontShorthands.push(variable)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** @type {import('stylelint').Rule} */
|
|
75
|
+
const ruleFunction = primary => {
|
|
76
|
+
return (root, result) => {
|
|
77
|
+
const validOptions = validateOptions(result, ruleName, {
|
|
78
|
+
actual: primary,
|
|
79
|
+
possible: [true],
|
|
80
|
+
})
|
|
81
|
+
let validValues = []
|
|
82
|
+
|
|
83
|
+
if (!validOptions) return
|
|
84
|
+
|
|
85
|
+
root.walkDecls(declNode => {
|
|
86
|
+
const {prop, value} = declNode
|
|
87
|
+
|
|
88
|
+
if (!propList.some(typographyProp => prop === typographyProp)) return
|
|
89
|
+
|
|
90
|
+
const problems = []
|
|
91
|
+
|
|
92
|
+
const checkForVariable = (vars, nodeValue) =>
|
|
93
|
+
vars.some(variable =>
|
|
94
|
+
new RegExp(`${variable['name'].replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(nodeValue),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
// Exact values to ignore.
|
|
98
|
+
if (value === 'inherit') {
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
switch (prop) {
|
|
103
|
+
case 'font-size':
|
|
104
|
+
validValues = fontSizes
|
|
105
|
+
break
|
|
106
|
+
case 'font-weight':
|
|
107
|
+
validValues = fontWeights
|
|
108
|
+
break
|
|
109
|
+
case 'line-height':
|
|
110
|
+
validValues = lineHeights
|
|
111
|
+
break
|
|
112
|
+
case 'font-family':
|
|
113
|
+
validValues = fontStacks
|
|
114
|
+
break
|
|
115
|
+
case 'font':
|
|
116
|
+
validValues = fontShorthands
|
|
117
|
+
break
|
|
118
|
+
default:
|
|
119
|
+
validValues = []
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (checkForVariable(validValues, value)) {
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const getReplacements = () => {
|
|
127
|
+
const replacementTokens = validValues.filter(variable => {
|
|
128
|
+
if (!(variable.values instanceof Array)) {
|
|
129
|
+
let nodeValue = value
|
|
130
|
+
|
|
131
|
+
if (prop === 'font-weight') {
|
|
132
|
+
nodeValue = getClosestFontWeight(fontWeightKeywordMap[value] || value, fontWeights)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return variable.values.toString() === nodeValue.toString()
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return variable.values.includes(value.replace('-', ''))
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
if (!replacementTokens.length) {
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return replacementTokens[0]
|
|
146
|
+
}
|
|
147
|
+
const replacement = getReplacements()
|
|
148
|
+
const fixable = replacement && !replacement.length
|
|
149
|
+
let fixedValue = ''
|
|
150
|
+
if (fixable) {
|
|
151
|
+
fixedValue = value.replace(value, `var(${replacement['name']})`)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
problems.push({
|
|
155
|
+
index: declarationValueIndex(declNode),
|
|
156
|
+
endIndex: declarationValueIndex(declNode) + value.length,
|
|
157
|
+
message: messages.rejected(value, replacement, prop),
|
|
158
|
+
fix: () => {
|
|
159
|
+
if (!fixable) return
|
|
160
|
+
declNode.value = fixedValue
|
|
161
|
+
},
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
if (problems.length) {
|
|
165
|
+
for (const err of problems) {
|
|
166
|
+
report({
|
|
167
|
+
index: err.index,
|
|
168
|
+
endIndex: err.endIndex,
|
|
169
|
+
message: err.message,
|
|
170
|
+
node: declNode,
|
|
171
|
+
result,
|
|
172
|
+
ruleName,
|
|
173
|
+
fix: err.fix,
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
ruleFunction.ruleName = ruleName
|
|
182
|
+
ruleFunction.messages = messages
|
|
183
|
+
ruleFunction.meta = {
|
|
184
|
+
fixable: true,
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export default createPlugin(ruleName, ruleFunction)
|