eslint-plugin-primer-react 8.1.0 → 8.2.0-rc.18e9359
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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-primer-react",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.2.0-rc.18e9359",
|
|
4
4
|
"description": "ESLint rules for Primer React",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"engines": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@styled-system/props": "^5.1.5",
|
|
35
|
-
"@typescript-eslint/utils": "8.
|
|
35
|
+
"@typescript-eslint/utils": "8.43.0",
|
|
36
36
|
"eslint-plugin-github": "^6.0.0",
|
|
37
37
|
"eslint-plugin-jsx-a11y": "^6.7.1",
|
|
38
38
|
"eslint-traverse": "^1.0.0",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"@github/markdownlint-github": "^0.6.3",
|
|
47
47
|
"@github/prettier-config": "0.0.6",
|
|
48
48
|
"@types/jest": "^30.0.0",
|
|
49
|
-
"@typescript-eslint/rule-tester": "8.
|
|
49
|
+
"@typescript-eslint/rule-tester": "8.42.0",
|
|
50
50
|
"eslint": "^9.0.0",
|
|
51
51
|
"eslint-plugin-eslint-comments": "^3.2.0",
|
|
52
52
|
"eslint-plugin-filenames": "^1.3.2",
|
|
@@ -56,6 +56,53 @@ ruleTester.run('use-styled-react-import', rule, {
|
|
|
56
56
|
],
|
|
57
57
|
},
|
|
58
58
|
|
|
59
|
+
// Invalid: ActionList.Item with sx prop and ActionList imported from @primer/react
|
|
60
|
+
{
|
|
61
|
+
code: `import { ActionList } from '@primer/react'
|
|
62
|
+
const Component = () => <ActionList.Item sx={{ color: 'red' }}>Content</ActionList.Item>`,
|
|
63
|
+
output: `import { ActionList } from '@primer/styled-react'
|
|
64
|
+
const Component = () => <ActionList.Item sx={{ color: 'red' }}>Content</ActionList.Item>`,
|
|
65
|
+
errors: [
|
|
66
|
+
{
|
|
67
|
+
messageId: 'useStyledReactImport',
|
|
68
|
+
data: {componentName: 'ActionList'},
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
// Invalid: FormControl used both with and without sx prop - should use alias
|
|
74
|
+
{
|
|
75
|
+
code: `import { FormControl } from '@primer/react'
|
|
76
|
+
const Component = () => (
|
|
77
|
+
<div>
|
|
78
|
+
<FormControl></FormControl>
|
|
79
|
+
<FormControl sx={{ color: 'red' }}>
|
|
80
|
+
<FormControl.Label visuallyHidden>Label</FormControl.Label>
|
|
81
|
+
</FormControl>
|
|
82
|
+
</div>
|
|
83
|
+
)`,
|
|
84
|
+
output: `import { FormControl } from '@primer/react'
|
|
85
|
+
import { FormControl as StyledFormControl } from '@primer/styled-react'
|
|
86
|
+
const Component = () => (
|
|
87
|
+
<div>
|
|
88
|
+
<FormControl></FormControl>
|
|
89
|
+
<StyledFormControl sx={{ color: 'red' }}>
|
|
90
|
+
<StyledFormControl.Label visuallyHidden>Label</StyledFormControl.Label>
|
|
91
|
+
</StyledFormControl>
|
|
92
|
+
</div>
|
|
93
|
+
)`,
|
|
94
|
+
errors: [
|
|
95
|
+
{
|
|
96
|
+
messageId: 'useStyledReactImportWithAlias',
|
|
97
|
+
data: {componentName: 'FormControl', aliasName: 'StyledFormControl'},
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
messageId: 'useAliasedComponent',
|
|
101
|
+
data: {componentName: 'FormControl', aliasName: 'StyledFormControl'},
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
|
|
59
106
|
// Invalid: Button with sx prop imported from @primer/react
|
|
60
107
|
{
|
|
61
108
|
code: `import { Button } from '@primer/react'
|
|
@@ -249,3 +296,64 @@ import { Button as StyledButton, Link } from '@primer/styled-react'
|
|
|
249
296
|
},
|
|
250
297
|
],
|
|
251
298
|
})
|
|
299
|
+
|
|
300
|
+
// Test configuration options
|
|
301
|
+
ruleTester.run('use-styled-react-import with custom configuration', rule, {
|
|
302
|
+
valid: [
|
|
303
|
+
// Valid: Custom component not in default list
|
|
304
|
+
{
|
|
305
|
+
code: `import { CustomButton } from '@primer/react'
|
|
306
|
+
const Component = () => <CustomButton sx={{ color: 'red' }}>Click me</CustomButton>`,
|
|
307
|
+
options: [{}], // Using default configuration
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
// Valid: Custom component in custom list used without sx prop
|
|
311
|
+
{
|
|
312
|
+
code: `import { CustomButton } from '@primer/react'
|
|
313
|
+
const Component = () => <CustomButton>Click me</CustomButton>`,
|
|
314
|
+
options: [{styledComponents: ['CustomButton']}],
|
|
315
|
+
},
|
|
316
|
+
|
|
317
|
+
// Valid: Custom component with sx prop imported from styled-react
|
|
318
|
+
{
|
|
319
|
+
code: `import { CustomButton } from '@primer/styled-react'
|
|
320
|
+
const Component = () => <CustomButton sx={{ color: 'red' }}>Click me</CustomButton>`,
|
|
321
|
+
options: [{styledComponents: ['CustomButton']}],
|
|
322
|
+
},
|
|
323
|
+
|
|
324
|
+
// Valid: Box not in custom list, so sx usage is allowed from @primer/react
|
|
325
|
+
{
|
|
326
|
+
code: `import { Box } from '@primer/react'
|
|
327
|
+
const Component = () => <Box sx={{ color: 'red' }}>Content</Box>`,
|
|
328
|
+
options: [{styledComponents: ['CustomButton']}], // Box not included
|
|
329
|
+
},
|
|
330
|
+
],
|
|
331
|
+
invalid: [
|
|
332
|
+
// Invalid: Custom component with sx prop should be from styled-react
|
|
333
|
+
{
|
|
334
|
+
code: `import { CustomButton } from '@primer/react'
|
|
335
|
+
const Component = () => <CustomButton sx={{ color: 'red' }}>Click me</CustomButton>`,
|
|
336
|
+
output: `import { CustomButton } from '@primer/styled-react'
|
|
337
|
+
const Component = () => <CustomButton sx={{ color: 'red' }}>Click me</CustomButton>`,
|
|
338
|
+
options: [{styledComponents: ['CustomButton']}],
|
|
339
|
+
errors: [
|
|
340
|
+
{
|
|
341
|
+
messageId: 'useStyledReactImport',
|
|
342
|
+
data: {componentName: 'CustomButton'},
|
|
343
|
+
},
|
|
344
|
+
],
|
|
345
|
+
},
|
|
346
|
+
// Invalid: Custom utility should be from styled-react
|
|
347
|
+
{
|
|
348
|
+
code: `import { customSx } from '@primer/react'`,
|
|
349
|
+
output: `import { customSx } from '@primer/styled-react'`,
|
|
350
|
+
options: [{styledUtilities: ['customSx']}],
|
|
351
|
+
errors: [
|
|
352
|
+
{
|
|
353
|
+
messageId: 'moveToStyledReact',
|
|
354
|
+
data: {importName: 'customSx'},
|
|
355
|
+
},
|
|
356
|
+
],
|
|
357
|
+
},
|
|
358
|
+
],
|
|
359
|
+
})
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
const url = require('../url')
|
|
4
4
|
const {getJSXOpeningElementName} = require('../utils/get-jsx-opening-element-name')
|
|
5
5
|
|
|
6
|
-
//
|
|
7
|
-
const
|
|
6
|
+
// Default components that should be imported from @primer/styled-react when used with sx prop
|
|
7
|
+
const defaultStyledComponents = [
|
|
8
8
|
'ActionList',
|
|
9
9
|
'ActionMenu',
|
|
10
10
|
'Box',
|
|
@@ -23,13 +23,13 @@ const styledComponents = new Set([
|
|
|
23
23
|
'Truncate',
|
|
24
24
|
'Octicon',
|
|
25
25
|
'Dialog',
|
|
26
|
-
]
|
|
26
|
+
]
|
|
27
27
|
|
|
28
|
-
//
|
|
29
|
-
const
|
|
28
|
+
// Default types that should be imported from @primer/styled-react
|
|
29
|
+
const defaultStyledTypes = ['BoxProps', 'SxProp', 'BetterSystemStyleObject']
|
|
30
30
|
|
|
31
|
-
//
|
|
32
|
-
const
|
|
31
|
+
// Default utilities that should be imported from @primer/styled-react
|
|
32
|
+
const defaultStyledUtilities = ['sx']
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* @type {import('eslint').Rule.RuleModule}
|
|
@@ -43,7 +43,29 @@ module.exports = {
|
|
|
43
43
|
url: url(module),
|
|
44
44
|
},
|
|
45
45
|
fixable: 'code',
|
|
46
|
-
schema: [
|
|
46
|
+
schema: [
|
|
47
|
+
{
|
|
48
|
+
type: 'object',
|
|
49
|
+
properties: {
|
|
50
|
+
styledComponents: {
|
|
51
|
+
type: 'array',
|
|
52
|
+
items: {type: 'string'},
|
|
53
|
+
description: 'Components that should be imported from @primer/styled-react when used with sx prop',
|
|
54
|
+
},
|
|
55
|
+
styledTypes: {
|
|
56
|
+
type: 'array',
|
|
57
|
+
items: {type: 'string'},
|
|
58
|
+
description: 'Types that should be imported from @primer/styled-react',
|
|
59
|
+
},
|
|
60
|
+
styledUtilities: {
|
|
61
|
+
type: 'array',
|
|
62
|
+
items: {type: 'string'},
|
|
63
|
+
description: 'Utilities that should be imported from @primer/styled-react',
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
additionalProperties: false,
|
|
67
|
+
},
|
|
68
|
+
],
|
|
47
69
|
messages: {
|
|
48
70
|
useStyledReactImport: 'Import {{ componentName }} from "@primer/styled-react" when using with sx prop',
|
|
49
71
|
useStyledReactImportWithAlias:
|
|
@@ -54,6 +76,11 @@ module.exports = {
|
|
|
54
76
|
},
|
|
55
77
|
},
|
|
56
78
|
create(context) {
|
|
79
|
+
// Get configuration options or use defaults
|
|
80
|
+
const options = context.options[0] || {}
|
|
81
|
+
const styledComponents = new Set(options.styledComponents || defaultStyledComponents)
|
|
82
|
+
const styledTypes = new Set(options.styledTypes || defaultStyledTypes)
|
|
83
|
+
const styledUtilities = new Set(options.styledUtilities || defaultStyledUtilities)
|
|
57
84
|
const componentsWithSx = new Set()
|
|
58
85
|
const componentsWithoutSx = new Set() // Track components used without sx
|
|
59
86
|
const allUsedComponents = new Set() // Track all used components
|
|
@@ -105,9 +132,14 @@ module.exports = {
|
|
|
105
132
|
// Check if this is an aliased component from styled-react
|
|
106
133
|
const originalComponentName = aliasMapping.get(componentName) || componentName
|
|
107
134
|
|
|
135
|
+
// For compound components like "ActionList.Item", we need to check the parent component
|
|
136
|
+
const parentComponentName = originalComponentName.includes('.')
|
|
137
|
+
? originalComponentName.split('.')[0]
|
|
138
|
+
: originalComponentName
|
|
139
|
+
|
|
108
140
|
// Track all used components that are in our styled components list
|
|
109
|
-
if (styledComponents.has(
|
|
110
|
-
allUsedComponents.add(
|
|
141
|
+
if (styledComponents.has(parentComponentName)) {
|
|
142
|
+
allUsedComponents.add(parentComponentName)
|
|
111
143
|
|
|
112
144
|
// Check if this component has an sx prop
|
|
113
145
|
const hasSxProp = openingElement.attributes.some(
|
|
@@ -115,17 +147,17 @@ module.exports = {
|
|
|
115
147
|
)
|
|
116
148
|
|
|
117
149
|
if (hasSxProp) {
|
|
118
|
-
componentsWithSx.add(
|
|
150
|
+
componentsWithSx.add(parentComponentName)
|
|
119
151
|
jsxElementsWithSx.push({node, componentName: originalComponentName, openingElement})
|
|
120
152
|
} else {
|
|
121
|
-
componentsWithoutSx.add(
|
|
153
|
+
componentsWithoutSx.add(parentComponentName)
|
|
122
154
|
|
|
123
155
|
// If this is an aliased component without sx, we need to track it for renaming
|
|
124
156
|
if (aliasMapping.has(componentName)) {
|
|
125
157
|
jsxElementsWithoutSx.push({
|
|
126
158
|
node,
|
|
127
159
|
localName: componentName,
|
|
128
|
-
originalName:
|
|
160
|
+
originalName: parentComponentName,
|
|
129
161
|
openingElement,
|
|
130
162
|
})
|
|
131
163
|
}
|
|
@@ -266,17 +298,14 @@ module.exports = {
|
|
|
266
298
|
messageId: 'useAliasedComponent',
|
|
267
299
|
data: {componentName, aliasName},
|
|
268
300
|
fix(fixer) {
|
|
269
|
-
const
|
|
301
|
+
const sourceCode = context.getSourceCode()
|
|
302
|
+
const jsxText = sourceCode.getText(jsxNode)
|
|
270
303
|
|
|
271
|
-
// Replace the component name
|
|
272
|
-
|
|
304
|
+
// Replace all instances of the component name (both main component and compound components)
|
|
305
|
+
const componentPattern = new RegExp(`\\b${componentName}(?=\\.|\\s|>)`, 'g')
|
|
306
|
+
const aliasedText = jsxText.replace(componentPattern, aliasName)
|
|
273
307
|
|
|
274
|
-
|
|
275
|
-
if (jsxNode.closingElement) {
|
|
276
|
-
fixes.push(fixer.replaceText(jsxNode.closingElement.name, aliasName))
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
return fixes
|
|
308
|
+
return fixer.replaceText(jsxNode, aliasedText)
|
|
280
309
|
},
|
|
281
310
|
})
|
|
282
311
|
}
|