eslint-plugin-primer-react 4.0.3 → 4.0.4-rc.5058fcb
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/.changeset/README.md +3 -3
- package/.eslintrc.js +39 -0
- package/.github/dependabot.yml +11 -5
- package/.github/workflows/add-to-inbox.yml +33 -0
- package/.github/workflows/ci.yml +34 -18
- package/.github/workflows/release.yml +2 -2
- package/.github/workflows/release_canary.yml +8 -5
- package/.github/workflows/release_candidate.yml +4 -4
- package/.markdownlint-cli2.cjs +26 -0
- package/.nvmrc +1 -0
- package/.prettierignore +3 -0
- package/CHANGELOG.md +7 -1
- package/README.md +4 -2
- package/docs/rules/a11y-explicit-heading.md +17 -7
- package/docs/rules/a11y-tooltip-interactive-trigger.md +6 -2
- package/docs/rules/direct-slot-children.md +49 -46
- package/docs/rules/new-color-css-vars-have-fallback.md +25 -0
- package/docs/rules/new-css-color-vars.md +19 -14
- package/docs/rules/no-deprecated-colors.md +91 -82
- package/docs/rules/no-system-props.md +12 -5
- package/package-lock.json +15583 -0
- package/package.json +22 -14
- package/src/configs/components.js +19 -19
- package/src/configs/recommended.js +9 -8
- package/src/index.js +4 -3
- package/src/rules/__tests__/a11y-explicit-heading.test.js +22 -25
- package/src/rules/__tests__/a11y-tooltip-interactive-trigger.test.js +43 -43
- package/src/rules/__tests__/direct-slot-children.test.js +36 -36
- package/src/rules/__tests__/new-color-css-vars.test.js +612 -44
- package/src/rules/__tests__/new-css-vars-have-fallback.test.js +31 -0
- package/src/rules/__tests__/no-deprecated-colors.test.js +66 -66
- package/src/rules/__tests__/no-system-props.test.js +51 -51
- package/src/rules/a11y-explicit-heading.js +16 -13
- package/src/rules/a11y-tooltip-interactive-trigger.js +21 -22
- package/src/rules/direct-slot-children.js +11 -11
- package/src/rules/new-color-css-vars-have-fallback.js +87 -0
- package/src/rules/new-color-css-vars.js +97 -83
- package/src/rules/no-deprecated-colors.js +15 -15
- package/src/rules/no-system-props.js +21 -21
- package/src/url.js +1 -1
- package/src/utils/__tests__/flatten-components.test.js +13 -13
- package/src/utils/css-variable-map.json +538 -184
- package/src/utils/flatten-components.js +11 -11
- package/src/utils/is-imported-from.js +1 -1
- package/src/utils/new-color-css-vars-map.json +326 -0
- /package/.github/{workflows/CODEOWNERS → CODEOWNERS} +0 -0
|
@@ -7,7 +7,7 @@ const isInteractive = child => {
|
|
|
7
7
|
const childName = getJSXOpeningElementName(child.openingElement)
|
|
8
8
|
return (
|
|
9
9
|
['button', 'summary', 'select', 'textarea', 'a', 'input', 'link', 'iconbutton', 'textinput'].includes(
|
|
10
|
-
childName.toLowerCase()
|
|
10
|
+
childName.toLowerCase(),
|
|
11
11
|
) && !hasDisabledAttr(child)
|
|
12
12
|
)
|
|
13
13
|
}
|
|
@@ -22,10 +22,9 @@ const isAnchorTag = el => {
|
|
|
22
22
|
return openingEl === 'a' || openingEl.toLowerCase() === 'link'
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
const isJSXValue =
|
|
26
|
-
const node = attributes.find(attribute => propName(attribute) === 'href')
|
|
27
|
-
const isJSXExpression = node.value.type === 'JSXExpressionContainer' && node
|
|
28
|
-
&& typeof getPropValue(node) === 'string';
|
|
25
|
+
const isJSXValue = attributes => {
|
|
26
|
+
const node = attributes.find(attribute => propName(attribute) === 'href')
|
|
27
|
+
const isJSXExpression = node.value.type === 'JSXExpressionContainer' && node && typeof getPropValue(node) === 'string'
|
|
29
28
|
|
|
30
29
|
return isJSXExpression
|
|
31
30
|
}
|
|
@@ -34,8 +33,8 @@ const isInteractiveAnchor = child => {
|
|
|
34
33
|
const hasHref = getJSXOpeningElementAttribute(child.openingElement, 'href')
|
|
35
34
|
if (!hasHref) return false
|
|
36
35
|
const href = getJSXOpeningElementAttribute(child.openingElement, 'href').value.value
|
|
37
|
-
const hasJSXValue = isJSXValue(child.openingElement.attributes)
|
|
38
|
-
const isAnchorInteractive = (typeof href === 'string' && href !== '' || hasJSXValue
|
|
36
|
+
const hasJSXValue = isJSXValue(child.openingElement.attributes)
|
|
37
|
+
const isAnchorInteractive = (typeof href === 'string' && href !== '') || hasJSXValue
|
|
39
38
|
|
|
40
39
|
return isAnchorInteractive
|
|
41
40
|
}
|
|
@@ -73,18 +72,18 @@ const checks = [
|
|
|
73
72
|
{
|
|
74
73
|
id: 'nonInteractiveLink',
|
|
75
74
|
filter: jsxElement => isAnchorTag(jsxElement),
|
|
76
|
-
check: isInteractiveAnchor
|
|
75
|
+
check: isInteractiveAnchor,
|
|
77
76
|
},
|
|
78
77
|
{
|
|
79
78
|
id: 'nonInteractiveInput',
|
|
80
79
|
filter: jsxElement => isInputTag(jsxElement),
|
|
81
|
-
check: isInteractiveInput
|
|
80
|
+
check: isInteractiveInput,
|
|
82
81
|
},
|
|
83
82
|
{
|
|
84
83
|
id: 'nonInteractiveTrigger',
|
|
85
84
|
filter: jsxElement => isOtherThanAnchorOrInput(jsxElement),
|
|
86
|
-
check: isInteractive
|
|
87
|
-
}
|
|
85
|
+
check: isInteractive,
|
|
86
|
+
},
|
|
88
87
|
]
|
|
89
88
|
|
|
90
89
|
const checkTriggerElement = jsxNode => {
|
|
@@ -137,10 +136,10 @@ module.exports = {
|
|
|
137
136
|
{
|
|
138
137
|
properties: {
|
|
139
138
|
skipImportCheck: {
|
|
140
|
-
type: 'boolean'
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
139
|
+
type: 'boolean',
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
},
|
|
144
143
|
],
|
|
145
144
|
messages: {
|
|
146
145
|
nonInteractiveTrigger:
|
|
@@ -149,8 +148,8 @@ module.exports = {
|
|
|
149
148
|
'Anchor tags without an href attribute are not interactive, therefore they cannot be used as a trigger for a tooltip. Please add an href attribute or use an alternative interactive element instead',
|
|
150
149
|
nonInteractiveInput:
|
|
151
150
|
'Hidden or disabled inputs are not interactive and cannot be used as a trigger for a tooltip. Please use an alternate input type or use a different interactive element instead',
|
|
152
|
-
singleChild: 'The `Tooltip` component expects a single React element as a child.'
|
|
153
|
-
}
|
|
151
|
+
singleChild: 'The `Tooltip` component expects a single React element as a child.',
|
|
152
|
+
},
|
|
154
153
|
},
|
|
155
154
|
create(context) {
|
|
156
155
|
return {
|
|
@@ -166,23 +165,23 @@ module.exports = {
|
|
|
166
165
|
if (jsxNode.children.filter(child => child.type === 'JSXElement').length > 1) {
|
|
167
166
|
context.report({
|
|
168
167
|
node: jsxNode,
|
|
169
|
-
messageId: 'singleChild'
|
|
168
|
+
messageId: 'singleChild',
|
|
170
169
|
})
|
|
171
170
|
} else {
|
|
172
171
|
// Check if the child is interactive
|
|
173
172
|
const errors = checkTriggerElement(jsxNode)
|
|
174
173
|
|
|
175
174
|
if (errors) {
|
|
176
|
-
for (const [
|
|
175
|
+
for (const [_key, value] of errors.entries()) {
|
|
177
176
|
context.report({
|
|
178
177
|
node: jsxNode,
|
|
179
|
-
messageId: value
|
|
178
|
+
messageId: value,
|
|
180
179
|
})
|
|
181
180
|
}
|
|
182
181
|
}
|
|
183
182
|
}
|
|
184
183
|
}
|
|
185
|
-
}
|
|
184
|
+
},
|
|
186
185
|
}
|
|
187
|
-
}
|
|
186
|
+
},
|
|
188
187
|
}
|
|
@@ -13,7 +13,7 @@ const slotParentToChildMap = {
|
|
|
13
13
|
RadioGroup: ['RadioGroup.Label', 'RadioGroup.Caption', 'RadioGroup.Validation'],
|
|
14
14
|
CheckboxGroup: ['CheckboxGroup.Label', 'CheckboxGroup.Caption', 'CheckboxGroup.Validation'],
|
|
15
15
|
MarkdownEditor: ['MarkdownEditor.Toolbar', 'MarkdownEditor.Actions', 'MarkdownEditor.Label'],
|
|
16
|
-
'MarkdownEditor.Footer': ['MarkdownEditor.Actions', 'MarkdownEditor.FooterButton']
|
|
16
|
+
'MarkdownEditor.Footer': ['MarkdownEditor.Actions', 'MarkdownEditor.FooterButton'],
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
const slotChildToParentMap = Object.entries(slotParentToChildMap).reduce((acc, [parent, children]) => {
|
|
@@ -34,14 +34,14 @@ module.exports = {
|
|
|
34
34
|
{
|
|
35
35
|
properties: {
|
|
36
36
|
skipImportCheck: {
|
|
37
|
-
type: 'boolean'
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
37
|
+
type: 'boolean',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
41
|
],
|
|
42
42
|
messages: {
|
|
43
|
-
directSlotChildren: '{{childName}} must be a direct child of {{parentName}}.'
|
|
44
|
-
}
|
|
43
|
+
directSlotChildren: '{{childName}} must be a direct child of {{parentName}}.',
|
|
44
|
+
},
|
|
45
45
|
},
|
|
46
46
|
create(context) {
|
|
47
47
|
const stack = []
|
|
@@ -67,8 +67,8 @@ module.exports = {
|
|
|
67
67
|
messageId: 'directSlotChildren',
|
|
68
68
|
data: {
|
|
69
69
|
childName: name,
|
|
70
|
-
parentName: expectedParentNames.length > 1 ? expectedParentNames.join(' or ') : expectedParentNames[0]
|
|
71
|
-
}
|
|
70
|
+
parentName: expectedParentNames.length > 1 ? expectedParentNames.join(' or ') : expectedParentNames[0],
|
|
71
|
+
},
|
|
72
72
|
})
|
|
73
73
|
}
|
|
74
74
|
}
|
|
@@ -81,7 +81,7 @@ module.exports = {
|
|
|
81
81
|
JSXClosingElement() {
|
|
82
82
|
// Pop the current element off the stack
|
|
83
83
|
stack.pop()
|
|
84
|
-
}
|
|
84
|
+
},
|
|
85
85
|
}
|
|
86
|
-
}
|
|
86
|
+
},
|
|
87
87
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const cssVars = require('../utils/new-color-css-vars-map')
|
|
2
|
+
|
|
3
|
+
const reportError = (propertyName, valueNode, context) => {
|
|
4
|
+
// performance optimisation: exit early
|
|
5
|
+
if (valueNode.type !== 'Literal' && valueNode.type !== 'TemplateElement') return
|
|
6
|
+
// get property value
|
|
7
|
+
const value = valueNode.type === 'Literal' ? valueNode.value : valueNode.value.cooked
|
|
8
|
+
// return if value is not a string
|
|
9
|
+
if (typeof value !== 'string') return
|
|
10
|
+
// return if value does not include variable
|
|
11
|
+
if (!value.includes('var(')) return
|
|
12
|
+
|
|
13
|
+
const varRegex = /var\([^(),)]+\)/g
|
|
14
|
+
|
|
15
|
+
const match = value.match(varRegex)
|
|
16
|
+
// return if no matches
|
|
17
|
+
if (!match) return
|
|
18
|
+
const vars = match.flatMap(match =>
|
|
19
|
+
match
|
|
20
|
+
.slice(4, -1)
|
|
21
|
+
.trim()
|
|
22
|
+
.split(/\s*,\s*/g),
|
|
23
|
+
)
|
|
24
|
+
for (const cssVar of vars) {
|
|
25
|
+
// return if no repalcement exists
|
|
26
|
+
if (!cssVars?.includes(cssVar)) return
|
|
27
|
+
// report the error
|
|
28
|
+
context.report({
|
|
29
|
+
node: valueNode,
|
|
30
|
+
message: `Expected a fallback value for CSS variable ${cssVar}. New color variables fallbacks, check primer.style/primitives to find the correct value.`,
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const reportOnObject = (node, context) => {
|
|
36
|
+
const propertyName = node.key.name
|
|
37
|
+
if (node.value?.type === 'Literal') {
|
|
38
|
+
reportError(propertyName, node.value, context)
|
|
39
|
+
} else if (node.value?.type === 'ConditionalExpression') {
|
|
40
|
+
reportError(propertyName, node.value.consequent, context)
|
|
41
|
+
reportError(propertyName, node.value.alternate, context)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const reportOnProperty = (node, context) => {
|
|
46
|
+
const propertyName = node.name.name
|
|
47
|
+
if (node.value?.type === 'Literal') {
|
|
48
|
+
reportError(propertyName, node.value, context)
|
|
49
|
+
} else if (node.value?.type === 'JSXExpressionContainer' && node.value.expression?.type === 'ConditionalExpression') {
|
|
50
|
+
reportError(propertyName, node.value.expression.consequent, context)
|
|
51
|
+
reportError(propertyName, node.value.expression.alternate, context)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const reportOnValue = (node, context) => {
|
|
56
|
+
if (node?.type === 'Literal') {
|
|
57
|
+
reportError(undefined, node, context)
|
|
58
|
+
} else if (node?.type === 'JSXExpressionContainer' && node.expression?.type === 'ConditionalExpression') {
|
|
59
|
+
reportError(undefined, node.value.expression.consequent, context)
|
|
60
|
+
reportError(undefined, node.value.expression.alternate, context)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const reportOnTemplateElement = (node, context) => {
|
|
65
|
+
reportError(undefined, node, context)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = {
|
|
69
|
+
meta: {
|
|
70
|
+
type: 'suggestion',
|
|
71
|
+
},
|
|
72
|
+
/** @param {import('eslint').Rule.RuleContext} context */
|
|
73
|
+
create(context) {
|
|
74
|
+
return {
|
|
75
|
+
// sx OR style property on elements
|
|
76
|
+
['JSXAttribute:matches([name.name=sx], [name.name=style]) ObjectExpression Property']: node =>
|
|
77
|
+
reportOnObject(node, context),
|
|
78
|
+
// property on element like stroke or fill
|
|
79
|
+
['JSXAttribute[name.name!=sx][name.name!=style]']: node => reportOnProperty(node, context),
|
|
80
|
+
// variable that is a value
|
|
81
|
+
[':matches(VariableDeclarator, ReturnStatement) > Literal']: node => reportOnValue(node, context),
|
|
82
|
+
// variable that is a value
|
|
83
|
+
[':matches(VariableDeclarator, ReturnStatement) > TemplateElement']: node =>
|
|
84
|
+
reportOnTemplateElement(node, context),
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
}
|
|
@@ -1,106 +1,120 @@
|
|
|
1
1
|
const cssVars = require('../utils/css-variable-map.json')
|
|
2
2
|
|
|
3
|
+
const reportError = (propertyName, valueNode, context, suggestFix = true) => {
|
|
4
|
+
// performance optimisation: exit early
|
|
5
|
+
if (valueNode.type !== 'Literal' && valueNode.type !== 'TemplateElement') return
|
|
6
|
+
// get property value
|
|
7
|
+
const value = valueNode.type === 'Literal' ? valueNode.value : valueNode.value.cooked
|
|
8
|
+
// return if value is not a string
|
|
9
|
+
if (typeof value !== 'string') return
|
|
10
|
+
// return if value does not include variable
|
|
11
|
+
if (!value.includes('var(')) return
|
|
12
|
+
|
|
13
|
+
const varRegex = /var\([^)]+\)/g
|
|
14
|
+
|
|
15
|
+
const match = value.match(varRegex)
|
|
16
|
+
if (!match) return
|
|
17
|
+
const vars = match.flatMap(match =>
|
|
18
|
+
match
|
|
19
|
+
.slice(4, -1)
|
|
20
|
+
.trim()
|
|
21
|
+
.split(/\s*,\s*/g),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
for (const cssVar of vars) {
|
|
25
|
+
// get the array of objects for the variable name (e.g. --color-fg-primary)
|
|
26
|
+
const cssVarObjects = cssVars[cssVar]
|
|
27
|
+
// get the object that contains the property name or the first one (default)
|
|
28
|
+
const varObjectForProp = propertyName
|
|
29
|
+
? cssVarObjects?.find(prop => prop.props.includes(propertyName))
|
|
30
|
+
: cssVarObjects?.[0]
|
|
31
|
+
// return if no replacement exists
|
|
32
|
+
if (!varObjectForProp?.replacement) return
|
|
33
|
+
// report the error
|
|
34
|
+
context.report({
|
|
35
|
+
node: valueNode,
|
|
36
|
+
message: `Replace var(${cssVar}) with var(${varObjectForProp.replacement}, var(${cssVar}))`,
|
|
37
|
+
fix: suggestFix
|
|
38
|
+
? fixer => {
|
|
39
|
+
const fixedString = value.replaceAll(cssVar, `${varObjectForProp.replacement}, var(${cssVar})`)
|
|
40
|
+
return fixer.replaceText(valueNode, valueNode.type === 'Literal' ? `'${fixedString}'` : fixedString)
|
|
41
|
+
}
|
|
42
|
+
: undefined,
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const reportOnObject = (node, context) => {
|
|
48
|
+
const propertyName = node.key.name
|
|
49
|
+
if (node.value?.type === 'Literal') {
|
|
50
|
+
reportError(propertyName, node.value, context)
|
|
51
|
+
} else if (node.value?.type === 'ConditionalExpression') {
|
|
52
|
+
reportError(propertyName, node.value.consequent, context)
|
|
53
|
+
reportError(propertyName, node.value.alternate, context)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const reportOnProperty = (node, context) => {
|
|
58
|
+
const propertyName = node.name.name
|
|
59
|
+
if (node.value?.type === 'Literal') {
|
|
60
|
+
reportError(propertyName, node.value, context)
|
|
61
|
+
} else if (node.value?.type === 'JSXExpressionContainer' && node.value.expression?.type === 'ConditionalExpression') {
|
|
62
|
+
reportError(propertyName, node.value.expression.consequent, context)
|
|
63
|
+
reportError(propertyName, node.value.expression.alternate, context)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const reportOnValue = (node, context) => {
|
|
68
|
+
if (node?.type === 'Literal') {
|
|
69
|
+
reportError(undefined, node, context)
|
|
70
|
+
} else if (node?.type === 'JSXExpressionContainer' && node.expression?.type === 'ConditionalExpression') {
|
|
71
|
+
reportError(undefined, node.value.expression.consequent, context)
|
|
72
|
+
reportError(undefined, node.value.expression.alternate, context)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const reportOnTemplateElement = (node, context) => {
|
|
77
|
+
reportError(undefined, node, context, false)
|
|
78
|
+
}
|
|
79
|
+
|
|
3
80
|
module.exports = {
|
|
4
81
|
meta: {
|
|
5
82
|
type: 'suggestion',
|
|
6
83
|
hasSuggestions: true,
|
|
7
84
|
fixable: 'code',
|
|
8
85
|
docs: {
|
|
9
|
-
description: 'Upgrade legacy CSS variables to Primitives v8 in sx prop'
|
|
86
|
+
description: 'Upgrade legacy CSS variables to Primitives v8 in sx prop',
|
|
10
87
|
},
|
|
11
88
|
schema: [
|
|
12
89
|
{
|
|
13
90
|
type: 'object',
|
|
14
91
|
properties: {
|
|
15
92
|
skipImportCheck: {
|
|
16
|
-
type: 'boolean'
|
|
93
|
+
type: 'boolean',
|
|
17
94
|
},
|
|
18
95
|
checkAllStrings: {
|
|
19
|
-
type: 'boolean'
|
|
20
|
-
}
|
|
96
|
+
type: 'boolean',
|
|
97
|
+
},
|
|
21
98
|
},
|
|
22
|
-
additionalProperties: false
|
|
23
|
-
}
|
|
24
|
-
]
|
|
99
|
+
additionalProperties: false,
|
|
100
|
+
},
|
|
101
|
+
],
|
|
25
102
|
},
|
|
26
103
|
/** @param {import('eslint').Rule.RuleContext} context */
|
|
27
104
|
create(context) {
|
|
28
|
-
const styledSystemProps = [
|
|
29
|
-
'bg',
|
|
30
|
-
'backgroundColor',
|
|
31
|
-
'color',
|
|
32
|
-
'borderColor',
|
|
33
|
-
'borderTopColor',
|
|
34
|
-
'borderRightColor',
|
|
35
|
-
'borderBottomColor',
|
|
36
|
-
'borderLeftColor',
|
|
37
|
-
'border',
|
|
38
|
-
'boxShadow',
|
|
39
|
-
'caretColor'
|
|
40
|
-
]
|
|
41
|
-
|
|
42
105
|
return {
|
|
43
|
-
|
|
44
|
-
JSXAttribute(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
// if variable is not defined in scope, give up (could be imported from different file)
|
|
56
|
-
if (!variable) return
|
|
57
|
-
|
|
58
|
-
const variableDeclarator = variable.identifiers[0].parent
|
|
59
|
-
const rawText = context.sourceCode.getText(variableDeclarator)
|
|
60
|
-
checkForVariables(variableDeclarator, rawText)
|
|
61
|
-
} else {
|
|
62
|
-
// worth a try!
|
|
63
|
-
const rawText = context.sourceCode.getText(node.value)
|
|
64
|
-
checkForVariables(node.value, rawText)
|
|
65
|
-
}
|
|
66
|
-
} else if (
|
|
67
|
-
styledSystemProps.includes(node.name.name) &&
|
|
68
|
-
node.value &&
|
|
69
|
-
node.value.type === 'Literal' &&
|
|
70
|
-
typeof node.value.value === 'string'
|
|
71
|
-
) {
|
|
72
|
-
checkForVariables(node.value, node.value.value)
|
|
73
|
-
}
|
|
74
|
-
}
|
|
106
|
+
// sx OR style property on elements
|
|
107
|
+
['JSXAttribute:matches([name.name=sx], [name.name=style]) ObjectExpression Property']: node =>
|
|
108
|
+
reportOnObject(node, context),
|
|
109
|
+
// variable that is an object
|
|
110
|
+
[':matches(VariableDeclarator, ReturnStatement, ConditionalExpression, ArrowFunctionExpression, CallExpression) > ObjectExpression Property, :matches(VariableDeclarator, ReturnStatement, ConditionalExpression, ArrowFunctionExpression, CallExpression) > ObjectExpression Property > ObjectExpression Property']:
|
|
111
|
+
node => reportOnObject(node, context),
|
|
112
|
+
// property on element like stroke or fill
|
|
113
|
+
['JSXAttribute[name.name!=sx][name.name!=style]']: node => reportOnProperty(node, context),
|
|
114
|
+
// variable that is a value
|
|
115
|
+
[':matches(VariableDeclarator, ReturnStatement) > Literal']: node => reportOnValue(node, context),
|
|
116
|
+
// variable that is a value
|
|
117
|
+
['VariableDeclarator TemplateElement']: node => reportOnTemplateElement(node, context),
|
|
75
118
|
}
|
|
76
|
-
|
|
77
|
-
function checkForVariables(node, rawText) {
|
|
78
|
-
// performance optimisation: exit early
|
|
79
|
-
if (!rawText.includes('var')) return
|
|
80
|
-
|
|
81
|
-
Object.keys(cssVars).forEach(cssVar => {
|
|
82
|
-
if (Array.isArray(cssVars[cssVar])) {
|
|
83
|
-
cssVars[cssVar].forEach(cssVarObject => {
|
|
84
|
-
const regex = new RegExp(`var\\(${cssVar}\\)`, 'g')
|
|
85
|
-
if (
|
|
86
|
-
cssVarObject.props.some(prop => rawText.includes(prop)) &&
|
|
87
|
-
regex.test(rawText) &&
|
|
88
|
-
!rawText.includes(cssVarObject.replacement)
|
|
89
|
-
) {
|
|
90
|
-
const fixedString = rawText.replace(regex, `var(${cssVarObject.replacement}, var(${cssVar}))`)
|
|
91
|
-
if (!rawText.includes(fixedString)) {
|
|
92
|
-
context.report({
|
|
93
|
-
node,
|
|
94
|
-
message: `Replace var(${cssVar}) with var(${cssVarObject.replacement}, var(${cssVar}))`,
|
|
95
|
-
fix: function(fixer) {
|
|
96
|
-
return fixer.replaceText(node, node.type === 'Literal' ? `"${fixedString}"` : fixedString)
|
|
97
|
-
}
|
|
98
|
-
})
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
})
|
|
102
|
-
}
|
|
103
|
-
})
|
|
104
|
-
}
|
|
105
|
-
}
|
|
119
|
+
},
|
|
106
120
|
}
|
|
@@ -17,15 +17,15 @@ module.exports = {
|
|
|
17
17
|
type: 'object',
|
|
18
18
|
properties: {
|
|
19
19
|
skipImportCheck: {
|
|
20
|
-
type: 'boolean'
|
|
20
|
+
type: 'boolean',
|
|
21
21
|
},
|
|
22
22
|
checkAllStrings: {
|
|
23
|
-
type: 'boolean'
|
|
24
|
-
}
|
|
23
|
+
type: 'boolean',
|
|
24
|
+
},
|
|
25
25
|
},
|
|
26
|
-
additionalProperties: false
|
|
27
|
-
}
|
|
28
|
-
]
|
|
26
|
+
additionalProperties: false,
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
29
|
},
|
|
30
30
|
create(context) {
|
|
31
31
|
// If `skipImportCheck` is true, this rule will check for deprecated colors
|
|
@@ -89,7 +89,7 @@ module.exports = {
|
|
|
89
89
|
path.node,
|
|
90
90
|
name,
|
|
91
91
|
str => [param, key, str].join('.'),
|
|
92
|
-
str => str
|
|
92
|
+
str => str,
|
|
93
93
|
)
|
|
94
94
|
}
|
|
95
95
|
|
|
@@ -132,9 +132,9 @@ module.exports = {
|
|
|
132
132
|
if (['colors', 'shadows'].includes(key) && Object.keys(deprecations).includes(name)) {
|
|
133
133
|
replaceDeprecatedColor(context, argument, name, str => [key, str].join('.'))
|
|
134
134
|
}
|
|
135
|
-
}
|
|
135
|
+
},
|
|
136
136
|
}
|
|
137
|
-
}
|
|
137
|
+
},
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
function isThemeGet(identifier, scope, skipImportCheck = false) {
|
|
@@ -156,7 +156,7 @@ function replaceDeprecatedColor(
|
|
|
156
156
|
node,
|
|
157
157
|
deprecatedName,
|
|
158
158
|
transformName = str => str,
|
|
159
|
-
transformReplacementValue = str => JSON.stringify(str)
|
|
159
|
+
transformReplacementValue = str => JSON.stringify(str),
|
|
160
160
|
) {
|
|
161
161
|
const replacement = deprecations[deprecatedName]
|
|
162
162
|
|
|
@@ -165,8 +165,8 @@ function replaceDeprecatedColor(
|
|
|
165
165
|
context.report({
|
|
166
166
|
node,
|
|
167
167
|
message: `"${transformName(
|
|
168
|
-
deprecatedName
|
|
169
|
-
)}" is deprecated. Go to https://primer.style/primitives or reach out in the #primer channel on Slack to find a suitable replacement
|
|
168
|
+
deprecatedName,
|
|
169
|
+
)}" is deprecated. Go to https://primer.style/primitives or reach out in the #primer channel on Slack to find a suitable replacement.`,
|
|
170
170
|
})
|
|
171
171
|
} else if (Array.isArray(replacement)) {
|
|
172
172
|
// Multiple possible replacements
|
|
@@ -177,8 +177,8 @@ function replaceDeprecatedColor(
|
|
|
177
177
|
desc: `Use "${transformName(replacementValue)}" instead.`,
|
|
178
178
|
fix(fixer) {
|
|
179
179
|
return fixer.replaceText(node, transformReplacementValue(transformName(replacementValue)))
|
|
180
|
-
}
|
|
181
|
-
}))
|
|
180
|
+
},
|
|
181
|
+
})),
|
|
182
182
|
})
|
|
183
183
|
} else {
|
|
184
184
|
// One replacement
|
|
@@ -187,7 +187,7 @@ function replaceDeprecatedColor(
|
|
|
187
187
|
message: `"${transformName(deprecatedName)}" is deprecated. Use "${transformName(replacement)}" instead.`,
|
|
188
188
|
fix(fixer) {
|
|
189
189
|
return fixer.replaceText(node, transformReplacementValue(transformName(replacement)))
|
|
190
|
-
}
|
|
190
|
+
},
|
|
191
191
|
})
|
|
192
192
|
}
|
|
193
193
|
}
|
|
@@ -5,7 +5,7 @@ const {some, last} = require('lodash')
|
|
|
5
5
|
|
|
6
6
|
// Components for which we allow all styled system props
|
|
7
7
|
const alwaysExcludedComponents = new Set([
|
|
8
|
-
'BaseStyles' // BaseStyles will be deprecated eventually
|
|
8
|
+
'BaseStyles', // BaseStyles will be deprecated eventually
|
|
9
9
|
])
|
|
10
10
|
|
|
11
11
|
// Excluded by default, but optionally included:
|
|
@@ -40,7 +40,7 @@ const excludedComponentProps = new Map([
|
|
|
40
40
|
['PageLayout.Pane', new Set(['padding', 'position', 'width'])],
|
|
41
41
|
['PageLayout.Content', new Set(['padding', 'width'])],
|
|
42
42
|
['ProgressBar', new Set(['bg'])],
|
|
43
|
-
['PointerBox', new Set(['bg'])]
|
|
43
|
+
['PointerBox', new Set(['bg'])],
|
|
44
44
|
])
|
|
45
45
|
|
|
46
46
|
const alwaysExcludedProps = new Set(['variant', 'size'])
|
|
@@ -53,17 +53,17 @@ module.exports = {
|
|
|
53
53
|
{
|
|
54
54
|
properties: {
|
|
55
55
|
skipImportCheck: {
|
|
56
|
-
type: 'boolean'
|
|
56
|
+
type: 'boolean',
|
|
57
57
|
},
|
|
58
58
|
includeUtilityComponents: {
|
|
59
|
-
type: 'boolean'
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
59
|
+
type: 'boolean',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
63
|
],
|
|
64
64
|
messages: {
|
|
65
|
-
noSystemProps: 'Styled-system props are deprecated ({{ componentName }} called with props: {{ propNames }})'
|
|
66
|
-
}
|
|
65
|
+
noSystemProps: 'Styled-system props are deprecated ({{ componentName }} called with props: {{ propNames }})',
|
|
66
|
+
},
|
|
67
67
|
},
|
|
68
68
|
create(context) {
|
|
69
69
|
// If `skipImportCheck` is true, this rule will check for deprecated styled system props
|
|
@@ -74,7 +74,7 @@ module.exports = {
|
|
|
74
74
|
|
|
75
75
|
const excludedComponents = new Set([
|
|
76
76
|
...alwaysExcludedComponents,
|
|
77
|
-
...(includeUtilityComponents ? [] : utilityComponents)
|
|
77
|
+
...(includeUtilityComponents ? [] : utilityComponents),
|
|
78
78
|
])
|
|
79
79
|
|
|
80
80
|
return {
|
|
@@ -113,11 +113,11 @@ module.exports = {
|
|
|
113
113
|
messageId: 'noSystemProps',
|
|
114
114
|
data: {
|
|
115
115
|
componentName,
|
|
116
|
-
propNames: systemProps.map(a => a.name.name).join(', ')
|
|
116
|
+
propNames: systemProps.map(a => a.name.name).join(', '),
|
|
117
117
|
},
|
|
118
118
|
fix(fixer) {
|
|
119
119
|
const existingSxProp = jsxNode.attributes.find(
|
|
120
|
-
attribute => attribute.type === 'JSXAttribute' && attribute.name.name === 'sx'
|
|
120
|
+
attribute => attribute.type === 'JSXAttribute' && attribute.name.name === 'sx',
|
|
121
121
|
)
|
|
122
122
|
const systemPropstylesMap = stylesMapFromPropNodes(systemProps, context)
|
|
123
123
|
if (existingSxProp && existingSxProp.value.expression.type !== 'ObjectExpression') {
|
|
@@ -137,19 +137,19 @@ module.exports = {
|
|
|
137
137
|
? // Update an existing sx prop
|
|
138
138
|
fixer.insertTextAfter(
|
|
139
139
|
last(existingSxProp.value.expression.properties),
|
|
140
|
-
`, ${objectEntriesStringFromStylesMap(stylesToAdd)}
|
|
140
|
+
`, ${objectEntriesStringFromStylesMap(stylesToAdd)}`,
|
|
141
141
|
)
|
|
142
142
|
: // Insert new sx prop
|
|
143
|
-
fixer.insertTextAfter(last(jsxNode.attributes), sxPropTextFromStylesMap(systemPropstylesMap))
|
|
143
|
+
fixer.insertTextAfter(last(jsxNode.attributes), sxPropTextFromStylesMap(systemPropstylesMap)),
|
|
144
144
|
]
|
|
145
|
-
: [])
|
|
145
|
+
: []),
|
|
146
146
|
]
|
|
147
|
-
}
|
|
147
|
+
},
|
|
148
148
|
})
|
|
149
149
|
}
|
|
150
|
-
}
|
|
150
|
+
},
|
|
151
151
|
}
|
|
152
|
-
}
|
|
152
|
+
},
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
const sxPropTextFromStylesMap = styles => {
|
|
@@ -165,8 +165,8 @@ const stylesMapFromPropNodes = (systemProps, context) => {
|
|
|
165
165
|
return new Map(
|
|
166
166
|
systemProps.map(a => [
|
|
167
167
|
a.name.name,
|
|
168
|
-
a.value === null ? 'true' : a.value.raw || context.getSourceCode().getText(a.value.expression)
|
|
169
|
-
])
|
|
168
|
+
a.value === null ? 'true' : a.value.raw || context.getSourceCode().getText(a.value.expression),
|
|
169
|
+
]),
|
|
170
170
|
)
|
|
171
171
|
}
|
|
172
172
|
|
|
@@ -183,6 +183,6 @@ const excludeSxEntriesFromStyleMap = (stylesMap, sxProp) => {
|
|
|
183
183
|
return new Map(
|
|
184
184
|
[...stylesMap].filter(([key]) => {
|
|
185
185
|
return !some(sxProp.value.expression.properties, p => p.type === 'Property' && p.key.name === key)
|
|
186
|
-
})
|
|
186
|
+
}),
|
|
187
187
|
)
|
|
188
188
|
}
|
package/src/url.js
CHANGED