Skip to content

Commit 15cfbb4

Browse files
Add no-deprecated-props rule (#156)
* 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
1 parent 2f8e27b commit 15cfbb4

File tree

7 files changed

+239
-0
lines changed

7 files changed

+239
-0
lines changed

.changeset/tidy-moons-sip.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'eslint-plugin-primer-react': minor
3+
---
4+
5+
Add no-deprecated-props rule

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,4 @@ ESLint rules for Primer React
3737
- [a11y-tooltip-interactive-trigger](https://github.com/primer/eslint-plugin-primer-react/blob/main/docs/rules/a11y-tooltip-interactive-trigger.md)
3838
- [a11y-explicit-heading](https://github.com/primer/eslint-plugin-primer-react/blob/main/docs/rules/a11y-explicit-heading.md)
3939
- [new-css-color-vars](https://github.com/primer/eslint-plugin-primer-react/blob/main/docs/rules/new-css-color-vars.md)
40+
- [no-deprecated-props](https://github.com/primer/eslint-plugin-primer-react/blob/main/docs/rules/no-deprecated-props.md)

docs/rules/no-deprecated-props.md

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
## Rule Details
2+
3+
This rule enforces to use the recommended API (`ActionList.GroupHeading`) component over the deprecated prop (`title` prop on `ActionList.Group`) for ActionList component.
4+
5+
👎 Examples of **incorrect** code for this rule:
6+
7+
```jsx
8+
/* eslint primer-react/no-deprecated-props: "error" */
9+
import {ActionList} from '@primer/react'
10+
11+
const App = () => (
12+
<ActionList>
13+
<ActionList.Group title="Group heading">
14+
<ActionList.Item>Item 1</ActionList.Item>
15+
</ActionList.Group>
16+
</ActionList>
17+
)
18+
```
19+
20+
👍 Examples of **correct** code for this rule:
21+
22+
```jsx
23+
/* eslint primer-react/no-deprecated-props: "error" */
24+
import {ActionList} from '@primer/react'
25+
26+
const App = () => (
27+
<ActionList>
28+
<ActionList.Group>
29+
<ActionList.GroupHeading as="h2">Group heading</ActionList.GroupHeading>
30+
<ActionList.Item>Item 1</ActionList.Item>
31+
</ActionList.Group>
32+
</ActionList>
33+
)
34+
```
35+
36+
```jsx
37+
/* eslint primer-react/no-deprecated-props: "error" */
38+
import {ActionList} from '@primer/react'
39+
40+
const App = () => (
41+
<ActionList role="lisbox">
42+
<ActionList.Group>
43+
<ActionList.GroupHeading>Group heading</ActionList.GroupHeading>
44+
<ActionList.Item>Item 1</ActionList.Item>
45+
</ActionList.Group>
46+
</ActionList>
47+
)
48+
```

src/configs/recommended.js

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ module.exports = {
1717
'primer-react/new-color-css-vars': 'error',
1818
'primer-react/a11y-explicit-heading': 'error',
1919
'primer-react/new-color-css-vars-have-fallback': 'error',
20+
'primer-react/no-deprecated-props': 'warn',
2021
},
2122
settings: {
2223
github: {

src/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ module.exports = {
88
'new-color-css-vars': require('./rules/new-color-css-vars'),
99
'a11y-explicit-heading': require('./rules/a11y-explicit-heading'),
1010
'new-color-css-vars-have-fallback': require('./rules/new-color-css-vars-have-fallback'),
11+
'no-deprecated-props': require('./rules/no-deprecated-props'),
1112
},
1213
configs: {
1314
recommended: require('./configs/recommended'),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
'use strict'
2+
3+
const {RuleTester} = require('eslint')
4+
const rule = require('../no-deprecated-props')
5+
6+
const ruleTester = new RuleTester({
7+
parserOptions: {
8+
ecmaVersion: 'latest',
9+
sourceType: 'module',
10+
ecmaFeatures: {
11+
jsx: true,
12+
},
13+
},
14+
})
15+
16+
ruleTester.run('no-deprecated-props', rule, {
17+
valid: [
18+
`import {ActionList} from '@primer/react';
19+
<ActionList>
20+
<ActionList.Group>
21+
<ActionList.GroupHeading as="h3">Group heading 1</ActionList.GroupHeading>
22+
<ActionList.Item>Item</ActionList.Item>
23+
</ActionList.Group>
24+
<ActionList.Group>
25+
<ActionList.GroupHeading as="h3">Group heading 2</ActionList.GroupHeading>
26+
<ActionList.Item>Item 2</ActionList.Item>
27+
</ActionList.Group>
28+
</ActionList>`,
29+
`import {ActionList} from '@primer/react';
30+
<ActionList>
31+
<ActionList.Group>
32+
<ActionList.GroupHeading>Group heading 1</ActionList.GroupHeading>
33+
<ActionList.Item>Item</ActionList.Item>
34+
</ActionList.Group>
35+
<ActionList.Group>
36+
<ActionList.GroupHeading>Group heading 2</ActionList.GroupHeading>
37+
<ActionList.Item>Item 2</ActionList.Item>
38+
</ActionList.Group>
39+
</ActionList>`,
40+
`import {ActionList} from '@primer/react';
41+
<ActionList>
42+
<ActionList.Group>
43+
<ActionList.GroupHeading as="h3">Group heading</ActionList.GroupHeading>
44+
<ActionList.Item>Item</ActionList.Item>
45+
</ActionList.Group>
46+
<ActionList.Item>Item 2</ActionList.Item>
47+
</ActionList>`,
48+
`import {ActionList} from '@primer/react';
49+
<ActionList role="listbox">
50+
<ActionList.Group>
51+
<ActionList.GroupHeading>Group heading</ActionList.GroupHeading>
52+
<ActionList.Item>Item</ActionList.Item>
53+
</ActionList.Group>
54+
<ActionList.Item>Item 2</ActionList.Item>
55+
</ActionList>`,
56+
`import {ActionList} from '@primer/react';
57+
<ActionList role="menu">
58+
<ActionList.Item>Item</ActionList.Item>
59+
<ActionList.Group>
60+
<ActionList.GroupHeading>Group heading</ActionList.GroupHeading>
61+
<ActionList.Item>Group item</ActionList.Item>
62+
</ActionList.Group>
63+
</ActionList>`,
64+
],
65+
invalid: [
66+
{
67+
code: `<ActionList.Group title="Group heading 1"></ActionList.Group>`,
68+
output: `<ActionList.Group><ActionList.GroupHeading>Group heading 1</ActionList.GroupHeading></ActionList.Group>`,
69+
errors: [
70+
{
71+
messageId: 'titlePropDeprecated',
72+
},
73+
],
74+
},
75+
{
76+
code: `<ActionList.Group title="Group heading 1" sx={{padding: 2}}></ActionList.Group>`,
77+
output: `<ActionList.Group sx={{padding: 2}}><ActionList.GroupHeading>Group heading 1</ActionList.GroupHeading></ActionList.Group>`,
78+
errors: [
79+
{
80+
messageId: 'titlePropDeprecated',
81+
},
82+
],
83+
},
84+
{
85+
code: `<ActionList.Group variant="filled" title="Group heading 1"></ActionList.Group>`,
86+
output: `<ActionList.Group variant="filled"><ActionList.GroupHeading>Group heading 1</ActionList.GroupHeading></ActionList.Group>`,
87+
errors: [
88+
{
89+
messageId: 'titlePropDeprecated',
90+
},
91+
],
92+
},
93+
{
94+
code: `<ActionList.Group title={titleVariable}></ActionList.Group>`,
95+
output: `<ActionList.Group><ActionList.GroupHeading>{titleVariable}</ActionList.GroupHeading></ActionList.Group>`,
96+
errors: [
97+
{
98+
messageId: 'titlePropDeprecated',
99+
},
100+
],
101+
},
102+
{
103+
code: `<ActionList.Group title={'Title'}></ActionList.Group>`,
104+
output: `<ActionList.Group><ActionList.GroupHeading>{'Title'}</ActionList.GroupHeading></ActionList.Group>`,
105+
errors: [
106+
{
107+
messageId: 'titlePropDeprecated',
108+
},
109+
],
110+
},
111+
{
112+
code: `<ActionList.Group title={condition ? 'Title' : undefined}></ActionList.Group>`,
113+
output: `<ActionList.Group><ActionList.GroupHeading>{condition ? 'Title' : undefined}</ActionList.GroupHeading></ActionList.Group>`,
114+
errors: [
115+
{
116+
messageId: 'titlePropDeprecated',
117+
},
118+
],
119+
},
120+
],
121+
})

src/rules/no-deprecated-props.js

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
'use strict'
2+
const {getJSXOpeningElementAttribute} = require('../utils/get-jsx-opening-element-attribute')
3+
const {getJSXOpeningElementName} = require('../utils/get-jsx-opening-element-name')
4+
5+
/**
6+
* @type {import('eslint').Rule.RuleModule}
7+
*/
8+
module.exports = {
9+
meta: {
10+
type: 'suggestion',
11+
docs: {
12+
description:
13+
'Avoid using deprecated `title` prop on `ActionList.Group` component. Use `ActionList.GroupHeading` instead.',
14+
recommended: true,
15+
url: 'https://primer.style/components/action-list/react/beta#actionlistgroupheading',
16+
},
17+
fixable: 'code',
18+
schema: [],
19+
messages: {
20+
titlePropDeprecated: 'The `title` prop is deprecated. Please use `ActionList.GroupHeading` instead.',
21+
},
22+
},
23+
create(context) {
24+
return {
25+
JSXOpeningElement(node) {
26+
const openingElName = getJSXOpeningElementName(node)
27+
if (openingElName !== 'ActionList.Group') {
28+
return
29+
}
30+
const title = getJSXOpeningElementAttribute(node, 'title')
31+
let groupTitle = ''
32+
if (title !== undefined) {
33+
context.report({
34+
node,
35+
messageId: 'titlePropDeprecated',
36+
fix(fixer) {
37+
// Group title is a string literal i.e. title="title"
38+
if (title.value.type === 'Literal') {
39+
groupTitle = title.value.value
40+
// Group title is a JSX expression i.e. title={title}
41+
} else if (title.value.type === 'JSXExpressionContainer') {
42+
groupTitle = context.sourceCode.getText(title.value)
43+
} else {
44+
// we don't provide fix for cases where the title prop is not a string literal or JSX expression
45+
return []
46+
}
47+
const start = title.range[0]
48+
const end = title.range[1]
49+
return [
50+
fixer.removeRange([start - 1, end]), // remove the space before the title as well
51+
fixer.insertTextAfterRange(
52+
[node.range[1], node.range[1]],
53+
`<ActionList.GroupHeading>${groupTitle}</ActionList.GroupHeading>`,
54+
),
55+
]
56+
},
57+
})
58+
}
59+
},
60+
}
61+
},
62+
}

0 commit comments

Comments
 (0)