eslint-plugin-smarthr 1.11.0 → 2.1.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 +19 -0
- package/README.md +0 -1
- package/libs/format_styled_components.js +1 -1
- package/package.json +4 -4
- package/rules/a11y-anchor-has-href-attribute/index.js +44 -80
- package/rules/a11y-clickable-element-has-text/index.js +9 -76
- package/rules/a11y-image-has-alt-attribute/index.js +14 -51
- package/rules/a11y-input-has-name-attribute/index.js +34 -70
- package/rules/a11y-prohibit-input-maxlength-attribute/index.js +5 -11
- package/rules/a11y-prohibit-input-placeholder/index.js +31 -52
- package/rules/a11y-prohibit-useless-sectioning-fragment/index.js +9 -44
- package/rules/a11y-trigger-has-button/index.js +18 -36
- package/rules/best-practice-for-async-current-target/index.js +14 -21
- package/rules/best-practice-for-button-element/index.js +10 -23
- package/rules/best-practice-for-data-test-attribute/index.js +9 -12
- package/rules/best-practice-for-date/index.js +16 -29
- package/rules/best-practice-for-nested-attributes-array-index/index.js +7 -15
- package/rules/best-practice-for-remote-trigger-dialog/index.js +10 -23
- package/rules/best-practice-for-tailwind-prohibit-root-margin/index.js +5 -93
- package/rules/design-system-guideline-prohibit-double-icons/index.js +6 -31
- package/rules/prohibit-export-array-type/index.js +6 -9
- package/rules/require-i18n-text/README.md +123 -0
- package/rules/require-i18n-text/index.js +94 -0
- package/test/a11y-clickable-element-has-text.js +0 -4
- package/test/a11y-image-has-alt-attribute.js +0 -1
- package/test/a11y-prohibit-useless-sectioning-fragment.js +7 -7
- package/test/a11y-trigger-has-button.js +8 -7
- package/test/best-practice-for-button-element.js +0 -3
- package/test/best-practice-for-data-test-attribute.js +9 -8
- package/test/best-practice-for-remote-trigger-dialog.js +3 -9
- package/test/best-practice-for-tailwind-prohibit-root-margin.js +16 -7
- package/test/design-system-guideline-prohibit-double-icons.js +1 -3
- package/test/require-i18n-text.js +170 -0
- package/rules/a11y-replace-unreadable-symbol/README.md +0 -38
- package/rules/a11y-replace-unreadable-symbol/index.js +0 -31
- package/test/a11y-replace-unreadable-symbol.js +0 -35
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
const rule = require('../rules/require-i18n-text')
|
|
2
|
+
const RuleTester = require('eslint').RuleTester
|
|
3
|
+
|
|
4
|
+
const ruleTester = new RuleTester({
|
|
5
|
+
languageOptions: {
|
|
6
|
+
parserOptions: {
|
|
7
|
+
ecmaFeatures: {
|
|
8
|
+
jsx: true,
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
const attributeError = (element, attr) =>
|
|
15
|
+
`${element}の${attr}属性に文字列リテラルが指定されています。多言語化対応のため、翻訳関数を使用してください`
|
|
16
|
+
const childTextError = '子要素に文字列リテラルが指定されています。多言語化対応のため、翻訳関数を使用してください'
|
|
17
|
+
|
|
18
|
+
const options = [
|
|
19
|
+
{
|
|
20
|
+
elements: {
|
|
21
|
+
img: ['alt', 'title'],
|
|
22
|
+
div: ['title'],
|
|
23
|
+
Button: ['label'],
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
ruleTester.run('require-i18n-text', rule, {
|
|
29
|
+
valid: [
|
|
30
|
+
// 翻訳関数を使用している場合
|
|
31
|
+
{ code: `<img alt={t('profile_picture')} />`, options },
|
|
32
|
+
{ code: `<div>{t('hello')}</div>`, options },
|
|
33
|
+
|
|
34
|
+
// 検査対象外の属性
|
|
35
|
+
{ code: `<img src="test.png" />`, options },
|
|
36
|
+
|
|
37
|
+
// 検査対象外の要素
|
|
38
|
+
{ code: `<Input label="test" />`, options },
|
|
39
|
+
|
|
40
|
+
// 数値リテラル(検査対象外)
|
|
41
|
+
{ code: `<div>{123}</div>`, options },
|
|
42
|
+
|
|
43
|
+
// 真偽値(検査対象外)
|
|
44
|
+
{ code: `<Button disabled={true} />`, options },
|
|
45
|
+
|
|
46
|
+
// 空文字列(検査対象外)
|
|
47
|
+
{ code: `<img alt="" />`, options },
|
|
48
|
+
|
|
49
|
+
// 空白のみのテキスト(検査対象外)
|
|
50
|
+
{ code: `<div> </div>`, options },
|
|
51
|
+
|
|
52
|
+
// デフォルト設定対象外の属性
|
|
53
|
+
{ code: `<img src="image.png" />` },
|
|
54
|
+
{ code: `<div data-testid="test" />` },
|
|
55
|
+
|
|
56
|
+
// ワイルドカード - 空配列で除外
|
|
57
|
+
{
|
|
58
|
+
code: `<Icon label="Icon text" />`,
|
|
59
|
+
options: [
|
|
60
|
+
{
|
|
61
|
+
elements: {
|
|
62
|
+
'*': ['label'],
|
|
63
|
+
Icon: [],
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
// デフォルト設定の上書き
|
|
70
|
+
{
|
|
71
|
+
code: `<img alt="text" />`,
|
|
72
|
+
options: [
|
|
73
|
+
{
|
|
74
|
+
elements: {
|
|
75
|
+
'*': ['data-tooltip'],
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
invalid: [
|
|
82
|
+
// 属性エラー: デフォルト設定
|
|
83
|
+
{
|
|
84
|
+
code: `<img alt="Profile picture" />`,
|
|
85
|
+
errors: [{ message: attributeError('img', 'alt') }],
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
code: `<CustomComponent aria-label="Label" />`,
|
|
89
|
+
errors: [{ message: attributeError('CustomComponent', 'aria-label') }],
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
code: `<DefinitionListItem term="Label" />`,
|
|
93
|
+
errors: [{ message: attributeError('DefinitionListItem', 'term') }],
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
code: `<button title="Click me" />`,
|
|
97
|
+
errors: [{ message: attributeError('button', 'title') }],
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
// 属性エラー: カスタムオプション
|
|
101
|
+
{
|
|
102
|
+
code: `<img alt="Profile picture" />`,
|
|
103
|
+
options,
|
|
104
|
+
errors: [{ message: attributeError('img', 'alt') }],
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
code: `<img alt={'Profile'} />`,
|
|
108
|
+
options,
|
|
109
|
+
errors: [{ message: attributeError('img', 'alt') }],
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
// 属性エラー: 同一要素の複数属性
|
|
113
|
+
{
|
|
114
|
+
code: `<img alt="Profile" title="User profile" />`,
|
|
115
|
+
options,
|
|
116
|
+
errors: [{ message: attributeError('img', 'alt') }, { message: attributeError('img', 'title') }],
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
// 属性エラー: ワイルドカード
|
|
120
|
+
{
|
|
121
|
+
code: `<CustomComponent label="Text" />`,
|
|
122
|
+
options: [
|
|
123
|
+
{
|
|
124
|
+
elements: {
|
|
125
|
+
'*': ['label'],
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
errors: [{ message: attributeError('CustomComponent', 'label') }],
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
// 属性エラー: 個別設定がワイルドカードより優先
|
|
133
|
+
{
|
|
134
|
+
code: `<Button label="Submit" helperText="Help" />`,
|
|
135
|
+
options: [
|
|
136
|
+
{
|
|
137
|
+
elements: {
|
|
138
|
+
'*': ['label'],
|
|
139
|
+
Button: ['label', 'helperText'],
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
errors: [{ message: attributeError('Button', 'label') }, { message: attributeError('Button', 'helperText') }],
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
// 子要素エラー(オプション未設定時でもチェックされる)
|
|
147
|
+
{
|
|
148
|
+
code: `<div>Hello World</div>`,
|
|
149
|
+
errors: [{ message: childTextError }],
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
// 複合エラー: 属性と子要素
|
|
153
|
+
{
|
|
154
|
+
code: `<Button label="Submit">Click here</Button>`,
|
|
155
|
+
options,
|
|
156
|
+
errors: [{ message: attributeError('Button', 'label') }, { message: childTextError }],
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
// 複合エラー: 入れ子構造
|
|
160
|
+
{
|
|
161
|
+
code: `<div title="Parent"><Button label="Child">Grandchild text</Button></div>`,
|
|
162
|
+
options,
|
|
163
|
+
errors: [
|
|
164
|
+
{ message: attributeError('div', 'title') },
|
|
165
|
+
{ message: attributeError('Button', 'label') },
|
|
166
|
+
{ message: childTextError },
|
|
167
|
+
],
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
})
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# smarthr/a11y-replace-unreadable-symbol
|
|
2
|
-
|
|
3
|
-
- 一部記号はスクリーンリーダーで読み上げられない、もしくは記号名そのままで読み上げられてしまい、意図が正しく伝えられない場合があります
|
|
4
|
-
- それらの記号を適切に読み上げられるコンポーネントに置き換えることを促すルールです
|
|
5
|
-
|
|
6
|
-
## rules
|
|
7
|
-
|
|
8
|
-
```js
|
|
9
|
-
{
|
|
10
|
-
rules: {
|
|
11
|
-
'smarthr/a11y-replace-unreadable-symbol': 'error', // 'warn', 'off',
|
|
12
|
-
},
|
|
13
|
-
}
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
## ❌ Incorrect
|
|
17
|
-
|
|
18
|
-
```jsx
|
|
19
|
-
<>XXXX年YY月ZZ日 〜 XXXX年YY月ZZ日</>
|
|
20
|
-
// スクリーンリーダーは "XXXX年YY月ZZ日XXXX年YY月ZZ日" と読み上げる場合があります
|
|
21
|
-
|
|
22
|
-
<p>選択できる数値の範囲は 0 ~ 9999 です</p>
|
|
23
|
-
// スクリーンリーダーは "選択できる数値の範囲は09999です" と読み上げる場合があります
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## ✅ Correct
|
|
27
|
-
|
|
28
|
-
```jsx
|
|
29
|
-
//
|
|
30
|
-
<>XXXX年YY月ZZ日 <RangeSeparator /> XXXX年YY月ZZ日</>
|
|
31
|
-
// スクリーンリーダーは "XXXX年YY月ZZ日からXXXX年YY月ZZ日" と読み上げます
|
|
32
|
-
|
|
33
|
-
<p>選択できる数値の範囲は 0 <RangeSeparator /> 9999 です</p>
|
|
34
|
-
// スクリーンリーダーは "選択できる数値の範囲は0から9999です" と読み上げます
|
|
35
|
-
|
|
36
|
-
<p>入力できる記号は <RangeSeparator decorators={{ text: '~', visuallyHiddenText: '半角チルダ' }} /> です</p>
|
|
37
|
-
// スクリーンリーダーは "入力できる記号は半角チルダです" と読み上げます
|
|
38
|
-
```
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
const TILDE_REGEX = /([~〜])/
|
|
2
|
-
|
|
3
|
-
const SCHEMA = []
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
7
|
-
*/
|
|
8
|
-
module.exports = {
|
|
9
|
-
meta: {
|
|
10
|
-
type: 'problem',
|
|
11
|
-
schema: SCHEMA,
|
|
12
|
-
},
|
|
13
|
-
create(context) {
|
|
14
|
-
return {
|
|
15
|
-
JSXText: (node) => {
|
|
16
|
-
const matcher = node.value.match(TILDE_REGEX)
|
|
17
|
-
|
|
18
|
-
if (matcher) {
|
|
19
|
-
context.report({
|
|
20
|
-
node,
|
|
21
|
-
message: `"${matcher[1]}"はスクリーンリーダーが正しく読み上げることができない場合があるため、smarthr-ui/RangeSeparatorに置き換えてください。
|
|
22
|
-
- エラー表示されている行に"${matcher[1]}"が存在しない場合、改行文字を含む関係で行番号がずれている場合があります。数行下の範囲を確認してください
|
|
23
|
-
- smarthr-ui/RangeSeparatorに置き換えることでスクリーンリーダーが "から" と読み上げることができます
|
|
24
|
-
- 前後の文脈などで "から" と読まれることが不適切な場合 \`<RangeSeparator decorators={{ visuallyHiddenText: () => "ANY" }} />\` のようにdecoratorsを指定してください`,
|
|
25
|
-
})
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
}
|
|
31
|
-
module.exports.schema = SCHEMA
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
const rule = require('../rules/a11y-replace-unreadable-symbol')
|
|
2
|
-
const RuleTester = require('eslint').RuleTester
|
|
3
|
-
|
|
4
|
-
const generateErrorText = (symbol, replaced, read) => `"${symbol}"はスクリーンリーダーが正しく読み上げることができない場合があるため、smarthr-ui/${replaced}に置き換えてください。
|
|
5
|
-
- エラー表示されている行に"${symbol}"が存在しない場合、改行文字を含む関係で行番号がずれている場合があります。数行下の範囲を確認してください
|
|
6
|
-
- smarthr-ui/${replaced}に置き換えることでスクリーンリーダーが "${read}" と読み上げることができます
|
|
7
|
-
- 前後の文脈などで "${read}" と読まれることが不適切な場合 \`<RangeSeparator decorators={{ visuallyHiddenText: () => "ANY" }} />\` のようにdecoratorsを指定してください`
|
|
8
|
-
|
|
9
|
-
const ruleTester = new RuleTester({
|
|
10
|
-
languageOptions: {
|
|
11
|
-
parserOptions: {
|
|
12
|
-
ecmaFeatures: {
|
|
13
|
-
jsx: true,
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
ruleTester.run('a11y-replace-unreadable-symbol', rule, {
|
|
20
|
-
valid: [
|
|
21
|
-
{ code: `<>ほげふが</>` },
|
|
22
|
-
{ code: `<RangeSeparator />` },
|
|
23
|
-
{ code: `<p>ほげ<RangeSeparator />ふが</p>` },
|
|
24
|
-
{ code: `<p>
|
|
25
|
-
ほげ
|
|
26
|
-
<RangeSeparator />
|
|
27
|
-
ふが
|
|
28
|
-
</p>` },
|
|
29
|
-
],
|
|
30
|
-
invalid: [
|
|
31
|
-
{ code: `<>~</>`, errors: [ { message: generateErrorText('~', 'RangeSeparator', 'から') } ] },
|
|
32
|
-
{ code: `<>ほげ~ふが</>`, errors: [ { message: generateErrorText('~', 'RangeSeparator', 'から') } ] },
|
|
33
|
-
{ code: `<p>ほげ〜ふが</p>`, errors: [ { message: generateErrorText('〜', 'RangeSeparator', 'から') } ] },
|
|
34
|
-
]
|
|
35
|
-
})
|