eslint-plugin-smarthr 0.2.9 → 0.2.12
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 +26 -0
- package/README.md +1 -0
- package/package.json +1 -1
- package/rules/a11y-image-has-alt-attribute/index.js +20 -2
- package/rules/a11y-prohibit-input-placeholder/index.js +37 -1
- package/rules/redundant-name/index.js +11 -7
- package/test/a11y-image-has-alt-attribute.js +2 -0
- package/test/a11y-prohhibit-input-placeholder.js +10 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,32 @@
|
|
|
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.2.12](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.2.11...v0.2.12) (2022-12-07)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* ファイルが複数の.を保つ場合(例 xxx.test.tsx)正常に動作しないバグを修正する ([#42](https://github.com/kufu/eslint-plugin-smarthr/issues/42)) ([23eb5b5](https://github.com/kufu/eslint-plugin-smarthr/commit/23eb5b51039085d3239adca42c65395b81869f9d))
|
|
11
|
+
|
|
12
|
+
### [0.2.11](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.2.10...v0.2.11) (2022-12-07)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* redundant-nameの省略文字列がファイルパスによって正常に生成できないバグを修正 ([49eb1d9](https://github.com/kufu/eslint-plugin-smarthr/commit/49eb1d9ad1e9c153b7cb150190a81e5fae6bedf6))
|
|
18
|
+
|
|
19
|
+
### [0.2.10](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.2.9...v0.2.10) (2022-11-22)
|
|
20
|
+
|
|
21
|
+
### Features
|
|
22
|
+
|
|
23
|
+
* a11y-prohibit-input-placeholder を最新のComboBoxに対応させる ([#39](https://github.com/kufu/eslint-plugin-smarthr/issues/39)) ([682281c](https://github.com/kufu/eslint-plugin-smarthr/pull/39/commits/682281cd0f6ed73b4ec1295f34680bd9576ba831))
|
|
24
|
+
* placeholder禁止対象にdate pickerが含まれていなかったため対応 ([#39](https://github.com/kufu/eslint-plugin-smarthr/issues/39)) ([abf89f0](https://github.com/kufu/eslint-plugin-smarthr/pull/39/commits/abf89f0fe5a88b4d03fdbd0e1ed344bae1c6397a))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Bug Fixes
|
|
28
|
+
|
|
29
|
+
* a11y-image-has-alt-attribute が svg > image を誤検知してしまうバグを修正する ([#40](https://github.com/kufu/eslint-plugin-smarthr/issues/40)) ([1f21879](https://github.com/kufu/eslint-plugin-smarthr/commit/1f21879a0309bfec15cfa186db4f6203cd80cc14))
|
|
30
|
+
|
|
5
31
|
### [0.2.9](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.2.8...v0.2.9) (2022-10-19)
|
|
6
32
|
|
|
7
33
|
|
package/README.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
- [a11y-clickable-element-has-text](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-clickable-element-has-text)
|
|
4
4
|
- [a11y-image-has-alt-attribute](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-image-has-alt-attribute)
|
|
5
|
+
- [a11y-prohibit-input-placeholder](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-prohibit-input-placeholder)
|
|
5
6
|
- [a11y-trigger-has-button](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-trigger-has-button)
|
|
6
7
|
- [best-practice-for-date](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/best-practice-for-date)
|
|
7
8
|
- [format-import-path](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/format-import-path)
|
package/package.json
CHANGED
|
@@ -7,6 +7,21 @@ const EXPECTED_NAMES = {
|
|
|
7
7
|
'^(img|svg)$': '(Img|Image|Icon)$',
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
const isWithinSvgJsxElement = (node) => {
|
|
11
|
+
if (
|
|
12
|
+
node.type === 'JSXElement' &&
|
|
13
|
+
node.openingElement.name?.name === 'svg'
|
|
14
|
+
) {
|
|
15
|
+
return true
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!node.parent) {
|
|
19
|
+
return false
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return isWithinSvgJsxElement(node.parent)
|
|
23
|
+
}
|
|
24
|
+
|
|
10
25
|
module.exports = {
|
|
11
26
|
meta: {
|
|
12
27
|
type: 'problem',
|
|
@@ -20,13 +35,16 @@ module.exports = {
|
|
|
20
35
|
return {
|
|
21
36
|
...generateTagFormatter({ context, EXPECTED_NAMES }),
|
|
22
37
|
JSXOpeningElement: (node) => {
|
|
23
|
-
|
|
38
|
+
const matcher = (node.name.name || '').match(/(img|image)$/i) // HINT: Iconは別途テキストが存在する場合が多いためチェックの対象外とする
|
|
39
|
+
if (matcher) {
|
|
24
40
|
const alt = node.attributes.find((a) => a.name?.name === 'alt')
|
|
25
41
|
|
|
26
42
|
let message = ''
|
|
27
43
|
|
|
28
44
|
if (!alt) {
|
|
29
|
-
|
|
45
|
+
if (matcher.input !== 'image' || !isWithinSvgJsxElement(node.parent)) {
|
|
46
|
+
message = '画像にはalt属性を指定してください。SVG component の場合、altを属性として受け取れるようにした上で `<svg role="img" aria-label={alt}>` のように指定してください。画像ではない場合、img or image を末尾に持たない名称に変更してください。'
|
|
47
|
+
}
|
|
30
48
|
} else if (alt.value.value === '') {
|
|
31
49
|
message = '画像の情報をテキストにした代替テキスト(`alt`)を設定してください。装飾目的の画像など、alt属性に指定すべき文字がない場合は背景画像にすることを検討してください。'
|
|
32
50
|
}
|
|
@@ -6,6 +6,7 @@ const EXPECTED_NAMES = {
|
|
|
6
6
|
'(t|T)extarea$': 'Textarea$',
|
|
7
7
|
'FieldSet$': 'FieldSet$',
|
|
8
8
|
'ComboBox$': 'ComboBox$',
|
|
9
|
+
'DatePicker$': 'DatePicker$',
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
module.exports = {
|
|
@@ -27,7 +28,7 @@ module.exports = {
|
|
|
27
28
|
return
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
if (!name.match(/((i|I)nput|(t|T)extarea|FieldSet|ComboBox)$/)) {
|
|
31
|
+
if (!name.match(/((i|I)nput|(t|T)extarea|FieldSet|ComboBox|DatePicker)$/)) {
|
|
31
32
|
return
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -46,6 +47,41 @@ module.exports = {
|
|
|
46
47
|
},
|
|
47
48
|
})
|
|
48
49
|
}
|
|
50
|
+
} else if (name.match(/ComboBox$/)) {
|
|
51
|
+
let defaultItem
|
|
52
|
+
let dropdownHelpMessage
|
|
53
|
+
|
|
54
|
+
node.attributes.forEach((a) => {
|
|
55
|
+
switch(a.name?.name) {
|
|
56
|
+
case 'defaultItem':
|
|
57
|
+
defaultItem = a
|
|
58
|
+
break
|
|
59
|
+
case 'dropdownHelpMessage':
|
|
60
|
+
dropdownHelpMessage = a
|
|
61
|
+
break
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
if (defaultItem) {
|
|
66
|
+
context.report({
|
|
67
|
+
node: placeholder,
|
|
68
|
+
messageId: 'a11y-prohibit-input-placeholder',
|
|
69
|
+
data: {
|
|
70
|
+
message: `${name} にはdefaultItemが設定されているため、placeholder属性を閲覧出来ません。削除してください。`,
|
|
71
|
+
},
|
|
72
|
+
})
|
|
73
|
+
} else if (!dropdownHelpMessage) {
|
|
74
|
+
context.report({
|
|
75
|
+
node: placeholder,
|
|
76
|
+
messageId: 'a11y-prohibit-input-placeholder',
|
|
77
|
+
data: {
|
|
78
|
+
message: `${name} にはplaceholder属性は設定せず、以下のいずれか、もしくは組み合わせての対応を検討してください。
|
|
79
|
+
- 選択肢をどんな値で絞り込めるかの説明をしたい場合は dropdownHelpMessage 属性に変更してください。
|
|
80
|
+
- 空の値の説明のためにplaceholderを利用している場合は defaultItem 属性に変更してください。
|
|
81
|
+
- 上記以外の説明を行いたい場合、ヒント用要素を設置してください。(例: '<div><${name} /><Hint>ヒント</Hint></div>')`,
|
|
82
|
+
},
|
|
83
|
+
})
|
|
84
|
+
}
|
|
49
85
|
} else {
|
|
50
86
|
context.report({
|
|
51
87
|
node: placeholder,
|
|
@@ -86,7 +86,8 @@ const fetchTerminalImportName = (filename) => {
|
|
|
86
86
|
const generateRedundantKeywords = ({ args, key, terminalImportName }) => {
|
|
87
87
|
const option = args.option[key] || {}
|
|
88
88
|
const ignoreKeywords = option.ignoreKeywords || DEFAULT_CONFIG[key].IGNORE_KEYWORDS
|
|
89
|
-
const terminalImportKeyword = terminalImportName ? terminalImportName.toLowerCase() : ''
|
|
89
|
+
const terminalImportKeyword = terminalImportName ? terminalImportName.toLowerCase() : ''
|
|
90
|
+
|
|
90
91
|
return args.keywords.reduce((prev, keyword) => {
|
|
91
92
|
if (keyword === terminalImportKeyword || ignoreKeywords.includes(keyword)) {
|
|
92
93
|
return prev
|
|
@@ -100,7 +101,7 @@ const generateRedundantKeywords = ({ args, key, terminalImportName }) => {
|
|
|
100
101
|
}, [])
|
|
101
102
|
}
|
|
102
103
|
const handleReportBetterName = ({
|
|
103
|
-
key,
|
|
104
|
+
key,
|
|
104
105
|
context,
|
|
105
106
|
option,
|
|
106
107
|
filename,
|
|
@@ -170,13 +171,13 @@ const handleReportBetterName = ({
|
|
|
170
171
|
Object.entries(option.betterNames).forEach(([regex, calc]) => {
|
|
171
172
|
if (calc && filename.match(new RegExp(regex))) {
|
|
172
173
|
switch(calc.operator) {
|
|
173
|
-
case '=':
|
|
174
|
+
case '=':
|
|
174
175
|
candidates = calc.names
|
|
175
176
|
break
|
|
176
|
-
case '-':
|
|
177
|
+
case '-':
|
|
177
178
|
candidates = candidates.filter((c) => !calc.names.includes(c))
|
|
178
179
|
break
|
|
179
|
-
case '+':
|
|
180
|
+
case '+':
|
|
180
181
|
candidates = uniq([...candidates, ...calc.names])
|
|
181
182
|
break
|
|
182
183
|
}
|
|
@@ -412,12 +413,15 @@ module.exports = {
|
|
|
412
413
|
let rules = {}
|
|
413
414
|
|
|
414
415
|
const option = context.options[0]
|
|
415
|
-
|
|
416
|
+
let filename = context.getFilename()
|
|
416
417
|
const keywords = uniq((() => {
|
|
417
418
|
const keywordMatcher = filename.match(new RegExp(`${rootPath}/(.+?)$`))
|
|
418
419
|
|
|
419
420
|
if (keywordMatcher) {
|
|
420
421
|
const keywords = keywordMatcher[1].split('/')
|
|
422
|
+
keywords[keywords.length - 1] = keywords[keywords.length - 1].split('.')[0]
|
|
423
|
+
|
|
424
|
+
filename = keywords.join('/')
|
|
421
425
|
|
|
422
426
|
if (keywords[keywords.length - 1] === 'index') {
|
|
423
427
|
keywords.pop()
|
|
@@ -435,7 +439,7 @@ module.exports = {
|
|
|
435
439
|
return []
|
|
436
440
|
})())
|
|
437
441
|
|
|
438
|
-
const args = {
|
|
442
|
+
const args = {
|
|
439
443
|
context,
|
|
440
444
|
option,
|
|
441
445
|
filename,
|
|
@@ -30,6 +30,7 @@ ruleTester.run('a11y-image-has-alt-attribute', rule, {
|
|
|
30
30
|
{ code: '<HogeImg alt="hoge" />' },
|
|
31
31
|
{ code: '<HogeImage alt="hoge" />' },
|
|
32
32
|
{ code: '<HogeIcon />' },
|
|
33
|
+
{ code: '<svg><image /></svg>' },
|
|
33
34
|
],
|
|
34
35
|
invalid: [
|
|
35
36
|
{ code: `import hoge from 'styled-components'`, errors: [ { message: "styled-components をimportする際は、名称が`styled` となるようにしてください。例: `import styled from 'styled-components'`" } ] },
|
|
@@ -40,5 +41,6 @@ ruleTester.run('a11y-image-has-alt-attribute', rule, {
|
|
|
40
41
|
{ code: 'const Hoge = styled(Image)``', errors: [ { message: `Hogeを正規表現 "/Image$/" がmatchする名称に変更してください` } ] },
|
|
41
42
|
{ code: '<img />', errors: [ { message: '画像にはalt属性を指定してください。SVG component の場合、altを属性として受け取れるようにした上で `<svg role="img" aria-label={alt}>` のように指定してください。画像ではない場合、img or image を末尾に持たない名称に変更してください。' } ] },
|
|
42
43
|
{ code: '<HogeImage alt="" />', errors: [ { message: '画像の情報をテキストにした代替テキスト(`alt`)を設定してください。装飾目的の画像など、alt属性に指定すべき文字がない場合は背景画像にすることを検討してください。' } ] },
|
|
44
|
+
{ code: '<hoge><image /></hoge>', errors: [ { message: '画像にはalt属性を指定してください。SVG component の場合、altを属性として受け取れるようにした上で `<svg role="img" aria-label={alt}>` のように指定してください。画像ではない場合、img or image を末尾に持たない名称に変更してください。' } ] },
|
|
43
45
|
]
|
|
44
46
|
})
|
|
@@ -35,8 +35,12 @@ ruleTester.run('a11y-prohibit-input-placeholder', rule, {
|
|
|
35
35
|
{ code: `<FugaFieldSet />` },
|
|
36
36
|
{ code: `<CustomComboBox />` },
|
|
37
37
|
{ code: `<SearchInput />` },
|
|
38
|
+
{ code: `<DatePicker />` },
|
|
38
39
|
{ code: `<CustomSearchInput tooltipMessage="hoge" />` },
|
|
39
40
|
{ code: `<CustomSearchInput tooltipMessage="hoge" placeholder="fuga" />` },
|
|
41
|
+
{ code: `<ComboBox defaultItem={items[0]} />` },
|
|
42
|
+
{ code: `<ComboBox defaultItem={items[0]} dropdownHelpMessage="fuga" />` },
|
|
43
|
+
{ code: `<ComboBox placeholder="hoge" dropdownHelpMessage="fuga" />` },
|
|
40
44
|
],
|
|
41
45
|
invalid: [
|
|
42
46
|
{ code: `import hoge from 'styled-components'`, errors: [ { message: "styled-components をimportする際は、名称が`styled` となるようにしてください。例: `import styled from 'styled-components'`" } ] },
|
|
@@ -58,7 +62,12 @@ ruleTester.run('a11y-prohibit-input-placeholder', rule, {
|
|
|
58
62
|
{ code: `<StyledInput placeholder={any} />`, errors: [ { message: `StyledInput にはplaceholder属性は設定せず、別途ヒント用要素の利用を検討してください。(例: '<div><StyledInput /><Hint>ヒント</Hint></div>')` } ] },
|
|
59
63
|
{ code: `<HogeTextarea placeholder="any" />`, errors: [ { message: `HogeTextarea にはplaceholder属性は設定せず、別途ヒント用要素の利用を検討してください。(例: '<div><HogeTextarea /><Hint>ヒント</Hint></div>')` } ] },
|
|
60
64
|
{ code: `<HogeFieldSet placeholder="any" />`, errors: [ { message: `HogeFieldSet にはplaceholder属性は設定せず、別途ヒント用要素の利用を検討してください。(例: '<div><HogeFieldSet /><Hint>ヒント</Hint></div>')` } ] },
|
|
61
|
-
{ code: `<
|
|
65
|
+
{ code: `<HogeDatePicker placeholder="any" />`, errors: [ { message: `HogeDatePicker にはplaceholder属性は設定せず、別途ヒント用要素の利用を検討してください。(例: '<div><HogeDatePicker /><Hint>ヒント</Hint></div>')` } ] },
|
|
66
|
+
{ code: `<HogeComboBox placeholder="any" />`, errors: [ { message: `HogeComboBox にはplaceholder属性は設定せず、以下のいずれか、もしくは組み合わせての対応を検討してください。
|
|
67
|
+
- 選択肢をどんな値で絞り込めるかの説明をしたい場合は dropdownHelpMessage 属性に変更してください。
|
|
68
|
+
- 空の値の説明のためにplaceholderを利用している場合は defaultItem 属性に変更してください。
|
|
69
|
+
- 上記以外の説明を行いたい場合、ヒント用要素を設置してください。(例: '<div><HogeComboBox /><Hint>ヒント</Hint></div>')` } ] },
|
|
62
70
|
{ code: `<SearchInput placeholder="any" />`, errors: [ { message: `SearchInput にはplaceholder属性を単独で利用せず、tooltipMessageオプションのみ、もしくはplaceholderとtooltipMessageの併用を検討してください。 (例: '<SearchInput tooltipMessage="ヒント" />', '<SearchInput tooltipMessage={hint} placeholder={hint} />')` } ] },
|
|
71
|
+
{ code: `<ComboBox defaultItem={items[0]} placeholder="any" />`, errors: [ { message: `ComboBox にはdefaultItemが設定されているため、placeholder属性を閲覧出来ません。削除してください。` } ] },
|
|
63
72
|
]
|
|
64
73
|
})
|