Skip to content

Commit d72e8c4

Browse files
authored
Fixes for use-styled-react-import rule for compound components (#406)
* Update rule to check sub components and imports * When a component is aliased all sub components also get aliased. * Fix typo in changeset for eslint-plugin-primer-react
1 parent 2f25480 commit d72e8c4

File tree

3 files changed

+68
-14
lines changed

3 files changed

+68
-14
lines changed

.changeset/good-cobras-pay.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eslint-plugin-primer-react": patch
3+
---
4+
5+
Fixes for `use-styled-react-import` rule for compound components.

src/rules/__tests__/use-styled-react-import.test.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,53 @@ ruleTester.run('use-styled-react-import', rule, {
5656
],
5757
},
5858

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+
59106
// Invalid: Button with sx prop imported from @primer/react
60107
{
61108
code: `import { Button } from '@primer/react'

src/rules/use-styled-react-import.js

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -132,27 +132,32 @@ module.exports = {
132132
// Check if this is an aliased component from styled-react
133133
const originalComponentName = aliasMapping.get(componentName) || componentName
134134

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+
135140
// Track all used components that are in our styled components list
136-
if (styledComponents.has(originalComponentName)) {
137-
allUsedComponents.add(originalComponentName)
141+
if (styledComponents.has(parentComponentName)) {
142+
allUsedComponents.add(parentComponentName)
138143

139144
// Check if this component has an sx prop
140145
const hasSxProp = openingElement.attributes.some(
141146
attr => attr.type === 'JSXAttribute' && attr.name && attr.name.name === 'sx',
142147
)
143148

144149
if (hasSxProp) {
145-
componentsWithSx.add(originalComponentName)
150+
componentsWithSx.add(parentComponentName)
146151
jsxElementsWithSx.push({node, componentName: originalComponentName, openingElement})
147152
} else {
148-
componentsWithoutSx.add(originalComponentName)
153+
componentsWithoutSx.add(parentComponentName)
149154

150155
// If this is an aliased component without sx, we need to track it for renaming
151156
if (aliasMapping.has(componentName)) {
152157
jsxElementsWithoutSx.push({
153158
node,
154159
localName: componentName,
155-
originalName: originalComponentName,
160+
originalName: parentComponentName,
156161
openingElement,
157162
})
158163
}
@@ -293,17 +298,14 @@ module.exports = {
293298
messageId: 'useAliasedComponent',
294299
data: {componentName, aliasName},
295300
fix(fixer) {
296-
const fixes = []
297-
298-
// Replace the component name in the JSX opening tag
299-
fixes.push(fixer.replaceText(openingElement.name, aliasName))
301+
const sourceCode = context.getSourceCode()
302+
const jsxText = sourceCode.getText(jsxNode)
300303

301-
// Replace the component name in the JSX closing tag if it exists
302-
if (jsxNode.closingElement) {
303-
fixes.push(fixer.replaceText(jsxNode.closingElement.name, aliasName))
304-
}
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)
305307

306-
return fixes
308+
return fixer.replaceText(jsxNode, aliasedText)
307309
},
308310
})
309311
}

0 commit comments

Comments
 (0)