diff --git a/.changeset/good-cobras-pay.md b/.changeset/good-cobras-pay.md new file mode 100644 index 00000000..56f6a6bb --- /dev/null +++ b/.changeset/good-cobras-pay.md @@ -0,0 +1,5 @@ +--- +"eslint-plugin-primer-react": patch +--- + +Fixes for `use-styled-react-import` rule for compound components. diff --git a/src/rules/__tests__/use-styled-react-import.test.js b/src/rules/__tests__/use-styled-react-import.test.js index 860f473c..dc06459b 100644 --- a/src/rules/__tests__/use-styled-react-import.test.js +++ b/src/rules/__tests__/use-styled-react-import.test.js @@ -56,6 +56,53 @@ ruleTester.run('use-styled-react-import', rule, { ], }, + // Invalid: ActionList.Item with sx prop and ActionList imported from @primer/react + { + code: `import { ActionList } from '@primer/react' + const Component = () => Content`, + output: `import { ActionList } from '@primer/styled-react' + const Component = () => Content`, + errors: [ + { + messageId: 'useStyledReactImport', + data: {componentName: 'ActionList'}, + }, + ], + }, + + // Invalid: FormControl used both with and without sx prop - should use alias + { + code: `import { FormControl } from '@primer/react' + const Component = () => ( +
+ + + Label + +
+ )`, + output: `import { FormControl } from '@primer/react' +import { FormControl as StyledFormControl } from '@primer/styled-react' + const Component = () => ( +
+ + + Label + +
+ )`, + errors: [ + { + messageId: 'useStyledReactImportWithAlias', + data: {componentName: 'FormControl', aliasName: 'StyledFormControl'}, + }, + { + messageId: 'useAliasedComponent', + data: {componentName: 'FormControl', aliasName: 'StyledFormControl'}, + }, + ], + }, + // Invalid: Button with sx prop imported from @primer/react { code: `import { Button } from '@primer/react' diff --git a/src/rules/use-styled-react-import.js b/src/rules/use-styled-react-import.js index 136ff644..606473a5 100644 --- a/src/rules/use-styled-react-import.js +++ b/src/rules/use-styled-react-import.js @@ -132,9 +132,14 @@ module.exports = { // Check if this is an aliased component from styled-react const originalComponentName = aliasMapping.get(componentName) || componentName + // For compound components like "ActionList.Item", we need to check the parent component + const parentComponentName = originalComponentName.includes('.') + ? originalComponentName.split('.')[0] + : originalComponentName + // Track all used components that are in our styled components list - if (styledComponents.has(originalComponentName)) { - allUsedComponents.add(originalComponentName) + if (styledComponents.has(parentComponentName)) { + allUsedComponents.add(parentComponentName) // Check if this component has an sx prop const hasSxProp = openingElement.attributes.some( @@ -142,17 +147,17 @@ module.exports = { ) if (hasSxProp) { - componentsWithSx.add(originalComponentName) + componentsWithSx.add(parentComponentName) jsxElementsWithSx.push({node, componentName: originalComponentName, openingElement}) } else { - componentsWithoutSx.add(originalComponentName) + componentsWithoutSx.add(parentComponentName) // If this is an aliased component without sx, we need to track it for renaming if (aliasMapping.has(componentName)) { jsxElementsWithoutSx.push({ node, localName: componentName, - originalName: originalComponentName, + originalName: parentComponentName, openingElement, }) } @@ -293,17 +298,14 @@ module.exports = { messageId: 'useAliasedComponent', data: {componentName, aliasName}, fix(fixer) { - const fixes = [] - - // Replace the component name in the JSX opening tag - fixes.push(fixer.replaceText(openingElement.name, aliasName)) + const sourceCode = context.getSourceCode() + const jsxText = sourceCode.getText(jsxNode) - // Replace the component name in the JSX closing tag if it exists - if (jsxNode.closingElement) { - fixes.push(fixer.replaceText(jsxNode.closingElement.name, aliasName)) - } + // Replace all instances of the component name (both main component and compound components) + const componentPattern = new RegExp(`\\b${componentName}(?=\\.|\\s|>)`, 'g') + const aliasedText = jsxText.replace(componentPattern, aliasName) - return fixes + return fixer.replaceText(jsxNode, aliasedText) }, }) }