eslint-plugin-smarthr 3.3.1 → 3.4.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,20 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ## [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
+
7
+
8
+ ### Features
9
+
10
+ * best-practice-for-rest-parameters の残余引数の命名規則をrestもしくはxxxRestにすることを促すように修正 ([#973](https://github.com/kufu/tamatebako/issues/973)) ([3824818](https://github.com/kufu/tamatebako/commit/3824818a68d0ab6b2b2dc04ff5292f9d8be03d9b))
11
+
12
+ ## [3.3.2](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v3.3.1...eslint-plugin-smarthr-v3.3.2) (2025-12-23)
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * best-practice-for-rest-parametersの誤検知を修正し、エラー文言を条件に合わせて出し分けるように修正 ([#967](https://github.com/kufu/tamatebako/issues/967)) ([cf71e12](https://github.com/kufu/tamatebako/commit/cf71e1256ef6453fe99080829e6969cea88d5a92))
18
+
5
19
  ## [3.3.1](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v3.3.0...eslint-plugin-smarthr-v3.3.1) (2025-12-19)
6
20
 
7
21
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-smarthr",
3
- "version": "3.3.1",
3
+ "version": "3.4.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.49.0"
29
+ "typescript-eslint": "^8.50.0"
30
30
  },
31
31
  "peerDependencies": {
32
32
  "eslint": "^9"
@@ -37,5 +37,5 @@
37
37
  "eslintplugin",
38
38
  "smarthr"
39
39
  ],
40
- "gitHead": "7390a378bca7e0f44fa90cb2a983cc807dc6c403"
40
+ "gitHead": "25b508c89653cc0e754e091f83d8b31346afb08e"
41
41
  }
@@ -3,13 +3,17 @@
3
3
  - 残余引数(rest parameters)の命名規則を設定するルールです
4
4
  - https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/rest_parameters
5
5
  - 残余引数にはrestという名称を設定することを推奨します
6
- - よく利用される `props` という名称と完全一致する場合、エラーになります
6
+ - よく利用される `props` などを利用するとエラーになります
7
7
  - コンポーネントが受け取れる属性を定義する際、多用される "Props" 型と勘違いされる可能性を減らすためです
8
8
  - 残余引数の時点でコンポーネントが受け取れる属性全てではないことが確定するためpropsの利用を禁止しています
9
- - `xxxProps` のように他単語と組合されている場合はエラーになりません
10
- - 残余引数以外でrestという名称と完全一致する設定することを禁止します
9
+ - `rest` という名称に揃えることで可読性を向上させることが出来ます
10
+ - restがすでに利用されており設定出来ない場合 `xxxRest` というフォーマットが利用出来ます
11
+ - 残余引数以外でrest、もしくはxxxRestというフォーマットの名称を設定することを禁止します
11
12
  - restは rest parametersから命名されたjsのイディオムのため、残余引数以外の箇所で利用すると混乱を招くためです
12
- - `xxxRest` のように他単語と組合されている場合はエラーになりません
13
+ - 残余引数内の属性を直接参照することを禁止します
14
+ - 例: `const hoge = rest.fuga`
15
+ - 例: `const { hoge } = rest`
16
+ - この条件を守る場合、残余引数がそのスコープ内で関心が薄い引数の集まりになり、可読性が向上します
13
17
 
14
18
  ## rules
15
19
 
@@ -24,7 +28,7 @@
24
28
  ## ❌ Incorrect
25
29
 
26
30
  ```js
27
- // 残余引数にpropsという名称が設定されているためNG
31
+ // 残余引数にrest、xxxRest以外の名称が利用されているためNG
28
32
  const hoge = (a, b, ...props) => {
29
33
  // any
30
34
  }
@@ -34,12 +38,35 @@ const hoge = ({ a, b, ...props }) => {
34
38
  // any
35
39
  }
36
40
 
37
- // 残余引数ではない箇所でrestという文字列と完全一致する場合NG
38
- const hoge = (a, rest, b) => {
41
+ // 残余引数ではない箇所で残余引数と勘違いしかねない命名がされているためNG
42
+ const hoge = (a, anyRest, b) => {
39
43
  // any
40
44
  }
41
45
  // 引数以外の場合でも混乱するためNG
42
- const rest = { /* any */ }
46
+ const hogeRest = { /* any */ }
47
+
48
+ // 残余引数内の属性を参照しているためNG
49
+ const ComponentA = ({ a, b, ...rest }) => {
50
+ ...
51
+
52
+ if (rest.abc) {
53
+ return <Children {...rest} />
54
+ }
55
+
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
+ }
43
70
  ```
44
71
 
45
72
  ## ✅ Correct
@@ -50,14 +77,25 @@ const rest = { /* any */ }
50
77
  const hoge = (a, b, ...rest) => {
51
78
  // any
52
79
  }
53
- // 残余引数でもprops以外は許容
54
- const hoge = ({ a, b, ...actionButtonProps }) => {
80
+ const fuga = ({ a, b, ...anyRest }) => {
55
81
  // any
56
82
  }
57
83
 
58
- // 残余引数以外の場合はpropsを許容
84
+ // 残余引数ではない場合、rest以外の名称を許容
59
85
  const hoge = (props) => {
60
86
  // any
61
87
  }
62
88
  const props = { /* any */ }
89
+
90
+ // 残余引数内の属性を参照しないようにコードが書かれているため許容
91
+ // HINT: 残余引数がそのスコープ内で関心が薄い引数の集まりになり、可読性が向上します
92
+ const Component = ({ a, b, abc, ...rest }) => {
93
+ ...
94
+
95
+ if (abc) {
96
+ return <Children {...rest} abc={abc} />
97
+ }
98
+
99
+ ...
100
+ }
63
101
  ```
@@ -1,5 +1,10 @@
1
1
  const SCHEMA = []
2
2
 
3
+ const REST_REGEX = /(^r|R)est$/
4
+ const MEMBER_EXPRESSION_REST_REGEX = /^(r|[a-zA-Z0-9_]+R)est\./
5
+ const DETAIL_LINK = `
6
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-rest-parameters`
7
+
3
8
  /**
4
9
  * @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
5
10
  */
@@ -9,26 +14,48 @@ module.exports = {
9
14
  schema: SCHEMA,
10
15
  },
11
16
  create(context) {
12
- const restAction = (node) => {
17
+ const actionNotRest = (node) => {
13
18
  context.report({
14
19
  node,
15
- message: `残余引数以外に 'rest' という名称を利用しないでください
16
- - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-rest-parameters
20
+ message: `残余引数以外に ${REST_REGEX} とマッチする名称を利用しないでください${DETAIL_LINK}
17
21
  - 残余引数(rest parameters)と混同する可能性があるため別の名称に修正してください`,
18
- });
22
+ })
23
+ }
24
+ const actionMemberExpressionName = (node) => {
25
+ if (node.parent.type === 'MemberExpression') {
26
+ return actionMemberExpressionName(node.parent)
27
+ }
28
+
29
+ if (MEMBER_EXPRESSION_REST_REGEX.test(context.sourceCode.getText(node))){
30
+ context.report({
31
+ node,
32
+ message: `残余引数内の属性を参照しないでください${DETAIL_LINK}`,
33
+ })
34
+ }
19
35
  }
20
36
 
21
37
  return {
22
- [`RestElement[argument.name='props']`]: (node) => {
38
+ [`ObjectPattern[properties.length=1]>RestElement`]: (node) => {
39
+ context.report({
40
+ node,
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) => {
23
54
  context.report({
24
55
  node,
25
- message: `残余引数には 'props' という名称を利用しないでください
26
- - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-rest-parameters
27
- - 'rest' という名称を推奨します`,
28
- });
56
+ message: `残余引数内の属性を参照しないでください${DETAIL_LINK}`,
57
+ })
29
58
  },
30
- [`:not(:matches(RestElement,JSXSpreadAttribute,ObjectPattern>Property,ObjectExpression>Property))>Identifier[name='rest']`]: restAction,
31
- [`:matches(ObjectPattern>Property[value.name='rest'],ObjectExpression>Property[key.name='rest'])`]: restAction,
32
59
  }
33
60
  },
34
61
  }
@@ -11,34 +11,42 @@ const ruleTester = new RuleTester({
11
11
  },
12
12
  })
13
13
 
14
- const ERROR_PROPS = `残余引数には 'props' という名称を利用しないでください
15
- - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-rest-parameters
16
- - 'rest' という名称を推奨します`
17
- const ERROR_REST = `残余引数以外に 'rest' という名称を利用しないでください
18
- - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-rest-parameters
14
+ const DETAIL_LINK = `
15
+ - 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-rest-parameters`
16
+ const ERROR_REST_NAME = `残余引数には /(^r|R)est$/ とマッチする名称を指定してください${DETAIL_LINK}`
17
+ const ERROR_NOT_REST_NAME = `残余引数以外に /(^r|R)est$/ とマッチする名称を利用しないでください${DETAIL_LINK}
19
18
  - 残余引数(rest parameters)と混同する可能性があるため別の名称に修正してください`
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, ...hoge) => {}` },
26
- { code: `const hoge = ({ a, b, ...xxxProps }) => {}` },
25
+ { code: `const hoge = ({ a, b, ...anyRest }) => {}` },
27
26
  { code: `const hoge = (props) => {}` },
28
27
  { code: `const props = {}` },
29
- { code: `const hogeRest = {}` },
30
28
  { code: `const hoge = { fuga: rest }` },
29
+ { code: `const hoge = hogeRest` },
30
+ { code: `const hoge = hoge.rest.fuga` },
31
+ { code: `const hoge = { ...anyRest }` },
32
+ { code: `const hoge = [rest]` },
33
+ { code: `hoge(fugaRest)` },
31
34
  { code: `<Any {...rest} />` },
32
35
  ],
33
36
  invalid: [
34
- { code: `const hoge = (a, b, ...props) => {}`, errors: [ { message: ERROR_PROPS } ] },
35
- { code: `const hoge = ({ a, b, ...props }) => {}`, errors: [ { message: ERROR_PROPS } ] },
36
- { code: `const hoge = (rest) => {}`, errors: [ { message: ERROR_REST } ] },
37
- { code: `const hoge = (a, b, rest) => {}`, errors: [ { message: ERROR_REST } ] },
38
- { code: `const hoge = ({ a: rest, b }) => {}`, errors: [ { message: ERROR_REST } ] },
39
- { code: `const rest = {}`, errors: [ { message: ERROR_REST } ] },
40
- { code: `const hoge = { rest }`, errors: [ { message: ERROR_REST } ] },
41
- { code: `const hoge = { rest: fuga }`, errors: [ { message: ERROR_REST } ] },
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 } ] },
40
+ { code: `const hoge = (rest) => {}`, errors: [ { message: ERROR_NOT_REST_NAME } ] },
41
+ { code: `const hogeRest = {}`, errors: [ { message: ERROR_NOT_REST_NAME } ] },
42
+ { code: `const hoge = (a, b, rest) => {}`, errors: [ { message: ERROR_NOT_REST_NAME } ] },
43
+ { code: `const hoge = ({ a: anyRest, b }) => {}`, errors: [ { message: ERROR_NOT_REST_NAME } ] },
44
+ { code: `const rest = {}`, errors: [ { message: ERROR_NOT_REST_NAME } ] },
45
+ { code: `const hoge = { hogeRest }`, errors: [ { message: ERROR_NOT_REST_NAME } ] },
46
+ { code: `const hoge = { rest: fuga }`, errors: [ { message: ERROR_NOT_REST_NAME } ] },
47
+ { code: `const hoge = rest.hoge`, 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 } ] },
42
50
  ]
43
51
  })
44
52