eslint-plugin-smarthr 1.4.0 → 1.4.2
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-delegate-element-has-role-presentation/index.js +2 -2
- package/rules/a11y-input-has-name-attribute/index.js +5 -5
- package/rules/a11y-input-in-form-control/README.md +5 -5
- package/rules/a11y-input-in-form-control/index.js +5 -5
- package/rules/a11y-prohibit-input-placeholder/index.js +42 -46
- package/rules/best-practice-for-tailwind-variants/index.js +2 -2
- package/test/a11y-input-has-name-attribute.js +4 -4
- package/test/a11y-input-in-form-control.js +9 -9
- package/test/a11y-prohhibit-input-placeholder.js +5 -5
- package/test/best-practice-for-tailwind-variants.js +1 -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.4.2](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v1.4.1...eslint-plugin-smarthr-v1.4.2) (2025-03-27)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* smarthr-uiのCheckbox,Comboboxに対応する ([#550](https://github.com/kufu/tamatebako/issues/550)) ([3978a12](https://github.com/kufu/tamatebako/commit/3978a120e4be158a4e9bd602bb1055b55600a41b))
|
|
11
|
+
|
|
12
|
+
## [1.4.1](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v1.4.0...eslint-plugin-smarthr-v1.4.1) (2025-03-25)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* tailwind-variantsでtv以外のimportを行った際、エラーになる問題を修正する ([#536](https://github.com/kufu/tamatebako/issues/536)) ([83efcf6](https://github.com/kufu/tamatebako/commit/83efcf671938ca471265af9c05bd47fbcce55fb9))
|
|
18
|
+
|
|
5
19
|
## [1.4.0](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v1.3.0...eslint-plugin-smarthr-v1.4.0) (2025-03-06)
|
|
6
20
|
|
|
7
21
|
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-smarthr",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.2",
|
|
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": ">=20.
|
|
9
|
+
"node": ">=20.19.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": "63fb01426aa3a021599ac0e224a0f1946ee9081f"
|
|
41
41
|
}
|
|
@@ -6,8 +6,8 @@ const EXPECTED_NAMES = {
|
|
|
6
6
|
'(s|S)elect$': 'Select$',
|
|
7
7
|
'InputFile$': 'InputFile$',
|
|
8
8
|
'RadioButtonPanel$': 'RadioButtonPanel$',
|
|
9
|
-
'Check(b|B)ox$': '
|
|
10
|
-
'Combo(b|B)ox$': '
|
|
9
|
+
'Check(b|B)ox$': 'Checkbox$',
|
|
10
|
+
'Combo(b|B)ox$': 'Combobox$',
|
|
11
11
|
'(Date|Wareki)Picker$': '(Date|Wareki)Picker$',
|
|
12
12
|
'TimePicker$': 'TimePicker$',
|
|
13
13
|
'DropZone$': 'DropZone$',
|
|
@@ -23,11 +23,11 @@ const EXPECTED_NAMES = {
|
|
|
23
23
|
'(i|I)nput$': 'Input$',
|
|
24
24
|
'(t|T)extarea$': 'Textarea$',
|
|
25
25
|
'(s|S)elect$': 'Select$',
|
|
26
|
-
InputFile
|
|
27
|
-
RadioButton
|
|
28
|
-
RadioButtonPanel
|
|
29
|
-
'Check(b|B)ox$': '
|
|
30
|
-
'Combo(b|B)ox$': '
|
|
26
|
+
'InputFile$': 'InputFile$',
|
|
27
|
+
'RadioButton$': 'RadioButton$',
|
|
28
|
+
'RadioButtonPanel$': 'RadioButtonPanel$',
|
|
29
|
+
'Check(b|B)ox$': 'Checkbox$',
|
|
30
|
+
'Combo(b|B)ox$': 'Combobox$',
|
|
31
31
|
'(Date|Wareki)Picker$': '(Date|Wareki)Picker$',
|
|
32
32
|
TimePicker$: 'TimePicker$',
|
|
33
33
|
DropZone$: 'DropZone$',
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
<Select />
|
|
33
33
|
</Section>
|
|
34
34
|
|
|
35
|
-
// RadioButton,
|
|
35
|
+
// RadioButton, CheckboxはFieldsetでグルーピングする必要があるためNG
|
|
36
36
|
<FormControl title="any heading">
|
|
37
37
|
<RadioButton>{a.label}</RadioButton>
|
|
38
38
|
</FormControl>
|
|
@@ -40,14 +40,14 @@
|
|
|
40
40
|
// FormControlが複数の入力要素を持ってしまっているのでNG
|
|
41
41
|
<FormControl title="any heading">
|
|
42
42
|
<Input />
|
|
43
|
-
<
|
|
43
|
+
<Combobox />
|
|
44
44
|
</FormControl>
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
// FormControlがネストしてしまっているのでNG
|
|
48
48
|
<FormControl>
|
|
49
49
|
<SubFormControl>
|
|
50
|
-
<
|
|
50
|
+
<Checkbox />
|
|
51
51
|
</SubFormControl>
|
|
52
52
|
</FormControl>
|
|
53
53
|
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
</FormControl>
|
|
98
98
|
</Section>
|
|
99
99
|
|
|
100
|
-
// smarthr-ui/
|
|
100
|
+
// smarthr-ui/Checkbox はlabelを含むため、なんの入力要素かが単独で伝えられるので
|
|
101
101
|
// FormControl・Fieldsetで囲む必要はない (囲んでも問題はない)
|
|
102
|
-
<
|
|
102
|
+
<Checkbox />
|
|
103
103
|
```
|
|
@@ -5,8 +5,8 @@ const EXPECTED_LABELED_INPUT_NAMES = {
|
|
|
5
5
|
'RadioButtons$': '(RadioButtons)$',
|
|
6
6
|
'RadioButtonPanel$': '(RadioButtonPanel)$',
|
|
7
7
|
'RadioButtonPanels$': '(RadioButtonPanels)$',
|
|
8
|
-
'Check(B
|
|
9
|
-
'Check(B
|
|
8
|
+
'Check(b|B)ox$': '(Checkbox)$',
|
|
9
|
+
'Check(b|B)ox(e)?s$': '(Checkboxes)$',
|
|
10
10
|
}
|
|
11
11
|
const EXPECTED_INPUT_NAMES = {
|
|
12
12
|
'(I|^i)nput$': '(Input)$',
|
|
@@ -14,7 +14,7 @@ const EXPECTED_INPUT_NAMES = {
|
|
|
14
14
|
'(T|^t)extarea$': '(Textarea)$',
|
|
15
15
|
'(S|^s)elect$': '(Select)$',
|
|
16
16
|
'InputFile$': '(InputFile)$',
|
|
17
|
-
'Combo(b|B)ox$': '(
|
|
17
|
+
'Combo(b|B)ox$': '(Combobox)$',
|
|
18
18
|
'(Date|Wareki)Picker$': '((Date|Wareki)Picker)$',
|
|
19
19
|
'TimePicker$': '(TimePicker)$',
|
|
20
20
|
...EXPECTED_LABELED_INPUT_NAMES,
|
|
@@ -236,7 +236,7 @@ module.exports = {
|
|
|
236
236
|
const layoutSectionAttribute = !isSection && name.match(LAYOUT_COMPONENT_REGEX) && openingElement.attributes.find(findAsSectioning)
|
|
237
237
|
|
|
238
238
|
if (isSection || layoutSectionAttribute) {
|
|
239
|
-
// HINT: smarthr-ui/
|
|
239
|
+
// HINT: smarthr-ui/Checkboxはlabelを単独で持つため、FormControl系でラップをする必要はない
|
|
240
240
|
// HINT: 擬似的にラベルが設定されている場合、無視する
|
|
241
241
|
if (!isCheckbox && !isPseudoLabel) {
|
|
242
242
|
const actualName = isSection ? name : `<${name} ${layoutSectionAttribute.name.name}="${layoutSectionAttribute.value.value}">`
|
|
@@ -288,7 +288,7 @@ module.exports = {
|
|
|
288
288
|
}
|
|
289
289
|
}
|
|
290
290
|
case 'Program': {
|
|
291
|
-
// HINT: smarthr-ui/
|
|
291
|
+
// HINT: smarthr-ui/Checkboxはlabelを単独で持つため、FormControl系でラップをする必要はない
|
|
292
292
|
// HINT: 擬似的にラベルが設定されている場合、無視する
|
|
293
293
|
if (!isCheckbox && !isPseudoLabel) {
|
|
294
294
|
const isSelect = !isRadio && nodeName.match(SELECT_REGEX)
|
|
@@ -5,11 +5,13 @@ const EXPECTED_NAMES = {
|
|
|
5
5
|
'SearchInput$': 'SearchInput$',
|
|
6
6
|
'(t|T)extarea$': 'Textarea$',
|
|
7
7
|
'FieldSet$': 'FieldSet$',
|
|
8
|
-
'
|
|
8
|
+
'Combo(b|B)ox$': 'Combobox$',
|
|
9
9
|
'(Date|Wareki)Picker$': '(Date|Wareki)Picker$',
|
|
10
10
|
'TimePicker$': 'TimePicker$',
|
|
11
11
|
}
|
|
12
|
-
const INPUT_TAG_REGEX = /((i|I)nput|(t|T)extarea|FieldSet|
|
|
12
|
+
const INPUT_TAG_REGEX = /((i|I)nput|(t|T)extarea|FieldSet|Combo(b|B)ox|(Date|Wareki|Time)Picker)$/
|
|
13
|
+
const SEARCH_INPUT_REGEX = /SearchInput$/
|
|
14
|
+
const COMBOBOX_REGEX = /Combo(b|B)ox$/
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
@@ -25,60 +27,54 @@ module.exports = {
|
|
|
25
27
|
JSXOpeningElement: (node) => {
|
|
26
28
|
const name = node.name.name
|
|
27
29
|
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (!name.match(INPUT_TAG_REGEX)) {
|
|
33
|
-
return
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const placeholder = node.attributes.find((a) => a.name?.name === 'placeholder')
|
|
37
|
-
|
|
38
|
-
if (placeholder) {
|
|
39
|
-
if (name.match(/SearchInput$/)) {
|
|
40
|
-
const tooltipMessage = node.attributes.find((a) => a.name?.name === 'tooltipMessage')
|
|
30
|
+
if (name && INPUT_TAG_REGEX.test(name)) {
|
|
31
|
+
const placeholder = node.attributes.find((a) => a.name?.name === 'placeholder')
|
|
41
32
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
message: `${name} にはplaceholder属性を単独で利用せず、tooltipMessageオプションのみ、もしくはplaceholderとtooltipMessageの併用を検討してください。 (例: '<${name} tooltipMessage="ヒント" />', '<${name} tooltipMessage={hint} placeholder={hint} />')`,
|
|
46
|
-
})
|
|
47
|
-
}
|
|
48
|
-
} else if (name.match(/ComboBox$/)) {
|
|
49
|
-
let defaultItem
|
|
50
|
-
let dropdownHelpMessage
|
|
33
|
+
if (placeholder) {
|
|
34
|
+
if (SEARCH_INPUT_REGEX.test(name)) {
|
|
35
|
+
const tooltipMessage = node.attributes.find((a) => a.name?.name === 'tooltipMessage')
|
|
51
36
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
case 'dropdownHelpMessage':
|
|
58
|
-
dropdownHelpMessage = a
|
|
59
|
-
break
|
|
37
|
+
if (!tooltipMessage) {
|
|
38
|
+
context.report({
|
|
39
|
+
node: placeholder,
|
|
40
|
+
message: `${name} にはplaceholder属性を単独で利用せず、tooltipMessageオプションのみ、もしくはplaceholderとtooltipMessageの併用を検討してください。 (例: '<${name} tooltipMessage="ヒント" />', '<${name} tooltipMessage={hint} placeholder={hint} />')`,
|
|
41
|
+
})
|
|
60
42
|
}
|
|
61
|
-
})
|
|
43
|
+
} else if (COMBOBOX_REGEX.test(name)) {
|
|
44
|
+
let defaultItem
|
|
45
|
+
let dropdownHelpMessage
|
|
62
46
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
47
|
+
node.attributes.forEach((a) => {
|
|
48
|
+
switch(a.name?.name) {
|
|
49
|
+
case 'defaultItem':
|
|
50
|
+
defaultItem = a
|
|
51
|
+
break
|
|
52
|
+
case 'dropdownHelpMessage':
|
|
53
|
+
dropdownHelpMessage = a
|
|
54
|
+
break
|
|
55
|
+
}
|
|
67
56
|
})
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
57
|
+
|
|
58
|
+
if (defaultItem) {
|
|
59
|
+
context.report({
|
|
60
|
+
node: placeholder,
|
|
61
|
+
message: `${name} にはdefaultItemが設定されているため、placeholder属性を閲覧出来ません。削除してください。`,
|
|
62
|
+
})
|
|
63
|
+
} else if (!dropdownHelpMessage) {
|
|
64
|
+
context.report({
|
|
65
|
+
node: placeholder,
|
|
66
|
+
message: `${name} にはplaceholder属性は設定せず、以下のいずれか、もしくは組み合わせての対応を検討してください。
|
|
72
67
|
- 選択肢をどんな値で絞り込めるかの説明をしたい場合は dropdownHelpMessage 属性に変更してください。
|
|
73
68
|
- 空の値の説明のためにplaceholderを利用している場合は defaultItem 属性に変更してください。
|
|
74
69
|
- 上記以外の説明を行いたい場合、ヒント用要素を設置してください。(例: '<div><${name} /><Hint>ヒント</Hint></div>')`,
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
context.report({
|
|
74
|
+
node: placeholder,
|
|
75
|
+
message: `${name} にはplaceholder属性は設定せず、別途ヒント用要素の利用を検討してください。(例: '<div><${name} /><Hint>ヒント</Hint></div>')`,
|
|
75
76
|
})
|
|
76
77
|
}
|
|
77
|
-
} else {
|
|
78
|
-
context.report({
|
|
79
|
-
node: placeholder,
|
|
80
|
-
message: `${name} にはplaceholder属性は設定せず、別途ヒント用要素の利用を検討してください。(例: '<div><${name} /><Hint>ヒント</Hint></div>')`,
|
|
81
|
-
})
|
|
82
78
|
}
|
|
83
79
|
}
|
|
84
80
|
},
|
|
@@ -4,7 +4,7 @@ const TV_COMPONENTS_METHOD = 'tv'
|
|
|
4
4
|
const TV_COMPONENTS = 'tailwind-variants'
|
|
5
5
|
const TV_RESULT_CONST_NAME_REGEX = /(C|c)lassNameGenerator$/
|
|
6
6
|
|
|
7
|
-
const findValidImportNameNode = (s) => s.type === 'ImportSpecifier' && s.
|
|
7
|
+
const findValidImportNameNode = (s) => s.type === 'ImportSpecifier' && s.imported.name === TV_COMPONENTS_METHOD && s.local.name !== TV_COMPONENTS_METHOD
|
|
8
8
|
|
|
9
9
|
const checkImportTailwindVariants = (node, context) => {
|
|
10
10
|
}
|
|
@@ -43,7 +43,7 @@ module.exports = {
|
|
|
43
43
|
return {
|
|
44
44
|
ImportDeclaration: (node) => {
|
|
45
45
|
if (node.source.value === TV_COMPONENTS) {
|
|
46
|
-
if (
|
|
46
|
+
if (node.specifiers.some(findValidImportNameNode)) {
|
|
47
47
|
context.report({
|
|
48
48
|
node,
|
|
49
49
|
message: `${TV_COMPONENTS} をimportする際は、名称が"${TV_COMPONENTS_METHOD}" となるようにしてください。例: "import { ${TV_COMPONENTS_METHOD} } from '${TV_COMPONENTS}'"`,
|
|
@@ -27,8 +27,8 @@ ruleTester.run('a11y-input-has-name-attribute', rule, {
|
|
|
27
27
|
{ code: `import { Select as HogeSelect } from './hoge'` },
|
|
28
28
|
{ code: `import { InputFile as HogeInputFile } from './hoge'` },
|
|
29
29
|
{ code: `import { HogeRadioButton as FugaRadioButton } from './hoge'` },
|
|
30
|
-
{ code: `import {
|
|
31
|
-
{ code: `import {
|
|
30
|
+
{ code: `import { Checkbox as FugaCheckbox } from './hoge'` },
|
|
31
|
+
{ code: `import { HogeCombobox as FugaCombobox } from './hoge'` },
|
|
32
32
|
{ code: `import { DatePicker as HogeDatePicker } from './hoge'` },
|
|
33
33
|
{ code: `import { WarekiPicker as HogeWarekiPicker } from './hoge'` },
|
|
34
34
|
{ code: `import { HogeDropZone as HogeFugaDropZone } from './hoge'` },
|
|
@@ -62,9 +62,9 @@ ruleTester.run('a11y-input-has-name-attribute', rule, {
|
|
|
62
62
|
- InputFileが型の場合、'import type { InputFile as HogeInputFileFuga }' もしくは 'import { type InputFile as HogeInputFileFuga }' のように明示的に型であることを宣言してください。名称変更が不要になります` } ] },
|
|
63
63
|
{ code: `import { HogeRadioButton as FugaRadioButtonAbc } from './hoge'`, errors: [ { message: `FugaRadioButtonAbcを正規表現 "/RadioButton$/" がmatchする名称に変更してください。
|
|
64
64
|
- HogeRadioButtonが型の場合、'import type { HogeRadioButton as FugaRadioButtonAbc }' もしくは 'import { type HogeRadioButton as FugaRadioButtonAbc }' のように明示的に型であることを宣言してください。名称変更が不要になります` } ] },
|
|
65
|
-
{ code: `import { CheckBox as FugaCheckBoxHoge } from './hoge'`, errors: [ { message: `FugaCheckBoxHogeを正規表現 "/
|
|
65
|
+
{ code: `import { CheckBox as FugaCheckBoxHoge } from './hoge'`, errors: [ { message: `FugaCheckBoxHogeを正規表現 "/Checkbox$/" がmatchする名称に変更してください。
|
|
66
66
|
- CheckBoxが型の場合、'import type { CheckBox as FugaCheckBoxHoge }' もしくは 'import { type CheckBox as FugaCheckBoxHoge }' のように明示的に型であることを宣言してください。名称変更が不要になります` } ] },
|
|
67
|
-
{ code: `import { HogeComboBox as ComboBoxFuga } from './hoge'`, errors: [ { message: `ComboBoxFugaを正規表現 "/
|
|
67
|
+
{ code: `import { HogeComboBox as ComboBoxFuga } from './hoge'`, errors: [ { message: `ComboBoxFugaを正規表現 "/Combobox$/" がmatchする名称に変更してください。
|
|
68
68
|
- HogeComboBoxが型の場合、'import type { HogeComboBox as ComboBoxFuga }' もしくは 'import { type HogeComboBox as ComboBoxFuga }' のように明示的に型であることを宣言してください。名称変更が不要になります` } ] },
|
|
69
69
|
{ code: `import { DatePicker as HogeDatePickerFuga } from './hoge'`, errors: [ { message: `HogeDatePickerFugaを正規表現 "/(Date|Wareki)Picker$/" がmatchする名称に変更してください。
|
|
70
70
|
- DatePickerが型の場合、'import type { DatePicker as HogeDatePickerFuga }' もしくは 'import { type DatePicker as HogeDatePickerFuga }' のように明示的に型であることを宣言してください。名称変更が不要になります` } ] },
|
|
@@ -13,13 +13,13 @@ const ruleTester = new RuleTester({
|
|
|
13
13
|
const noLabeledInput = (name) => `${name} を、smarthr-ui/FormControl もしくはそれを拡張したコンポーネントが囲むようマークアップを変更してください。
|
|
14
14
|
- FormControlで入力要素を囲むことでラベルと入力要素が適切に紐づき、操作性が高まります
|
|
15
15
|
- ${name}が入力要素とラベル・タイトル・説明など含む概念を表示するコンポーネントの場合、コンポーネント名を/((FormGroup)$|(FormControl)$|((F|^f)ieldset)$)/とマッチするように修正してください
|
|
16
|
-
- ${name}が入力要素自体を表現するコンポーネントの一部である場合、ルートとなるコンポーネントの名称を/((I|^i)nput$|SearchInput$|(T|^t)extarea$|(S|^s)elect$|InputFile$|Combo(b|B)ox$|(Date|Wareki)Picker$|TimePicker$|RadioButton$|RadioButtons$|RadioButtonPanel$|RadioButtonPanels$|Check(B
|
|
16
|
+
- ${name}が入力要素自体を表現するコンポーネントの一部である場合、ルートとなるコンポーネントの名称を/((I|^i)nput$|SearchInput$|(T|^t)extarea$|(S|^s)elect$|InputFile$|Combo(b|B)ox$|(Date|Wareki)Picker$|TimePicker$|RadioButton$|RadioButtons$|RadioButtonPanel$|RadioButtonPanels$|Check(b|B)ox$|Check(b|B)ox(e)?s$)/とマッチするように修正してください
|
|
17
17
|
- 上記のいずれの方法も適切ではない場合、${name}のtitle属性に "どんな値を入力すれば良いのか" の説明を設定してください
|
|
18
18
|
- 例: <${name} title="姓を全角カタカナのみで入力してください" />`
|
|
19
19
|
const noLabeledSelect = (name) => `${name} を、smarthr-ui/FormControl もしくはそれを拡張したコンポーネントが囲むようマークアップを変更してください。
|
|
20
20
|
- FormControlで入力要素を囲むことでラベルと入力要素が適切に紐づき、操作性が高まります
|
|
21
21
|
- ${name}が入力要素とラベル・タイトル・説明など含む概念を表示するコンポーネントの場合、コンポーネント名を/((FormGroup)$|(FormControl)$|((F|^f)ieldset)$)/とマッチするように修正してください
|
|
22
|
-
- ${name}が入力要素自体を表現するコンポーネントの一部である場合、ルートとなるコンポーネントの名称を/((I|^i)nput$|SearchInput$|(T|^t)extarea$|(S|^s)elect$|InputFile$|Combo(b|B)ox$|(Date|Wareki)Picker$|TimePicker$|RadioButton$|RadioButtons$|RadioButtonPanel$|RadioButtonPanels$|Check(B
|
|
22
|
+
- ${name}が入力要素自体を表現するコンポーネントの一部である場合、ルートとなるコンポーネントの名称を/((I|^i)nput$|SearchInput$|(T|^t)extarea$|(S|^s)elect$|InputFile$|Combo(b|B)ox$|(Date|Wareki)Picker$|TimePicker$|RadioButton$|RadioButtons$|RadioButtonPanel$|RadioButtonPanels$|Check(b|B)ox$|Check(b|B)ox(e)?s$)/とマッチするように修正してください
|
|
23
23
|
- 上記のいずれの方法も適切ではない場合、${name}のtitle属性に "どんな値を選択すれば良いのか" の説明を設定してください
|
|
24
24
|
- 例: <${name} title="検索対象を選択してください" />`
|
|
25
25
|
const invalidPureCheckboxInFormControl = (name) => `HogeFormControl が ${name} を含んでいます。smarthr-ui/FormControl を smarthr-ui/Fieldset に変更し、正しくグルーピングされるように修正してください。
|
|
@@ -40,7 +40,7 @@ const noLabeledInputInFieldset = (name) => `HogeFieldset が ラベルを持た
|
|
|
40
40
|
- 方法1: HogeFieldset を smarthr-ui/FormControl、もしくはそれを拡張したコンポーネントに変更してください
|
|
41
41
|
- 方法2: ${name} がlabel要素を含むコンポーネントである場合、名称を/(Form(Control|Group))$/にマッチするものに変更してください
|
|
42
42
|
- smarthr-ui/FormControl、smarthr-ui/FormGroup はlabel要素を内包しています
|
|
43
|
-
- 方法3: ${name} がRadioButton、もしくはCheckboxを表すコンポーネントの場合、名称を/(RadioButton$|RadioButtons$|RadioButtonPanel$|RadioButtonPanels$|Check(B
|
|
43
|
+
- 方法3: ${name} がRadioButton、もしくはCheckboxを表すコンポーネントの場合、名称を/(RadioButton$|RadioButtons$|RadioButtonPanel$|RadioButtonPanels$|Check(b|B)ox$|Check(b|B)ox(e)?s$)/にマッチするものに変更してください
|
|
44
44
|
- smarthr-ui/RadioButton、smarthr-ui/RadioButtonPanel、smarthr-ui/Checkbox はlabel要素を内包しています
|
|
45
45
|
- 方法4: HogeFieldset が smarthr-ui/Fieldset、もしくはそれを拡張しているコンポーネントではない場合、名称を /Fieldset$/ にマッチしないものに変更してください
|
|
46
46
|
- 方法5: 別途label要素が存在し、それらと紐づけたい場合はlabel要素のhtmlFor属性、${name}のid属性に同じ文字列を指定してください。この文字列はhtml内で一意である必要があります
|
|
@@ -50,7 +50,7 @@ const noLabeledInputInFieldsetWithSelect = (name) => `HogeFieldset が ラベル
|
|
|
50
50
|
- 方法1: HogeFieldset を smarthr-ui/FormControl、もしくはそれを拡張したコンポーネントに変更してください
|
|
51
51
|
- 方法2: ${name} がlabel要素を含むコンポーネントである場合、名称を/(Form(Control|Group))$/にマッチするものに変更してください
|
|
52
52
|
- smarthr-ui/FormControl、smarthr-ui/FormGroup はlabel要素を内包しています
|
|
53
|
-
- 方法3: ${name} がRadioButton、もしくはCheckboxを表すコンポーネントの場合、名称を/(RadioButton$|RadioButtons$|RadioButtonPanel$|RadioButtonPanels$|Check(B
|
|
53
|
+
- 方法3: ${name} がRadioButton、もしくはCheckboxを表すコンポーネントの場合、名称を/(RadioButton$|RadioButtons$|RadioButtonPanel$|RadioButtonPanels$|Check(b|B)ox$|Check(b|B)ox(e)?s$)/にマッチするものに変更してください
|
|
54
54
|
- smarthr-ui/RadioButton、smarthr-ui/RadioButtonPanel、smarthr-ui/Checkbox はlabel要素を内包しています
|
|
55
55
|
- 方法4: HogeFieldset が smarthr-ui/Fieldset、もしくはそれを拡張しているコンポーネントではない場合、名称を /Fieldset$/ にマッチしないものに変更してください
|
|
56
56
|
- 方法5: 別途label要素が存在し、それらと紐づけたい場合はlabel要素のhtmlFor属性、${name}のid属性に同じ文字列を指定してください。この文字列はhtml内で一意である必要があります
|
|
@@ -74,7 +74,7 @@ const invalidChildreninFormControl = (children) => `FormControl が、${children
|
|
|
74
74
|
- FormControlではなく、smarthr-ui/Fieldset、もしくはsmarthr-ui/Section + smarthr-ui/Heading などでのマークアップを検討してください
|
|
75
75
|
- 方法2: 親要素であるFormControlがsmarthr-ui/FormControlを拡張したコンポーネントではない場合、コンポーネント名を/(Form(Control|Group))$/と一致しない名称に変更してください`
|
|
76
76
|
const requireMultiInputInFormControlWithRoleGroup = () => `HogeFormControl内に入力要素が2個以上存在しないため、'role=\"group\"'を削除してください。'role=\"group\"'は複数の入力要素を一つのグループとして扱うための属性です。
|
|
77
|
-
- HogeFormControl内に2つ以上の入力要素が存在する場合、入力要素を含むコンポーネント名全てを/((I|^i)nput$|SearchInput$|(T|^t)extarea$|(S|^s)elect$|InputFile$|Combo(b|B)ox$|(Date|Wareki)Picker$|TimePicker$|RadioButton$|RadioButtons$|RadioButtonPanel$|RadioButtonPanels$|Check(B
|
|
77
|
+
- HogeFormControl内に2つ以上の入力要素が存在する場合、入力要素を含むコンポーネント名全てを/((I|^i)nput$|SearchInput$|(T|^t)extarea$|(S|^s)elect$|InputFile$|Combo(b|B)ox$|(Date|Wareki)Picker$|TimePicker$|RadioButton$|RadioButtons$|RadioButtonPanel$|RadioButtonPanels$|Check(b|B)ox$|Check(b|B)ox(e)?s$)/、もしくは/((FormGroup)$|(FormControl)$|((F|^f)ieldset)$)/にマッチする名称に変更してください`
|
|
78
78
|
|
|
79
79
|
ruleTester.run('a11y-input-in-form-control', rule, {
|
|
80
80
|
valid: [
|
|
@@ -88,7 +88,7 @@ ruleTester.run('a11y-input-in-form-control', rule, {
|
|
|
88
88
|
{ code: 'const HogeSelect = styled(Select)``' },
|
|
89
89
|
{ code: 'const HogeRadioButton = styled(FugaRadioButton)``' },
|
|
90
90
|
{ code: 'const HogeRadioButtonPanel = styled(FugaRadioButtonPanel)``' },
|
|
91
|
-
{ code: 'const
|
|
91
|
+
{ code: 'const HogeCheckbox = styled(FugaCheckBox)``' },
|
|
92
92
|
{ code: 'const DatePicker = styled(AnyDatePicker)``' },
|
|
93
93
|
{ code: 'const WarekiPicker = styled(AnyWarekiPicker)``' },
|
|
94
94
|
{ code: '<input type="hidden" />' },
|
|
@@ -140,7 +140,7 @@ ruleTester.run('a11y-input-in-form-control', rule, {
|
|
|
140
140
|
],
|
|
141
141
|
invalid: [
|
|
142
142
|
{ code: `import hoge from 'styled-components'`, errors: [ { message: `styled-components をimportする際は、名称が"styled" となるようにしてください。例: "import styled from 'styled-components'"` } ] },
|
|
143
|
-
{ code: `import { ComboBox as ComboBoxHoge } from './hoge'`, errors: [ { message: `ComboBoxHogeを正規表現 "/(
|
|
143
|
+
{ code: `import { ComboBox as ComboBoxHoge } from './hoge'`, errors: [ { message: `ComboBoxHogeを正規表現 "/(Combobox)$/" がmatchする名称に変更してください。
|
|
144
144
|
- ComboBoxが型の場合、'import type { ComboBox as ComboBoxHoge }' もしくは 'import { type ComboBox as ComboBoxHoge }' のように明示的に型であることを宣言してください。名称変更が不要になります` } ] },
|
|
145
145
|
{ code: 'const RadioButton = styled(FugaRadioButtonPanel)``', errors: [
|
|
146
146
|
{ message: `RadioButtonを正規表現 "/(RadioButtonPanel)$/" がmatchする名称に変更してください。` },
|
|
@@ -157,8 +157,8 @@ ruleTester.run('a11y-input-in-form-control', rule, {
|
|
|
157
157
|
{ code: '<select />', errors: [ { message: noLabeledSelect('select') } ] },
|
|
158
158
|
{ code: '<HogeSelect />', errors: [ { message: noLabeledSelect('HogeSelect') } ] },
|
|
159
159
|
{ code: '<HogeInputFile />', errors: [ { message: noLabeledInput('HogeInputFile') } ] },
|
|
160
|
-
{ code: '<
|
|
161
|
-
{ code: '<
|
|
160
|
+
{ code: '<HogeCombobox />', errors: [ { message: noLabeledInput('HogeCombobox') } ] },
|
|
161
|
+
{ code: '<HogeCombobox inputAttributes={{ any }} />', errors: [ { message: noLabeledInput('HogeCombobox') } ] },
|
|
162
162
|
{ code: '<HogeDatePicker />', errors: [ { message: noLabeledInput('HogeDatePicker') } ] },
|
|
163
163
|
{ code: '<HogeWarekiPicker />', errors: [ { message: noLabeledInput('HogeWarekiPicker') } ] },
|
|
164
164
|
{ code: '<HogeFormControl><Input type="checkbox" /><Input type="checkbox" /></HogeFormControl>', errors: [ { message: invalidPureCheckboxInFormControl('Input') } ] },
|
|
@@ -18,7 +18,7 @@ ruleTester.run('a11y-prohibit-input-placeholder', rule, {
|
|
|
18
18
|
{ code: `import { Input as HogeInput } from './hoge'` },
|
|
19
19
|
{ code: `import { HogeSearchInput as FugaSearchInput } from './hoge'` },
|
|
20
20
|
{ code: `import { HogeTextarea as FugaHogeTextarea } from './hoge'` },
|
|
21
|
-
{ code: `import {
|
|
21
|
+
{ code: `import { Combobox as FugaHogeCombobox } from './hoge'` },
|
|
22
22
|
{ code: `import { AbcDatePicker as StyledDatePicker } from './hoge'` },
|
|
23
23
|
{ code: `import { AbcWarekiPicker as StyledWarekiPicker } from './hoge'` },
|
|
24
24
|
{ code: 'const HogeInput = styled.input``' },
|
|
@@ -28,7 +28,7 @@ ruleTester.run('a11y-prohibit-input-placeholder', rule, {
|
|
|
28
28
|
{ code: 'const HogeTextarea = styled(Textarea)``' },
|
|
29
29
|
{ code: 'const hoge = styled.fieldset``' },
|
|
30
30
|
{ code: 'const HogeFieldSet = styled(FieldSet)``' },
|
|
31
|
-
{ code: 'const
|
|
31
|
+
{ code: 'const HogeCombobox = styled(ComboBox)``' },
|
|
32
32
|
{ code: 'const HogeSearchInput = styled(SearchInput)``' },
|
|
33
33
|
{ code: `<input />` },
|
|
34
34
|
{ code: `<textarea />` },
|
|
@@ -57,8 +57,8 @@ ruleTester.run('a11y-prohibit-input-placeholder', rule, {
|
|
|
57
57
|
- SearchInputが型の場合、'import type { SearchInput as Hoge }' もしくは 'import { type SearchInput as Hoge }' のように明示的に型であることを宣言してください。名称変更が不要になります` } ] },
|
|
58
58
|
{ code: `import { HogeTextarea as HogeTextareaFuga } from './hoge'`, errors: [ { message: `HogeTextareaFugaを正規表現 "/Textarea$/" がmatchする名称に変更してください。
|
|
59
59
|
- HogeTextareaが型の場合、'import type { HogeTextarea as HogeTextareaFuga }' もしくは 'import { type HogeTextarea as HogeTextareaFuga }' のように明示的に型であることを宣言してください。名称変更が不要になります` } ] },
|
|
60
|
-
{ code: `import {
|
|
61
|
-
-
|
|
60
|
+
{ code: `import { HogeCombobox as ComboboxFuga } from './hoge'`, errors: [ { message: `ComboboxFugaを正規表現 "/Combobox$/" がmatchする名称に変更してください。
|
|
61
|
+
- HogeComboboxが型の場合、'import type { HogeCombobox as ComboboxFuga }' もしくは 'import { type HogeCombobox as ComboboxFuga }' のように明示的に型であることを宣言してください。名称変更が不要になります` } ] },
|
|
62
62
|
{ code: `import { DatePicker as HogeDatePickerFuga } from './hoge'`, errors: [ { message: `HogeDatePickerFugaを正規表現 "/(Date|Wareki)Picker$/" がmatchする名称に変更してください。
|
|
63
63
|
- DatePickerが型の場合、'import type { DatePicker as HogeDatePickerFuga }' もしくは 'import { type DatePicker as HogeDatePickerFuga }' のように明示的に型であることを宣言してください。名称変更が不要になります` } ] },
|
|
64
64
|
{ code: `import { WarekiPicker as HogeWarekiPickerFuga } from './hoge'`, errors: [ { message: `HogeWarekiPickerFugaを正規表現 "/(Date|Wareki)Picker$/" がmatchする名称に変更してください。
|
|
@@ -68,7 +68,7 @@ ruleTester.run('a11y-prohibit-input-placeholder', rule, {
|
|
|
68
68
|
{ code: 'const Hoge = styled.textarea``', errors: [ { message: `Hogeを正規表現 "/Textarea$/" がmatchする名称に変更してください。` } ] },
|
|
69
69
|
{ code: 'const Hoge = styled(StyledTextarea)``', errors: [ { message: `Hogeを正規表現 "/Textarea$/" がmatchする名称に変更してください。` } ] },
|
|
70
70
|
{ code: 'const Hoge = styled(FieldSet)``', errors: [ { message: `Hogeを正規表現 "/FieldSet$/" がmatchする名称に変更してください。` } ] },
|
|
71
|
-
{ code: 'const Hoge = styled(
|
|
71
|
+
{ code: 'const Hoge = styled(Combobox)``', errors: [ { message: `Hogeを正規表現 "/Combobox$/" がmatchする名称に変更してください。` } ] },
|
|
72
72
|
{
|
|
73
73
|
code: 'const Hoge = styled(SearchInput)``',
|
|
74
74
|
errors: [
|
|
@@ -14,6 +14,7 @@ const ruleTester = new RuleTester({
|
|
|
14
14
|
ruleTester.run('best-practice-for-button-element', rule, {
|
|
15
15
|
valid: [
|
|
16
16
|
{ code: `import { tv } from 'tailwind-variants'` },
|
|
17
|
+
{ code: `import { defaultConfig } from 'tailwind-variants'` },
|
|
17
18
|
{ code: `const classNameGenerator = tv()` },
|
|
18
19
|
{ code: `const xxxClassNameGenerator = tv()` },
|
|
19
20
|
{ code: `const hoge = useMemo(() => classNameGenerator(), [])` },
|