diff --git a/.changeset/smart-rocks-fail.md b/.changeset/smart-rocks-fail.md
new file mode 100644
index 00000000..adf6e15d
--- /dev/null
+++ b/.changeset/smart-rocks-fail.md
@@ -0,0 +1,5 @@
+---
+"eslint-plugin-primer-react": minor
+---
+
+Add rule `use-styled-react-import` to enforce importing components with sx prop from @primer/styled-react
diff --git a/docs/rules/use-styled-react-import.md b/docs/rules/use-styled-react-import.md
new file mode 100644
index 00000000..78a06128
--- /dev/null
+++ b/docs/rules/use-styled-react-import.md
@@ -0,0 +1,128 @@
+# use-styled-react-import
+
+💼 This rule is _disabled_ in the ✅ `recommended` config.
+
+🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
+
+
+
+Enforce importing components that use `sx` prop from `@primer/styled-react` instead of `@primer/react`, and vice versa.
+
+## Rule Details
+
+This rule detects when certain Primer React components are used with the `sx` prop and ensures they are imported from the temporary `@primer/styled-react` package instead of `@primer/react`. When the same components are used without the `sx` prop, it ensures they are imported from `@primer/react` instead of `@primer/styled-react`.
+
+When a component is used both with and without the `sx` prop in the same file, the rule will import the styled version with an alias (e.g., `StyledButton`) and update the JSX usage accordingly to avoid naming conflicts.
+
+It also moves certain types and utilities to the styled-react package.
+
+### Components that should be imported from `@primer/styled-react` when used with `sx`:
+
+- ActionList
+- ActionMenu
+- Box
+- Breadcrumbs
+- Button
+- Flash
+- FormControl
+- Heading
+- IconButton
+- Label
+- Link
+- LinkButton
+- PageLayout
+- Text
+- TextInput
+- Truncate
+- Octicon
+- Dialog
+
+### Types and utilities that should always be imported from `@primer/styled-react`:
+
+- `BoxProps` (type)
+- `SxProp` (type)
+- `BetterSystemStyleObject` (type)
+- `sx` (utility)
+
+## Examples
+
+### ❌ Incorrect
+
+```jsx
+import {Button, Link} from '@primer/react'
+
+const Component = () =>
+```
+
+```jsx
+import {Box} from '@primer/react'
+
+const Component = () => Content
+```
+
+```jsx
+import {sx} from '@primer/react'
+```
+
+```jsx
+import {Button} from '@primer/styled-react'
+
+const Component = () =>
+```
+
+```jsx
+import {Button} from '@primer/react'
+
+const Component1 = () =>
+const Component2 = () =>
+```
+
+### ✅ Correct
+
+```jsx
+import {Link} from '@primer/react'
+import {Button} from '@primer/styled-react'
+
+const Component = () =>
+```
+
+```jsx
+import {Box} from '@primer/styled-react'
+
+const Component = () => Content
+```
+
+```jsx
+import {sx} from '@primer/styled-react'
+```
+
+```jsx
+// Components without sx prop can stay in @primer/react
+import {Button} from '@primer/react'
+
+const Component = () =>
+```
+
+```jsx
+// Components imported from styled-react but used without sx prop should be moved back
+import {Button} from '@primer/react'
+
+const Component = () =>
+```
+
+```jsx
+// When a component is used both ways, use an alias for the styled version
+import {Button} from '@primer/react'
+import {Button as StyledButton} from '@primer/styled-react'
+
+const Component1 = () =>
+const Component2 = () => Styled me
+```
+
+## Options
+
+This rule has no options.
+
+## When Not To Use It
+
+This rule is specifically for migrating components that use the `sx` prop to the temporary `@primer/styled-react` package. If you're not using the `sx` prop or not participating in this migration, you can disable this rule.
diff --git a/src/index.js b/src/index.js
index 68de6f20..1dc3315e 100644
--- a/src/index.js
+++ b/src/index.js
@@ -19,6 +19,7 @@ module.exports = {
'prefer-action-list-item-onselect': require('./rules/prefer-action-list-item-onselect'),
'enforce-css-module-identifier-casing': require('./rules/enforce-css-module-identifier-casing'),
'enforce-css-module-default-import': require('./rules/enforce-css-module-default-import'),
+ 'use-styled-react-import': require('./rules/use-styled-react-import'),
},
configs: {
recommended: require('./configs/recommended'),
diff --git a/src/rules/__tests__/use-styled-react-import.test.js b/src/rules/__tests__/use-styled-react-import.test.js
new file mode 100644
index 00000000..bd11ae7d
--- /dev/null
+++ b/src/rules/__tests__/use-styled-react-import.test.js
@@ -0,0 +1,251 @@
+const rule = require('../use-styled-react-import')
+const {RuleTester} = require('eslint')
+
+const ruleTester = new RuleTester({
+ languageOptions: {
+ ecmaVersion: 'latest',
+ sourceType: 'module',
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ },
+})
+
+ruleTester.run('use-styled-react-import', rule, {
+ valid: [
+ // Valid: Component used without sx prop
+ `import { Button } from '@primer/react'
+ const Component = () => `,
+
+ // Valid: Component with sx prop imported from styled-react
+ `import { Button } from '@primer/styled-react'
+ const Component = () => `,
+
+ // Valid: Utilities imported from styled-react
+ `import { sx } from '@primer/styled-react'`,
+
+ // Valid: Component not in the styled list
+ `import { Avatar } from '@primer/react'
+ const Component = () => `,
+
+ // Valid: Component not imported from @primer/react
+ `import { Button } from '@github-ui/button'
+ const Component = () => `,
+
+ // Valid: Mixed imports - component without sx prop
+ `import { Button, Text } from '@primer/react'
+ const Component = () => `,
+
+ // Valid: Component without sx prop imported from styled-react (when not used)
+ `import { Button } from '@primer/styled-react'`,
+ ],
+ invalid: [
+ // Invalid: Box with sx prop imported from @primer/react
+ {
+ code: `import { Box } from '@primer/react'
+ const Component = () => Content`,
+ output: `import { Box } from '@primer/styled-react'
+ const Component = () => Content`,
+ errors: [
+ {
+ messageId: 'useStyledReactImport',
+ data: {componentName: 'Box'},
+ },
+ ],
+ },
+
+ // Invalid: Button with sx prop imported from @primer/react
+ {
+ code: `import { Button } from '@primer/react'
+ const Component = () => `,
+ output: `import { Button } from '@primer/styled-react'
+ const Component = () => `,
+ errors: [
+ {
+ messageId: 'useStyledReactImport',
+ data: {componentName: 'Button'},
+ },
+ ],
+ },
+
+ // Invalid: Multiple components, one with sx prop
+ {
+ code: `import { Button, Box, Avatar } from '@primer/react'
+ const Component = () => (
+
+
+
Styled box
+
+
+ )`,
+ output: `import { Button, Avatar } from '@primer/react'
+import { Box } from '@primer/styled-react'
+ const Component = () => (
+
+
+
Styled box
+
+
+ )`,
+ errors: [
+ {
+ messageId: 'useStyledReactImport',
+ data: {componentName: 'Box'},
+ },
+ ],
+ },
+
+ // Invalid: Utility import from @primer/react that should be from styled-react
+ {
+ code: `import { sx } from '@primer/react'`,
+ output: `import { sx } from '@primer/styled-react'`,
+ errors: [
+ {
+ messageId: 'moveToStyledReact',
+ data: {importName: 'sx'},
+ },
+ ],
+ },
+
+ // Invalid: Button and Link, only Button uses sx
+ {
+ code: `import { Button, Link } from '@primer/react'
+ const Component = () => `,
+ output: `import { Link } from '@primer/react'
+import { Button } from '@primer/styled-react'
+ const Component = () => `,
+ errors: [
+ {
+ messageId: 'useStyledReactImport',
+ data: {componentName: 'Button'},
+ },
+ ],
+ },
+
+ // Invalid: Button imported from styled-react but used without sx prop
+ {
+ code: `import { Button } from '@primer/styled-react'
+ const Component = () => `,
+ output: `import { Button } from '@primer/react'
+ const Component = () => `,
+ errors: [
+ {
+ messageId: 'usePrimerReactImport',
+ data: {componentName: 'Button'},
+ },
+ ],
+ },
+
+ // Invalid: and imported from styled-react but used without sx prop
+ {
+ code: `import { Button } from '@primer/react'
+import { Button as StyledButton, Link } from '@primer/styled-react'
+ const Component = () => (
+
+
+
+ Styled button
+
+ )`,
+ output: `import { Button, Link } from '@primer/react'
+
+ const Component = () => (
+
+
+
+
+
+ )`,
+ errors: [
+ {
+ messageId: 'usePrimerReactImport',
+ data: {componentName: 'Button'},
+ },
+ {
+ messageId: 'usePrimerReactImport',
+ data: {componentName: 'Link'},
+ },
+ {
+ messageId: 'usePrimerReactImport',
+ data: {componentName: 'Button'},
+ },
+ ],
+ },
+
+ // Invalid: Box imported from styled-react but used without sx prop
+ {
+ code: `import { Box } from '@primer/styled-react'
+ const Component = () => Content`,
+ output: `import { Box } from '@primer/react'
+ const Component = () => Content`,
+ errors: [
+ {
+ messageId: 'usePrimerReactImport',
+ data: {componentName: 'Box'},
+ },
+ ],
+ },
+
+ // Invalid: Multiple components from styled-react, one used without sx
+ {
+ code: `import { Button, Box } from '@primer/styled-react'
+ const Component = () => (
+
+
+ Styled box
+
+ )`,
+ output: `import { Box } from '@primer/styled-react'
+import { Button } from '@primer/react'
+ const Component = () => (
+
+
+ Styled box
+
+ )`,
+ errors: [
+ {
+ messageId: 'usePrimerReactImport',
+ data: {componentName: 'Button'},
+ },
+ ],
+ },
+
+ // Invalid: Button used both with and without sx prop - should use alias
+ {
+ code: `import { Button, Link } from '@primer/react'
+ const Component = () => (
+
+
+
+
+
+ )`,
+ output: `import { Button } from '@primer/react'
+import { Button as StyledButton, Link } from '@primer/styled-react'
+ const Component = () => (
+
+
+
+ Styled button
+
+ )`,
+ errors: [
+ {
+ messageId: 'useStyledReactImportWithAlias',
+ data: {componentName: 'Button', aliasName: 'StyledButton'},
+ },
+ {
+ messageId: 'useStyledReactImport',
+ data: {componentName: 'Link'},
+ },
+ {
+ messageId: 'useAliasedComponent',
+ data: {componentName: 'Button', aliasName: 'StyledButton'},
+ },
+ ],
+ },
+ ],
+})
diff --git a/src/rules/use-styled-react-import.js b/src/rules/use-styled-react-import.js
new file mode 100644
index 00000000..b4ba55bd
--- /dev/null
+++ b/src/rules/use-styled-react-import.js
@@ -0,0 +1,483 @@
+'use strict'
+
+const url = require('../url')
+const {getJSXOpeningElementName} = require('../utils/get-jsx-opening-element-name')
+
+// Components that should be imported from @primer/styled-react when used with sx prop
+const styledComponents = new Set([
+ 'ActionList',
+ 'ActionMenu',
+ 'Box',
+ 'Breadcrumbs',
+ 'Button',
+ 'Flash',
+ 'FormControl',
+ 'Heading',
+ 'IconButton',
+ 'Label',
+ 'Link',
+ 'LinkButton',
+ 'PageLayout',
+ 'Text',
+ 'TextInput',
+ 'Truncate',
+ 'Octicon',
+ 'Dialog',
+])
+
+// Types that should be imported from @primer/styled-react
+const styledTypes = new Set(['BoxProps', 'SxProp', 'BetterSystemStyleObject'])
+
+// Utilities that should be imported from @primer/styled-react
+const styledUtilities = new Set(['sx'])
+
+/**
+ * @type {import('eslint').Rule.RuleModule}
+ */
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'Enforce importing components that use sx prop from @primer/styled-react',
+ recommended: false,
+ url: url(module),
+ },
+ fixable: 'code',
+ schema: [],
+ messages: {
+ useStyledReactImport: 'Import {{ componentName }} from "@primer/styled-react" when using with sx prop',
+ useStyledReactImportWithAlias:
+ 'Import {{ componentName }} as {{ aliasName }} from "@primer/styled-react" when using with sx prop (conflicts with non-sx usage)',
+ useAliasedComponent: 'Use {{ aliasName }} instead of {{ componentName }} when using sx prop',
+ moveToStyledReact: 'Move {{ importName }} import to "@primer/styled-react"',
+ usePrimerReactImport: 'Import {{ componentName }} from "@primer/react" when not using sx prop',
+ },
+ },
+ create(context) {
+ const componentsWithSx = new Set()
+ const componentsWithoutSx = new Set() // Track components used without sx
+ const allUsedComponents = new Set() // Track all used components
+ const primerReactImports = new Map() // Map of component name to import node
+ const styledReactImports = new Map() // Map of components imported from styled-react to import node
+ const aliasMapping = new Map() // Map local name to original component name for aliased imports
+ const jsxElementsWithSx = [] // Track JSX elements that use sx prop
+ const jsxElementsWithoutSx = [] // Track JSX elements that don't use sx prop
+
+ return {
+ ImportDeclaration(node) {
+ const importSource = node.source.value
+
+ if (importSource === '@primer/react') {
+ // Track imports from @primer/react
+ for (const specifier of node.specifiers) {
+ if (specifier.type === 'ImportSpecifier') {
+ const importedName = specifier.imported.name
+ if (
+ styledComponents.has(importedName) ||
+ styledTypes.has(importedName) ||
+ styledUtilities.has(importedName)
+ ) {
+ primerReactImports.set(importedName, {node, specifier})
+ }
+ }
+ }
+ } else if (importSource === '@primer/styled-react') {
+ // Track what's imported from styled-react
+ for (const specifier of node.specifiers) {
+ if (specifier.type === 'ImportSpecifier') {
+ const importedName = specifier.imported.name
+ const localName = specifier.local.name
+ styledReactImports.set(importedName, {node, specifier})
+
+ // Track alias mapping for styled-react imports
+ if (localName !== importedName) {
+ aliasMapping.set(localName, importedName)
+ }
+ }
+ }
+ }
+ },
+
+ JSXElement(node) {
+ const openingElement = node.openingElement
+ const componentName = getJSXOpeningElementName(openingElement)
+
+ // Check if this is an aliased component from styled-react
+ const originalComponentName = aliasMapping.get(componentName) || componentName
+
+ // Track all used components that are in our styled components list
+ if (styledComponents.has(originalComponentName)) {
+ allUsedComponents.add(originalComponentName)
+
+ // Check if this component has an sx prop
+ const hasSxProp = openingElement.attributes.some(
+ attr => attr.type === 'JSXAttribute' && attr.name && attr.name.name === 'sx',
+ )
+
+ if (hasSxProp) {
+ componentsWithSx.add(originalComponentName)
+ jsxElementsWithSx.push({node, componentName: originalComponentName, openingElement})
+ } else {
+ componentsWithoutSx.add(originalComponentName)
+
+ // 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,
+ openingElement,
+ })
+ }
+ }
+ }
+ },
+
+ 'Program:exit': function () {
+ // Group components by import node to handle multiple changes to same import
+ const importNodeChanges = new Map()
+
+ // Collect all changes needed for components used with sx prop
+ for (const componentName of componentsWithSx) {
+ const importInfo = primerReactImports.get(componentName)
+ if (importInfo && !styledReactImports.has(componentName)) {
+ const hasConflict = componentsWithoutSx.has(componentName)
+ const {node: importNode} = importInfo
+
+ if (!importNodeChanges.has(importNode)) {
+ importNodeChanges.set(importNode, {
+ toMove: [],
+ toAlias: [],
+ originalSpecifiers: [...importNode.specifiers],
+ })
+ }
+
+ const changes = importNodeChanges.get(importNode)
+ if (hasConflict) {
+ changes.toAlias.push(componentName)
+ } else {
+ changes.toMove.push(componentName)
+ }
+ }
+ }
+
+ // Report errors for components used with sx prop that are imported from @primer/react
+ for (const componentName of componentsWithSx) {
+ const importInfo = primerReactImports.get(componentName)
+ if (importInfo && !styledReactImports.has(componentName)) {
+ // Check if this component is also used without sx prop (conflict scenario)
+ const hasConflict = componentsWithoutSx.has(componentName)
+
+ context.report({
+ node: importInfo.specifier,
+ messageId: hasConflict ? 'useStyledReactImportWithAlias' : 'useStyledReactImport',
+ data: hasConflict ? {componentName, aliasName: `Styled${componentName}`} : {componentName},
+ fix(fixer) {
+ const {node: importNode, specifier} = importInfo
+ const changes = importNodeChanges.get(importNode)
+
+ if (!changes) {
+ return null
+ }
+
+ // Only apply the fix once per import node (for the first component processed)
+ const isFirstComponent =
+ changes.originalSpecifiers[0] === specifier ||
+ (changes.toMove.length > 0 && changes.toMove[0] === componentName) ||
+ (changes.toAlias.length > 0 && changes.toAlias[0] === componentName)
+
+ if (!isFirstComponent) {
+ return null
+ }
+
+ const fixes = []
+ const componentsToMove = new Set(changes.toMove)
+
+ // Find specifiers that remain in original import
+ const remainingSpecifiers = changes.originalSpecifiers.filter(spec => {
+ const name = spec.imported.name
+ // Keep components that are not being moved (only aliased components stay for non-sx usage)
+ return !componentsToMove.has(name)
+ })
+
+ // If no components remain, replace with new imports directly
+ if (remainingSpecifiers.length === 0) {
+ // Build the new imports to replace the original
+ const newImports = []
+
+ // Add imports for moved components
+ for (const componentName of changes.toMove) {
+ newImports.push(`import { ${componentName} } from '@primer/styled-react'`)
+ }
+
+ // Add aliased imports for conflicted components
+ for (const componentName of changes.toAlias) {
+ const aliasName = `Styled${componentName}`
+ newImports.push(`import { ${componentName} as ${aliasName} } from '@primer/styled-react'`)
+ }
+
+ fixes.push(fixer.replaceText(importNode, newImports.join('\n')))
+ } else {
+ // Otherwise, update the import to only include remaining components
+ const remainingNames = remainingSpecifiers.map(spec => spec.imported.name)
+ fixes.push(
+ fixer.replaceText(importNode, `import { ${remainingNames.join(', ')} } from '@primer/react'`),
+ )
+
+ // Combine all styled-react imports into a single import statement
+ const styledReactImports = []
+
+ // Add aliased components first
+ for (const componentName of changes.toAlias) {
+ const aliasName = `Styled${componentName}`
+ styledReactImports.push(`${componentName} as ${aliasName}`)
+ }
+
+ // Add moved components second
+ for (const componentName of changes.toMove) {
+ styledReactImports.push(componentName)
+ }
+
+ if (styledReactImports.length > 0) {
+ fixes.push(
+ fixer.insertTextAfter(
+ importNode,
+ `\nimport { ${styledReactImports.join(', ')} } from '@primer/styled-react'`,
+ ),
+ )
+ }
+ }
+
+ return fixes
+ },
+ })
+ }
+ }
+
+ // Report on JSX elements that should use aliased components
+ for (const {node: jsxNode, componentName, openingElement} of jsxElementsWithSx) {
+ const hasConflict = componentsWithoutSx.has(componentName)
+ const isImportedFromPrimerReact = primerReactImports.has(componentName)
+
+ if (hasConflict && isImportedFromPrimerReact && !styledReactImports.has(componentName)) {
+ const aliasName = `Styled${componentName}`
+ context.report({
+ node: openingElement,
+ 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))
+
+ // Replace the component name in the JSX closing tag if it exists
+ if (jsxNode.closingElement) {
+ fixes.push(fixer.replaceText(jsxNode.closingElement.name, aliasName))
+ }
+
+ return fixes
+ },
+ })
+ }
+ }
+
+ // Group styled-react imports that need to be moved to primer-react
+ const styledReactImportNodeChanges = new Map()
+
+ // Collect components that need to be moved from styled-react to primer-react
+ for (const componentName of allUsedComponents) {
+ if (!componentsWithSx.has(componentName) && styledReactImports.has(componentName)) {
+ const importInfo = styledReactImports.get(componentName)
+ const {node: importNode} = importInfo
+
+ if (!styledReactImportNodeChanges.has(importNode)) {
+ styledReactImportNodeChanges.set(importNode, {
+ toMove: [],
+ originalSpecifiers: [...importNode.specifiers],
+ })
+ }
+
+ styledReactImportNodeChanges.get(importNode).toMove.push(componentName)
+ }
+ }
+
+ // Find existing primer-react import nodes to merge with
+ const primerReactImportNodes = new Set()
+ for (const [, {node}] of primerReactImports) {
+ primerReactImportNodes.add(node)
+ }
+
+ // Report errors for components used WITHOUT sx prop that are imported from @primer/styled-react
+ for (const componentName of allUsedComponents) {
+ // If component is used but NOT with sx prop, and it's imported from styled-react
+ if (!componentsWithSx.has(componentName) && styledReactImports.has(componentName)) {
+ const importInfo = styledReactImports.get(componentName)
+ context.report({
+ node: importInfo.specifier,
+ messageId: 'usePrimerReactImport',
+ data: {componentName},
+ fix(fixer) {
+ const {node: importNode} = importInfo
+ const changes = styledReactImportNodeChanges.get(importNode)
+
+ if (!changes) {
+ return null
+ }
+
+ // Only apply the fix once per import node (for the first component processed)
+ const isFirstComponent = changes.toMove[0] === componentName
+
+ if (!isFirstComponent) {
+ return null
+ }
+
+ const fixes = []
+ const componentsToMove = new Set(changes.toMove)
+
+ // Find specifiers that remain in styled-react import
+ const remainingSpecifiers = changes.originalSpecifiers.filter(spec => {
+ const name = spec.imported.name
+ return !componentsToMove.has(name)
+ })
+
+ // Check if there's an existing primer-react import to merge with
+ const existingPrimerReactImport = Array.from(primerReactImportNodes)[0]
+
+ if (existingPrimerReactImport && remainingSpecifiers.length === 0) {
+ // Case: No remaining styled-react imports, merge with existing primer-react import
+ const existingSpecifiers = existingPrimerReactImport.specifiers.map(spec => spec.imported.name)
+ const newSpecifiers = [...existingSpecifiers, ...changes.toMove].filter(
+ (name, index, arr) => arr.indexOf(name) === index,
+ )
+
+ fixes.push(
+ fixer.replaceText(
+ existingPrimerReactImport,
+ `import { ${newSpecifiers.join(', ')} } from '@primer/react'`,
+ ),
+ )
+ fixes.push(fixer.remove(importNode))
+ } else if (existingPrimerReactImport && remainingSpecifiers.length > 0) {
+ // Case: Some styled-react imports remain, merge moved components with existing primer-react
+ const existingSpecifiers = existingPrimerReactImport.specifiers.map(spec => spec.imported.name)
+ const newSpecifiers = [...existingSpecifiers, ...changes.toMove].filter(
+ (name, index, arr) => arr.indexOf(name) === index,
+ )
+
+ fixes.push(
+ fixer.replaceText(
+ existingPrimerReactImport,
+ `import { ${newSpecifiers.join(', ')} } from '@primer/react'`,
+ ),
+ )
+
+ const remainingNames = remainingSpecifiers.map(spec => spec.imported.name)
+ fixes.push(
+ fixer.replaceText(
+ importNode,
+ `import { ${remainingNames.join(', ')} } from '@primer/styled-react'`,
+ ),
+ )
+ } else if (remainingSpecifiers.length === 0) {
+ // Case: No existing primer-react import, no remaining styled-react imports
+ const movedComponents = changes.toMove.join(', ')
+ fixes.push(fixer.replaceText(importNode, `import { ${movedComponents} } from '@primer/react'`))
+ } else {
+ // Case: No existing primer-react import, some styled-react imports remain
+ const remainingNames = remainingSpecifiers.map(spec => spec.imported.name)
+ fixes.push(
+ fixer.replaceText(
+ importNode,
+ `import { ${remainingNames.join(', ')} } from '@primer/styled-react'`,
+ ),
+ )
+
+ const movedComponents = changes.toMove.join(', ')
+ fixes.push(fixer.insertTextAfter(importNode, `\nimport { ${movedComponents} } from '@primer/react'`))
+ }
+
+ return fixes
+ },
+ })
+ }
+ }
+
+ // Report and fix JSX elements that use aliased components without sx prop
+ for (const {node: jsxNode, originalName, openingElement} of jsxElementsWithoutSx) {
+ if (!componentsWithSx.has(originalName) && styledReactImports.has(originalName)) {
+ context.report({
+ node: openingElement,
+ messageId: 'usePrimerReactImport',
+ data: {componentName: originalName},
+ fix(fixer) {
+ const fixes = []
+
+ // Replace the aliased component name with the original component name in JSX opening tag
+ fixes.push(fixer.replaceText(openingElement.name, originalName))
+
+ // Replace the aliased component name in JSX closing tag if it exists
+ if (jsxNode.closingElement) {
+ fixes.push(fixer.replaceText(jsxNode.closingElement.name, originalName))
+ }
+
+ return fixes
+ },
+ })
+ }
+ }
+
+ // Also report for types and utilities that should always be from styled-react
+ for (const [importName, importInfo] of primerReactImports) {
+ if ((styledTypes.has(importName) || styledUtilities.has(importName)) && !styledReactImports.has(importName)) {
+ context.report({
+ node: importInfo.specifier,
+ messageId: 'moveToStyledReact',
+ data: {importName},
+ fix(fixer) {
+ const {node: importNode, specifier} = importInfo
+ const otherSpecifiers = importNode.specifiers.filter(s => s !== specifier)
+
+ // If this is the only import, replace the whole import
+ if (otherSpecifiers.length === 0) {
+ const prefix = styledTypes.has(importName) ? 'type ' : ''
+ return fixer.replaceText(importNode, `import { ${prefix}${importName} } from '@primer/styled-react'`)
+ }
+
+ // Otherwise, remove from current import and add new import
+ const fixes = []
+
+ // Remove the specifier from current import
+ if (importNode.specifiers.length === 1) {
+ fixes.push(fixer.remove(importNode))
+ } else {
+ const isFirst = importNode.specifiers[0] === specifier
+ const isLast = importNode.specifiers[importNode.specifiers.length - 1] === specifier
+
+ if (isFirst) {
+ const nextSpecifier = importNode.specifiers[1]
+ fixes.push(fixer.removeRange([specifier.range[0], nextSpecifier.range[0]]))
+ } else if (isLast) {
+ const prevSpecifier = importNode.specifiers[importNode.specifiers.length - 2]
+ fixes.push(fixer.removeRange([prevSpecifier.range[1], specifier.range[1]]))
+ } else {
+ const nextSpecifier = importNode.specifiers[importNode.specifiers.indexOf(specifier) + 1]
+ fixes.push(fixer.removeRange([specifier.range[0], nextSpecifier.range[0]]))
+ }
+ }
+
+ // Add new import
+ const prefix = styledTypes.has(importName) ? 'type ' : ''
+ fixes.push(
+ fixer.insertTextAfter(importNode, `\nimport { ${prefix}${importName} } from '@primer/styled-react'`),
+ )
+
+ return fixes
+ },
+ })
+ }
+ }
+ },
+ }
+ },
+}