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 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.9.0",
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.17.1"
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": "de243698bc64eeb8c3ac0592d96d129eabe1847a"
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
+ })