Skip to content

Commit

Permalink
Add no-deprecated-props rule (#156)
Browse files Browse the repository at this point in the history
* add no deprecated props rule

* update the tests and remove the space after title node is removed

* export the rule and add docs

* Add new test cases to discuss about the fix

* auto fix for JSX expressions

* add the rule to readme

* add cahngeset

* make the rule suggestion
  • Loading branch information
broccolinisoup authored Apr 22, 2024
1 parent 2f8e27b commit 15cfbb4
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/tidy-moons-sip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'eslint-plugin-primer-react': minor
---

Add no-deprecated-props rule
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ ESLint rules for Primer React
- [a11y-tooltip-interactive-trigger](https://github.com/primer/eslint-plugin-primer-react/blob/main/docs/rules/a11y-tooltip-interactive-trigger.md)
- [a11y-explicit-heading](https://github.com/primer/eslint-plugin-primer-react/blob/main/docs/rules/a11y-explicit-heading.md)
- [new-css-color-vars](https://github.com/primer/eslint-plugin-primer-react/blob/main/docs/rules/new-css-color-vars.md)
- [no-deprecated-props](https://github.com/primer/eslint-plugin-primer-react/blob/main/docs/rules/no-deprecated-props.md)
48 changes: 48 additions & 0 deletions docs/rules/no-deprecated-props.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
## Rule Details

This rule enforces to use the recommended API (`ActionList.GroupHeading`) component over the deprecated prop (`title` prop on `ActionList.Group`) for ActionList component.

👎 Examples of **incorrect** code for this rule:

```jsx
/* eslint primer-react/no-deprecated-props: "error" */
import {ActionList} from '@primer/react'

const App = () => (
<ActionList>
<ActionList.Group title="Group heading">
<ActionList.Item>Item 1</ActionList.Item>
</ActionList.Group>
</ActionList>
)
```

👍 Examples of **correct** code for this rule:

```jsx
/* eslint primer-react/no-deprecated-props: "error" */
import {ActionList} from '@primer/react'

const App = () => (
<ActionList>
<ActionList.Group>
<ActionList.GroupHeading as="h2">Group heading</ActionList.GroupHeading>
<ActionList.Item>Item 1</ActionList.Item>
</ActionList.Group>
</ActionList>
)
```

```jsx
/* eslint primer-react/no-deprecated-props: "error" */
import {ActionList} from '@primer/react'

const App = () => (
<ActionList role="lisbox">
<ActionList.Group>
<ActionList.GroupHeading>Group heading</ActionList.GroupHeading>
<ActionList.Item>Item 1</ActionList.Item>
</ActionList.Group>
</ActionList>
)
```
1 change: 1 addition & 0 deletions src/configs/recommended.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module.exports = {
'primer-react/new-color-css-vars': 'error',
'primer-react/a11y-explicit-heading': 'error',
'primer-react/new-color-css-vars-have-fallback': 'error',
'primer-react/no-deprecated-props': 'warn',
},
settings: {
github: {
Expand Down
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module.exports = {
'new-color-css-vars': require('./rules/new-color-css-vars'),
'a11y-explicit-heading': require('./rules/a11y-explicit-heading'),
'new-color-css-vars-have-fallback': require('./rules/new-color-css-vars-have-fallback'),
'no-deprecated-props': require('./rules/no-deprecated-props'),
},
configs: {
recommended: require('./configs/recommended'),
Expand Down
121 changes: 121 additions & 0 deletions src/rules/__tests__/no-deprecated-props.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
'use strict'

const {RuleTester} = require('eslint')
const rule = require('../no-deprecated-props')

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

ruleTester.run('no-deprecated-props', rule, {
valid: [
`import {ActionList} from '@primer/react';
<ActionList>
<ActionList.Group>
<ActionList.GroupHeading as="h3">Group heading 1</ActionList.GroupHeading>
<ActionList.Item>Item</ActionList.Item>
</ActionList.Group>
<ActionList.Group>
<ActionList.GroupHeading as="h3">Group heading 2</ActionList.GroupHeading>
<ActionList.Item>Item 2</ActionList.Item>
</ActionList.Group>
</ActionList>`,
`import {ActionList} from '@primer/react';
<ActionList>
<ActionList.Group>
<ActionList.GroupHeading>Group heading 1</ActionList.GroupHeading>
<ActionList.Item>Item</ActionList.Item>
</ActionList.Group>
<ActionList.Group>
<ActionList.GroupHeading>Group heading 2</ActionList.GroupHeading>
<ActionList.Item>Item 2</ActionList.Item>
</ActionList.Group>
</ActionList>`,
`import {ActionList} from '@primer/react';
<ActionList>
<ActionList.Group>
<ActionList.GroupHeading as="h3">Group heading</ActionList.GroupHeading>
<ActionList.Item>Item</ActionList.Item>
</ActionList.Group>
<ActionList.Item>Item 2</ActionList.Item>
</ActionList>`,
`import {ActionList} from '@primer/react';
<ActionList role="listbox">
<ActionList.Group>
<ActionList.GroupHeading>Group heading</ActionList.GroupHeading>
<ActionList.Item>Item</ActionList.Item>
</ActionList.Group>
<ActionList.Item>Item 2</ActionList.Item>
</ActionList>`,
`import {ActionList} from '@primer/react';
<ActionList role="menu">
<ActionList.Item>Item</ActionList.Item>
<ActionList.Group>
<ActionList.GroupHeading>Group heading</ActionList.GroupHeading>
<ActionList.Item>Group item</ActionList.Item>
</ActionList.Group>
</ActionList>`,
],
invalid: [
{
code: `<ActionList.Group title="Group heading 1"></ActionList.Group>`,
output: `<ActionList.Group><ActionList.GroupHeading>Group heading 1</ActionList.GroupHeading></ActionList.Group>`,
errors: [
{
messageId: 'titlePropDeprecated',
},
],
},
{
code: `<ActionList.Group title="Group heading 1" sx={{padding: 2}}></ActionList.Group>`,
output: `<ActionList.Group sx={{padding: 2}}><ActionList.GroupHeading>Group heading 1</ActionList.GroupHeading></ActionList.Group>`,
errors: [
{
messageId: 'titlePropDeprecated',
},
],
},
{
code: `<ActionList.Group variant="filled" title="Group heading 1"></ActionList.Group>`,
output: `<ActionList.Group variant="filled"><ActionList.GroupHeading>Group heading 1</ActionList.GroupHeading></ActionList.Group>`,
errors: [
{
messageId: 'titlePropDeprecated',
},
],
},
{
code: `<ActionList.Group title={titleVariable}></ActionList.Group>`,
output: `<ActionList.Group><ActionList.GroupHeading>{titleVariable}</ActionList.GroupHeading></ActionList.Group>`,
errors: [
{
messageId: 'titlePropDeprecated',
},
],
},
{
code: `<ActionList.Group title={'Title'}></ActionList.Group>`,
output: `<ActionList.Group><ActionList.GroupHeading>{'Title'}</ActionList.GroupHeading></ActionList.Group>`,
errors: [
{
messageId: 'titlePropDeprecated',
},
],
},
{
code: `<ActionList.Group title={condition ? 'Title' : undefined}></ActionList.Group>`,
output: `<ActionList.Group><ActionList.GroupHeading>{condition ? 'Title' : undefined}</ActionList.GroupHeading></ActionList.Group>`,
errors: [
{
messageId: 'titlePropDeprecated',
},
],
},
],
})
62 changes: 62 additions & 0 deletions src/rules/no-deprecated-props.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
'use strict'
const {getJSXOpeningElementAttribute} = require('../utils/get-jsx-opening-element-attribute')
const {getJSXOpeningElementName} = require('../utils/get-jsx-opening-element-name')

/**
* @type {import('eslint').Rule.RuleModule}
*/
module.exports = {
meta: {
type: 'suggestion',
docs: {
description:
'Avoid using deprecated `title` prop on `ActionList.Group` component. Use `ActionList.GroupHeading` instead.',
recommended: true,
url: 'https://primer.style/components/action-list/react/beta#actionlistgroupheading',
},
fixable: 'code',
schema: [],
messages: {
titlePropDeprecated: 'The `title` prop is deprecated. Please use `ActionList.GroupHeading` instead.',
},
},
create(context) {
return {
JSXOpeningElement(node) {
const openingElName = getJSXOpeningElementName(node)
if (openingElName !== 'ActionList.Group') {
return
}
const title = getJSXOpeningElementAttribute(node, 'title')
let groupTitle = ''
if (title !== undefined) {
context.report({
node,
messageId: 'titlePropDeprecated',
fix(fixer) {
// Group title is a string literal i.e. title="title"
if (title.value.type === 'Literal') {
groupTitle = title.value.value
// Group title is a JSX expression i.e. title={title}
} else if (title.value.type === 'JSXExpressionContainer') {
groupTitle = context.sourceCode.getText(title.value)
} else {
// we don't provide fix for cases where the title prop is not a string literal or JSX expression
return []
}
const start = title.range[0]
const end = title.range[1]
return [
fixer.removeRange([start - 1, end]), // remove the space before the title as well
fixer.insertTextAfterRange(
[node.range[1], node.range[1]],
`<ActionList.GroupHeading>${groupTitle}</ActionList.GroupHeading>`,
),
]
},
})
}
},
}
},
}

0 comments on commit 15cfbb4

Please sign in to comment.