eslint-plugin-smarthr 0.3.5 → 0.3.7
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/libs/common.js +20 -4
- package/package.json +1 -1
- package/rules/a11y-clickable-element-has-text/README.md +14 -2
- package/rules/a11y-clickable-element-has-text/index.js +56 -45
- package/rules/a11y-image-has-alt-attribute/index.js +29 -20
- package/rules/format-import-path/README.md +2 -0
- package/rules/no-import-other-domain/README.md +2 -0
- package/rules/redundant-name/README.md +2 -0
- package/rules/require-barrel-import/README.md +2 -0
- package/test/a11y-clickable-element-has-text.js +20 -1
- package/test/a11y-image-has-alt-attribute.js +10 -3
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
|
+
### [0.3.7](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.3.6...v0.3.7) (2023-08-24)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* a11y-clickable-element-has-text のチェック時、リンク内部に名称の末尾がTextがつくコンポーネントがある場合、チェックを通過するように修正 ([#69](https://github.com/kufu/eslint-plugin-smarthr/issues/69)) ([182b5d5](https://github.com/kufu/eslint-plugin-smarthr/commit/182b5d5e52c1faee26011572c48271e4c03512e1))
|
|
11
|
+
|
|
12
|
+
### [0.3.6](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.3.5...v0.3.6) (2023-08-20)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* .eslintrc.js などの設定からparserOptions.project が設定されている場合、tsconfig.jsonの読み込み先を変更する ([#68](https://github.com/kufu/eslint-plugin-smarthr/issues/68)) ([3897faf](https://github.com/kufu/eslint-plugin-smarthr/commit/3897fafbf3bf8ccdc42a06700ff832ec97dc7ff1))
|
|
18
|
+
|
|
5
19
|
### [0.3.5](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.3.4...v0.3.5) (2023-07-28)
|
|
6
20
|
|
|
7
21
|
|
package/libs/common.js
CHANGED
|
@@ -3,13 +3,29 @@ const path = require('path')
|
|
|
3
3
|
const fs = require('fs')
|
|
4
4
|
|
|
5
5
|
const replacePaths = (() => {
|
|
6
|
-
const
|
|
6
|
+
const cwd = process.cwd()
|
|
7
|
+
const eslintrc = (() => {
|
|
8
|
+
let file = `${cwd}/.eslintrc.js`
|
|
9
|
+
if (fs.existsSync(file)) {
|
|
10
|
+
return require(file)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
file = `${cwd}/.eslintrc`
|
|
14
|
+
|
|
15
|
+
if (fs.existsSync(file)) {
|
|
16
|
+
return JSON5.parse(fs.readFileSync(file))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return {}
|
|
20
|
+
})()
|
|
21
|
+
|
|
22
|
+
const tsconfigFile = `${cwd}/${eslintrc.parserOptions?.project || 'tsconfig.json'}`
|
|
7
23
|
|
|
8
|
-
if (!
|
|
9
|
-
throw new Error(
|
|
24
|
+
if (!fs.existsSync(tsconfigFile)) {
|
|
25
|
+
throw new Error(`${tsconfigFile} を設置してください`)
|
|
10
26
|
}
|
|
11
27
|
|
|
12
|
-
const { compilerOptions } = JSON5.parse(
|
|
28
|
+
const { compilerOptions } = JSON5.parse(fs.readFileSync(tsconfigFile))
|
|
13
29
|
|
|
14
30
|
if (!compilerOptions || !compilerOptions.paths) {
|
|
15
31
|
throw new Error('tsconfig.json の compilerOptions.paths に `"@/*": ["any_path/*"]` 形式でフロントエンドのroot dir を指定してください')
|
package/package.json
CHANGED
|
@@ -37,6 +37,12 @@
|
|
|
37
37
|
</XxxButton>
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
+
```jsx
|
|
41
|
+
<XxxAnchor>>
|
|
42
|
+
<XxxTextYyyy />
|
|
43
|
+
</XxxAnchor>
|
|
44
|
+
```
|
|
45
|
+
|
|
40
46
|
## ✅ Correct
|
|
41
47
|
|
|
42
48
|
```jsx
|
|
@@ -65,19 +71,25 @@
|
|
|
65
71
|
<YyyAnchoor />
|
|
66
72
|
```
|
|
67
73
|
|
|
74
|
+
```jsx
|
|
75
|
+
<XxxAnchor>>
|
|
76
|
+
<XxxText />
|
|
77
|
+
</XxxAnchor>
|
|
78
|
+
```
|
|
79
|
+
|
|
68
80
|
```jsx
|
|
69
81
|
/*
|
|
70
82
|
rules: {
|
|
71
83
|
'smarthr/a11y-clickable-element-has-text': [
|
|
72
84
|
'error',
|
|
73
85
|
{
|
|
74
|
-
componentsWithText: ['
|
|
86
|
+
componentsWithText: ['Hoge'],
|
|
75
87
|
},
|
|
76
88
|
]
|
|
77
89
|
},
|
|
78
90
|
*/
|
|
79
91
|
|
|
80
92
|
<XxxButton>
|
|
81
|
-
<
|
|
93
|
+
<Hoge />
|
|
82
94
|
</XxxButton>
|
|
83
95
|
```
|
|
@@ -15,12 +15,27 @@ const EXPECTED_NAMES = {
|
|
|
15
15
|
'(b|B)utton$': 'Button$',
|
|
16
16
|
'Anchor$': 'Anchor$',
|
|
17
17
|
'Link$': 'Link$',
|
|
18
|
+
'Text$': 'Text$',
|
|
19
|
+
'Message$': 'Message$',
|
|
18
20
|
'^a$': '(Anchor|Link)$',
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
const REGEX_NLSP = /^\s*\n+\s*$/
|
|
24
|
+
const REGEX_CLICKABLE_ELEMENT = /^(a|(.*?)Anchor(Button)?|(.*?)Link|(b|B)utton)$/
|
|
25
|
+
const REGEX_SMARTHR_LOGO = /SmartHRLogo$/
|
|
26
|
+
const REGEX_TEXT_COMPONENT = /(Text|Message)$/
|
|
27
|
+
|
|
28
|
+
const HIT_TYPES_RECURSICVE_SEARCH = ['JSXText', 'JSXExpressionContainer']
|
|
29
|
+
const HIT_TEXT_ATTRS = ['visuallyHiddenText', 'alt']
|
|
30
|
+
|
|
31
|
+
const filterFalsyJSXText = (cs) => cs.filter(checkFalsyJSXText)
|
|
32
|
+
const checkFalsyJSXText = (c) => (
|
|
33
|
+
!(c.type === 'JSXText' && c.value.match(REGEX_NLSP))
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
const message = `a, buttonなどのクリッカブルな要素内にはテキストを設定してください。
|
|
37
|
+
- 要素内にアイコン、画像のみを設置する場合はaltなどの代替テキスト用属性を指定してください
|
|
38
|
+
- クリッカブルな要素内に設置しているコンポーネントがテキストを含んでいる場合、"XxxxText" のように末尾に "Text" もしくは "Message" という名称を設定してください`
|
|
24
39
|
|
|
25
40
|
module.exports = {
|
|
26
41
|
meta: {
|
|
@@ -41,69 +56,65 @@ module.exports = {
|
|
|
41
56
|
|
|
42
57
|
const node = parentNode.openingElement
|
|
43
58
|
|
|
44
|
-
if (!node.name.name || !node.name.name.match(
|
|
59
|
+
if (!node.name.name || !node.name.name.match(REGEX_CLICKABLE_ELEMENT)) {
|
|
45
60
|
return
|
|
46
61
|
}
|
|
47
62
|
|
|
48
63
|
const recursiveSearch = (c) => {
|
|
49
|
-
if (
|
|
64
|
+
if (HIT_TYPES_RECURSICVE_SEARCH.includes(c.type)) {
|
|
50
65
|
return true
|
|
51
66
|
}
|
|
52
67
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
return
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return false
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (c.type === 'JSXElement') {
|
|
62
|
-
// // HINT: SmartHRLogo コンポーネントは内部でaltを持っているため対象外にする
|
|
63
|
-
if (c.openingElement.name.name.match(/SmartHRLogo$/)) {
|
|
64
|
-
return true
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (componentsWithText.includes(c.openingElement.name.name)) {
|
|
68
|
-
return true
|
|
68
|
+
switch (c.type) {
|
|
69
|
+
case 'JSXFragment': {
|
|
70
|
+
return c.children && filterFalsyJSXText(c.children).some(recursiveSearch)
|
|
69
71
|
}
|
|
72
|
+
case 'JSXElement': {
|
|
73
|
+
// // HINT: SmartHRLogo コンポーネントは内部でaltを持っているため対象外にする
|
|
74
|
+
if (c.openingElement.name.name.match(REGEX_SMARTHR_LOGO)) {
|
|
75
|
+
return true
|
|
76
|
+
}
|
|
70
77
|
|
|
71
|
-
|
|
72
|
-
let existRole = false
|
|
73
|
-
let existAriaLabel = false
|
|
74
|
-
const result = c.openingElement.attributes.reduce((prev, a) => {
|
|
75
|
-
existRole = existRole || (a.name.name === 'role' && a.value.value === 'img')
|
|
76
|
-
existAriaLabel = existAriaLabel || a.name.name === 'aria-label'
|
|
78
|
+
const tagName = c.openingElement.name.name
|
|
77
79
|
|
|
78
|
-
if (
|
|
79
|
-
return
|
|
80
|
+
if (tagName.match(REGEX_TEXT_COMPONENT) || componentsWithText.includes(tagName)) {
|
|
81
|
+
return true
|
|
80
82
|
}
|
|
81
83
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
// HINT: role & aria-label を同時に設定されている場合は許可
|
|
85
|
+
let existRole = false
|
|
86
|
+
let existAriaLabel = false
|
|
87
|
+
const result = c.openingElement.attributes.reduce((prev, a) => {
|
|
88
|
+
existRole = existRole || (a.name.name === 'role' && a.value.value === 'img')
|
|
89
|
+
existAriaLabel = existAriaLabel || a.name.name === 'aria-label'
|
|
90
|
+
|
|
91
|
+
if (
|
|
92
|
+
prev ||
|
|
93
|
+
!HIT_TEXT_ATTRS.includes(a.name.name)
|
|
94
|
+
) {
|
|
95
|
+
return prev
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return (!!a.value.value || a.value.type === 'JSXExpressionContainer') ? a : prev
|
|
99
|
+
}, null)
|
|
100
|
+
|
|
101
|
+
if (
|
|
102
|
+
result ||
|
|
103
|
+
(existRole && existAriaLabel) ||
|
|
104
|
+
(c.children && filterFalsyJSXText(c.children).some(recursiveSearch))
|
|
105
|
+
) {
|
|
106
|
+
return true
|
|
84
107
|
}
|
|
85
|
-
|
|
86
|
-
return (!!a.value.value || a.value.type === 'JSXExpressionContainer') ? a : prev
|
|
87
|
-
}, null)
|
|
88
|
-
|
|
89
|
-
if (result || (existRole && existAriaLabel)) {
|
|
90
|
-
return true
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (c.children && filterFalsyJSXText(c.children).some(recursiveSearch)) {
|
|
94
|
-
return true
|
|
95
108
|
}
|
|
96
109
|
}
|
|
97
110
|
|
|
98
111
|
return false
|
|
99
112
|
}
|
|
100
113
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (!child) {
|
|
114
|
+
if (!filterFalsyJSXText(parentNode.children).find(recursiveSearch)) {
|
|
104
115
|
context.report({
|
|
105
116
|
node,
|
|
106
|
-
message
|
|
117
|
+
message,
|
|
107
118
|
});
|
|
108
119
|
}
|
|
109
120
|
},
|
|
@@ -7,6 +7,9 @@ const EXPECTED_NAMES = {
|
|
|
7
7
|
'^(img|svg)$': '(Img|Image|Icon)$',
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
const REGEX_IMG = /(img|image)$/i // HINT: Iconは別途テキストが存在する場合が多いためチェックの対象外とする
|
|
11
|
+
|
|
12
|
+
const findAltAttr = (a) => a.name?.name === 'alt'
|
|
10
13
|
const isWithinSvgJsxElement = (node) => {
|
|
11
14
|
if (
|
|
12
15
|
node.type === 'JSXElement' &&
|
|
@@ -15,13 +18,16 @@ const isWithinSvgJsxElement = (node) => {
|
|
|
15
18
|
return true
|
|
16
19
|
}
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
return false
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return isWithinSvgJsxElement(node.parent)
|
|
21
|
+
return node.parent ? isWithinSvgJsxElement(node.parent) : false
|
|
23
22
|
}
|
|
24
23
|
|
|
24
|
+
const MESSAGE_NOT_EXIST_ALT = `画像にはalt属性を指定してください。
|
|
25
|
+
- コンポーネントが画像ではない場合、img or image を末尾に持たない名称に変更してください。
|
|
26
|
+
- ボタンやリンクの先頭・末尾などに設置するアイコンとしての役割を持つ画像の場合、コンポーネント名の末尾を "Icon" に変更してください。
|
|
27
|
+
- SVG component の場合、altを属性として受け取れるようにした上で '<svg role="img" aria-label={alt}>' のように指定してください。`
|
|
28
|
+
const MESSAGE_NULL_ALT = `画像の情報をテキストにした代替テキスト('alt')を設定してください。
|
|
29
|
+
- 装飾目的の画像など、alt属性に指定すべき文字がない場合は背景画像にすることを検討してください。`
|
|
30
|
+
|
|
25
31
|
module.exports = {
|
|
26
32
|
meta: {
|
|
27
33
|
type: 'problem',
|
|
@@ -31,25 +37,28 @@ module.exports = {
|
|
|
31
37
|
return {
|
|
32
38
|
...generateTagFormatter({ context, EXPECTED_NAMES }),
|
|
33
39
|
JSXOpeningElement: (node) => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const alt = node.attributes.find((a) => a.name?.name === 'alt')
|
|
40
|
+
if (node.name.name) {
|
|
41
|
+
const matcher = node.name.name.match(REGEX_IMG)
|
|
37
42
|
|
|
38
|
-
|
|
43
|
+
if (matcher) {
|
|
44
|
+
const alt = node.attributes.find(findAltAttr)
|
|
39
45
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
let message = ''
|
|
47
|
+
|
|
48
|
+
if (!alt) {
|
|
49
|
+
if (matcher.input !== 'image' || !isWithinSvgJsxElement(node.parent)) {
|
|
50
|
+
message = MESSAGE_NOT_EXIST_ALT
|
|
51
|
+
}
|
|
52
|
+
} else if (alt.value.value === '') {
|
|
53
|
+
message = MESSAGE_NULL_ALT
|
|
43
54
|
}
|
|
44
|
-
} else if (alt.value.value === '') {
|
|
45
|
-
message = '画像の情報をテキストにした代替テキスト(`alt`)を設定してください。装飾目的の画像など、alt属性に指定すべき文字がない場合は背景画像にすることを検討してください。'
|
|
46
|
-
}
|
|
47
55
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
56
|
+
if (message) {
|
|
57
|
+
context.report({
|
|
58
|
+
node,
|
|
59
|
+
message,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
53
62
|
}
|
|
54
63
|
}
|
|
55
64
|
},
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
## config
|
|
9
9
|
|
|
10
10
|
- tsconfig.json の compilerOptions.pathsに '@/*', もしくは '~/*' としてroot path を指定する必要があります
|
|
11
|
+
- tsconfig.json はデフォルトではコマンド実行をしたディレクトリから読み込みます
|
|
12
|
+
- tsconfig.json の設置ディレクトリを変更したい場合、 `.eslintrc` などのeslint設定ファイルに `parserOptions.project` を設定してください
|
|
11
13
|
- ドメインを識別するために以下の設定を記述する必要があります
|
|
12
14
|
- globalModuleDir
|
|
13
15
|
- 全体で利用するファイルを収めているディレクトリを相対パスで指定します
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
## config
|
|
8
8
|
|
|
9
9
|
- tsconfig.json の compilerOptions.pathsに '@/*', もしくは '~/*' としてroot path を指定する必要があります
|
|
10
|
+
- tsconfig.json はデフォルトではコマンド実行をしたディレクトリから読み込みます
|
|
11
|
+
- tsconfig.json の設置ディレクトリを変更したい場合、 `.eslintrc` などのeslint設定ファイルに `parserOptions.project` を設定してください
|
|
10
12
|
- ドメインを識別するために以下の設定を記述する必要があります
|
|
11
13
|
- globalModuleDir
|
|
12
14
|
- 全体で利用するファイルを収めているディレクトリを相対パスで指定します
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
## config
|
|
7
7
|
|
|
8
8
|
- tsconfig.json の compilerOptions.pathsに '@/*', もしくは '~/*' としてroot path を指定する必要があります
|
|
9
|
+
- tsconfig.json はデフォルトではコマンド実行をしたディレクトリから読み込みます
|
|
10
|
+
- tsconfig.json の設置ディレクトリを変更したい場合、 `.eslintrc` などのeslint設定ファイルに `parserOptions.project` を設定してください
|
|
9
11
|
- 以下の設定を行えます。全て省略可能です。
|
|
10
12
|
- ignoreKeywords
|
|
11
13
|
- ディレクトリ名から生成されるキーワードに含めたくない文字列を指定します
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# smarthr/require-barrel-import
|
|
2
2
|
|
|
3
3
|
- tsconfig.json の compilerOptions.pathsに '@/*', もしくは '~/*' としてroot path を指定する必要があります
|
|
4
|
+
- tsconfig.json はデフォルトではコマンド実行をしたディレクトリから読み込みます
|
|
5
|
+
- tsconfig.json の設置ディレクトリを変更したい場合、 `.eslintrc` などのeslint設定ファイルに `parserOptions.project` を設定してください
|
|
4
6
|
- importした対象が本来exportされているべきであるbarrel(index.tsなど)が有る場合、import pathの変更を促します
|
|
5
7
|
- 例: Page/parts/Menu/Item の import は Page/parts/Menu から行わせたい
|
|
6
8
|
- ディレクトリ内のindexファイルを捜査し、対象を決定します
|
|
@@ -12,7 +12,9 @@ const ruleTester = new RuleTester({
|
|
|
12
12
|
},
|
|
13
13
|
})
|
|
14
14
|
|
|
15
|
-
const defaultErrorMessage =
|
|
15
|
+
const defaultErrorMessage = `a, buttonなどのクリッカブルな要素内にはテキストを設定してください。
|
|
16
|
+
- 要素内にアイコン、画像のみを設置する場合はaltなどの代替テキスト用属性を指定してください
|
|
17
|
+
- クリッカブルな要素内に設置しているコンポーネントがテキストを含んでいる場合、"XxxxText" のように末尾に "Text" もしくは "Message" という名称を設定してください`
|
|
16
18
|
|
|
17
19
|
ruleTester.run('a11y-clickable-element-has-text', rule, {
|
|
18
20
|
valid: [
|
|
@@ -30,6 +32,8 @@ ruleTester.run('a11y-clickable-element-has-text', rule, {
|
|
|
30
32
|
{ code: 'const HogeAnchor = styled.a(() => ``)' },
|
|
31
33
|
{ code: 'const HogeAnchor = styled("a")(() => ``)' },
|
|
32
34
|
{ code: 'const HogeAnchor = styled(Anchor)(() => ``)' },
|
|
35
|
+
{ code: 'const FugaText = styled(HogeText)(() => ``)' },
|
|
36
|
+
{ code: 'const FugaMessage = styled(HogeMessage)(() => ``)' },
|
|
33
37
|
{
|
|
34
38
|
code: `<a>ほげ</a>`,
|
|
35
39
|
},
|
|
@@ -105,6 +109,15 @@ ruleTester.run('a11y-clickable-element-has-text', rule, {
|
|
|
105
109
|
{
|
|
106
110
|
code: `<a><svg role="img" aria-label="hoge" /></a>`,
|
|
107
111
|
},
|
|
112
|
+
{
|
|
113
|
+
code: `<a><Text /></a>`,
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
code: `<a><HogeText /></a>`,
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
code: `<a><FormattedMessage /></a>`,
|
|
120
|
+
},
|
|
108
121
|
{
|
|
109
122
|
code: `<a><AnyComponent /></a>`,
|
|
110
123
|
options: [{
|
|
@@ -126,6 +139,8 @@ ruleTester.run('a11y-clickable-element-has-text', rule, {
|
|
|
126
139
|
{ code: 'const Piyo = styled("a")(() => ``)', errors: [ { message: `Piyoを正規表現 "/(Anchor|Link)$/" がmatchする名称に変更してください` } ] },
|
|
127
140
|
{ code: 'const Piyo = styled("a")``', errors: [ { message: `Piyoを正規表現 "/(Anchor|Link)$/" がmatchする名称に変更してください` } ] },
|
|
128
141
|
{ code: 'const Piyo = styled(Anchor)(() => ``)', errors: [ { message: `Piyoを正規表現 "/Anchor$/" がmatchする名称に変更してください` } ] },
|
|
142
|
+
{ code: 'const Hoge = styled(Text)``', errors: [ { message: `Hogeを正規表現 "/Text$/" がmatchする名称に変更してください` } ] },
|
|
143
|
+
{ code: 'const Hoge = styled(HogeMessage)``', errors: [ { message: `Hogeを正規表現 "/Message$/" がmatchする名称に変更してください` } ] },
|
|
129
144
|
{
|
|
130
145
|
code: `<a><img src="hoge.jpg" /></a>`,
|
|
131
146
|
errors: [{ message: defaultErrorMessage }]
|
|
@@ -174,6 +189,10 @@ ruleTester.run('a11y-clickable-element-has-text', rule, {
|
|
|
174
189
|
code: `<a><div role="article" aria-label="hoge" /></a>`,
|
|
175
190
|
errors: [{ message: defaultErrorMessage }]
|
|
176
191
|
},
|
|
192
|
+
{
|
|
193
|
+
code: `<a><TextWithHoge /></a>`,
|
|
194
|
+
errors: [{ message: defaultErrorMessage }]
|
|
195
|
+
},
|
|
177
196
|
{
|
|
178
197
|
code: `<a><AnyComponent /></a>`,
|
|
179
198
|
options: [{
|
|
@@ -12,6 +12,13 @@ const ruleTester = new RuleTester({
|
|
|
12
12
|
},
|
|
13
13
|
})
|
|
14
14
|
|
|
15
|
+
const messageNotExistAlt = `画像にはalt属性を指定してください。
|
|
16
|
+
- コンポーネントが画像ではない場合、img or image を末尾に持たない名称に変更してください。
|
|
17
|
+
- ボタンやリンクの先頭・末尾などに設置するアイコンとしての役割を持つ画像の場合、コンポーネント名の末尾を "Icon" に変更してください。
|
|
18
|
+
- SVG component の場合、altを属性として受け取れるようにした上で '<svg role="img" aria-label={alt}>' のように指定してください。`
|
|
19
|
+
const messageNullAlt = `画像の情報をテキストにした代替テキスト('alt')を設定してください。
|
|
20
|
+
- 装飾目的の画像など、alt属性に指定すべき文字がない場合は背景画像にすることを検討してください。`
|
|
21
|
+
|
|
15
22
|
ruleTester.run('a11y-image-has-alt-attribute', rule, {
|
|
16
23
|
valid: [
|
|
17
24
|
{ code: `import styled from 'styled-components'` },
|
|
@@ -39,8 +46,8 @@ ruleTester.run('a11y-image-has-alt-attribute', rule, {
|
|
|
39
46
|
{ code: 'const Hoge = styled(Icon)``', errors: [ { message: `Hogeを正規表現 "/Icon$/" がmatchする名称に変更してください` } ] },
|
|
40
47
|
{ code: 'const Hoge = styled(Img)``', errors: [ { message: `Hogeを正規表現 "/Img$/" がmatchする名称に変更してください` } ] },
|
|
41
48
|
{ code: 'const Hoge = styled(Image)``', errors: [ { message: `Hogeを正規表現 "/Image$/" がmatchする名称に変更してください` } ] },
|
|
42
|
-
{ code: '<img />', errors: [ { message:
|
|
43
|
-
{ code: '<HogeImage alt="" />', errors: [ { message:
|
|
44
|
-
{ code: '<hoge><image /></hoge>', errors: [ { message:
|
|
49
|
+
{ code: '<img />', errors: [ { message: messageNotExistAlt } ] },
|
|
50
|
+
{ code: '<HogeImage alt="" />', errors: [ { message: messageNullAlt } ] },
|
|
51
|
+
{ code: '<hoge><image /></hoge>', errors: [ { message: messageNotExistAlt } ] },
|
|
45
52
|
]
|
|
46
53
|
})
|