Skip to content

feat: Add rule to enforce importing components with sx prop from @primer/styled-react #379

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tricky-bees-promise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eslint-plugin-primer-react": minor
---

feat: Add rule to enforce importing components with sx prop from @primer/styled-react
95 changes: 95 additions & 0 deletions docs/rules/use-styled-react-import.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# 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).

<!-- end auto-generated rule header -->

Enforce importing components that use `sx` prop from `@primer/styled-react` instead of `@primer/react`.

## 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`. 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 = () => <Button sx={{color: 'red'}}>Click me</Button>
```

```jsx
import {Box} from '@primer/react'

const Component = () => <Box sx={{padding: 2}}>Content</Box>
```

```jsx
import {sx} from '@primer/react'
```

### ✅ Correct

```jsx
import {Link} from '@primer/react'
import {Button} from '@primer/styled-react'

const Component = () => <Button sx={{color: 'red'}}>Click me</Button>
```

```jsx
import {Box} from '@primer/styled-react'

const Component = () => <Box sx={{padding: 2}}>Content</Box>
```

```jsx
import {sx} from '@primer/styled-react'
```

```jsx
// Components without sx prop can stay in @primer/react
import {Button} from '@primer/react'

const Component = () => <Button>Click me</Button>
```

## 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.
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
118 changes: 118 additions & 0 deletions src/rules/__tests__/use-styled-react-import.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
const rule = require('../use-styled-react-import')
const {RuleTester} = require('eslint')

const ruleTester = new RuleTester({
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
})

ruleTester.run('use-styled-react-import', rule, {
valid: [
// Valid: Component used without sx prop
`import { Button } from '@primer/react'
const Component = () => <Button>Click me</Button>`,

// Valid: Component with sx prop imported from styled-react
`import { Button } from '@primer/styled-react'
const Component = () => <Button sx={{ color: 'red' }}>Click me</Button>`,

// 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 = () => <Avatar sx={{ color: 'red' }} />`,

// Valid: Mixed imports - component without sx prop
`import { Button, Text } from '@primer/react'
const Component = () => <Button>Click me</Button>`,
],
invalid: [
// Invalid: Box with sx prop imported from @primer/react
{
code: `import { Box } from '@primer/react'
const Component = () => <Box sx={{ color: 'red' }}>Content</Box>`,
output: `import { Box } from '@primer/styled-react'
const Component = () => <Box sx={{ color: 'red' }}>Content</Box>`,
errors: [
{
messageId: 'useStyledReactImport',
data: {componentName: 'Box'},
},
],
},

// Invalid: Button with sx prop imported from @primer/react
{
code: `import { Button } from '@primer/react'
const Component = () => <Button sx={{ margin: 2 }}>Click me</Button>`,
output: `import { Button } from '@primer/styled-react'
const Component = () => <Button sx={{ margin: 2 }}>Click me</Button>`,
errors: [
{
messageId: 'useStyledReactImport',
data: {componentName: 'Button'},
},
],
},

// Invalid: Multiple components, one with sx prop
{
code: `import { Button, Box, Avatar } from '@primer/react'
const Component = () => (
<div>
<Button>Regular button</Button>
<Box sx={{ padding: 2 }}>Styled box</Box>
<Avatar />
</div>
)`,
output: `import { Button, Avatar } from '@primer/react'
import { Box } from '@primer/styled-react'
const Component = () => (
<div>
<Button>Regular button</Button>
<Box sx={{ padding: 2 }}>Styled box</Box>
<Avatar />
</div>
)`,
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 = () => <Button sx={{ color: 'red' }}>Click me</Button>`,
output: `import { Link } from '@primer/react'
import { Button } from '@primer/styled-react'
const Component = () => <Button sx={{ color: 'red' }}>Click me</Button>`,
errors: [
{
messageId: 'useStyledReactImport',
data: {componentName: 'Button'},
},
],
},
],
})
Loading