eslint-plugin-smarthr 3.3.2 → 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 CHANGED
@@ -2,6 +2,21 @@
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
+
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)
14
+
15
+
16
+ ### Features
17
+
18
+ * best-practice-for-rest-parameters の残余引数の命名規則をrestもしくはxxxRestにすることを促すように修正 ([#973](https://github.com/kufu/tamatebako/issues/973)) ([3824818](https://github.com/kufu/tamatebako/commit/3824818a68d0ab6b2b2dc04ff5292f9d8be03d9b))
19
+
5
20
  ## [3.3.2](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v3.3.1...eslint-plugin-smarthr-v3.3.2) (2025-12-23)
6
21
 
7
22
 
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.2",
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.0"
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": "6b6b572bcc35d4b23cb20ef6b29bedf011e3a881"
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,17 +1,18 @@
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` などを利用するとエラーになります
7
7
  - コンポーネントが受け取れる属性を定義する際、多用される "Props" 型と勘違いされる可能性を減らすためです
8
8
  - 残余引数の時点でコンポーネントが受け取れる属性全てではないことが確定するためpropsの利用を禁止しています
9
9
  - `rest` という名称に揃えることで可読性を向上させることが出来ます
10
- - 残余引数以外でrestという名称と完全一致する設定することを禁止します
10
+ - restがすでに利用されており設定出来ない場合 `xxxRest` というフォーマットが利用出来ます
11
+ - 残余引数以外でrest、もしくはxxxRestというフォーマットの名称を設定することを禁止します
11
12
  - restは rest parametersから命名されたjsのイディオムのため、残余引数以外の箇所で利用すると混乱を招くためです
12
- - `xxxRest` のように他単語と組合されている場合はエラーになりません
13
13
  - 残余引数内の属性を直接参照することを禁止します
14
14
  - 例: `const hoge = rest.fuga`
15
+ - 例: `const { hoge } = rest`
15
16
  - この条件を守る場合、残余引数がそのスコープ内で関心が薄い引数の集まりになり、可読性が向上します
16
17
 
17
18
  ## rules
@@ -27,7 +28,7 @@
27
28
  ## ❌ Incorrect
28
29
 
29
30
  ```js
30
- // 残余引数にrest以外の名称が利用されているためNG
31
+ // 残余引数にrest、xxxRest以外の名称が利用されているためNG
31
32
  const hoge = (a, b, ...props) => {
32
33
  // any
33
34
  }
@@ -37,15 +38,15 @@ const hoge = ({ a, b, ...props }) => {
37
38
  // any
38
39
  }
39
40
 
40
- // 残余引数ではない箇所でrestという文字列と完全一致する場合NG
41
- const hoge = (a, rest, b) => {
41
+ // 残余引数ではない箇所で残余引数と勘違いしかねない命名がされているためNG
42
+ const hoge = (a, anyRest, b) => {
42
43
  // any
43
44
  }
44
45
  // 引数以外の場合でも混乱するためNG
45
- const rest = { /* any */ }
46
+ const hogeRest = { /* any */ }
46
47
 
47
48
  // 残余引数内の属性を参照しているためNG
48
- const Component = ({ a, b, ...rest }) => {
49
+ const ComponentA = ({ a, b, ...rest }) => {
49
50
  ...
50
51
 
51
52
  if (rest.abc) {
@@ -54,6 +55,18 @@ const Component = ({ a, b, ...rest }) => {
54
55
 
55
56
  ...
56
57
  }
58
+ const ComponentB = ({ a, b, ...rest }) => {
59
+ ...
60
+
61
+ // 残余引数を構造分解することもNG
62
+ const { abc } = rest
63
+
64
+ if (abc) {
65
+ return <Children {...rest} />
66
+ }
67
+
68
+ ...
69
+ }
57
70
  ```
58
71
 
59
72
  ## ✅ Correct
@@ -64,6 +77,9 @@ const Component = ({ a, b, ...rest }) => {
64
77
  const hoge = (a, b, ...rest) => {
65
78
  // any
66
79
  }
80
+ const fuga = ({ a, b, ...anyRest }) => {
81
+ // any
82
+ }
67
83
 
68
84
  // 残余引数ではない場合、rest以外の名称を許容
69
85
  const hoge = (props) => {
@@ -1,6 +1,7 @@
1
1
  const SCHEMA = []
2
2
 
3
- const MEMBER_EXPRESSION_REST_REGEX = /^rest\./
3
+ const REST_REGEX = /(^r|R)est$/
4
+ const MEMBER_EXPRESSION_REST_REGEX = /^(r|[a-zA-Z0-9_]+R)est\./
4
5
  const DETAIL_LINK = `
5
6
  - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-rest-parameters`
6
7
 
@@ -16,9 +17,9 @@ module.exports = {
16
17
  const actionNotRest = (node) => {
17
18
  context.report({
18
19
  node,
19
- message: `残余引数以外に 'rest' という名称を利用しないでください${DETAIL_LINK}
20
+ message: `残余引数以外に ${REST_REGEX} とマッチする名称を利用しないでください${DETAIL_LINK}
20
21
  - 残余引数(rest parameters)と混同する可能性があるため別の名称に修正してください`,
21
- });
22
+ })
22
23
  }
23
24
  const actionMemberExpressionName = (node) => {
24
25
  if (node.parent.type === 'MemberExpression') {
@@ -29,20 +30,32 @@ module.exports = {
29
30
  context.report({
30
31
  node,
31
32
  message: `残余引数内の属性を参照しないでください${DETAIL_LINK}`,
32
- });
33
+ })
33
34
  }
34
35
  }
35
36
 
36
37
  return {
37
- [`RestElement:not([argument.name='rest'])`]: (node) => {
38
+ [`ObjectPattern[properties.length=1]>RestElement`]: (node) => {
38
39
  context.report({
39
40
  node,
40
- message: `残余引数には 'rest' という名称を指定してください${DETAIL_LINK}`,
41
- });
41
+ message: `意味のない残余引数のため、単一の引数に変更してください${DETAIL_LINK}`,
42
+ })
43
+ },
44
+ [`RestElement:not([argument.name=${REST_REGEX}])`]: (node) => {
45
+ context.report({
46
+ node,
47
+ message: `残余引数には ${REST_REGEX} とマッチする名称を指定してください${DETAIL_LINK}`,
48
+ })
49
+ },
50
+ [`:not(:matches(RestElement,JSXSpreadAttribute,JSXSpreadAttribute>TSAsExpression,SpreadElement,SpreadElement>TSAsExpression,MemberExpression,VariableDeclarator,ArrayExpression,CallExpression,ObjectPattern>Property,ObjectExpression>Property))>Identifier[name=${REST_REGEX}]`]: actionNotRest,
51
+ [`:matches(VariableDeclarator[id.name=${REST_REGEX}],ObjectPattern>Property[value.name=${REST_REGEX}],ObjectExpression>Property[key.name=${REST_REGEX}])`]: actionNotRest,
52
+ [`MemberExpression[object.name=${REST_REGEX}]`]: actionMemberExpressionName,
53
+ [`VariableDeclarator[id.type='ObjectPattern'][init.name=${REST_REGEX}]`]: (node) => {
54
+ context.report({
55
+ node,
56
+ message: `残余引数内の属性を参照しないでください${DETAIL_LINK}`,
57
+ })
42
58
  },
43
- [`:not(:matches(RestElement,JSXSpreadAttribute,JSXSpreadAttribute>TSAsExpression,SpreadElement,SpreadElement>TSAsExpression,MemberExpression,VariableDeclarator,ArrayExpression,CallExpression,ObjectPattern>Property,ObjectExpression>Property))>Identifier[name='rest']`]: actionNotRest,
44
- [`:matches(VariableDeclarator[id.name='rest'],ObjectPattern>Property[value.name='rest'],ObjectExpression>Property[key.name='rest'])`]: actionNotRest,
45
- [`MemberExpression[object.name='rest']`]: actionMemberExpressionName,
46
59
  }
47
60
  },
48
61
  }
@@ -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
+ })
@@ -13,37 +13,40 @@ const ruleTester = new RuleTester({
13
13
 
14
14
  const DETAIL_LINK = `
15
15
  - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-rest-parameters`
16
- const ERROR_REST_NAME = `残余引数には 'rest' という名称を指定してください${DETAIL_LINK}`
17
- const ERROR_NOT_REST_NAME = `残余引数以外に 'rest' という名称を利用しないでください${DETAIL_LINK}
16
+ const ERROR_REST_NAME = `残余引数には /(^r|R)est$/ とマッチする名称を指定してください${DETAIL_LINK}`
17
+ const ERROR_NOT_REST_NAME = `残余引数以外に /(^r|R)est$/ とマッチする名称を利用しないでください${DETAIL_LINK}
18
18
  - 残余引数(rest parameters)と混同する可能性があるため別の名称に修正してください`
19
19
  const ERROR_REST_CHILD_REF = `残余引数内の属性を参照しないでください${DETAIL_LINK}`
20
20
 
21
21
  ruleTester.run('best-practice-for-rest-parameters', rule, {
22
22
  valid: [
23
+ { code: `const hoge = (...rest) => {}` },
23
24
  { code: `const hoge = (a, b, ...rest) => {}` },
24
- { code: `const hoge = ({ a, b, ...rest }) => {}` },
25
+ { code: `const hoge = ({ a, b, ...anyRest }) => {}` },
25
26
  { code: `const hoge = (props) => {}` },
26
27
  { code: `const props = {}` },
27
- { code: `const hogeRest = {}` },
28
28
  { code: `const hoge = { fuga: rest }` },
29
- { code: `const hoge = rest` },
29
+ { code: `const hoge = hogeRest` },
30
30
  { code: `const hoge = hoge.rest.fuga` },
31
- { code: `const hoge = { ...rest }` },
31
+ { code: `const hoge = { ...anyRest }` },
32
32
  { code: `const hoge = [rest]` },
33
- { code: `hoge(rest)` },
33
+ { code: `hoge(fugaRest)` },
34
34
  { code: `<Any {...rest} />` },
35
35
  ],
36
36
  invalid: [
37
- { code: `const hoge = (a, b, ...props) => {}`, errors: [ { message: ERROR_REST_NAME } ] },
38
- { code: `const hoge = ({ a, b, ...props }) => {}`, errors: [ { message: ERROR_REST_NAME } ] },
37
+ { code: `const hoge = ({ ...rest }) => {}`, errors: [ { message: `意味のない残余引数のため、単一の引数に変更してください${DETAIL_LINK}` } ] },
38
+ { code: `const hoge = (a, b, ...any) => {}`, errors: [ { message: ERROR_REST_NAME } ] },
39
+ { code: `const hoge = ({ a, b, ...restHoge }) => {}`, errors: [ { message: ERROR_REST_NAME } ] },
39
40
  { code: `const hoge = (rest) => {}`, errors: [ { message: ERROR_NOT_REST_NAME } ] },
41
+ { code: `const hogeRest = {}`, errors: [ { message: ERROR_NOT_REST_NAME } ] },
40
42
  { code: `const hoge = (a, b, rest) => {}`, errors: [ { message: ERROR_NOT_REST_NAME } ] },
41
- { code: `const hoge = ({ a: rest, b }) => {}`, errors: [ { message: ERROR_NOT_REST_NAME } ] },
43
+ { code: `const hoge = ({ a: anyRest, b }) => {}`, errors: [ { message: ERROR_NOT_REST_NAME } ] },
42
44
  { code: `const rest = {}`, errors: [ { message: ERROR_NOT_REST_NAME } ] },
43
- { code: `const hoge = { rest }`, errors: [ { message: ERROR_NOT_REST_NAME } ] },
45
+ { code: `const hoge = { hogeRest }`, errors: [ { message: ERROR_NOT_REST_NAME } ] },
44
46
  { code: `const hoge = { rest: fuga }`, errors: [ { message: ERROR_NOT_REST_NAME } ] },
45
47
  { code: `const hoge = rest.hoge`, errors: [ { message: ERROR_REST_CHILD_REF } ] },
46
- { code: `const hoge = rest.hoge.fuga`, errors: [ { message: ERROR_REST_CHILD_REF } ] },
48
+ { code: `const hoge = anyRest.hoge.fuga`, errors: [ { message: ERROR_REST_CHILD_REF } ] },
49
+ { code: `const { any } = rest`, errors: [ { message: ERROR_REST_CHILD_REF } ] },
47
50
  ]
48
51
  })
49
52
 
@@ -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
+ })