eslint-plugin-smarthr 3.4.0 → 3.5.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 +8 -0
- package/README.md +2 -0
- package/package.json +3 -3
- package/rules/best-practice-for-optional-chaining/README.md +75 -0
- package/rules/best-practice-for-optional-chaining/index.js +40 -0
- package/rules/best-practice-for-rest-parameters/README.md +1 -1
- package/rules/best-practice-for-unnesessary-early-return/README.md +161 -0
- package/rules/best-practice-for-unnesessary-early-return/index.js +92 -0
- package/test/best-practice-for-optional-chaining.js +48 -0
- package/test/best-practice-for-unnesessary-early-return.js +189 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
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
|
+
## [3.5.0](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v3.4.0...eslint-plugin-smarthr-v3.5.0) (2025-12-26)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* smarthr/best-practice-for-optional-chaining を追加 ([#979](https://github.com/kufu/tamatebako/issues/979)) ([ff18a55](https://github.com/kufu/tamatebako/commit/ff18a55970f026c2d828cf22f99e9969d90d7d82))
|
|
11
|
+
* smarthr/best-practice-for-unnesessary-early-return を追加 ([#977](https://github.com/kufu/tamatebako/issues/977)) ([cd0b1a2](https://github.com/kufu/tamatebako/commit/cd0b1a205e82f01ce2be4fe43b69555d86ab5fd1))
|
|
12
|
+
|
|
5
13
|
## [3.4.0](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v3.3.2...eslint-plugin-smarthr-v3.4.0) (2025-12-24)
|
|
6
14
|
|
|
7
15
|
|
package/README.md
CHANGED
|
@@ -21,11 +21,13 @@
|
|
|
21
21
|
- [best-practice-for-date](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-date)
|
|
22
22
|
- [best-practice-for-layouts](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-layouts)
|
|
23
23
|
- [best-practice-for-nested-attributes-array-index](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-nested-attributes-array-index)
|
|
24
|
+
- [best-practice-for-optional-chaining](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-optional-chaining)
|
|
24
25
|
- [best-practice-for-prohibit-import-smarthr-ui-local](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-prohibit-import-smarthr-ui-local)
|
|
25
26
|
- [best-practice-for-remote-trigger-dialog](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-remote-trigger-dialog)
|
|
26
27
|
- [best-practice-for-rest-parameters](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-rest-parameters)
|
|
27
28
|
- [best-practice-for-tailwind-prohibit-root-margin](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-tailwind-prohibit-root-margin)
|
|
28
29
|
- [best-practice-for-tailwind-variants](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-tailwind-variants)
|
|
30
|
+
- [best-practice-for-unnesessary-early-return](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-unnesessary-early-return)
|
|
29
31
|
- [design-system-guideline-prohibit-double-icons](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/design-system-guideline-prohibit-double-icons)
|
|
30
32
|
- [format-import-path](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/format-import-path)
|
|
31
33
|
- [format-translate-component](https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/format-translate-component)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-smarthr",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.0",
|
|
4
4
|
"author": "SmartHR",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "A sharable ESLint plugin for SmartHR",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"json5": "^2.2.3"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"typescript-eslint": "^8.50.
|
|
29
|
+
"typescript-eslint": "^8.50.1"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"eslint": "^9"
|
|
@@ -37,5 +37,5 @@
|
|
|
37
37
|
"eslintplugin",
|
|
38
38
|
"smarthr"
|
|
39
39
|
],
|
|
40
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "2709611af1e9178d321a0fbfc6564e2cbc454c72"
|
|
41
41
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# smarthr/best-practice-for-optional-chaining
|
|
2
|
+
|
|
3
|
+
- optional chaining(xxx?.yyy記法)を使うことを促すルールです
|
|
4
|
+
|
|
5
|
+
## rules
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
{
|
|
9
|
+
rules: {
|
|
10
|
+
'smarthr/best-practice-for-optional-chaining': 'error', // 'warn', 'off'
|
|
11
|
+
},
|
|
12
|
+
}
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## ❌ Incorrect
|
|
16
|
+
|
|
17
|
+
```jsx
|
|
18
|
+
if (action) action()
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```jsx
|
|
22
|
+
if (obj.action) {
|
|
23
|
+
obj.action(hoge, fuga)
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
## ✅ Correct
|
|
29
|
+
|
|
30
|
+
```jsx
|
|
31
|
+
action?.()
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```jsx
|
|
35
|
+
obj.action?.(hoge, fuga)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```jsx
|
|
39
|
+
// 実行する関数と無関係の条件の場合は許容
|
|
40
|
+
if (any) {
|
|
41
|
+
action()
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```jsx
|
|
46
|
+
// 実行する関数の存在チェック以外が条件に含まれている場合は許容
|
|
47
|
+
if (action && any) {
|
|
48
|
+
action()
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```jsx
|
|
53
|
+
// if - else などifに他条件が連なる場合は許容
|
|
54
|
+
if (action) {
|
|
55
|
+
action()
|
|
56
|
+
} else {
|
|
57
|
+
...
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```jsx
|
|
62
|
+
// if内で関数呼び出し以外の処理が存在すれば許容
|
|
63
|
+
if (action) {
|
|
64
|
+
return action()
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```jsx
|
|
69
|
+
// else if内の場合は許容
|
|
70
|
+
if (any) {
|
|
71
|
+
...
|
|
72
|
+
} else if (action) {
|
|
73
|
+
action()
|
|
74
|
+
}
|
|
75
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const SCHEMA = []
|
|
2
|
+
|
|
3
|
+
const REPLACABLE_CALLEE = `[consequent.expression.callee.type='Identifier']`
|
|
4
|
+
const WITHOUT_BODY_IF_ID = `[test.type='Identifier']${REPLACABLE_CALLEE}`
|
|
5
|
+
const WITH_BODY_IF_ID = WITHOUT_BODY_IF_ID.replace(REPLACABLE_CALLEE, "[consequent.body.length=1][consequent.body.0.expression.callee.type='Identifier']")
|
|
6
|
+
const SELECTOR = `IfStatement[alternate=null]:not([parent.type='IfStatement']):matches(${WITHOUT_BODY_IF_ID},${WITHOUT_BODY_IF_ID.replaceAll("'Identifier'", "'MemberExpression'")},${WITH_BODY_IF_ID},${WITH_BODY_IF_ID.replaceAll("'Identifier'", "'MemberExpression'")})`
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
11
|
+
*/
|
|
12
|
+
module.exports = {
|
|
13
|
+
meta: {
|
|
14
|
+
type: 'suggestion',
|
|
15
|
+
fixable: 'code',
|
|
16
|
+
schema: SCHEMA,
|
|
17
|
+
},
|
|
18
|
+
create(context) {
|
|
19
|
+
return {
|
|
20
|
+
[SELECTOR]: (node) => {
|
|
21
|
+
const expression = node.consequent.expression || node.consequent.body[0].expression
|
|
22
|
+
const calleName = context.sourceCode.getText(expression.callee)
|
|
23
|
+
|
|
24
|
+
if (context.sourceCode.getText(node.test) === calleName) {
|
|
25
|
+
context.report({
|
|
26
|
+
node,
|
|
27
|
+
message: `optional chaining(xxx?.yyyy記法)を利用してください
|
|
28
|
+
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-optional-chaining`,
|
|
29
|
+
fix: (fixer) => fixer.replaceText(
|
|
30
|
+
node,
|
|
31
|
+
context.sourceCode.getText(expression).replace(new RegExp(`^${calleName}\\(`), `${calleName}\?\.(`),
|
|
32
|
+
),
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
module.exports.schema = SCHEMA
|
|
40
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# smarthr/best-practice-for-rest-parameters
|
|
2
2
|
|
|
3
|
-
- 残余引数(rest parameters)
|
|
3
|
+
- 残余引数(rest parameters)の利用方法を設定するルールです
|
|
4
4
|
- https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/rest_parameters
|
|
5
5
|
- 残余引数にはrestという名称を設定することを推奨します
|
|
6
6
|
- よく利用される `props` などを利用するとエラーになります
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# smarthr/best-practice-for-unnesessary-early-return
|
|
2
|
+
|
|
3
|
+
- 不必要な早期returnをチェックするルールです
|
|
4
|
+
- 不要な早期returnとは **直後に実行される処理の条件を逆転している早期return** を指します
|
|
5
|
+
|
|
6
|
+
```jsx
|
|
7
|
+
// 不要な早期returnの例
|
|
8
|
+
const anyAction = (a) => {
|
|
9
|
+
if (!a) {
|
|
10
|
+
return
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
otherAction()
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
- 上記の例の場合、実質 `otherActionを実行する条件だけが存在する` ため、実際にコードを読む際、条件を逆転させて考える余計なコストが発生します
|
|
18
|
+
- 下記の様に早期returnを利用せず書くことを推奨します
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
```jsx
|
|
22
|
+
const anyAction = (a) => {
|
|
23
|
+
if (a) {
|
|
24
|
+
otherAction()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- 上記のような条件に修正することで、本質的に実行したい処理の条件がわかりやすくなります
|
|
30
|
+
- このルールは以下の条件すべてに合致する場合、NGにします
|
|
31
|
+
- 早期return以降にifやelse、switch, tryがない
|
|
32
|
+
- 早期returnの条件ブロック以降に変数宣言がない
|
|
33
|
+
- 早期returnの条件ブロック以降にreturnがない
|
|
34
|
+
- 早期returnが値を返していない
|
|
35
|
+
|
|
36
|
+
## rules
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
{
|
|
40
|
+
rules: {
|
|
41
|
+
'smarthr/best-practice-for-unnesessary-early-return': 'error', // 'warn', 'off'
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## ❌ Incorrect
|
|
47
|
+
|
|
48
|
+
```jsx
|
|
49
|
+
const anyAction = (a) => {
|
|
50
|
+
// otherActionの実行条件を逆転させているだけのためNG
|
|
51
|
+
if (!a) {
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
otherAction()
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```jsx
|
|
60
|
+
const anyAction = (a, b) => {
|
|
61
|
+
// 早期returnの条件内容自体は無関係にチェックするためNG
|
|
62
|
+
if (!a || !b) {
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
otherAction()
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```jsx
|
|
71
|
+
const anyAction = (a, b) => {
|
|
72
|
+
switch (a) {
|
|
73
|
+
...
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 早期returnより前に条件などがあっても、条件が実質逆転しているためNG
|
|
77
|
+
if (!b) {
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
otherAction()
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```jsx
|
|
86
|
+
const anyAction = (a, b) => {
|
|
87
|
+
if (!a) {
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
// 早期returnが分割されているため、前述の早期returnの条件とまとめるべきなのでNG
|
|
91
|
+
if (!b) {
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
otherAction()
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## ✅ Correct
|
|
100
|
+
|
|
101
|
+
```jsx
|
|
102
|
+
const anyAction = (a) => {
|
|
103
|
+
if (a) {
|
|
104
|
+
otherAction()
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```jsx
|
|
110
|
+
// 許容する早期returnの例
|
|
111
|
+
const sample1 = (a) => {
|
|
112
|
+
if (a) {
|
|
113
|
+
// 早期returnで値を返しているため許容
|
|
114
|
+
return true
|
|
115
|
+
}
|
|
116
|
+
...
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const sample2 = (a) => {
|
|
120
|
+
if (!a) {
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 早期return後に変数宣言しているため許容
|
|
125
|
+
const caculated = calc(a)
|
|
126
|
+
...
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const sample3 = (a) => {
|
|
130
|
+
if (!a) {
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
// 早期returnの条件以外に条件が存在するため許容
|
|
134
|
+
else if (a === any) {
|
|
135
|
+
...
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const sample4 = (a, b) => {
|
|
140
|
+
if (!a) {
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
otherAction1(a)
|
|
145
|
+
|
|
146
|
+
// この場合も別条件のため許容
|
|
147
|
+
if (b === any) {
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
...
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const sample5 = (a, b) => {
|
|
154
|
+
if (!a) {
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 早期returnとは別のreturnが関数スコープのrootにあるため許容
|
|
159
|
+
return ...
|
|
160
|
+
}
|
|
161
|
+
```
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
const SCHEMA = []
|
|
2
|
+
|
|
3
|
+
const EARLY_RETURN_IF_STATEMENT = `:matches(ArrowFunctionExpression,FunctionExpression,FunctionDeclaration)>BlockStatement>IfStatement[alternate=null]:matches([consequent.type='ReturnStatement'],[consequent.body.length=1])>`
|
|
4
|
+
const NULL_RETURN_STATEMENT = 'ReturnStatement[argument=null]'
|
|
5
|
+
|
|
6
|
+
const FUNCTION_REGEX = /^(Arrow)?Function(Expression|Declaration)$/
|
|
7
|
+
|
|
8
|
+
const searchFunction = (node) => FUNCTION_REGEX.test(node.type) ? node : searchFunction(node.parent)
|
|
9
|
+
|
|
10
|
+
const getEarlyReturn = (b) => {
|
|
11
|
+
let ret = null
|
|
12
|
+
switch (b.consequent.type) {
|
|
13
|
+
case 'ReturnStatement':
|
|
14
|
+
ret = b.consequent
|
|
15
|
+
|
|
16
|
+
break
|
|
17
|
+
case 'BlockStatement':
|
|
18
|
+
if (b.consequent.body.length === 1 && b.consequent.body[0].type === 'ReturnStatement') {
|
|
19
|
+
ret = b.consequent.body[0]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
break
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return ret?.argument === null ? ret : null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const DETAIL_LINK = `
|
|
29
|
+
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-unnesessary-early-return`
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
33
|
+
*/
|
|
34
|
+
module.exports = {
|
|
35
|
+
meta: {
|
|
36
|
+
type: 'suggestion',
|
|
37
|
+
schema: SCHEMA,
|
|
38
|
+
},
|
|
39
|
+
create(context) {
|
|
40
|
+
const action = (node) => {
|
|
41
|
+
const fn = searchFunction(node).body.body
|
|
42
|
+
// 0: 最初の早期returnを検索中
|
|
43
|
+
// 1: 最初の早期returnを発見、その後も連続して早期returnが存在する場合
|
|
44
|
+
// 2: 1の後、早期returnの連続が途切れた場合
|
|
45
|
+
let flg = 0
|
|
46
|
+
|
|
47
|
+
for (let i = 0; i < fn.length; i++) {
|
|
48
|
+
const b = fn[i]
|
|
49
|
+
|
|
50
|
+
if (!flg) {
|
|
51
|
+
if (b.type === 'IfStatement' && getEarlyReturn(b)) {
|
|
52
|
+
flg = 1
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
continue
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
switch (b.type) {
|
|
59
|
+
case 'VariableDeclaration':
|
|
60
|
+
case 'ReturnStatement':
|
|
61
|
+
case 'SwitchStatement':
|
|
62
|
+
case 'TryStatement':
|
|
63
|
+
return
|
|
64
|
+
case 'IfStatement':
|
|
65
|
+
if (flg === 1 && node === getEarlyReturn(b)) {
|
|
66
|
+
context.report({
|
|
67
|
+
node,
|
|
68
|
+
message: `早期returnのifが分割されています${DETAIL_LINK}
|
|
69
|
+
- 一つのifにまとめるよう、条件を調整してください`,
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
flg = 2
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
context.report({
|
|
79
|
+
node,
|
|
80
|
+
message: `後続の処理の逆の条件の早期returnのため修正してください。${DETAIL_LINK}
|
|
81
|
+
- 本質的に行いたい処理の条件とは逆がifに記述されているため、ロジックを確認する際条件を逆転させて考える余計な手間が発生しています
|
|
82
|
+
- 条件を逆転させたうえで後続の処理をifの内部に移動してください`,
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
[`${EARLY_RETURN_IF_STATEMENT}BlockStatement>${NULL_RETURN_STATEMENT}`]: action,
|
|
88
|
+
[`${EARLY_RETURN_IF_STATEMENT}${NULL_RETURN_STATEMENT}`]: action,
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
module.exports.schema = SCHEMA
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const rule = require('../rules/best-practice-for-optional-chaining')
|
|
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 ERROR = `optional chaining(xxx?.yyyy記法)を利用してください
|
|
15
|
+
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-optional-chaining`
|
|
16
|
+
|
|
17
|
+
ruleTester.run('best-practice-for-optional-chaining', rule, {
|
|
18
|
+
valid: [
|
|
19
|
+
{ code: `action?.()` },
|
|
20
|
+
{ code: `obj.action?.(hoge, fuga)` },
|
|
21
|
+
{ code: `if (any) { action() }` },
|
|
22
|
+
{ code: `if (action && any) { action() }` },
|
|
23
|
+
{ code: `if (action) { action() } else { any() }` },
|
|
24
|
+
{ code: `if (obj.hoge) obj.hoge.fuga.action()` },
|
|
25
|
+
{ code: `() => { if (action) return action() }` },
|
|
26
|
+
{ code: `if (any) {} else if (action) { action() }` },
|
|
27
|
+
],
|
|
28
|
+
invalid: [
|
|
29
|
+
{ code: `if (action) action()`, errors: [{ message: ERROR }], output: 'action?.()' },
|
|
30
|
+
{ code: `if (obj.action) { obj.action(hoge, fuga) }`, errors: [{ message: ERROR }], output: 'obj.action?.(hoge, fuga)' },
|
|
31
|
+
{ code: `if (obj.hoge.fuga.action) obj.hoge.fuga.action()`, errors: [{ message: ERROR }], output: 'obj.hoge.fuga.action?.()' },
|
|
32
|
+
{ code: `
|
|
33
|
+
if (obj.hoge.fuga.action) {
|
|
34
|
+
obj.hoge.fuga.action(
|
|
35
|
+
a,
|
|
36
|
+
b,
|
|
37
|
+
c
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
`, errors: [{ message: ERROR }], output: `
|
|
41
|
+
obj.hoge.fuga.action?.(
|
|
42
|
+
a,
|
|
43
|
+
b,
|
|
44
|
+
c
|
|
45
|
+
)
|
|
46
|
+
` },
|
|
47
|
+
],
|
|
48
|
+
})
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
const rule = require('../rules/best-practice-for-unnesessary-early-return')
|
|
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 UNNESESSARY_EARLY_RETURN_ERROR = `後続の処理の逆の条件の早期returnのため修正してください。
|
|
15
|
+
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-unnesessary-early-return
|
|
16
|
+
- 本質的に行いたい処理の条件とは逆がifに記述されているため、ロジックを確認する際条件を逆転させて考える余計な手間が発生しています
|
|
17
|
+
- 条件を逆転させたうえで後続の処理をifの内部に移動してください`
|
|
18
|
+
const SPLIT_EARLY_RETURN_ERROR = `早期returnのifが分割されています
|
|
19
|
+
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-unnesessary-early-return
|
|
20
|
+
- 一つのifにまとめるよう、条件を調整してください`
|
|
21
|
+
|
|
22
|
+
ruleTester.run('best-practice-for-unnesessary-early-return', rule, {
|
|
23
|
+
valid: [
|
|
24
|
+
{ code: `
|
|
25
|
+
const anyAction = (a) => {
|
|
26
|
+
if (a) {
|
|
27
|
+
otherAction()
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
` },
|
|
31
|
+
{ code: `
|
|
32
|
+
const sample2 = (a) => {
|
|
33
|
+
if (!a) return
|
|
34
|
+
|
|
35
|
+
const caculated = calc(a)
|
|
36
|
+
}
|
|
37
|
+
` },
|
|
38
|
+
{ code: `
|
|
39
|
+
const anyAction = (a) => {
|
|
40
|
+
if (!a) {
|
|
41
|
+
return
|
|
42
|
+
} else {
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
` },
|
|
46
|
+
{ code: `
|
|
47
|
+
const anyAction = (a) => {
|
|
48
|
+
if (!a) {
|
|
49
|
+
return
|
|
50
|
+
} else if (any) {
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
` },
|
|
54
|
+
{ code: `
|
|
55
|
+
const sample4 = (a, b) => {
|
|
56
|
+
if (!a) {
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
otherAction1(a)
|
|
61
|
+
|
|
62
|
+
if (b === any) {
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
otherAction2(b)
|
|
67
|
+
}
|
|
68
|
+
` },
|
|
69
|
+
{ code: `
|
|
70
|
+
const sample4 = (a, b) => {
|
|
71
|
+
if (!a) {
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
switch(a) {
|
|
76
|
+
case 'hoge':
|
|
77
|
+
break
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
` },
|
|
81
|
+
{ code: `
|
|
82
|
+
const anyAction = (a) => {
|
|
83
|
+
if (!a) {
|
|
84
|
+
otherAction1()
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
otherAction2()
|
|
89
|
+
}
|
|
90
|
+
` },
|
|
91
|
+
{ code: `
|
|
92
|
+
const anyAction = (a) => {
|
|
93
|
+
if (!a) {
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return otherAction2()
|
|
98
|
+
}
|
|
99
|
+
` },
|
|
100
|
+
{ code: `
|
|
101
|
+
const anyAction = (a) => {
|
|
102
|
+
if (!a) {
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
otherAction()
|
|
108
|
+
} catch {
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
` },
|
|
112
|
+
],
|
|
113
|
+
invalid: [
|
|
114
|
+
{ code: `
|
|
115
|
+
const anyAction = (a) => {
|
|
116
|
+
if (!a) {
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
otherAction()
|
|
121
|
+
}
|
|
122
|
+
`, errors: [ { message: UNNESESSARY_EARLY_RETURN_ERROR } ] },
|
|
123
|
+
{ code: `
|
|
124
|
+
const anyAction = (a) => {
|
|
125
|
+
const hoge = any
|
|
126
|
+
|
|
127
|
+
if (!a) {
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
otherAction()
|
|
132
|
+
}
|
|
133
|
+
`, errors: [ { message: UNNESESSARY_EARLY_RETURN_ERROR } ] },
|
|
134
|
+
{ code: `
|
|
135
|
+
const anyAction = (a, b) => {
|
|
136
|
+
if (!a || !b) {
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
otherAction()
|
|
141
|
+
}
|
|
142
|
+
`, errors: [ { message: UNNESESSARY_EARLY_RETURN_ERROR } ] },
|
|
143
|
+
{ code: `
|
|
144
|
+
const anyAction = (a, b) => {
|
|
145
|
+
switch (a) {
|
|
146
|
+
case 'hoge':
|
|
147
|
+
break
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!b) {
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
otherAction()
|
|
155
|
+
}
|
|
156
|
+
`, errors: [ { message: UNNESESSARY_EARLY_RETURN_ERROR } ] },
|
|
157
|
+
{ code: `
|
|
158
|
+
const anyAction = (a, b) => {
|
|
159
|
+
// 早期returnより前に非早期returnのif,switchがある場合
|
|
160
|
+
if (a && b) {
|
|
161
|
+
return 'hoge'
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// 早期return後にswitchがある場合強制終了されるが
|
|
165
|
+
// このswitchは早期return前のため後続でエラーになる
|
|
166
|
+
switch (a) {
|
|
167
|
+
case 'hoge':
|
|
168
|
+
break
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (!b) {
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
otherAction()
|
|
176
|
+
}
|
|
177
|
+
`, errors: [ { message: UNNESESSARY_EARLY_RETURN_ERROR } ] },
|
|
178
|
+
{ code: `
|
|
179
|
+
const anyAction = (a, b) => {
|
|
180
|
+
if (!a) return
|
|
181
|
+
if (!b) {
|
|
182
|
+
return
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
otherAction()
|
|
186
|
+
}
|
|
187
|
+
`, errors: [ SPLIT_EARLY_RETURN_ERROR ] },
|
|
188
|
+
]
|
|
189
|
+
})
|