eslint-plugin-smarthr 0.3.27 → 0.4.1
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 +24 -0
- package/README.md +1 -1
- package/package.json +1 -2
- package/rules/a11y-anchor-has-href-attribute/README.md +3 -1
- package/rules/a11y-input-in-form-control/index.js +17 -4
- package/rules/a11y-numbered-text-within-ol/README.md +82 -0
- package/rules/a11y-numbered-text-within-ol/index.js +134 -0
- package/test/a11y-input-in-form-control.js +15 -5
- package/test/a11y-numbered-text-within-ol.js +111 -0
- package/rules/redundant-name/README.md +0 -97
- package/rules/redundant-name/index.js +0 -505
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
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.4.1](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.4.0...v0.4.1) (2024-02-21)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* a11y-numbered-text-within-olを追加する ([#105](https://github.com/kufu/eslint-plugin-smarthr/issues/105)) ([167b92f](https://github.com/kufu/eslint-plugin-smarthr/commit/167b92f0f29db8ee9a446d25e09e04a7b11ce340))
|
|
11
|
+
* ComboBoxなどのinputAttributesでtitle属性が指定された場合、擬似的にラベルが付いていると判定するように修正 ([#113](https://github.com/kufu/eslint-plugin-smarthr/issues/113)) ([5f3b594](https://github.com/kufu/eslint-plugin-smarthr/commit/5f3b5943ec64a18d094a9c66627ad1db2bbabe08))
|
|
12
|
+
|
|
13
|
+
## [0.4.0](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.3.27...v0.4.0) (2024-02-05)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### ⚠ BREAKING CHANGES
|
|
17
|
+
|
|
18
|
+
* 利用者がいなくなったsmarthr/redundant-nameを削除する (#111)
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* a11y-input-in-form-controlでlabelが設定されている可能性が高いRadio, Checkboxの複数形コンポーネントを正しく判定できるようにする ([#112](https://github.com/kufu/eslint-plugin-smarthr/issues/112)) ([77ee8f4](https://github.com/kufu/eslint-plugin-smarthr/commit/77ee8f4cac883eb0a198875305a416f0172a584e))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Bug Fixes
|
|
26
|
+
|
|
27
|
+
* 利用者がいなくなったsmarthr/redundant-nameを削除する ([#111](https://github.com/kufu/eslint-plugin-smarthr/issues/111)) ([2bc1011](https://github.com/kufu/eslint-plugin-smarthr/commit/2bc10118cc0a18366300c3816f091060d2a0677d))
|
|
28
|
+
|
|
5
29
|
### [0.3.27](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.3.26...v0.3.27) (2024-01-28)
|
|
6
30
|
|
|
7
31
|
|
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
- [a11y-image-has-alt-attribute](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-image-has-alt-attribute)
|
|
8
8
|
- [a11y-input-has-name-attribute](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-input-has-name-attribute)
|
|
9
9
|
- [a11y-input-in-form-control](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-input-in-form-control)
|
|
10
|
+
- [a11y-numbered-text-within-ol](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-numbered-text-within-ol)
|
|
10
11
|
- [a11y-prohibit-input-placeholder](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-prohibit-input-placeholder)
|
|
11
12
|
- [a11y-prohibit-useless-sectioning-fragment](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-prohibit-useless-sectioning-fragment)
|
|
12
13
|
- [a11y-trigger-has-button](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/a11y-trigger-has-button)
|
|
@@ -20,7 +21,6 @@
|
|
|
20
21
|
- [prohibit-file-name](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/prohibit-file-name)
|
|
21
22
|
- [prohibit-import](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/prohibit-import)
|
|
22
23
|
- [prohibit-path-within-template-literal](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/prohibit-path-within-template-literal)
|
|
23
|
-
- [redundant-name](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/redundant-name)
|
|
24
24
|
- [require-barrel-import](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/require-barrel-import)
|
|
25
25
|
- [require-declaration](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/require-declaration)
|
|
26
26
|
- [require-export](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/require-export)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-smarthr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"author": "SmartHR",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "A sharable ESLint plugin for SmartHR",
|
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
"url": "https://github.com/kufu/eslint-plugin-smarthr/issues"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"inflected": "^2.1.0",
|
|
26
25
|
"json5": "^2.2.0"
|
|
27
26
|
},
|
|
28
27
|
"devDependencies": {
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
- URL遷移を行う場合、hrefが設定されていないとキーボード操作やコンテキストメニューからの遷移ができなくなります
|
|
7
7
|
- これらの操作は href属性を参照します
|
|
8
8
|
- 無効化されたリンクであることを表したい場合 `href={undefined}` を設定してください
|
|
9
|
-
- checkTypeオプションに 'smart' を指定することで spread attributeが設定されている場合はcorrect
|
|
9
|
+
- checkTypeオプションに 'smart' を指定することで spread attributeが設定されている場合はcorrectに出来ます
|
|
10
|
+
- react-router-dom packageを利用している場合、a要素にto属性が指定されている場合、href属性が指定されているものとして許容します
|
|
11
|
+
- next/link コンポーネント直下のa要素にhref属性が指定されていないことを許容します
|
|
10
12
|
|
|
11
13
|
## rules
|
|
12
14
|
|
|
@@ -2,8 +2,11 @@ const { generateTagFormatter } = require('../../libs/format_styled_components')
|
|
|
2
2
|
|
|
3
3
|
const EXPECTED_LABELED_INPUT_NAMES = {
|
|
4
4
|
'RadioButton$': '(RadioButton)$',
|
|
5
|
+
'RadioButtons$': '(RadioButtons)$',
|
|
5
6
|
'RadioButtonPanel$': '(RadioButtonPanel)$',
|
|
7
|
+
'RadioButtonPanels$': '(RadioButtonPanels)$',
|
|
6
8
|
'Check(B|b)ox$': '(CheckBox)$',
|
|
9
|
+
'Check(B|b)ox(e)?s$': '(CheckBoxes)$',
|
|
7
10
|
}
|
|
8
11
|
const EXPECTED_INPUT_NAMES = {
|
|
9
12
|
'(I|^i)nput$': '(Input)$',
|
|
@@ -43,9 +46,9 @@ const FORM_CONTROL_INPUTS_REGEX = new RegExp(`(${Object.keys(EXPECTED_INPUT_NAME
|
|
|
43
46
|
const LABELED_INPUTS_REGEX = new RegExp(`(${Object.keys(EXPECTED_LABELED_INPUT_NAMES).join('|')})`)
|
|
44
47
|
const SEARCH_INPUT_REGEX = /SearchInput$/
|
|
45
48
|
const INPUT_REGEX = /(i|I)nput$/
|
|
46
|
-
const RADIO_BUTTONS_REGEX = /RadioButton(Panel)?$/
|
|
47
|
-
const CHECKBOX_REGEX = /Check(B|b)ox?$/
|
|
48
|
-
const SELECT_REGEX = /(S|s)elect?$/
|
|
49
|
+
const RADIO_BUTTONS_REGEX = /RadioButton(Panel)?(s)?$/
|
|
50
|
+
const CHECKBOX_REGEX = /Check(B|b)ox(s|es)?$/
|
|
51
|
+
const SELECT_REGEX = /(S|s)elect(s)?$/
|
|
49
52
|
const FROM_CONTROLS_REGEX = new RegExp(`(${Object.keys(EXPECTED_FORM_CONTROL_NAMES).join('|')})`)
|
|
50
53
|
const FORM_CONTROL_REGEX = /(Form(Control|Group))$/
|
|
51
54
|
const FIELDSET_REGEX = /Fieldset$/
|
|
@@ -54,11 +57,13 @@ const SECTIONING_REGEX = /(((A|^a)(rticle|side))|(N|^n)av|(S|^s)ection|^Sectioni
|
|
|
54
57
|
const BARE_SECTIONING_TAG_REGEX = /^(article|aside|nav|section)$/
|
|
55
58
|
const LAYOUT_COMPONENT_REGEX = /((C(ent|lust)er)|Reel|Sidebar|Stack)$/
|
|
56
59
|
const AS_REGEX = /^(as|forwardedAs)$/
|
|
60
|
+
const SUFFIX_S_REGEX = /s$/
|
|
57
61
|
|
|
58
62
|
const IGNORE_INPUT_CHECK_PARENT_TYPE = /^(Program|ExportNamedDeclaration)$/
|
|
59
63
|
|
|
60
64
|
const findRoleGroup = (a) => a.name?.name === 'role' && a.value.value === 'group'
|
|
61
65
|
const findAsSectioning = (a) => a.name?.name.match(AS_REGEX) && a.value.value.match(BARE_SECTIONING_TAG_REGEX)
|
|
66
|
+
const findTitle = (i) => i.key.name === 'title'
|
|
62
67
|
|
|
63
68
|
const SCHEMA = [
|
|
64
69
|
{
|
|
@@ -117,6 +122,12 @@ module.exports = {
|
|
|
117
122
|
case 'title':
|
|
118
123
|
isPseudoLabel = true
|
|
119
124
|
break
|
|
125
|
+
case 'inputAttributes': {
|
|
126
|
+
if (!isPseudoLabel && i.value.expression.type === 'ObjectExpression' && i.value.expression.properties.some(findTitle)) {
|
|
127
|
+
isPseudoLabel = true
|
|
128
|
+
}
|
|
129
|
+
break
|
|
130
|
+
}
|
|
120
131
|
case 'type':
|
|
121
132
|
switch (i.value.value) {
|
|
122
133
|
case 'radio':
|
|
@@ -132,6 +143,8 @@ module.exports = {
|
|
|
132
143
|
}
|
|
133
144
|
}
|
|
134
145
|
}
|
|
146
|
+
|
|
147
|
+
const isPreMultiple = isAdditionalMultiInput || isFormControlInput && nodeName.match(SUFFIX_S_REGEX)
|
|
135
148
|
const isRadio = (isPureInput && isTypeRadio) || nodeName.match(RADIO_BUTTONS_REGEX);
|
|
136
149
|
const isCheckbox = !isRadio && (isPureInput && isTypeCheck || nodeName.match(CHECKBOX_REGEX));
|
|
137
150
|
|
|
@@ -154,7 +167,7 @@ module.exports = {
|
|
|
154
167
|
}
|
|
155
168
|
}
|
|
156
169
|
|
|
157
|
-
const isMultiInput =
|
|
170
|
+
const isMultiInput = isPreMultiple || hit || isInMap
|
|
158
171
|
const matcherFormControl = name.match(FORM_CONTROL_REGEX)
|
|
159
172
|
|
|
160
173
|
if (matcherFormControl) {
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# smarthr/a11y-numbered-text-within-ol
|
|
2
|
+
|
|
3
|
+
- "1. hoge", "2. fuga" ... のように連番のテキストをもつコンポーネントはol要素でマークアップすることを促すルールです
|
|
4
|
+
- ol要素でマークアップすることで連番テキストをもつ要素同士の関係、順番に意味があることを適切に示すことが出来ます
|
|
5
|
+
|
|
6
|
+
## rules
|
|
7
|
+
|
|
8
|
+
```js
|
|
9
|
+
{
|
|
10
|
+
rules: {
|
|
11
|
+
'smarthr/a11y-numbered-text-within-ol': 'error', // 'warn', 'off',
|
|
12
|
+
},
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## ❌ Incorrect
|
|
17
|
+
|
|
18
|
+
```jsx
|
|
19
|
+
// ol要素で囲まれていないためNG
|
|
20
|
+
<Any>1. hoge</Any>
|
|
21
|
+
<Any>2. fuga</Any>
|
|
22
|
+
|
|
23
|
+
// 属性でも同様にチェックする
|
|
24
|
+
<Any title="1. hoge" />
|
|
25
|
+
<Any title="2. fuga" />
|
|
26
|
+
|
|
27
|
+
// ol要素内で連番を設定しているとNG
|
|
28
|
+
<OrderedList>
|
|
29
|
+
<li>1. hoge</li>
|
|
30
|
+
<li>2. fuga</li>
|
|
31
|
+
</OrderedList>
|
|
32
|
+
|
|
33
|
+
// 同一のol要素で囲まれていないためNG
|
|
34
|
+
<OrderedList>
|
|
35
|
+
<li>hoge</li>
|
|
36
|
+
</OrderedList>
|
|
37
|
+
<OrderedList>
|
|
38
|
+
<li>fuga</li>
|
|
39
|
+
</OrderedList>>
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## ✅ Correct
|
|
44
|
+
|
|
45
|
+
```jsx
|
|
46
|
+
<ol>
|
|
47
|
+
<li>hoge</li>
|
|
48
|
+
<li>fuga</li>
|
|
49
|
+
</ol>
|
|
50
|
+
|
|
51
|
+
<OrderedList>
|
|
52
|
+
<Any title="hoge" />
|
|
53
|
+
<Any title="fuga" />
|
|
54
|
+
</OrderedList>
|
|
55
|
+
|
|
56
|
+
// デフォルトの連番からフォーマット、スタイルを変更したい場合
|
|
57
|
+
// counter-reset + counter-increment で表現する
|
|
58
|
+
// 参考: [MDN CSS カウンターの使用](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_counter_styles/Using_CSS_counters)
|
|
59
|
+
<OrderedList>
|
|
60
|
+
<li>
|
|
61
|
+
<NumberedHeading>hoge</NumberedHeading>
|
|
62
|
+
<Any />
|
|
63
|
+
</li>
|
|
64
|
+
<li>
|
|
65
|
+
<NumberedHeading>fuga</NumberedHeading>
|
|
66
|
+
<Any />
|
|
67
|
+
</li>
|
|
68
|
+
</OrderedList>
|
|
69
|
+
|
|
70
|
+
...
|
|
71
|
+
|
|
72
|
+
const OrderedList = styled.ol`
|
|
73
|
+
list-style: none; // デフォルトのstyleを消す
|
|
74
|
+
counter-reset: hoge; // カウンターの名称。わかりやすいものなら何でもOK
|
|
75
|
+
`
|
|
76
|
+
const NumberedHeading = styled(Heading)`
|
|
77
|
+
&::before {
|
|
78
|
+
counter-increment: hoge; // hogeカウンターを+1する
|
|
79
|
+
content: "No " counter(hoge) ": "; // 表示される連番のフォーマット
|
|
80
|
+
}
|
|
81
|
+
`
|
|
82
|
+
```
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
const { generateTagFormatter } = require('../../libs/format_styled_components')
|
|
2
|
+
|
|
3
|
+
const EXPECTED_NAMES = {
|
|
4
|
+
'(Ordered(.*)List|^ol)$': '(Ordered(.*)List)$',
|
|
5
|
+
'(S|s)elect$': '(Select)$',
|
|
6
|
+
}
|
|
7
|
+
const UNEXPECTED_NAMES = EXPECTED_NAMES
|
|
8
|
+
|
|
9
|
+
const NUMBERED_TEXT_REGEX = /^[\s\n]*(([1-9])([^1-9]{2})[^\s\n]*)/
|
|
10
|
+
const ORDERED_LIST_REGEX = /(Ordered(.*)List|^ol)$/
|
|
11
|
+
const SELECT_REGEX = /(S|s)elect$/
|
|
12
|
+
|
|
13
|
+
const searchOrderedList = (node) => {
|
|
14
|
+
if (node.type === 'JSXElement' && node.openingElement.name?.name) {
|
|
15
|
+
const name = node.openingElement.name.name
|
|
16
|
+
|
|
17
|
+
if (name.match(ORDERED_LIST_REGEX)) {
|
|
18
|
+
return node.openingElement
|
|
19
|
+
} else if (name.match(SELECT_REGEX)) {
|
|
20
|
+
// HINT: select要素の場合、optionのラベルに連番がついている場合がありえるのでignoreする
|
|
21
|
+
// 通常と処理を分けるためnullではなく0を返す
|
|
22
|
+
return 0
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (node.type === 'Program') {
|
|
27
|
+
return null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return searchOrderedList(node.parent)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const checkNumberedTextInOl = (result, node, context) => {
|
|
34
|
+
if (result) {
|
|
35
|
+
context.report({
|
|
36
|
+
node,
|
|
37
|
+
message: `${result.name.name} 内で連番がテキストとして記述されています。連番はol要素で表現できるため、削除してください。
|
|
38
|
+
- ol要素のデフォルトで表示される連番のフォーマット、スタイルから変更したい場合、counter-reset と counter-increment を利用してください
|
|
39
|
+
- 参考: [MDN CSS カウンターの使用](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_counter_styles/Using_CSS_counters)`,
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const renderTag = (node) => `\`${node.name.name}="${node.value.value}"\``
|
|
45
|
+
const renderNode = (node, matcher) => node.type === 'JSXText' ? `\`${matcher[1]}\`` : renderTag(node)
|
|
46
|
+
|
|
47
|
+
const SCHEMA = []
|
|
48
|
+
|
|
49
|
+
module.exports = {
|
|
50
|
+
meta: {
|
|
51
|
+
type: 'problem',
|
|
52
|
+
schema: SCHEMA,
|
|
53
|
+
},
|
|
54
|
+
create(context) {
|
|
55
|
+
let firstNumber = 0
|
|
56
|
+
let firstNumberedNode = null
|
|
57
|
+
let firstNumberedMatcher = null
|
|
58
|
+
|
|
59
|
+
function checker(node, matcher) {
|
|
60
|
+
if (matcher) {
|
|
61
|
+
const result = searchOrderedList(node)
|
|
62
|
+
|
|
63
|
+
if (result !== 0) {
|
|
64
|
+
checkNumberedTextInOl(result, node, context)
|
|
65
|
+
|
|
66
|
+
const nowNumber = matcher[2] * 1
|
|
67
|
+
|
|
68
|
+
if (firstNumberedNode && nowNumber !== firstNumber) {
|
|
69
|
+
if (nowNumber === firstNumber + 1) {
|
|
70
|
+
const resultFirst = searchOrderedList(firstNumberedNode)
|
|
71
|
+
|
|
72
|
+
if (!resultFirst) {
|
|
73
|
+
if (!result) {
|
|
74
|
+
context.report({
|
|
75
|
+
node: firstNumberedNode,
|
|
76
|
+
message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
|
|
77
|
+
- ${renderNode(firstNumberedNode, firstNumberedMatcher)} と ${renderNode(node, matcher)} が同じol要素内に存在するように修正してください
|
|
78
|
+
- 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります`,
|
|
79
|
+
})
|
|
80
|
+
} else {
|
|
81
|
+
context.report({
|
|
82
|
+
node: firstNumberedNode,
|
|
83
|
+
message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
|
|
84
|
+
- ${renderNode(firstNumberedNode, firstNumberedMatcher)} が ${renderNode(node, matcher)} を囲んでいるol要素内(<${result.name.name}>)に存在するように修正してください
|
|
85
|
+
- 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol要素内(<${result.name.name}>)に存在する必要があります`,
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
if (!result) {
|
|
90
|
+
context.report({
|
|
91
|
+
node,
|
|
92
|
+
message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
|
|
93
|
+
- ${renderNode(node, matcher)} が ${renderNode(firstNumberedNode, firstNumberedMatcher)} を囲んでいるol要素内(<${resultFirst.name.name}>)に存在するように修正してください
|
|
94
|
+
- 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol要素内(<${resultFirst.name.name}>)に存在する必要があります`,
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
firstNumberedNode = null
|
|
98
|
+
} else if (resultFirst !== result) {
|
|
99
|
+
context.report({
|
|
100
|
+
node,
|
|
101
|
+
message: `連番を含むテキストが同一のol要素でマークアップされていません。同一のol要素でマークアップすることでリスト内の要素関連性を正しく表せるためマークアップの修正を行ってください。
|
|
102
|
+
- ${renderNode(firstNumberedNode, firstNumberedMatcher)} と ${renderNode(node, matcher)} が同じol要素内に存在するように修正してください
|
|
103
|
+
- 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります`,
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
firstNumber = nowNumber
|
|
110
|
+
firstNumberedNode = node
|
|
111
|
+
firstNumberedMatcher = matcher
|
|
112
|
+
} else if (!firstNumberedNode || nowNumber === firstNumber) {
|
|
113
|
+
firstNumber = nowNumber
|
|
114
|
+
firstNumberedNode = node
|
|
115
|
+
firstNumberedMatcher = matcher
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
...generateTagFormatter({ context, EXPECTED_NAMES, UNEXPECTED_NAMES }),
|
|
123
|
+
JSXAttribute: (node) => {
|
|
124
|
+
if (node.value?.value) {
|
|
125
|
+
checker(node, node.value.value.match(NUMBERED_TEXT_REGEX))
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
JSXText: (node) => {
|
|
129
|
+
checker(node, node.value.match(NUMBERED_TEXT_REGEX))
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
}
|
|
134
|
+
module.exports.schema = SCHEMA
|
|
@@ -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$|RadioButtonPanel$|Check(B|b)ox$)/とマッチするように修正してください
|
|
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$)/とマッチするように修正してください
|
|
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$|RadioButtonPanel$|Check(B|b)ox$)/とマッチするように修正してください
|
|
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$)/とマッチするように修正してください
|
|
25
25
|
- 上記のいずれの方法も適切ではない場合、${name}のtitle属性に "どんな値を選択すれば良いのか" の説明を設定してください
|
|
26
26
|
- 例: <${name} title="検索対象を選択してください" />`
|
|
27
27
|
const invalidPureCheckboxInFormControl = (name) => `HogeFormControl が ${name} を含んでいます。smarthr-ui/FormControl を smarthr-ui/Fieldset に変更し、正しくグルーピングされるように修正してください。
|
|
@@ -42,7 +42,7 @@ const noLabeledInputInFieldset = (name) => `HogeFieldset が ラベルを持た
|
|
|
42
42
|
- 方法1: HogeFieldset を smarthr-ui/FormControl、もしくはそれを拡張したコンポーネントに変更してください
|
|
43
43
|
- 方法2: ${name} がlabel要素を含むコンポーネントである場合、名称を/(Form(Control|Group))$/にマッチするものに変更してください
|
|
44
44
|
- smarthr-ui/FormControl、smarthr-ui/FormGroup はlabel要素を内包しています
|
|
45
|
-
- 方法3: ${name} がRadioButton、もしくはCheckboxを表すコンポーネントの場合、名称を/(RadioButton$|RadioButtonPanel$|Check(B|b)ox$)/にマッチするものに変更してください
|
|
45
|
+
- 方法3: ${name} がRadioButton、もしくはCheckboxを表すコンポーネントの場合、名称を/(RadioButton$|RadioButtons$|RadioButtonPanel$|RadioButtonPanels$|Check(B|b)ox$|Check(B|b)ox(e)?s$)/にマッチするものに変更してください
|
|
46
46
|
- smarthr-ui/RadioButton、smarthr-ui/RadioButtonPanel、smarthr-ui/Checkbox はlabel要素を内包しています
|
|
47
47
|
- 方法4: HogeFieldset が smarthr-ui/Fieldset、もしくはそれを拡張しているコンポーネントではない場合、名称を /Fieldset$/ にマッチしないものに変更してください
|
|
48
48
|
- 方法5: 別途label要素が存在し、それらと紐づけたい場合はlabel要素のhtmlFor属性、${name}のid属性に同じ文字列を指定してください。この文字列はhtml内で一意である必要があります
|
|
@@ -52,7 +52,7 @@ const noLabeledInputInFieldsetWithSelect = (name) => `HogeFieldset が ラベル
|
|
|
52
52
|
- 方法1: HogeFieldset を smarthr-ui/FormControl、もしくはそれを拡張したコンポーネントに変更してください
|
|
53
53
|
- 方法2: ${name} がlabel要素を含むコンポーネントである場合、名称を/(Form(Control|Group))$/にマッチするものに変更してください
|
|
54
54
|
- smarthr-ui/FormControl、smarthr-ui/FormGroup はlabel要素を内包しています
|
|
55
|
-
- 方法3: ${name} がRadioButton、もしくはCheckboxを表すコンポーネントの場合、名称を/(RadioButton$|RadioButtonPanel$|Check(B|b)ox$)/にマッチするものに変更してください
|
|
55
|
+
- 方法3: ${name} がRadioButton、もしくはCheckboxを表すコンポーネントの場合、名称を/(RadioButton$|RadioButtons$|RadioButtonPanel$|RadioButtonPanels$|Check(B|b)ox$|Check(B|b)ox(e)?s$)/にマッチするものに変更してください
|
|
56
56
|
- smarthr-ui/RadioButton、smarthr-ui/RadioButtonPanel、smarthr-ui/Checkbox はlabel要素を内包しています
|
|
57
57
|
- 方法4: HogeFieldset が smarthr-ui/Fieldset、もしくはそれを拡張しているコンポーネントではない場合、名称を /Fieldset$/ にマッチしないものに変更してください
|
|
58
58
|
- 方法5: 別途label要素が存在し、それらと紐づけたい場合はlabel要素のhtmlFor属性、${name}のid属性に同じ文字列を指定してください。この文字列はhtml内で一意である必要があります
|
|
@@ -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$|RadioButtonPanel$|Check(B|b)ox$)/、もしくは/((FormGroup)$|(FormControl)$|((F|^f)ieldset)$)/にマッチする名称に変更してください`
|
|
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)$)/にマッチする名称に変更してください`
|
|
80
80
|
|
|
81
81
|
ruleTester.run('a11y-input-in-form-control', rule, {
|
|
82
82
|
valid: [
|
|
@@ -129,6 +129,12 @@ ruleTester.run('a11y-input-in-form-control', rule, {
|
|
|
129
129
|
{ code: '<HogeFieldset><HogeCheckBox /><HogeInput title="any" /></HogeFieldset>' },
|
|
130
130
|
{ code: '<FugaSection><HogeInput title="any" /></FugaSection>' },
|
|
131
131
|
{ code: '<HogeTextarea title="any" />' },
|
|
132
|
+
{ code: '<HogeComboBox inputAttributes={{ title: "any" }} />' },
|
|
133
|
+
{ code: '<HogeComboBox inputAttributes={{ title }} />' },
|
|
134
|
+
{ code: '<Fieldset><HogeRadioButtons /></Fieldset>' },
|
|
135
|
+
{ code: '<Fieldset><HogeRadioButtonPanels /></Fieldset>' },
|
|
136
|
+
{ code: '<Fieldset><HogeCheckBoxs /></Fieldset>' },
|
|
137
|
+
{ code: '<Fieldset><HogeCheckBoxes /></Fieldset>' },
|
|
132
138
|
],
|
|
133
139
|
invalid: [
|
|
134
140
|
{ code: `import hoge from 'styled-components'`, errors: [ { message: `styled-components をimportする際は、名称が"styled" となるようにしてください。例: "import styled from 'styled-components'"` } ] },
|
|
@@ -150,6 +156,7 @@ ruleTester.run('a11y-input-in-form-control', rule, {
|
|
|
150
156
|
{ code: '<HogeSelect />', errors: [ { message: noLabeledSelect('HogeSelect') } ] },
|
|
151
157
|
{ code: '<HogeInputFile />', errors: [ { message: noLabeledInput('HogeInputFile') } ] },
|
|
152
158
|
{ code: '<HogeComboBox />', errors: [ { message: noLabeledInput('HogeComboBox') } ] },
|
|
159
|
+
{ code: '<HogeComboBox inputAttributes={{ any }} />', errors: [ { message: noLabeledInput('HogeComboBox') } ] },
|
|
153
160
|
{ code: '<HogeDatePicker />', errors: [ { message: noLabeledInput('HogeDatePicker') } ] },
|
|
154
161
|
{ code: '<HogeFormControl><Input type="checkbox" /><Input type="checkbox" /></HogeFormControl>', errors: [ { message: invalidPureCheckboxInFormControl('Input') } ] },
|
|
155
162
|
{ code: '<HogeFormControl><HogeCheckBox /><Input /></HogeFormControl>', errors: [ { message: invalidMultiInputsInFormControl() } ] },
|
|
@@ -169,5 +176,8 @@ ruleTester.run('a11y-input-in-form-control', rule, {
|
|
|
169
176
|
{ code: '<FormControl><HogeFieldset /></FormControl>', errors: [ { message: invalidChildreninFormControl('HogeFieldset') } ] },
|
|
170
177
|
{ code: '<FormControl><HogeFormControl /></FormControl>', errors: [ { message: invalidChildreninFormControl('HogeFormControl') } ] },
|
|
171
178
|
{ code: '<HogeFormControl role="group"><HogeInput /></HogeFormControl>', errors: [ { message: requireMultiInputInFormControlWithRoleGroup() } ] },
|
|
179
|
+
{ code: '<HogeFormControl><HogeRadioButtons /></HogeFormControl>', errors: [ { message: invalidRadioInFormControl('HogeRadioButtons') } ] },
|
|
180
|
+
{ code: '<HogeFormControl><HogeCheckBoxs /></HogeFormControl>', errors: [ { message: invalidMultiInputsInFormControl() } ] },
|
|
181
|
+
{ code: '<HogeFormControl><HogeCheckBoxes /></HogeFormControl>', errors: [ { message: invalidMultiInputsInFormControl() } ] },
|
|
172
182
|
]
|
|
173
183
|
})
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
const rule = require('../rules/a11y-numbered-text-within-ol')
|
|
2
|
+
const RuleTester = require('eslint').RuleTester
|
|
3
|
+
|
|
4
|
+
const ruleTester = new RuleTester({
|
|
5
|
+
parserOptions: {
|
|
6
|
+
ecmaVersion: 2018,
|
|
7
|
+
ecmaFeatures: {
|
|
8
|
+
experimentalObjectRestSpread: true,
|
|
9
|
+
jsx: true,
|
|
10
|
+
},
|
|
11
|
+
sourceType: 'module',
|
|
12
|
+
},
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
ruleTester.run('a11y-numbered-text-within-ol', rule, {
|
|
16
|
+
valid: [
|
|
17
|
+
{ code: `const HogeOrderedFugaList = styled.ol` },
|
|
18
|
+
{ code: `const HogeOrderedFugaList = styled(HogeOrderedList)` },
|
|
19
|
+
{ code: `<p>1: hoge</p>` },
|
|
20
|
+
{ code: `<p title="2. hoge" />` },
|
|
21
|
+
{ code: `<><p>2: hoge</p><p>1: hoge</p></>` },
|
|
22
|
+
{ code: `<ol><li>abc</li><li>def</li></ol>` },
|
|
23
|
+
{ code: `<OrderedList><li>abc</li><li>def</li></OrderedList>` },
|
|
24
|
+
{ code: `<Select><optgroup><option value="hoge">1. hoge</option><option value="fuga">2. fuga</option></optgroup></Select>` },
|
|
25
|
+
],
|
|
26
|
+
invalid: [
|
|
27
|
+
{ code: `<><p>1: hoge</p><p>2: hoge</p></>`, errors: [ { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
|
|
28
|
+
- \`1: hoge\` と \`2: hoge\` が同じol要素内に存在するように修正してください
|
|
29
|
+
- 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` } ] },
|
|
30
|
+
{ code: `
|
|
31
|
+
<>
|
|
32
|
+
<div>
|
|
33
|
+
1. abc
|
|
34
|
+
</div>
|
|
35
|
+
<p>
|
|
36
|
+
2. def
|
|
37
|
+
</p>
|
|
38
|
+
</>`, errors: [ { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
|
|
39
|
+
- \`1. abc\` と \`2. def\` が同じol要素内に存在するように修正してください
|
|
40
|
+
- 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` } ] },
|
|
41
|
+
{ code: `<><p>2: hoge</p><p>1: hoge</p><p>2: hoge</p></>`, errors: [ { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
|
|
42
|
+
- \`1: hoge\` と \`2: hoge\` が同じol要素内に存在するように修正してください
|
|
43
|
+
- 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` } ] },
|
|
44
|
+
{ code: `<><p>1: hoge</p><ol><li>2: hoge</li></ol></>`, errors: [ { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
|
|
45
|
+
- \`1: hoge\` が \`2: hoge\` を囲んでいるol要素内(<ol>)に存在するように修正してください
|
|
46
|
+
- 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol要素内(<ol>)に存在する必要があります` }, { message: `ol 内で連番がテキストとして記述されています。連番はol要素で表現できるため、削除してください。
|
|
47
|
+
- ol要素のデフォルトで表示される連番のフォーマット、スタイルから変更したい場合、counter-reset と counter-increment を利用してください
|
|
48
|
+
- 参考: [MDN CSS カウンターの使用](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_counter_styles/Using_CSS_counters)` } ] },
|
|
49
|
+
{ code: `<><p>1: hoge</p><p title="2. hoge" /></>`, errors: [ { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
|
|
50
|
+
- \`1: hoge\` と \`title="2. hoge"\` が同じol要素内に存在するように修正してください
|
|
51
|
+
- 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` } ] },
|
|
52
|
+
{ code: `<><ol><li>1: hoge</li></ol><p>2: hoge</p></>`, errors: [ { message: `ol 内で連番がテキストとして記述されています。連番はol要素で表現できるため、削除してください。
|
|
53
|
+
- ol要素のデフォルトで表示される連番のフォーマット、スタイルから変更したい場合、counter-reset と counter-increment を利用してください
|
|
54
|
+
- 参考: [MDN CSS カウンターの使用](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_counter_styles/Using_CSS_counters)` }, { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
|
|
55
|
+
- \`2: hoge\` が \`1: hoge\` を囲んでいるol要素内(<ol>)に存在するように修正してください
|
|
56
|
+
- 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol要素内(<ol>)に存在する必要があります` } ] },
|
|
57
|
+
{ code: `<><ol><li>1: hoge</li></ol><ol><li>2: hoge</li></ol></>`, errors: [ { message: `ol 内で連番がテキストとして記述されています。連番はol要素で表現できるため、削除してください。
|
|
58
|
+
- ol要素のデフォルトで表示される連番のフォーマット、スタイルから変更したい場合、counter-reset と counter-increment を利用してください
|
|
59
|
+
- 参考: [MDN CSS カウンターの使用](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_counter_styles/Using_CSS_counters)` }, { message: `ol 内で連番がテキストとして記述されています。連番はol要素で表現できるため、削除してください。
|
|
60
|
+
- ol要素のデフォルトで表示される連番のフォーマット、スタイルから変更したい場合、counter-reset と counter-increment を利用してください
|
|
61
|
+
- 参考: [MDN CSS カウンターの使用](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_counter_styles/Using_CSS_counters)` }, { message: `連番を含むテキストが同一のol要素でマークアップされていません。同一のol要素でマークアップすることでリスト内の要素関連性を正しく表せるためマークアップの修正を行ってください。
|
|
62
|
+
- \`1: hoge\` と \`2: hoge\` が同じol要素内に存在するように修正してください
|
|
63
|
+
- 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` } ] },
|
|
64
|
+
{ code: `<><Hoge any="1: hoge" /><Hoge any="2: hoge" /></>`, errors: [ { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
|
|
65
|
+
- \`any="1: hoge"\` と \`any="2: hoge"\` が同じol要素内に存在するように修正してください
|
|
66
|
+
- 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` } ] },
|
|
67
|
+
{ code: `<><Hoge any="2: hoge" /><Hoge any="1: hoge" /><Hoge any="2: hoge" /></>`, errors: [ { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
|
|
68
|
+
- \`any="1: hoge"\` と \`any="2: hoge"\` が同じol要素内に存在するように修正してください
|
|
69
|
+
- 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` } ] },
|
|
70
|
+
{ code: `<><Hoge any="1: hoge" /><ol><li><Hoge any="2: hoge" /></li></ol></>`, errors: [ { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
|
|
71
|
+
- \`any="1: hoge"\` が \`any="2: hoge"\` を囲んでいるol要素内(<ol>)に存在するように修正してください
|
|
72
|
+
- 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol要素内(<ol>)に存在する必要があります` }, { message: `ol 内で連番がテキストとして記述されています。連番はol要素で表現できるため、削除してください。
|
|
73
|
+
- ol要素のデフォルトで表示される連番のフォーマット、スタイルから変更したい場合、counter-reset と counter-increment を利用してください
|
|
74
|
+
- 参考: [MDN CSS カウンターの使用](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_counter_styles/Using_CSS_counters)` } ] },
|
|
75
|
+
{ code: `<><ol><li><Hoge any="1: hoge" /></li></ol><Hoge any="2: hoge" /></>`, errors: [ { message: `ol 内で連番がテキストとして記述されています。連番はol要素で表現できるため、削除してください。
|
|
76
|
+
- ol要素のデフォルトで表示される連番のフォーマット、スタイルから変更したい場合、counter-reset と counter-increment を利用してください
|
|
77
|
+
- 参考: [MDN CSS カウンターの使用](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_counter_styles/Using_CSS_counters)` }, { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
|
|
78
|
+
- \`any="2: hoge"\` が \`any="1: hoge"\` を囲んでいるol要素内(<ol>)に存在するように修正してください
|
|
79
|
+
- 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol要素内(<ol>)に存在する必要があります` } ] },
|
|
80
|
+
{ code: `<><ol><li><Hoge any="1: hoge" /></li></ol><ol><li><Hoge any="2: hoge" /></li></ol></>`, errors: [ { message: `ol 内で連番がテキストとして記述されています。連番はol要素で表現できるため、削除してください。
|
|
81
|
+
- ol要素のデフォルトで表示される連番のフォーマット、スタイルから変更したい場合、counter-reset と counter-increment を利用してください
|
|
82
|
+
- 参考: [MDN CSS カウンターの使用](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_counter_styles/Using_CSS_counters)` }, { message: `ol 内で連番がテキストとして記述されています。連番はol要素で表現できるため、削除してください。
|
|
83
|
+
- ol要素のデフォルトで表示される連番のフォーマット、スタイルから変更したい場合、counter-reset と counter-increment を利用してください
|
|
84
|
+
- 参考: [MDN CSS カウンターの使用](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_counter_styles/Using_CSS_counters)` }, { message: `連番を含むテキストが同一のol要素でマークアップされていません。同一のol要素でマークアップすることでリスト内の要素関連性を正しく表せるためマークアップの修正を行ってください。
|
|
85
|
+
- \`any="1: hoge"\` と \`any="2: hoge"\` が同じol要素内に存在するように修正してください
|
|
86
|
+
- 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` } ] },
|
|
87
|
+
{ code: `<><Hoge any="1: hoge" /><Hoge any="2: hoge" /><Hoge any="3: hoge" /><Hoge any="4: hoge" /></>`, errors: [ { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
|
|
88
|
+
- \`any="1: hoge"\` と \`any="2: hoge"\` が同じol要素内に存在するように修正してください
|
|
89
|
+
- 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` }, { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
|
|
90
|
+
- \`any="2: hoge"\` と \`any="3: hoge"\` が同じol要素内に存在するように修正してください
|
|
91
|
+
- 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` }, { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
|
|
92
|
+
- \`any="3: hoge"\` と \`any="4: hoge"\` が同じol要素内に存在するように修正してください
|
|
93
|
+
- 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` } ] },
|
|
94
|
+
{ code: `<><Hoge any="1: hoge" /><Hoge any="3: hoge" /><Hoge any="4: hoge" /><Hoge any="6: hoge" /></>`, errors: [ { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
|
|
95
|
+
- \`any="3: hoge"\` と \`any="4: hoge"\` が同じol要素内に存在するように修正してください
|
|
96
|
+
- 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` } ] },
|
|
97
|
+
{ code: `<><p>1: hoge</p><p>2: hoge</p><p>3: hoge</p><p>4: hoge</p></>`, errors: [ { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
|
|
98
|
+
- \`1: hoge\` と \`2: hoge\` が同じol要素内に存在するように修正してください
|
|
99
|
+
- 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` }, { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
|
|
100
|
+
- \`2: hoge\` と \`3: hoge\` が同じol要素内に存在するように修正してください
|
|
101
|
+
- 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` }, { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
|
|
102
|
+
- \`3: hoge\` と \`4: hoge\` が同じol要素内に存在するように修正してください
|
|
103
|
+
- 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` } ] },
|
|
104
|
+
{ code: `<><p>1: hoge</p><p>3: hoge</p><p>4: hoge</p><p>6: hoge</p></>`, errors: [ { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
|
|
105
|
+
- \`3: hoge\` と \`4: hoge\` が同じol要素内に存在するように修正してください
|
|
106
|
+
- 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` } ] },
|
|
107
|
+
{ code: `<><p>3: hoge</p><select><option value="hoge">1: hoge</option></select><p>4: hoge</p></>`, errors: [ { message: `連番を含むテキストがol要素でマークアップされていません。ol要素でマークアップすることで関連する順番に意味のある要素を適切にマークアップできるため以下の方法で修正してください。
|
|
108
|
+
- \`3: hoge\` と \`4: hoge\` が同じol要素内に存在するように修正してください
|
|
109
|
+
- 上記以外にも関連する連番をふくむ要素が存在する場合、それらも同じol内に存在する必要があります` } ] },
|
|
110
|
+
]
|
|
111
|
+
})
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
# smarthr/redundant-name
|
|
2
|
-
|
|
3
|
-
- ファイル、コードの冗長な部分を取り除くことを提案するruleです
|
|
4
|
-
- ファイルが設置されているディレクトリ構造からキーワードを生成し、取り除く文字列を生成します
|
|
5
|
-
|
|
6
|
-
## config
|
|
7
|
-
|
|
8
|
-
- tsconfig.json の compilerOptions.pathsに '@/*', もしくは '~/*' としてroot path を指定する必要があります
|
|
9
|
-
- tsconfig.json はデフォルトではコマンド実行をしたディレクトリから読み込みます
|
|
10
|
-
- tsconfig.json の設置ディレクトリを変更したい場合、 `.eslintrc` などのeslint設定ファイルに `parserOptions.project` を設定してください
|
|
11
|
-
- 以下の設定を行えます。全て省略可能です。
|
|
12
|
-
- ignoreKeywords
|
|
13
|
-
- ディレクトリ名から生成されるキーワードに含めたくない文字列を指定します
|
|
14
|
-
- betterNames
|
|
15
|
-
- 対象の名前を修正する候補を指定します
|
|
16
|
-
- allowedNames
|
|
17
|
-
- 許可する名前を指定します
|
|
18
|
-
- suffix:
|
|
19
|
-
- type のみ指定出来ます
|
|
20
|
-
- type のsuffixを指定します
|
|
21
|
-
|
|
22
|
-
### ファイル例
|
|
23
|
-
- `@/crews/index/views/page.tsx` の場合
|
|
24
|
-
- 生成されるキーワードは `['crews', 'crew', 'index', 'page']`
|
|
25
|
-
- `@/crews/index/views/parts/Abc.tsx` の場合
|
|
26
|
-
- 生成されるキーワードは `['crews', 'crew', 'index', 'Abc']`
|
|
27
|
-
- `@/crews/index/repositories/index.ts` の場合
|
|
28
|
-
- 生成されるキーワードは `['crews', 'crew', 'index', 'repositories', 'repository']`
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
## rules
|
|
32
|
-
|
|
33
|
-
```js
|
|
34
|
-
const ignorekeywords = ['views', 'parts']
|
|
35
|
-
const betterNames = {
|
|
36
|
-
'\/repositories\/': {
|
|
37
|
-
operator: '-',
|
|
38
|
-
names: ['repository', 'Repository'],
|
|
39
|
-
},
|
|
40
|
-
'\/entities\/': {
|
|
41
|
-
operator: '+',
|
|
42
|
-
names: ['entity'],
|
|
43
|
-
},
|
|
44
|
-
'\/slices\/': {
|
|
45
|
-
operator: '=',
|
|
46
|
-
names: ['index'],
|
|
47
|
-
},
|
|
48
|
-
}
|
|
49
|
-
// const allowedNames = {
|
|
50
|
-
// '\/views\/crews\/histories\/': ['crewId'],
|
|
51
|
-
// }
|
|
52
|
-
|
|
53
|
-
{
|
|
54
|
-
rules: {
|
|
55
|
-
'smarthr/redundant-name': [
|
|
56
|
-
'error', // 'warn', 'off'
|
|
57
|
-
{
|
|
58
|
-
ignores: [ '\.stories\.' ], // ファイルパスに対して正規表現として一致する場合はチェック対象外にする
|
|
59
|
-
type: { ignorekeywords, suffix: ['Props', 'Type'] },
|
|
60
|
-
file: { ignorekeywords, betternames },
|
|
61
|
-
// property: { ignorekeywords, allowedNames },
|
|
62
|
-
// function: { ignorekeywords },
|
|
63
|
-
// variable: { ignorekeywords },
|
|
64
|
-
// class: { ignorekeywords },
|
|
65
|
-
}
|
|
66
|
-
]
|
|
67
|
-
},
|
|
68
|
-
}
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
## ❌ Incorrect
|
|
72
|
-
|
|
73
|
-
```js
|
|
74
|
-
// @/crews/index/views/page.tsx
|
|
75
|
-
|
|
76
|
-
type CrewIndexPage = { hoge: string }
|
|
77
|
-
type CrewsView = { hoge: string }
|
|
78
|
-
```
|
|
79
|
-
```js
|
|
80
|
-
// @/crews/show/repositories/index.tsx
|
|
81
|
-
|
|
82
|
-
type CrewIndexRepository = { hoge: () => any }
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
## ✅ Correct
|
|
86
|
-
|
|
87
|
-
```js
|
|
88
|
-
// @/crews/index/views/page.tsx
|
|
89
|
-
|
|
90
|
-
type ItemProps = { hoge: string }
|
|
91
|
-
```
|
|
92
|
-
```js
|
|
93
|
-
// @/crews/show/repositories/index.tsx
|
|
94
|
-
|
|
95
|
-
type IndexProps = { hoge: () => any }
|
|
96
|
-
type ResponseType = { hoge: () => any }
|
|
97
|
-
```
|
|
@@ -1,505 +0,0 @@
|
|
|
1
|
-
const path = require('path')
|
|
2
|
-
const Inflector = require('inflected')
|
|
3
|
-
|
|
4
|
-
const { rootPath } = require('../../libs/common')
|
|
5
|
-
|
|
6
|
-
const uniq = (array) => array.filter((elem, index, self) => self.indexOf(elem) === index)
|
|
7
|
-
|
|
8
|
-
const COMMON_DEFAULT_CONFIG = {
|
|
9
|
-
IGNORE_KEYWORDS: ['redux', 'views', 'pages', 'parts'],
|
|
10
|
-
}
|
|
11
|
-
const DEFAULT_CONFIG = {
|
|
12
|
-
ignores: [],
|
|
13
|
-
type: {
|
|
14
|
-
IGNORE_KEYWORDS: [
|
|
15
|
-
'redux', 'views', 'pages', 'parts',
|
|
16
|
-
'props', 'type', 'action', 'actions',
|
|
17
|
-
],
|
|
18
|
-
SUFFIX: ['Props', 'Type'],
|
|
19
|
-
},
|
|
20
|
-
file: COMMON_DEFAULT_CONFIG,
|
|
21
|
-
property: COMMON_DEFAULT_CONFIG,
|
|
22
|
-
function: COMMON_DEFAULT_CONFIG,
|
|
23
|
-
functionParams: COMMON_DEFAULT_CONFIG,
|
|
24
|
-
variable: COMMON_DEFAULT_CONFIG,
|
|
25
|
-
class: COMMON_DEFAULT_CONFIG,
|
|
26
|
-
method: COMMON_DEFAULT_CONFIG,
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const BETTER_NAMES_CALCULATER_PROPERTY = {
|
|
30
|
-
type: 'object',
|
|
31
|
-
properties: {
|
|
32
|
-
operator: ['-', '+', '='],
|
|
33
|
-
names: {
|
|
34
|
-
type: 'array',
|
|
35
|
-
items: 'string',
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
}
|
|
39
|
-
const DEFAULT_SCHEMA_PROPERTY = {
|
|
40
|
-
ignoreKeywords: { type: 'array', items: { type: 'string' } },
|
|
41
|
-
betterNames: {
|
|
42
|
-
type: 'object',
|
|
43
|
-
properties: {
|
|
44
|
-
operator: ['-', '+', '='],
|
|
45
|
-
names: {
|
|
46
|
-
type: 'array',
|
|
47
|
-
items: 'string',
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
allowedNames: {
|
|
52
|
-
type: 'array',
|
|
53
|
-
items: 'string',
|
|
54
|
-
},
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const SCHEMA = [
|
|
58
|
-
{
|
|
59
|
-
type: 'object',
|
|
60
|
-
properties: {
|
|
61
|
-
ignores: { type: 'array', items: { type: 'string' }, default: [] },
|
|
62
|
-
type: {
|
|
63
|
-
...DEFAULT_SCHEMA_PROPERTY,
|
|
64
|
-
suffix: { type: 'array', items: { type: 'string' } },
|
|
65
|
-
},
|
|
66
|
-
file: DEFAULT_SCHEMA_PROPERTY,
|
|
67
|
-
property: DEFAULT_SCHEMA_PROPERTY,
|
|
68
|
-
function: DEFAULT_SCHEMA_PROPERTY,
|
|
69
|
-
functionParams: DEFAULT_SCHEMA_PROPERTY,
|
|
70
|
-
variable: DEFAULT_SCHEMA_PROPERTY,
|
|
71
|
-
class: DEFAULT_SCHEMA_PROPERTY,
|
|
72
|
-
method: DEFAULT_SCHEMA_PROPERTY,
|
|
73
|
-
},
|
|
74
|
-
additionalProperties: false,
|
|
75
|
-
}
|
|
76
|
-
]
|
|
77
|
-
|
|
78
|
-
const fetchTerminalImportName = (filename) => {
|
|
79
|
-
const names = filename.split('/')
|
|
80
|
-
let name = names.pop()
|
|
81
|
-
|
|
82
|
-
if (name === 'index') {
|
|
83
|
-
name = names.pop()
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return name
|
|
87
|
-
}
|
|
88
|
-
const generateRedundantKeywords = ({ args, key, terminalImportName }) => {
|
|
89
|
-
const option = args.option[key] || {}
|
|
90
|
-
const ignoreKeywords = option.ignoreKeywords || DEFAULT_CONFIG[key].IGNORE_KEYWORDS
|
|
91
|
-
const terminalImportKeyword = terminalImportName ? terminalImportName.toLowerCase() : ''
|
|
92
|
-
|
|
93
|
-
return args.keywords.reduce((prev, keyword) => {
|
|
94
|
-
if (keyword === terminalImportKeyword || ignoreKeywords.includes(keyword)) {
|
|
95
|
-
return prev
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return [...prev, ...uniq([
|
|
99
|
-
Inflector.pluralize(keyword),
|
|
100
|
-
keyword,
|
|
101
|
-
Inflector.singularize(keyword),
|
|
102
|
-
])]
|
|
103
|
-
}, [])
|
|
104
|
-
}
|
|
105
|
-
const handleReportBetterName = ({
|
|
106
|
-
key,
|
|
107
|
-
context,
|
|
108
|
-
option,
|
|
109
|
-
filename,
|
|
110
|
-
redundantKeywords,
|
|
111
|
-
defaultBetterName,
|
|
112
|
-
fetchName,
|
|
113
|
-
generateMessage,
|
|
114
|
-
}) => {
|
|
115
|
-
if (!generateMessage) {
|
|
116
|
-
generateMessage = (({ name, betterName }) => `${name} からパスで推測できる箇所を取り除いてしてください (例: ${betterName})`)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return (node) => {
|
|
120
|
-
const name = fetchName(node)
|
|
121
|
-
|
|
122
|
-
if (
|
|
123
|
-
!name ||
|
|
124
|
-
option.allowedNames &&
|
|
125
|
-
Object.entries(option.allowedNames).find(([regex, calcs]) => filename.match(new RegExp(regex)) && calcs.find((c) => c === name))
|
|
126
|
-
) {
|
|
127
|
-
return
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
let candidates = []
|
|
131
|
-
let conciseName = redundantKeywords.reduce((prev, keyword) => {
|
|
132
|
-
const regex = new RegExp(`(${keyword})`, 'i')
|
|
133
|
-
const matcher = prev.match(regex)
|
|
134
|
-
|
|
135
|
-
if (matcher) {
|
|
136
|
-
candidates.push(matcher[1])
|
|
137
|
-
|
|
138
|
-
return prev.replace(regex, '')
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return prev
|
|
142
|
-
}, name)
|
|
143
|
-
|
|
144
|
-
if (name !== conciseName) {
|
|
145
|
-
conciseName = conciseName
|
|
146
|
-
.replace(/^_+/, '')
|
|
147
|
-
.replace(/_+$/, '')
|
|
148
|
-
.replace(/_+/, '_')
|
|
149
|
-
let fullRedundant = false
|
|
150
|
-
|
|
151
|
-
if (!conciseName) {
|
|
152
|
-
fullRedundant = true
|
|
153
|
-
// HINT: 1keywordで構成されている名称はそのままにする
|
|
154
|
-
conciseName = candidates.length === 1 ? name : defaultBetterName
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// HINT: camelCase、lower_snake_case の場合、keywordが取り除かれた結果違うケースになってしまう場合があるので対応する
|
|
158
|
-
if (name.match(/^[a-z]/) && conciseName.match(/^[A-Z]/)) {
|
|
159
|
-
conciseName = `${conciseName[0].toLowerCase()}${conciseName.slice(1)}`
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (fullRedundant) {
|
|
163
|
-
if (name.match(/^[A-Z]/)) {
|
|
164
|
-
candidates = candidates.map((k) => `${k[0].toUpperCase()}${k.slice(1)}`)
|
|
165
|
-
}
|
|
166
|
-
} else {
|
|
167
|
-
candidates = []
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
candidates = uniq([conciseName, ...candidates].filter((k) => !!k))
|
|
171
|
-
|
|
172
|
-
if (option.betterNames) {
|
|
173
|
-
Object.entries(option.betterNames).forEach(([regex, calc]) => {
|
|
174
|
-
if (calc && filename.match(new RegExp(regex))) {
|
|
175
|
-
switch(calc.operator) {
|
|
176
|
-
case '=':
|
|
177
|
-
candidates = calc.names
|
|
178
|
-
break
|
|
179
|
-
case '-':
|
|
180
|
-
candidates = candidates.filter((c) => !calc.names.includes(c))
|
|
181
|
-
break
|
|
182
|
-
case '+':
|
|
183
|
-
candidates = uniq([...candidates, ...calc.names])
|
|
184
|
-
break
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
})
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
candidates = candidates.filter((c) => c !== name)
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (candidates.length > 0) {
|
|
194
|
-
context.report({
|
|
195
|
-
node,
|
|
196
|
-
message: generateMessage({ name, betterName: candidates.join(', ') }),
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const generateTypeRedundant = (args) => {
|
|
203
|
-
const { context, filename } = args
|
|
204
|
-
const key = 'type'
|
|
205
|
-
const redundantKeywords = generateRedundantKeywords({ args, key })
|
|
206
|
-
const option = args.option[key]
|
|
207
|
-
const defaultConfig = DEFAULT_CONFIG[key]
|
|
208
|
-
|
|
209
|
-
return (node) => {
|
|
210
|
-
const typeName = node.id.name
|
|
211
|
-
|
|
212
|
-
if (
|
|
213
|
-
option.allowedNames &&
|
|
214
|
-
Object.entries(option.allowedNames).find(([regex, calcs]) => filename.match(new RegExp(regex)) && calcs.find((c) => c === typeName))
|
|
215
|
-
) {
|
|
216
|
-
return
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const suffix = option.suffix || defaultConfig.SUFFIX
|
|
220
|
-
|
|
221
|
-
let SuffixedName = typeName
|
|
222
|
-
let report = null
|
|
223
|
-
|
|
224
|
-
if (!typeName.match(new RegExp(`(${suffix.join('|')})$`))) {
|
|
225
|
-
SuffixedName = `${typeName}${suffix[0]}`
|
|
226
|
-
report = {
|
|
227
|
-
node,
|
|
228
|
-
message: `type ${typeName} の名称の末尾に ${suffix.join(', ')} ${suffix.length > 1 ? 'のいずれか' : ''}を追加してください`,
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
let betterName = redundantKeywords.reduce((prev, keyword) => {
|
|
233
|
-
const result = prev.replace(new RegExp(keyword, 'i'), '')
|
|
234
|
-
|
|
235
|
-
return result === 's' || result.match(/^s[A-Z]/) ? `Multiple${result.slice(1)}` : result
|
|
236
|
-
}, SuffixedName) || suffix[0]
|
|
237
|
-
|
|
238
|
-
if (betterName === 'Multiple') {
|
|
239
|
-
betterName = `${betterName}${suffix[0]}`
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (SuffixedName !== betterName) {
|
|
243
|
-
report = {
|
|
244
|
-
node,
|
|
245
|
-
message: `type ${typeName} の名称からパスで推測できる箇所を取り除いてしてください (例: ${betterName})`,
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (report) {
|
|
250
|
-
context.report(report)
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const generateTypePropertyRedundant = (args) => {
|
|
256
|
-
const key = 'property'
|
|
257
|
-
return handleReportBetterName({
|
|
258
|
-
...args,
|
|
259
|
-
key,
|
|
260
|
-
option: args.option[key],
|
|
261
|
-
redundantKeywords: generateRedundantKeywords({ args, key }),
|
|
262
|
-
defaultBetterName: '',
|
|
263
|
-
fetchName: (node) => node.key.name,
|
|
264
|
-
})
|
|
265
|
-
}
|
|
266
|
-
const generateTypePropertyFunctionParamsRedundant = (args) => {
|
|
267
|
-
const key = 'property'
|
|
268
|
-
const redundant = handleReportBetterName({
|
|
269
|
-
...args,
|
|
270
|
-
key,
|
|
271
|
-
option: args.option[key],
|
|
272
|
-
redundantKeywords: generateRedundantKeywords({ args, key }),
|
|
273
|
-
defaultBetterName: '',
|
|
274
|
-
fetchName: (node) => node.name,
|
|
275
|
-
})
|
|
276
|
-
|
|
277
|
-
return (node) => {
|
|
278
|
-
node.params.forEach((param) => redundant(param))
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const generatePropertyRedundant = (args) => {
|
|
283
|
-
const key = 'property'
|
|
284
|
-
|
|
285
|
-
return handleReportBetterName({
|
|
286
|
-
...args,
|
|
287
|
-
key,
|
|
288
|
-
option: args.option[key],
|
|
289
|
-
redundantKeywords: generateRedundantKeywords({ args, key }),
|
|
290
|
-
defaultBetterName: 'item',
|
|
291
|
-
fetchName: (node) => {
|
|
292
|
-
// argumentsとしてわたされたobjectの展開などの場合は許可する
|
|
293
|
-
// このファイル内で修正すべき場合などは冗長な名前を修正するべき場合はtype propertyなどで判断出来る
|
|
294
|
-
if (node.parent.type === 'ObjectPattern') {
|
|
295
|
-
return null
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
return node.key.name
|
|
299
|
-
},
|
|
300
|
-
})
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
const generateFileRedundant = (args) => {
|
|
304
|
-
const key = 'file'
|
|
305
|
-
const terminalImportName = fetchTerminalImportName(args.filename)
|
|
306
|
-
|
|
307
|
-
return handleReportBetterName({
|
|
308
|
-
...args,
|
|
309
|
-
key,
|
|
310
|
-
option: args.option[key],
|
|
311
|
-
redundantKeywords: generateRedundantKeywords({ args, key, terminalImportName }),
|
|
312
|
-
defaultBetterName: 'index',
|
|
313
|
-
fetchName: () => terminalImportName,
|
|
314
|
-
generateMessage: ({ name, betterName }) => `${name} のファイル名からパスで推測できる箇所を取り除いてしてください (例: ${betterName})`
|
|
315
|
-
})
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
const generateFunctionRedundant = (args) => {
|
|
319
|
-
const key = 'function'
|
|
320
|
-
|
|
321
|
-
return handleReportBetterName({
|
|
322
|
-
...args,
|
|
323
|
-
key,
|
|
324
|
-
option: args.option[key],
|
|
325
|
-
redundantKeywords: generateRedundantKeywords({ args, key, terminalImportName: fetchTerminalImportName(args.filename) }),
|
|
326
|
-
defaultBetterName: '',
|
|
327
|
-
fetchName: (node) => node.id.name,
|
|
328
|
-
})
|
|
329
|
-
}
|
|
330
|
-
const generateFunctionParamsRedundant = (args) => {
|
|
331
|
-
const key = 'functionParams'
|
|
332
|
-
const redundant = handleReportBetterName({
|
|
333
|
-
...args,
|
|
334
|
-
key,
|
|
335
|
-
option: args.option[key],
|
|
336
|
-
redundantKeywords: generateRedundantKeywords({ args, key }),
|
|
337
|
-
defaultBetterName: '',
|
|
338
|
-
fetchName: (node) => node.name,
|
|
339
|
-
})
|
|
340
|
-
|
|
341
|
-
return (node) => {
|
|
342
|
-
node.params.forEach((param) => redundant(param))
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
const generateVariableRedundant = (args) => {
|
|
347
|
-
const key = 'variable'
|
|
348
|
-
|
|
349
|
-
return handleReportBetterName({
|
|
350
|
-
...args,
|
|
351
|
-
key,
|
|
352
|
-
option: args.option[key],
|
|
353
|
-
redundantKeywords: generateRedundantKeywords({ args, key, terminalImportName: fetchTerminalImportName(args.filename) }),
|
|
354
|
-
defaultBetterName: '',
|
|
355
|
-
fetchName: (node) => node.id.name,
|
|
356
|
-
})
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
const generateClassRedundant = (args) => {
|
|
360
|
-
const key = 'class'
|
|
361
|
-
|
|
362
|
-
return handleReportBetterName({
|
|
363
|
-
...args,
|
|
364
|
-
key,
|
|
365
|
-
option: args.option[key],
|
|
366
|
-
redundantKeywords: generateRedundantKeywords({ args, key, terminalImportName: fetchTerminalImportName(args.filename) }),
|
|
367
|
-
defaultBetterName: '',
|
|
368
|
-
fetchName: (node) => node.id.name,
|
|
369
|
-
})
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
const generateMethodRedundant = (args) => {
|
|
373
|
-
const key = 'method'
|
|
374
|
-
|
|
375
|
-
return handleReportBetterName({
|
|
376
|
-
...args,
|
|
377
|
-
key,
|
|
378
|
-
option: args.option[key],
|
|
379
|
-
redundantKeywords: generateRedundantKeywords({ args, key }),
|
|
380
|
-
defaultBetterName: 'item',
|
|
381
|
-
fetchName: (node) => node.key.name,
|
|
382
|
-
})
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
module.exports = {
|
|
386
|
-
meta: {
|
|
387
|
-
type: 'suggestion',
|
|
388
|
-
schema: SCHEMA,
|
|
389
|
-
},
|
|
390
|
-
create(context) {
|
|
391
|
-
if (!rootPath) {
|
|
392
|
-
throw new Error('tsconfig.json の compilerOptions.paths に `@/*`、もしくは `~/*` 形式でフロントエンドのroot dir を指定してください(例: `"@/*": ["./any_path/*"]`)')
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
let rules = {}
|
|
396
|
-
|
|
397
|
-
const option = context.options[0]
|
|
398
|
-
let filename = context.getFilename()
|
|
399
|
-
|
|
400
|
-
if ((option.ignores || []).some((i) => !!filename.match(new RegExp(i)))) {
|
|
401
|
-
return {}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
const keywords = uniq((() => {
|
|
405
|
-
const keywordMatcher = filename.match(new RegExp(`${rootPath}/(.+?)$`))
|
|
406
|
-
|
|
407
|
-
if (keywordMatcher) {
|
|
408
|
-
const keywords = keywordMatcher[1].split('/')
|
|
409
|
-
keywords[keywords.length - 1] = keywords[keywords.length - 1].split('.')[0]
|
|
410
|
-
|
|
411
|
-
filename = `${rootPath}/${keywords.join('/')}`
|
|
412
|
-
|
|
413
|
-
if (keywords[keywords.length - 1] === 'index') {
|
|
414
|
-
keywords.pop()
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// HINT: ファイル名 > ディレクトリ名 > 親ディレクトリ名 ...
|
|
418
|
-
// の順でキーワードとしての重要度が上がる。reverseして重要度順に並べる
|
|
419
|
-
return keywords.reverse().reduce((prev, dir, index) => {
|
|
420
|
-
prev.push(dir.replace(/_/g, '').toLowerCase())
|
|
421
|
-
|
|
422
|
-
return prev
|
|
423
|
-
}, [])
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
return []
|
|
427
|
-
})())
|
|
428
|
-
|
|
429
|
-
const args = {
|
|
430
|
-
context,
|
|
431
|
-
option,
|
|
432
|
-
filename,
|
|
433
|
-
keywords,
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
const addRule = (key, redundant) => {
|
|
437
|
-
const addedRules = rules[key] || []
|
|
438
|
-
|
|
439
|
-
rules[key] = [...addedRules, redundant]
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
if (option.type) {
|
|
443
|
-
addRule('TSTypeAliasDeclaration', generateTypeRedundant(args))
|
|
444
|
-
// addRule('TSInterfaceDeclaration', generateTypeRedundant(args)) // 必要になったら実装する
|
|
445
|
-
}
|
|
446
|
-
if (option.property) {
|
|
447
|
-
const typePropRedundant = generateTypePropertyRedundant(args)
|
|
448
|
-
const typeFuncParamRedundant = generateTypePropertyFunctionParamsRedundant(args)
|
|
449
|
-
const redundant = generatePropertyRedundant(args)
|
|
450
|
-
|
|
451
|
-
addRule('TSPropertySignature', (node) => {
|
|
452
|
-
typePropRedundant(node)
|
|
453
|
-
|
|
454
|
-
if (node.typeAnnotation.typeAnnotation.type === 'TSFunctionType') {
|
|
455
|
-
typeFuncParamRedundant(node.typeAnnotation.typeAnnotation)
|
|
456
|
-
}
|
|
457
|
-
})
|
|
458
|
-
addRule('Property', redundant)
|
|
459
|
-
addRule('PropertyDefinition', redundant)
|
|
460
|
-
}
|
|
461
|
-
if (option.file) {
|
|
462
|
-
addRule('Program', generateFileRedundant(args))
|
|
463
|
-
}
|
|
464
|
-
if (option.function) {
|
|
465
|
-
addRule('FunctionDeclaration', generateFunctionRedundant(args))
|
|
466
|
-
}
|
|
467
|
-
if (option.functionParams) {
|
|
468
|
-
const redundant = generateFunctionParamsRedundant(args)
|
|
469
|
-
|
|
470
|
-
addRule('FunctionDeclaration', redundant)
|
|
471
|
-
addRule('ArrowFunctionExpression', redundant)
|
|
472
|
-
addRule('MethodDefinition', (node) => {
|
|
473
|
-
if (node.value.type === 'FunctionExpression') {
|
|
474
|
-
redundant(node.value)
|
|
475
|
-
}
|
|
476
|
-
})
|
|
477
|
-
}
|
|
478
|
-
if (option.variable) {
|
|
479
|
-
const redundant = generateVariableRedundant(args)
|
|
480
|
-
|
|
481
|
-
addRule('VariableDeclarator', redundant)
|
|
482
|
-
addRule('TSEnumDeclaration', redundant)
|
|
483
|
-
}
|
|
484
|
-
if (option.class) {
|
|
485
|
-
addRule('ClassDeclaration', generateClassRedundant(args))
|
|
486
|
-
}
|
|
487
|
-
if (option.method) {
|
|
488
|
-
addRule('MethodDefinition', generateMethodRedundant(args))
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
Object.keys(rules).forEach((key) => {
|
|
492
|
-
const redundants = rules[key]
|
|
493
|
-
rules[key] = (node) => {
|
|
494
|
-
redundants.forEach((redundant) => {
|
|
495
|
-
redundant(node)
|
|
496
|
-
})
|
|
497
|
-
}
|
|
498
|
-
})
|
|
499
|
-
|
|
500
|
-
return rules
|
|
501
|
-
},
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
module.exports.schema = SCHEMA
|
|
505
|
-
module.exports.default_config = DEFAULT_CONFIG
|