eslint-plugin-smarthr 1.9.0 → 1.11.0
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/CHANGELOG.md +14 -0
- package/package.json +3 -3
- package/rules/a11y-input-in-form-control/index.js +3 -0
- package/rules/a11y-prohibit-checkbox-or-radio-in-table-cell/README.md +40 -0
- package/rules/a11y-prohibit-checkbox-or-radio-in-table-cell/index.js +71 -0
- package/test/a11y-input-in-form-control.js +2 -0
- package/test/a11y-prohibit-checkbox-or-radio-in-table-cell.js +73 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
## [1.11.0](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v1.10.0...eslint-plugin-smarthr-v1.11.0) (2025-10-01)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* a11y-input-in-form-controlのaria-label,aria-labelledbyが設定されている場合、FormControlでラップしなくてもよしとする ([#795](https://github.com/kufu/tamatebako/issues/795)) ([443892f](https://github.com/kufu/tamatebako/commit/443892ffb39732157d77a179484da3f529e43885))
|
|
11
|
+
|
|
12
|
+
## [1.10.0](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v1.9.0...eslint-plugin-smarthr-v1.10.0) (2025-10-01)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* セル内のCheckboxおよびRadioButtonを禁止するルールを追加 ([#792](https://github.com/kufu/tamatebako/issues/792)) ([17ab980](https://github.com/kufu/tamatebako/commit/17ab98018c81a81376ac213e3309e60080ed9326))
|
|
18
|
+
|
|
5
19
|
## [1.9.0](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v1.8.1...eslint-plugin-smarthr-v1.9.0) (2025-09-08)
|
|
6
20
|
|
|
7
21
|
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-smarthr",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"author": "SmartHR",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "A sharable ESLint plugin for SmartHR",
|
|
7
7
|
"main": "index.js",
|
|
8
8
|
"engines": {
|
|
9
|
-
"node": ">=22.
|
|
9
|
+
"node": ">=22.18.0"
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
12
|
"test": "jest"
|
|
@@ -37,5 +37,5 @@
|
|
|
37
37
|
"eslintplugin",
|
|
38
38
|
"smarthr"
|
|
39
39
|
],
|
|
40
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "5877e7cf1fee3324112f56f65c5aa2f753dd2aca"
|
|
41
41
|
}
|
|
@@ -77,8 +77,11 @@ module.exports = {
|
|
|
77
77
|
for (const i of node.attributes) {
|
|
78
78
|
if (i.name) {
|
|
79
79
|
// HINT: idが設定されている場合、htmlForでlabelと紐づく可能性が高いため無視する
|
|
80
|
+
// aria-label, aria-labelledbyが設定されている場合は疑似ラベルが設定されているため許容する
|
|
80
81
|
switch (i.name.name) {
|
|
81
82
|
case 'id':
|
|
83
|
+
case 'aria-label':
|
|
84
|
+
case 'aria-labelledby':
|
|
82
85
|
isPseudoLabel = true
|
|
83
86
|
break
|
|
84
87
|
case 'type':
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# smarthr/a11y-prohibit-checkbox-or-radio-in-table-cell
|
|
2
|
+
|
|
3
|
+
- テーブルセル(Th, Td)内に Checkbox, RadioButton を配置することを禁止するルールです
|
|
4
|
+
- SmartHR UI には、デフォルトでアクセシブルネームを設定する TdCheckbox, ThCheckbox, TdRadioButton といったより適切なコンポーネントが用意されています
|
|
5
|
+
|
|
6
|
+
## rules
|
|
7
|
+
|
|
8
|
+
```js
|
|
9
|
+
{
|
|
10
|
+
rules: {
|
|
11
|
+
'smarthr/a11y-prohibit-checkbox-or-radio-in-table-cell': [
|
|
12
|
+
'error', // 'warn', 'off'
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## ❌ Incorrect
|
|
19
|
+
|
|
20
|
+
```jsx
|
|
21
|
+
<Td>
|
|
22
|
+
<Checkbox name="foo" />
|
|
23
|
+
</Td>
|
|
24
|
+
|
|
25
|
+
<Th>
|
|
26
|
+
<Checkbox name="bar" />
|
|
27
|
+
</Th>
|
|
28
|
+
|
|
29
|
+
<Td>
|
|
30
|
+
<RadioButton name="baz" />
|
|
31
|
+
</Td>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## ✅ Correct
|
|
35
|
+
|
|
36
|
+
```jsx
|
|
37
|
+
<TdCheckbox name="foo" />
|
|
38
|
+
<ThCheckbox name="bar" />
|
|
39
|
+
<TdRadioButton name="baz" />
|
|
40
|
+
```
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const findClosestThFromAncestor = (node) => {
|
|
2
|
+
if (node.type === 'JSXElement' && node.openingElement.name.name === 'Th') {
|
|
3
|
+
return node
|
|
4
|
+
}
|
|
5
|
+
if (node.parent) {
|
|
6
|
+
return findClosestThFromAncestor(node.parent)
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
12
|
+
*/
|
|
13
|
+
module.exports = {
|
|
14
|
+
meta: {
|
|
15
|
+
type: 'problem',
|
|
16
|
+
fixable: 'code',
|
|
17
|
+
schema: [],
|
|
18
|
+
messages: {
|
|
19
|
+
default: '{{cell}} の子孫に {{component}} を置くことはできません。代わりに {{preferred}} を使用してください。',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
create(context) {
|
|
23
|
+
const sourceCode = context.sourceCode
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
'JSXElement[openingElement.name.name=/Td$/] JSXElement[openingElement.name.name=/Check(b|B)ox$/]': (node) => {
|
|
27
|
+
context.report({
|
|
28
|
+
node,
|
|
29
|
+
messageId: 'default',
|
|
30
|
+
data: {
|
|
31
|
+
cell: 'Td',
|
|
32
|
+
component: 'Checkbox',
|
|
33
|
+
preferred: 'TdCheckbox',
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
},
|
|
37
|
+
'JSXElement[openingElement.name.name=/Td$/] JSXElement[openingElement.name.name=/RadioButton$/]': (node) => {
|
|
38
|
+
context.report({
|
|
39
|
+
node,
|
|
40
|
+
messageId: 'default',
|
|
41
|
+
data: {
|
|
42
|
+
cell: 'Td',
|
|
43
|
+
component: 'RadioButton',
|
|
44
|
+
preferred: 'TdRadioButton',
|
|
45
|
+
},
|
|
46
|
+
})
|
|
47
|
+
},
|
|
48
|
+
'JSXElement[openingElement.name.name=/Th$/] JSXElement[openingElement.name.name=/Check(b|B)ox$/]': (node) => {
|
|
49
|
+
context.report({
|
|
50
|
+
node,
|
|
51
|
+
messageId: 'default',
|
|
52
|
+
data: {
|
|
53
|
+
cell: 'Th',
|
|
54
|
+
component: 'Checkbox',
|
|
55
|
+
preferred: 'ThCheckbox',
|
|
56
|
+
},
|
|
57
|
+
*fix(fixer) {
|
|
58
|
+
const th = findClosestThFromAncestor(node)
|
|
59
|
+
if (th) {
|
|
60
|
+
const thCheckbox = sourceCode.getText(node).replace(/<Check(b|B)ox/, '<ThCheckbox')
|
|
61
|
+
yield fixer.insertTextAfter(th, thCheckbox)
|
|
62
|
+
yield fixer.remove(th)
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports.schema = []
|
|
@@ -111,6 +111,8 @@ ruleTester.run('a11y-input-in-form-control', rule, {
|
|
|
111
111
|
{ code: '<Fieldset><HogeCheckBoxs /></Fieldset>' },
|
|
112
112
|
{ code: '<Fieldset><HogeCheckBoxes /></Fieldset>' },
|
|
113
113
|
{ code: '<HogeFormControl>{ dateInput ? <DateInput /> : <Input /> }</HogeFormControl>'},
|
|
114
|
+
{ code: '<Input aria-label="hoge" />' },
|
|
115
|
+
{ code: '<Select aria-labelledby="hoge" />' },
|
|
114
116
|
],
|
|
115
117
|
invalid: [
|
|
116
118
|
{ code: `<input />`, errors: [ { message: noLabeledInput('input') } ] },
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const rule = require('../rules/a11y-prohibit-checkbox-or-radio-in-table-cell')
|
|
2
|
+
|
|
3
|
+
const RuleTester = require('eslint').RuleTester
|
|
4
|
+
|
|
5
|
+
const ruleTester = new RuleTester({
|
|
6
|
+
languageOptions: {
|
|
7
|
+
parserOptions: {
|
|
8
|
+
ecmaFeatures: {
|
|
9
|
+
jsx: true,
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
ruleTester.run('a11y-prohibit-checkbox-or-radio-in-table-cell', rule, {
|
|
16
|
+
valid: ['<TdCheckbox />', '<ThCheckbox />', '<TdRadioButton />', '<Td>hello</Td>', '<Th>hello</Th>'],
|
|
17
|
+
invalid: [
|
|
18
|
+
{
|
|
19
|
+
code: `<Td><Checkbox /></Td>`,
|
|
20
|
+
errors: [{ message: 'Td の子孫に Checkbox を置くことはできません。代わりに TdCheckbox を使用してください。' }],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
code: `<Th><Checkbox /></Th>`,
|
|
24
|
+
output: `<ThCheckbox />`,
|
|
25
|
+
errors: [{ message: 'Th の子孫に Checkbox を置くことはできません。代わりに ThCheckbox を使用してください。' }],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
code: `<Th><Checkbox id="my-checkbox" name="agree" error /></Th>`,
|
|
29
|
+
output: `<ThCheckbox id="my-checkbox" name="agree" error />`,
|
|
30
|
+
errors: [{ message: 'Th の子孫に Checkbox を置くことはできません。代わりに ThCheckbox を使用してください。' }],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
code: `<Td><RadioButton /></Td>`,
|
|
34
|
+
errors: [{ message: 'Td の子孫に RadioButton を置くことはできません。代わりに TdRadioButton を使用してください。' }],
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
{
|
|
38
|
+
code: `<Td><div><div><Checkbox /></div></div></Td>`,
|
|
39
|
+
errors: [{ message: 'Td の子孫に Checkbox を置くことはできません。代わりに TdCheckbox を使用してください。' }],
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
code: `<Td><><><Checkbox /></></></Td>`,
|
|
43
|
+
errors: [{ message: 'Td の子孫に Checkbox を置くことはできません。代わりに TdCheckbox を使用してください。' }],
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
{
|
|
47
|
+
code: `<CustomTd><CustomCheckbox /></CustomTd>`,
|
|
48
|
+
errors: [{ message: 'Td の子孫に Checkbox を置くことはできません。代わりに TdCheckbox を使用してください。' }],
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
code: `<CustomTh><CustomCheckbox /></CustomTh>`,
|
|
52
|
+
output: null,
|
|
53
|
+
errors: [{ message: 'Th の子孫に Checkbox を置くことはできません。代わりに ThCheckbox を使用してください。' }],
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
code: `<CustomTd><CustomRadioButton /></CustomTd>`,
|
|
57
|
+
errors: [{ message: 'Td の子孫に RadioButton を置くことはできません。代わりに TdRadioButton を使用してください。' }],
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: "https://smarthr.atlassian.net/browse/A11Y2-23",
|
|
61
|
+
code: `
|
|
62
|
+
<CheckTd onClick={() => toggleChecked(crewEvaluation.id)}>
|
|
63
|
+
<CheckBox
|
|
64
|
+
name="checkEvaluation"
|
|
65
|
+
checked={checked}
|
|
66
|
+
onChange={() => toggleChecked(crewEvaluation.id)}
|
|
67
|
+
/>
|
|
68
|
+
</CheckTd>
|
|
69
|
+
`,
|
|
70
|
+
errors: [{ message: 'Td の子孫に Checkbox を置くことはできません。代わりに TdCheckbox を使用してください。' }],
|
|
71
|
+
}
|
|
72
|
+
],
|
|
73
|
+
})
|