eslint-plugin-smarthr 0.5.13 → 0.5.15
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 +25 -0
- package/README.md +1 -0
- package/package.json +1 -1
- package/rules/a11y-clickable-element-has-text/index.js +16 -19
- package/rules/a11y-delegate-element-has-role-presentation/index.js +1 -0
- package/rules/a11y-input-has-name-attribute/index.js +1 -0
- package/rules/a11y-input-in-form-control/index.js +1 -0
- package/rules/a11y-prohibit-input-placeholder/index.js +3 -1
- package/rules/design-system-guideline-prohibit-double-icons/README.md +5 -9
- package/test/a11y-clickable-element-has-text.js +3 -0
- package/test/a11y-delegate-element-has-role-presantation.js +2 -2
- package/test/a11y-input-in-form-control.js +3 -3
- package/test/a11y-prohhibit-input-placeholder.js +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
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
|
+
### [0.5.15](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.5.14...v0.5.15) (2024-09-10)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* a11y系ルールの対象にsmarthr-ui/TimePickerを追加する ([#146](https://github.com/kufu/eslint-plugin-smarthr/issues/146)) ([90d9bf4](https://github.com/kufu/eslint-plugin-smarthr/commit/90d9bf42d770026e7ebc9442096e677fab298841))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* README.md内のIncorrect examplesを修正 ([fe0f7d2](https://github.com/kufu/eslint-plugin-smarthr/commit/fe0f7d2b8bf9773a8ff7e962084093bc6441bcda))
|
|
16
|
+
|
|
17
|
+
### [0.5.14](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.5.11...v0.5.14) (2024-07-02)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* best-practice-for-data-test-attributeを追加 ([#141](https://github.com/kufu/eslint-plugin-smarthr/issues/141)) ([30a8f00](https://github.com/kufu/eslint-plugin-smarthr/commit/30a8f003e1cdb79c709390be0322703e74de284d))
|
|
23
|
+
* ButtonやTextLinkコンポーネントにprefix, suffixの両属性を同時に設定できないルールを追加 ([1eb7568](https://github.com/kufu/eslint-plugin-smarthr/commit/1eb75680a1125391106994532b58cc0591853711))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Bug Fixes
|
|
27
|
+
|
|
28
|
+
* a11y-clickable-element-has-text でtext属性を持つコンポーネントが存在する場合、真となるように修正 ([#143](https://github.com/kufu/eslint-plugin-smarthr/issues/143)) ([46b7048](https://github.com/kufu/eslint-plugin-smarthr/commit/46b7048dc3bb5a29455e205fabdd584fa478d2f1))
|
|
29
|
+
|
|
5
30
|
### [0.5.13](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.5.12...v0.5.13) (2024-06-21)
|
|
6
31
|
|
|
7
32
|
|
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
- [best-practice-for-date](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/best-practice-for-date)
|
|
20
20
|
- [best-practice-for-layouts](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/best-practice-for-layouts)
|
|
21
21
|
- [best-practice-for-remote-trigger-dialog](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/best-practice-for-remote-trigger-dialog)
|
|
22
|
+
- [design-system-guideline-prohibit-double-icons](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/design-system-guideline-prohibit-double-icons)
|
|
22
23
|
- [format-import-path](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/format-import-path)
|
|
23
24
|
- [format-translate-component](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/format-translate-component)
|
|
24
25
|
- [jsx-start-with-spread-attributes](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/jsx-start-with-spread-attributes)
|
package/package.json
CHANGED
|
@@ -32,8 +32,6 @@ const REGEX_SMARTHR_LOGO = /SmartHRLogo$/
|
|
|
32
32
|
const REGEX_TEXT_COMPONENT = /(Text|Message)$/
|
|
33
33
|
const REGEX_JSX_TYPE = /^(JSXText|JSXExpressionContainer)$/
|
|
34
34
|
|
|
35
|
-
const HIT_TEXT_ATTR = 'alt'
|
|
36
|
-
|
|
37
35
|
const filterFalsyJSXText = (cs) => cs.filter(checkFalsyJSXText)
|
|
38
36
|
const checkFalsyJSXText = (c) => (
|
|
39
37
|
!(c.type === 'JSXText' && c.value.match(REGEX_NLSP))
|
|
@@ -91,30 +89,29 @@ module.exports = {
|
|
|
91
89
|
return true
|
|
92
90
|
}
|
|
93
91
|
|
|
94
|
-
// HINT: role & aria-label
|
|
92
|
+
// HINT: role & aria-label を同時に設定されている場合か、text属性が設定されている場合許可
|
|
95
93
|
let existRole = false
|
|
96
94
|
let existAriaLabel = false
|
|
97
95
|
const result = c.openingElement.attributes.reduce((prev, a) => {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
96
|
+
const n = a.name.name
|
|
97
|
+
|
|
98
|
+
if (prev || n === 'text') {
|
|
99
|
+
return true
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const v = a.value?.value
|
|
103
|
+
|
|
104
|
+
existRole = existRole || (n === 'role' && v === 'img')
|
|
105
|
+
existAriaLabel = existAriaLabel || n === 'aria-label'
|
|
106
|
+
|
|
107
|
+
if (existRole && existAriaLabel) {
|
|
108
|
+
return true
|
|
106
109
|
}
|
|
107
110
|
|
|
108
|
-
return (
|
|
111
|
+
return n === 'alt' && (v || a.value.type === 'JSXExpressionContainer') || false
|
|
109
112
|
}, null)
|
|
110
113
|
|
|
111
|
-
|
|
112
|
-
result ||
|
|
113
|
-
(existRole && existAriaLabel) ||
|
|
114
|
-
(c.children && filterFalsyJSXText(c.children).some(recursiveSearch))
|
|
115
|
-
) {
|
|
116
|
-
return true
|
|
117
|
-
}
|
|
114
|
+
return result || (c.children && filterFalsyJSXText(c.children).some(recursiveSearch))
|
|
118
115
|
}
|
|
119
116
|
}
|
|
120
117
|
|
|
@@ -10,6 +10,7 @@ const EXPECTED_NAMES = {
|
|
|
10
10
|
'Check(b|B)ox$': 'CheckBox$',
|
|
11
11
|
'Combo(b|B)ox$': 'ComboBox$',
|
|
12
12
|
'DatePicker$': 'DatePicker$',
|
|
13
|
+
'TimePicker$': 'TimePicker$',
|
|
13
14
|
'DropZone$': 'DropZone$',
|
|
14
15
|
}
|
|
15
16
|
const TARGET_TAG_NAME_REGEX = new RegExp(`(${Object.keys(EXPECTED_NAMES).join('|')})`)
|
|
@@ -7,7 +7,9 @@ const EXPECTED_NAMES = {
|
|
|
7
7
|
'FieldSet$': 'FieldSet$',
|
|
8
8
|
'ComboBox$': 'ComboBox$',
|
|
9
9
|
'DatePicker$': 'DatePicker$',
|
|
10
|
+
'TimePicker$': 'TimePicker$',
|
|
10
11
|
}
|
|
12
|
+
const INPUT_TAG_REGEX = /((i|I)nput|(t|T)extarea|FieldSet|ComboBox|(Date|Time)Picker)$/
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
@@ -27,7 +29,7 @@ module.exports = {
|
|
|
27
29
|
return
|
|
28
30
|
}
|
|
29
31
|
|
|
30
|
-
if (!name.match(
|
|
32
|
+
if (!name.match(INPUT_TAG_REGEX)) {
|
|
31
33
|
return
|
|
32
34
|
}
|
|
33
35
|
|
|
@@ -21,15 +21,11 @@
|
|
|
21
21
|
## ❌ Incorrect
|
|
22
22
|
|
|
23
23
|
```jsx
|
|
24
|
-
<Button>hoge</Button>
|
|
25
|
-
<Button suffix
|
|
26
|
-
<
|
|
27
|
-
<
|
|
28
|
-
<
|
|
29
|
-
<TextLink prefix={PREFIX}>hoge</TextLink>
|
|
30
|
-
<StyledButton>hoge</StyledButton>
|
|
31
|
-
<StyledLink>hoge</StyledLink>
|
|
32
|
-
<Input prefix={PREFIX} suffix={SUFFIX} />
|
|
24
|
+
<Button suffix={SUFFIX} prefix={PREFIX}>hoge</Button>
|
|
25
|
+
<Button suffix prefix>hoge</Button>
|
|
26
|
+
<StyledButton suffix={undefined} prefix={null}>hoge</StyledButton>
|
|
27
|
+
<Link prefix="PREFIX" suffix="SUFFIX">hoge</Link>
|
|
28
|
+
<StyledLink prefix="PREFIX" suffix="SUFFIX">hoge</StyledLink>
|
|
33
29
|
```
|
|
34
30
|
|
|
35
31
|
## ✅ Correct
|
|
@@ -12,7 +12,7 @@ const ruleTester = new RuleTester({
|
|
|
12
12
|
},
|
|
13
13
|
});
|
|
14
14
|
|
|
15
|
-
const defaultInteractiveRegex = '/((i|I)nput$|(t|T)extarea$|(s|S)elect$|InputFile$|RadioButtonPanel$|Check(b|B)ox$|Combo(b|B)ox$|DatePicker$|DropZone$|Switch$|SegmentedControl$|RightFixedNote$|FieldSet$|Fieldset$|FormControl$|FormGroup$|(b|B)utton$|Anchor$|Link$|TabItem$|^a$|(f|F)orm$|ActionDialogWithTrigger$|RemoteDialogTrigger$|RemoteTrigger(.+)Dialog$|FormDialog$|Pagination$|SideNav$|AccordionPanel$)/'
|
|
15
|
+
const defaultInteractiveRegex = '/((i|I)nput$|(t|T)extarea$|(s|S)elect$|InputFile$|RadioButtonPanel$|Check(b|B)ox$|Combo(b|B)ox$|DatePicker$|TimePicker$|DropZone$|Switch$|SegmentedControl$|RightFixedNote$|FieldSet$|Fieldset$|FormControl$|FormGroup$|(b|B)utton$|Anchor$|Link$|TabItem$|^a$|(f|F)orm$|ActionDialogWithTrigger$|RemoteDialogTrigger$|RemoteTrigger(.+)Dialog$|FormDialog$|Pagination$|SideNav$|AccordionPanel$|FilterDropdown$)/'
|
|
16
16
|
const messageNonInteractiveEventHandler = (nodeName, onAttrs, interactiveComponentRegex = defaultInteractiveRegex) => {
|
|
17
17
|
const onAttrsText = onAttrs.join(', ')
|
|
18
18
|
|
|
@@ -67,7 +67,7 @@ ruleTester.run('smarthr/a11y-delegate-element-has-role-presentation', rule, {
|
|
|
67
67
|
{ code: '<div onClick={any} onSubmit={any2} role="presentation"><Hoge /></div>', errors: [ { message: messageRolePresentationNotHasInteractive('div', ['onClick', 'onSubmit']) } ] },
|
|
68
68
|
{ code: '<div onClick={any}><Link /></div>', errors: [ { message: messageNonInteractiveEventHandler('div', ['onClick']) } ] },
|
|
69
69
|
{ code: '<Wrapper onClick={any}><Link /></Wrapper>', errors: [ { message: messageNonInteractiveEventHandler('Wrapper', ['onClick']) } ] },
|
|
70
|
-
{ code: '<Wrapper onSubmit={any}><Hoge /></Wrapper>', options: [{ additionalInteractiveComponentRegex: ['^Hoge$'] }], errors: [ { message: messageNonInteractiveEventHandler('Wrapper', ['onSubmit'], '/((i|I)nput$|(t|T)extarea$|(s|S)elect$|InputFile$|RadioButtonPanel$|Check(b|B)ox$|Combo(b|B)ox$|DatePicker$|DropZone$|Switch$|SegmentedControl$|RightFixedNote$|FieldSet$|Fieldset$|FormControl$|FormGroup$|(b|B)utton$|Anchor$|Link$|TabItem$|^a$|(f|F)orm$|ActionDialogWithTrigger$|RemoteDialogTrigger$|RemoteTrigger(.+)Dialog$|FormDialog$|Pagination$|SideNav$|AccordionPanel$|^Hoge$)/') } ] },
|
|
70
|
+
{ code: '<Wrapper onSubmit={any}><Hoge /></Wrapper>', options: [{ additionalInteractiveComponentRegex: ['^Hoge$'] }], errors: [ { message: messageNonInteractiveEventHandler('Wrapper', ['onSubmit'], '/((i|I)nput$|(t|T)extarea$|(s|S)elect$|InputFile$|RadioButtonPanel$|Check(b|B)ox$|Combo(b|B)ox$|DatePicker$|TimePicker$|DropZone$|Switch$|SegmentedControl$|RightFixedNote$|FieldSet$|Fieldset$|FormControl$|FormGroup$|(b|B)utton$|Anchor$|Link$|TabItem$|^a$|(f|F)orm$|ActionDialogWithTrigger$|RemoteDialogTrigger$|RemoteTrigger(.+)Dialog$|FormDialog$|Pagination$|SideNav$|AccordionPanel$|FilterDropdown$|^Hoge$)/') } ] },
|
|
71
71
|
{ code: '<Wrapper onClick={any} onChange={anyany}><any><Link /></any></Wrapper>', errors: [ { message: messageNonInteractiveEventHandler('Wrapper', ['onClick', 'onChange']) } ] },
|
|
72
72
|
{ code: '<Wrapper onClick={any}>{any ? null : (hoge ? <AnyLink /> : null)}</Wrapper>', errors: [ { message: messageNonInteractiveEventHandler('Wrapper', ['onClick']) } ] },
|
|
73
73
|
],
|
|
@@ -15,13 +15,13 @@ const ruleTester = new RuleTester({
|
|
|
15
15
|
const noLabeledInput = (name) => `${name} を、smarthr-ui/FormControl もしくはそれを拡張したコンポーネントが囲むようマークアップを変更してください。
|
|
16
16
|
- FormControlで入力要素を囲むことでラベルと入力要素が適切に紐づき、操作性が高まります
|
|
17
17
|
- ${name}が入力要素とラベル・タイトル・説明など含む概念を表示するコンポーネントの場合、コンポーネント名を/((FormGroup)$|(FormControl)$|((F|^f)ieldset)$)/とマッチするように修正してください
|
|
18
|
-
- ${name}が入力要素自体を表現するコンポーネントの一部である場合、ルートとなるコンポーネントの名称を/((I|^i)nput$|SearchInput$|(T|^t)extarea$|(S|^s)elect$|InputFile$|Combo(b|B)ox$|DatePicker$|RadioButton$|RadioButtons$|RadioButtonPanel$|RadioButtonPanels$|Check(B|b)ox$|Check(B|b)ox(e)?s$)/とマッチするように修正してください
|
|
18
|
+
- ${name}が入力要素自体を表現するコンポーネントの一部である場合、ルートとなるコンポーネントの名称を/((I|^i)nput$|SearchInput$|(T|^t)extarea$|(S|^s)elect$|InputFile$|Combo(b|B)ox$|DatePicker$|TimePicker$|RadioButton$|RadioButtons$|RadioButtonPanel$|RadioButtonPanels$|Check(B|b)ox$|Check(B|b)ox(e)?s$)/とマッチするように修正してください
|
|
19
19
|
- 上記のいずれの方法も適切ではない場合、${name}のtitle属性に "どんな値を入力すれば良いのか" の説明を設定してください
|
|
20
20
|
- 例: <${name} title="姓を全角カタカナのみで入力してください" />`
|
|
21
21
|
const noLabeledSelect = (name) => `${name} を、smarthr-ui/FormControl もしくはそれを拡張したコンポーネントが囲むようマークアップを変更してください。
|
|
22
22
|
- FormControlで入力要素を囲むことでラベルと入力要素が適切に紐づき、操作性が高まります
|
|
23
23
|
- ${name}が入力要素とラベル・タイトル・説明など含む概念を表示するコンポーネントの場合、コンポーネント名を/((FormGroup)$|(FormControl)$|((F|^f)ieldset)$)/とマッチするように修正してください
|
|
24
|
-
- ${name}が入力要素自体を表現するコンポーネントの一部である場合、ルートとなるコンポーネントの名称を/((I|^i)nput$|SearchInput$|(T|^t)extarea$|(S|^s)elect$|InputFile$|Combo(b|B)ox$|DatePicker$|RadioButton$|RadioButtons$|RadioButtonPanel$|RadioButtonPanels$|Check(B|b)ox$|Check(B|b)ox(e)?s$)/とマッチするように修正してください
|
|
24
|
+
- ${name}が入力要素自体を表現するコンポーネントの一部である場合、ルートとなるコンポーネントの名称を/((I|^i)nput$|SearchInput$|(T|^t)extarea$|(S|^s)elect$|InputFile$|Combo(b|B)ox$|DatePicker$|TimePicker$|RadioButton$|RadioButtons$|RadioButtonPanel$|RadioButtonPanels$|Check(B|b)ox$|Check(B|b)ox(e)?s$)/とマッチするように修正してください
|
|
25
25
|
- 上記のいずれの方法も適切ではない場合、${name}のtitle属性に "どんな値を選択すれば良いのか" の説明を設定してください
|
|
26
26
|
- 例: <${name} title="検索対象を選択してください" />`
|
|
27
27
|
const invalidPureCheckboxInFormControl = (name) => `HogeFormControl が ${name} を含んでいます。smarthr-ui/FormControl を smarthr-ui/Fieldset に変更し、正しくグルーピングされるように修正してください。
|
|
@@ -76,7 +76,7 @@ const invalidChildreninFormControl = (children) => `FormControl が、${children
|
|
|
76
76
|
- FormControlではなく、smarthr-ui/Fieldset、もしくはsmarthr-ui/Section + smarthr-ui/Heading などでのマークアップを検討してください
|
|
77
77
|
- 方法2: 親要素であるFormControlがsmarthr-ui/FormControlを拡張したコンポーネントではない場合、コンポーネント名を/(Form(Control|Group))$/と一致しない名称に変更してください`
|
|
78
78
|
const requireMultiInputInFormControlWithRoleGroup = () => `HogeFormControl内に入力要素が2個以上存在しないため、'role=\"group\"'を削除してください。'role=\"group\"'は複数の入力要素を一つのグループとして扱うための属性です。
|
|
79
|
-
- HogeFormControl内に2つ以上の入力要素が存在する場合、入力要素を含むコンポーネント名全てを/((I|^i)nput$|SearchInput$|(T|^t)extarea$|(S|^s)elect$|InputFile$|Combo(b|B)ox$|DatePicker$|RadioButton$|RadioButtons$|RadioButtonPanel$|RadioButtonPanels$|Check(B|b)ox$|Check(B|b)ox(e)?s$)/、もしくは/((FormGroup)$|(FormControl)$|((F|^f)ieldset)$)/にマッチする名称に変更してください`
|
|
79
|
+
- HogeFormControl内に2つ以上の入力要素が存在する場合、入力要素を含むコンポーネント名全てを/((I|^i)nput$|SearchInput$|(T|^t)extarea$|(S|^s)elect$|InputFile$|Combo(b|B)ox$|DatePicker$|TimePicker$|RadioButton$|RadioButtons$|RadioButtonPanel$|RadioButtonPanels$|Check(B|b)ox$|Check(B|b)ox(e)?s$)/、もしくは/((FormGroup)$|(FormControl)$|((F|^f)ieldset)$)/にマッチする名称に変更してください`
|
|
80
80
|
|
|
81
81
|
ruleTester.run('a11y-input-in-form-control', rule, {
|
|
82
82
|
valid: [
|
|
@@ -41,6 +41,7 @@ ruleTester.run('a11y-prohibit-input-placeholder', rule, {
|
|
|
41
41
|
{ code: `<CustomComboBox />` },
|
|
42
42
|
{ code: `<SearchInput />` },
|
|
43
43
|
{ code: `<DatePicker />` },
|
|
44
|
+
{ code: `<TimePicker />` },
|
|
44
45
|
{ code: `<CustomSearchInput tooltipMessage="hoge" />` },
|
|
45
46
|
{ code: `<CustomSearchInput tooltipMessage="hoge" placeholder="fuga" />` },
|
|
46
47
|
{ code: `<ComboBox defaultItem={items[0]} />` },
|