@primer/stylelint-config 13.0.0-rc.f33e046 → 13.0.0-rc.f49bf6d

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.
@@ -1,24 +1,186 @@
1
- import {createVariableRule} from './lib/variable-rules.js'
2
-
3
- export default createVariableRule(
4
- 'primer/typography',
5
- {
6
- 'font-size': {
7
- expects: 'a font-size variable',
8
- values: ['$body-font-size', '$h{000,00,0,1,2,3,4,5,6}-size', '$font-size-*', '1', '1em', 'inherit'],
9
- },
10
- 'font-weight': {
11
- props: 'font-weight',
12
- values: ['$font-weight-*', 'inherit'],
13
- replacements: {
14
- bold: '$font-weight-bold',
15
- normal: '$font-weight-normal',
16
- },
17
- },
18
- 'line-height': {
19
- props: 'line-height',
20
- values: ['$body-line-height', '$lh-*', '0', '1', '1em', 'inherit'],
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
- 'https://primer.style/css/utilities/typography',
24
- )
26
+ })
27
+
28
+ const fontWeightKeywordMap = {
29
+ normal: 400,
30
+ bold: 600,
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, secondaryOptions, context) => {
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.startsWith(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
+ if (replacementTokens.length > 1) {
146
+ return replacementTokens
147
+ }
148
+
149
+ return replacementTokens[0]
150
+ }
151
+ const replacement = getReplacements()
152
+ const fixable = replacement && !replacement.length
153
+
154
+ if (fixable && context.fix) {
155
+ declNode.value = value.replace(value, `var(${replacement['name']})`)
156
+ } else {
157
+ problems.push({
158
+ index: declarationValueIndex(declNode),
159
+ endIndex: declarationValueIndex(declNode) + value.length,
160
+ message: messages.rejected(value, replacement, prop),
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
+ })
174
+ }
175
+ }
176
+ })
177
+ }
178
+ }
179
+
180
+ ruleFunction.ruleName = ruleName
181
+ ruleFunction.messages = messages
182
+ ruleFunction.meta = {
183
+ fixable: true,
184
+ }
185
+
186
+ export default createPlugin(ruleName, ruleFunction)