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.1.0",
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.39.0",
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.39.0",
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
- // Components that should be imported from @primer/styled-react when used with sx prop
7
- const styledComponents = new Set([
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
- // Types that should be imported from @primer/styled-react
29
- const styledTypes = new Set(['BoxProps', 'SxProp', 'BetterSystemStyleObject'])
28
+ // Default types that should be imported from @primer/styled-react
29
+ const defaultStyledTypes = ['BoxProps', 'SxProp', 'BetterSystemStyleObject']
30
30
 
31
- // Utilities that should be imported from @primer/styled-react
32
- const styledUtilities = new Set(['sx'])
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(originalComponentName)) {
110
- allUsedComponents.add(originalComponentName)
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(originalComponentName)
150
+ componentsWithSx.add(parentComponentName)
119
151
  jsxElementsWithSx.push({node, componentName: originalComponentName, openingElement})
120
152
  } else {
121
- componentsWithoutSx.add(originalComponentName)
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: originalComponentName,
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 fixes = []
301
+ const sourceCode = context.getSourceCode()
302
+ const jsxText = sourceCode.getText(jsxNode)
270
303
 
271
- // Replace the component name in the JSX opening tag
272
- fixes.push(fixer.replaceText(openingElement.name, aliasName))
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
- // Replace the component name in the JSX closing tag if it exists
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
  }