eslint-plugin-primer-react 5.3.0 → 5.4.0-rc.61b506f
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release_tracking.yml +1 -1
- package/CHANGELOG.md +10 -0
- package/README.md +1 -0
- package/docs/rules/a11y-use-next-tooltip.md +33 -0
- package/package-lock.json +14216 -0
- package/package.json +2 -2
- package/src/configs/recommended.js +1 -0
- package/src/index.js +1 -0
- package/src/rules/__tests__/a11y-use-next-tooltip.test.js +61 -0
- package/src/rules/a11y-use-next-tooltip.js +128 -0
- package/src/rules/no-system-props.js +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-primer-react",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.4.0-rc.61b506f",
|
|
4
4
|
"description": "ESLint rules for Primer React",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"eslint-plugin-prettier": "^5.0.1",
|
|
45
45
|
"jest": "^29.7.0",
|
|
46
46
|
"markdownlint-cli2": "^0.13.0",
|
|
47
|
-
"markdownlint-cli2-formatter-pretty": "^0.0.
|
|
47
|
+
"markdownlint-cli2-formatter-pretty": "^0.0.6"
|
|
48
48
|
},
|
|
49
49
|
"prettier": "@github/prettier-config"
|
|
50
50
|
}
|
package/src/index.js
CHANGED
|
@@ -9,6 +9,7 @@ module.exports = {
|
|
|
9
9
|
'no-deprecated-props': require('./rules/no-deprecated-props'),
|
|
10
10
|
'a11y-link-in-text-block': require('./rules/a11y-link-in-text-block'),
|
|
11
11
|
'a11y-remove-disable-tooltip': require('./rules/a11y-remove-disable-tooltip'),
|
|
12
|
+
'a11y-use-next-tooltip': require('./rules/a11y-use-next-tooltip'),
|
|
12
13
|
},
|
|
13
14
|
configs: {
|
|
14
15
|
recommended: require('./configs/recommended'),
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const rule = require('../a11y-use-next-tooltip')
|
|
2
|
+
const {RuleTester} = require('eslint')
|
|
3
|
+
|
|
4
|
+
const ruleTester = new RuleTester({
|
|
5
|
+
parserOptions: {
|
|
6
|
+
ecmaVersion: 'latest',
|
|
7
|
+
sourceType: 'module',
|
|
8
|
+
ecmaFeatures: {
|
|
9
|
+
jsx: true,
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
ruleTester.run('a11y-use-next-tooltip', rule, {
|
|
15
|
+
valid: [
|
|
16
|
+
`import {Tooltip} from '@primer/react/next';`,
|
|
17
|
+
`import {UnderlineNav, Button} from '@primer/react';
|
|
18
|
+
import {Tooltip} from '@primer/react/next';`,
|
|
19
|
+
`import {UnderlineNav, Button} from '@primer/react';
|
|
20
|
+
import {Tooltip, SelectPanel} from '@primer/react/next';`,
|
|
21
|
+
],
|
|
22
|
+
invalid: [
|
|
23
|
+
{
|
|
24
|
+
code: `import {Tooltip} from '@primer/react';`,
|
|
25
|
+
errors: [{messageId: 'useNextTooltip'}],
|
|
26
|
+
output: `import {Tooltip} from '@primer/react/next';`,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
code: `import {Tooltip, Button} from '@primer/react';\n<Tooltip aria-label="tooltip text"><Button>Button</Button></Tooltip>`,
|
|
30
|
+
errors: [{messageId: 'useNextTooltip'}, {messageId: 'useTextProp'}],
|
|
31
|
+
output: `import {Button} from '@primer/react';\nimport {Tooltip} from '@primer/react/next';\n<Tooltip text="tooltip text"><Button>Button</Button></Tooltip>`,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
code: `import {ActionList, ActionMenu, Tooltip, Button} from '@primer/react';\n<Tooltip aria-label="tooltip text"><Button>Button</Button></Tooltip>`,
|
|
35
|
+
errors: [
|
|
36
|
+
{messageId: 'useNextTooltip', line: 1},
|
|
37
|
+
{messageId: 'useTextProp', line: 2},
|
|
38
|
+
],
|
|
39
|
+
output: `import {ActionList, ActionMenu, Button} from '@primer/react';\nimport {Tooltip} from '@primer/react/next';\n<Tooltip text="tooltip text"><Button>Button</Button></Tooltip>`,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
code: `import {ActionList, ActionMenu, Button, Tooltip} from '@primer/react';\n<Tooltip aria-label="tooltip text"><Button aria-label="test">Button</Button></Tooltip>`,
|
|
43
|
+
errors: [
|
|
44
|
+
{messageId: 'useNextTooltip', line: 1},
|
|
45
|
+
{messageId: 'useTextProp', line: 2},
|
|
46
|
+
],
|
|
47
|
+
output: `import {ActionList, ActionMenu, Button} from '@primer/react';\nimport {Tooltip} from '@primer/react/next';\n<Tooltip text="tooltip text"><Button aria-label="test">Button</Button></Tooltip>`,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
code: `import {ActionList, ActionMenu, Tooltip, Button} from '@primer/react';\n<Tooltip aria-label="tooltip text" noDelay={true} wrap={true} align="left"><Button>Button</Button></Tooltip>`,
|
|
51
|
+
errors: [
|
|
52
|
+
{messageId: 'useNextTooltip', line: 1},
|
|
53
|
+
{messageId: 'useTextProp', line: 2},
|
|
54
|
+
{messageId: 'noDelayRemoved', line: 2},
|
|
55
|
+
{messageId: 'wrapRemoved', line: 2},
|
|
56
|
+
{messageId: 'alignRemoved', line: 2},
|
|
57
|
+
],
|
|
58
|
+
output: `import {ActionList, ActionMenu, Button} from '@primer/react';\nimport {Tooltip} from '@primer/react/next';\n<Tooltip text="tooltip text" ><Button>Button</Button></Tooltip>`,
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
})
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
const url = require('../url')
|
|
3
|
+
const {getJSXOpeningElementAttribute} = require('../utils/get-jsx-opening-element-attribute')
|
|
4
|
+
const {getJSXOpeningElementName} = require('../utils/get-jsx-opening-element-name')
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
meta: {
|
|
8
|
+
type: 'suggestion',
|
|
9
|
+
docs: {
|
|
10
|
+
description: 'recommends the use of @primer/react/next Tooltip component',
|
|
11
|
+
category: 'Best Practices',
|
|
12
|
+
recommended: true,
|
|
13
|
+
url: url(module),
|
|
14
|
+
},
|
|
15
|
+
fixable: true,
|
|
16
|
+
schema: [],
|
|
17
|
+
messages: {
|
|
18
|
+
useNextTooltip: 'Please use @primer/react/next Tooltip component that has accessibility improvements',
|
|
19
|
+
useTextProp: 'Please use the text prop instead of aria-label',
|
|
20
|
+
noDelayRemoved: 'noDelay prop is removed. Tooltip now has no delay by default',
|
|
21
|
+
wrapRemoved: 'wrap prop is removed. Tooltip now wraps by default',
|
|
22
|
+
alignRemoved: 'align prop is removed. Please use the direction prop instead.',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
create(context) {
|
|
26
|
+
return {
|
|
27
|
+
ImportDeclaration(node) {
|
|
28
|
+
if (node.source.value !== '@primer/react') {
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
const hasTooltip = node.specifiers.some(
|
|
32
|
+
specifier => specifier.imported && specifier.imported.name === 'Tooltip',
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
const hasOtherImports = node.specifiers.length > 1
|
|
36
|
+
if (!hasTooltip) {
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
context.report({
|
|
40
|
+
node,
|
|
41
|
+
messageId: 'useNextTooltip',
|
|
42
|
+
fix(fixer) {
|
|
43
|
+
// If Tooltip is the only import, replace the whole import statement
|
|
44
|
+
if (!hasOtherImports) {
|
|
45
|
+
return fixer.replaceText(node.source, `'@primer/react/next'`)
|
|
46
|
+
} else {
|
|
47
|
+
// Otherwise, remove Tooltip from the import statement and add a new import statement with the correct path
|
|
48
|
+
const tooltipSpecifier = node.specifiers.find(
|
|
49
|
+
specifier => specifier.imported && specifier.imported.name === 'Tooltip',
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
const tokensToRemove = [' ', ',']
|
|
53
|
+
const tooltipIsFirstImport = tooltipSpecifier === node.specifiers[0]
|
|
54
|
+
const tooltipIsLastImport = tooltipSpecifier === node.specifiers[node.specifiers.length - 1]
|
|
55
|
+
const tooltipIsNotFirstOrLastImport = !tooltipIsFirstImport && !tooltipIsLastImport
|
|
56
|
+
|
|
57
|
+
const sourceCode = context.getSourceCode()
|
|
58
|
+
const canRemoveBefore = tooltipIsNotFirstOrLastImport
|
|
59
|
+
? false
|
|
60
|
+
: tokensToRemove.includes(sourceCode.getTokenBefore(tooltipSpecifier).value)
|
|
61
|
+
const canRemoveAfter = tokensToRemove.includes(sourceCode.getTokenAfter(tooltipSpecifier).value)
|
|
62
|
+
const start = canRemoveBefore
|
|
63
|
+
? sourceCode.getTokenBefore(tooltipSpecifier).range[0]
|
|
64
|
+
: tooltipSpecifier.range[0]
|
|
65
|
+
const end = canRemoveAfter
|
|
66
|
+
? sourceCode.getTokenAfter(tooltipSpecifier).range[1] + 1
|
|
67
|
+
: tooltipSpecifier.range[1]
|
|
68
|
+
return [
|
|
69
|
+
// remove tooltip specifier and the space and comma after it
|
|
70
|
+
fixer.removeRange([start, end]),
|
|
71
|
+
fixer.insertTextAfterRange(
|
|
72
|
+
[node.range[1], node.range[1]],
|
|
73
|
+
`\nimport {Tooltip} from '@primer/react/next';`,
|
|
74
|
+
),
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
})
|
|
79
|
+
},
|
|
80
|
+
JSXOpeningElement(node) {
|
|
81
|
+
const openingElName = getJSXOpeningElementName(node)
|
|
82
|
+
if (openingElName !== 'Tooltip') {
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
const ariaLabel = getJSXOpeningElementAttribute(node, 'aria-label')
|
|
86
|
+
if (ariaLabel !== undefined) {
|
|
87
|
+
context.report({
|
|
88
|
+
node,
|
|
89
|
+
messageId: 'useTextProp',
|
|
90
|
+
fix(fixer) {
|
|
91
|
+
return fixer.replaceText(ariaLabel.name, 'text')
|
|
92
|
+
},
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
const noDelay = getJSXOpeningElementAttribute(node, 'noDelay')
|
|
96
|
+
if (noDelay !== undefined) {
|
|
97
|
+
context.report({
|
|
98
|
+
node,
|
|
99
|
+
messageId: 'noDelayRemoved',
|
|
100
|
+
fix(fixer) {
|
|
101
|
+
return fixer.remove(noDelay)
|
|
102
|
+
},
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
const wrap = getJSXOpeningElementAttribute(node, 'wrap')
|
|
106
|
+
if (wrap !== undefined) {
|
|
107
|
+
context.report({
|
|
108
|
+
node,
|
|
109
|
+
messageId: 'wrapRemoved',
|
|
110
|
+
fix(fixer) {
|
|
111
|
+
return fixer.remove(wrap)
|
|
112
|
+
},
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
const align = getJSXOpeningElementAttribute(node, 'align')
|
|
116
|
+
if (align !== undefined) {
|
|
117
|
+
context.report({
|
|
118
|
+
node,
|
|
119
|
+
messageId: 'alignRemoved',
|
|
120
|
+
fix(fixer) {
|
|
121
|
+
return fixer.remove(align)
|
|
122
|
+
},
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
}
|
|
@@ -15,6 +15,7 @@ const utilityComponents = new Set(['Box', 'Text'])
|
|
|
15
15
|
// Components for which we allow a set of prop names
|
|
16
16
|
const excludedComponentProps = new Map([
|
|
17
17
|
['ActionMenu.Overlay', new Set(['width', 'height', 'maxHeight', 'position', 'top', 'right', 'bottom', 'left'])],
|
|
18
|
+
['ActionMenu.Button', new Set(['alignContent'])],
|
|
18
19
|
['Autocomplete.Overlay', new Set(['width', 'height', 'maxHeight', 'position', 'top', 'right', 'bottom', 'left'])],
|
|
19
20
|
['AnchoredOverlay', new Set(['width', 'height'])],
|
|
20
21
|
['Avatar', new Set(['size'])],
|