eslint-plugin-unicorn-ts 0.0.1-security → 50.0.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.

Potentially problematic release.


This version of eslint-plugin-unicorn-ts might be problematic. Click here for more details.

Files changed (214) hide show
  1. package/configs/all.js +6 -0
  2. package/configs/flat-config-base.js +10 -0
  3. package/configs/legacy-config-base.js +10 -0
  4. package/configs/recommended.js +117 -0
  5. package/index.js +91 -0
  6. package/license +9 -0
  7. package/package.json +186 -4
  8. package/readme.md +356 -0
  9. package/rules/ast/call-or-new-expression.js +127 -0
  10. package/rules/ast/function-types.js +5 -0
  11. package/rules/ast/index.js +39 -0
  12. package/rules/ast/is-arrow-function-body.js +7 -0
  13. package/rules/ast/is-empty-node.js +20 -0
  14. package/rules/ast/is-expression-statement.js +11 -0
  15. package/rules/ast/is-function.js +8 -0
  16. package/rules/ast/is-member-expression.js +101 -0
  17. package/rules/ast/is-method-call.js +65 -0
  18. package/rules/ast/is-reference-identifier.js +156 -0
  19. package/rules/ast/is-static-require.js +14 -0
  20. package/rules/ast/is-undefined.js +7 -0
  21. package/rules/ast/literal.js +29 -0
  22. package/rules/better-regex.js +144 -0
  23. package/rules/catch-error-name.js +136 -0
  24. package/rules/consistent-destructuring.js +168 -0
  25. package/rules/consistent-function-scoping.js +223 -0
  26. package/rules/custom-error-definition.js +215 -0
  27. package/rules/empty-brace-spaces.js +72 -0
  28. package/rules/error-message.js +104 -0
  29. package/rules/escape-case.js +63 -0
  30. package/rules/expiring-todo-comments.js +580 -0
  31. package/rules/explicit-length-check.js +229 -0
  32. package/rules/filename-case.js +258 -0
  33. package/rules/fix/add-parenthesizes-to-return-or-throw-expression.js +21 -0
  34. package/rules/fix/append-argument.js +20 -0
  35. package/rules/fix/extend-fix-range.js +15 -0
  36. package/rules/fix/fix-space-around-keywords.js +35 -0
  37. package/rules/fix/index.js +23 -0
  38. package/rules/fix/remove-argument.js +32 -0
  39. package/rules/fix/remove-member-expression-property.js +11 -0
  40. package/rules/fix/remove-method-call.js +20 -0
  41. package/rules/fix/remove-parentheses.js +11 -0
  42. package/rules/fix/remove-spaces-after.js +14 -0
  43. package/rules/fix/rename-variable.js +9 -0
  44. package/rules/fix/replace-argument.js +8 -0
  45. package/rules/fix/replace-node-or-token-and-spaces-before.js +21 -0
  46. package/rules/fix/replace-reference-identifier.js +35 -0
  47. package/rules/fix/replace-string-literal.js +11 -0
  48. package/rules/fix/replace-string-raw.js +14 -0
  49. package/rules/fix/replace-template-element.js +11 -0
  50. package/rules/fix/switch-call-expression-to-new-expression.js +18 -0
  51. package/rules/fix/switch-new-expression-to-call-expression.js +34 -0
  52. package/rules/import-style.js +364 -0
  53. package/rules/new-for-builtins.js +85 -0
  54. package/rules/no-abusive-eslint-disable.js +48 -0
  55. package/rules/no-array-callback-reference.js +256 -0
  56. package/rules/no-array-for-each.js +473 -0
  57. package/rules/no-array-method-this-argument.js +188 -0
  58. package/rules/no-array-push-push.js +144 -0
  59. package/rules/no-array-reduce.js +126 -0
  60. package/rules/no-await-expression-member.js +90 -0
  61. package/rules/no-console-spaces.js +86 -0
  62. package/rules/no-document-cookie.js +25 -0
  63. package/rules/no-empty-file.js +57 -0
  64. package/rules/no-for-loop.js +427 -0
  65. package/rules/no-hex-escape.js +46 -0
  66. package/rules/no-instanceof-array.js +65 -0
  67. package/rules/no-invalid-remove-event-listener.js +60 -0
  68. package/rules/no-keyword-prefix.js +199 -0
  69. package/rules/no-lonely-if.js +151 -0
  70. package/rules/no-negated-condition.js +144 -0
  71. package/rules/no-nested-ternary.js +58 -0
  72. package/rules/no-new-array.js +104 -0
  73. package/rules/no-new-buffer.js +98 -0
  74. package/rules/no-null.js +153 -0
  75. package/rules/no-object-as-default-parameter.js +50 -0
  76. package/rules/no-process-exit.js +104 -0
  77. package/rules/no-static-only-class.js +224 -0
  78. package/rules/no-thenable.js +198 -0
  79. package/rules/no-this-assignment.js +38 -0
  80. package/rules/no-typeof-undefined.js +143 -0
  81. package/rules/no-unnecessary-await.js +107 -0
  82. package/rules/no-unnecessary-polyfills.js +176 -0
  83. package/rules/no-unreadable-array-destructuring.js +83 -0
  84. package/rules/no-unreadable-iife.js +45 -0
  85. package/rules/no-unused-properties.js +238 -0
  86. package/rules/no-useless-fallback-in-spread.js +68 -0
  87. package/rules/no-useless-length-check.js +152 -0
  88. package/rules/no-useless-promise-resolve-reject.js +212 -0
  89. package/rules/no-useless-spread.js +381 -0
  90. package/rules/no-useless-switch-case.js +71 -0
  91. package/rules/no-useless-undefined.js +301 -0
  92. package/rules/no-zero-fractions.js +79 -0
  93. package/rules/number-literal-case.js +52 -0
  94. package/rules/numeric-separators-style.js +181 -0
  95. package/rules/prefer-add-event-listener.js +188 -0
  96. package/rules/prefer-array-find.js +423 -0
  97. package/rules/prefer-array-flat-map.js +82 -0
  98. package/rules/prefer-array-flat.js +279 -0
  99. package/rules/prefer-array-index-of.js +32 -0
  100. package/rules/prefer-array-some.js +157 -0
  101. package/rules/prefer-at.js +374 -0
  102. package/rules/prefer-blob-reading-methods.js +45 -0
  103. package/rules/prefer-code-point.js +67 -0
  104. package/rules/prefer-date-now.js +135 -0
  105. package/rules/prefer-default-parameters.js +219 -0
  106. package/rules/prefer-dom-node-append.js +48 -0
  107. package/rules/prefer-dom-node-dataset.js +120 -0
  108. package/rules/prefer-dom-node-remove.js +122 -0
  109. package/rules/prefer-dom-node-text-content.js +75 -0
  110. package/rules/prefer-event-target.js +117 -0
  111. package/rules/prefer-export-from.js +413 -0
  112. package/rules/prefer-includes.js +98 -0
  113. package/rules/prefer-json-parse-buffer.js +159 -0
  114. package/rules/prefer-keyboard-event-key.js +186 -0
  115. package/rules/prefer-logical-operator-over-ternary.js +159 -0
  116. package/rules/prefer-math-trunc.js +109 -0
  117. package/rules/prefer-modern-dom-apis.js +141 -0
  118. package/rules/prefer-modern-math-apis.js +212 -0
  119. package/rules/prefer-module.js +349 -0
  120. package/rules/prefer-native-coercion-functions.js +185 -0
  121. package/rules/prefer-negative-index.js +213 -0
  122. package/rules/prefer-node-protocol.js +61 -0
  123. package/rules/prefer-number-properties.js +126 -0
  124. package/rules/prefer-object-from-entries.js +252 -0
  125. package/rules/prefer-optional-catch-binding.js +75 -0
  126. package/rules/prefer-prototype-methods.js +88 -0
  127. package/rules/prefer-query-selector.js +135 -0
  128. package/rules/prefer-reflect-apply.js +97 -0
  129. package/rules/prefer-regexp-test.js +156 -0
  130. package/rules/prefer-set-has.js +186 -0
  131. package/rules/prefer-set-size.js +103 -0
  132. package/rules/prefer-spread.js +529 -0
  133. package/rules/prefer-string-replace-all.js +145 -0
  134. package/rules/prefer-string-slice.js +182 -0
  135. package/rules/prefer-string-starts-ends-with.js +199 -0
  136. package/rules/prefer-string-trim-start-end.js +44 -0
  137. package/rules/prefer-switch.js +344 -0
  138. package/rules/prefer-ternary.js +282 -0
  139. package/rules/prefer-top-level-await.js +152 -0
  140. package/rules/prefer-type-error.js +151 -0
  141. package/rules/prevent-abbreviations.js +645 -0
  142. package/rules/relative-url-style.js +168 -0
  143. package/rules/require-array-join-separator.js +63 -0
  144. package/rules/require-number-to-fixed-digits-argument.js +54 -0
  145. package/rules/require-post-message-target-origin.js +71 -0
  146. package/rules/shared/abbreviations.js +262 -0
  147. package/rules/shared/dom-events.js +275 -0
  148. package/rules/shared/event-keys.js +52 -0
  149. package/rules/shared/negative-index.js +46 -0
  150. package/rules/shared/simple-array-search-rule.js +128 -0
  151. package/rules/shared/typed-array.js +16 -0
  152. package/rules/string-content.js +187 -0
  153. package/rules/switch-case-braces.js +109 -0
  154. package/rules/template-indent.js +219 -0
  155. package/rules/text-encoding-identifier-case.js +108 -0
  156. package/rules/throw-new-error.js +53 -0
  157. package/rules/utils/array-or-object-prototype-property.js +63 -0
  158. package/rules/utils/assert-token.js +32 -0
  159. package/rules/utils/avoid-capture.js +146 -0
  160. package/rules/utils/boolean.js +92 -0
  161. package/rules/utils/builtins.js +36 -0
  162. package/rules/utils/cartesian-product-samples.js +24 -0
  163. package/rules/utils/create-deprecated-rules.js +25 -0
  164. package/rules/utils/escape-string.js +26 -0
  165. package/rules/utils/escape-template-element-raw.js +6 -0
  166. package/rules/utils/get-ancestor.js +20 -0
  167. package/rules/utils/get-builtin-rule.js +7 -0
  168. package/rules/utils/get-call-expression-arguments-text.js +21 -0
  169. package/rules/utils/get-class-head-location.js +22 -0
  170. package/rules/utils/get-documentation-url.js +10 -0
  171. package/rules/utils/get-indent-string.js +11 -0
  172. package/rules/utils/get-previous-node.js +24 -0
  173. package/rules/utils/get-references.js +9 -0
  174. package/rules/utils/get-scopes.js +14 -0
  175. package/rules/utils/get-switch-case-head-location.js +21 -0
  176. package/rules/utils/get-variable-identifiers.js +7 -0
  177. package/rules/utils/global-reference-tracker.js +72 -0
  178. package/rules/utils/has-optional-chain-element.js +21 -0
  179. package/rules/utils/has-same-range.js +7 -0
  180. package/rules/utils/index.js +53 -0
  181. package/rules/utils/is-function-self-used-inside.js +43 -0
  182. package/rules/utils/is-left-hand-side.js +22 -0
  183. package/rules/utils/is-logical-expression.js +16 -0
  184. package/rules/utils/is-method-named.js +9 -0
  185. package/rules/utils/is-new-expression-with-parentheses.js +26 -0
  186. package/rules/utils/is-node-matches.js +53 -0
  187. package/rules/utils/is-node-value-not-dom-node.js +21 -0
  188. package/rules/utils/is-node-value-not-function.js +42 -0
  189. package/rules/utils/is-number.js +224 -0
  190. package/rules/utils/is-object-method.js +11 -0
  191. package/rules/utils/is-on-same-line.js +7 -0
  192. package/rules/utils/is-same-identifier.js +8 -0
  193. package/rules/utils/is-same-reference.js +173 -0
  194. package/rules/utils/is-shadowed.js +33 -0
  195. package/rules/utils/is-shorthand-export-local.js +9 -0
  196. package/rules/utils/is-shorthand-import-local.js +9 -0
  197. package/rules/utils/is-shorthand-property-assignment-pattern-left.js +10 -0
  198. package/rules/utils/is-shorthand-property-value.js +8 -0
  199. package/rules/utils/is-value-not-usable.js +5 -0
  200. package/rules/utils/lodash.js +1589 -0
  201. package/rules/utils/needs-semicolon.js +114 -0
  202. package/rules/utils/numeric.js +53 -0
  203. package/rules/utils/parentheses.js +73 -0
  204. package/rules/utils/resolve-variable-name.js +20 -0
  205. package/rules/utils/rule.js +190 -0
  206. package/rules/utils/should-add-parentheses-to-conditional-expression-child.js +17 -0
  207. package/rules/utils/should-add-parentheses-to-expression-statement-expression.js +26 -0
  208. package/rules/utils/should-add-parentheses-to-logical-expression-child.js +47 -0
  209. package/rules/utils/should-add-parentheses-to-member-expression-object.js +47 -0
  210. package/rules/utils/should-add-parentheses-to-new-expression-callee.js +32 -0
  211. package/rules/utils/should-add-parentheses-to-spread-element-argument.js +22 -0
  212. package/rules/utils/singular.js +18 -0
  213. package/rules/utils/to-location.js +21 -0
  214. package/README.md +0 -5
@@ -0,0 +1,473 @@
1
+ 'use strict';
2
+ const {
3
+ isParenthesized,
4
+ isCommaToken,
5
+ isSemicolonToken,
6
+ isClosingParenToken,
7
+ findVariable,
8
+ hasSideEffect,
9
+ } = require('@eslint-community/eslint-utils');
10
+ const {extendFixRange} = require('./fix/index.js');
11
+ const needsSemicolon = require('./utils/needs-semicolon.js');
12
+ const shouldAddParenthesesToExpressionStatementExpression = require('./utils/should-add-parentheses-to-expression-statement-expression.js');
13
+ const shouldAddParenthesesToMemberExpressionObject = require('./utils/should-add-parentheses-to-member-expression-object.js');
14
+ const {getParentheses, getParenthesizedRange} = require('./utils/parentheses.js');
15
+ const isFunctionSelfUsedInside = require('./utils/is-function-self-used-inside.js');
16
+ const {isNodeMatches} = require('./utils/is-node-matches.js');
17
+ const assertToken = require('./utils/assert-token.js');
18
+ const {fixSpaceAroundKeyword, removeParentheses} = require('./fix/index.js');
19
+ const {isArrowFunctionBody, isMethodCall, isReferenceIdentifier, functionTypes} = require('./ast/index.js');
20
+
21
+ const MESSAGE_ID_ERROR = 'no-array-for-each/error';
22
+ const MESSAGE_ID_SUGGESTION = 'no-array-for-each/suggestion';
23
+ const messages = {
24
+ [MESSAGE_ID_ERROR]: 'Use `for…of` instead of `.forEach(…)`.',
25
+ [MESSAGE_ID_SUGGESTION]: 'Switch to `for…of`.',
26
+ };
27
+
28
+ const continueAbleNodeTypes = new Set([
29
+ 'WhileStatement',
30
+ 'DoWhileStatement',
31
+ 'ForStatement',
32
+ 'ForOfStatement',
33
+ 'ForInStatement',
34
+ ]);
35
+
36
+ const stripChainExpression = node =>
37
+ (node.parent.type === 'ChainExpression' && node.parent.expression === node)
38
+ ? node.parent
39
+ : node;
40
+
41
+ function isReturnStatementInContinueAbleNodes(returnStatement, callbackFunction) {
42
+ for (let node = returnStatement; node && node !== callbackFunction; node = node.parent) {
43
+ if (continueAbleNodeTypes.has(node.type)) {
44
+ return true;
45
+ }
46
+ }
47
+
48
+ return false;
49
+ }
50
+
51
+ function shouldSwitchReturnStatementToBlockStatement(returnStatement) {
52
+ const {parent} = returnStatement;
53
+
54
+ switch (parent.type) {
55
+ case 'IfStatement': {
56
+ return parent.consequent === returnStatement || parent.alternate === returnStatement;
57
+ }
58
+
59
+ // These parent's body need switch to `BlockStatement` too, but since they are "continueAble", won't fix
60
+ // case 'ForStatement':
61
+ // case 'ForInStatement':
62
+ // case 'ForOfStatement':
63
+ // case 'WhileStatement':
64
+ // case 'DoWhileStatement':
65
+ case 'WithStatement': {
66
+ return parent.body === returnStatement;
67
+ }
68
+
69
+ default: {
70
+ return false;
71
+ }
72
+ }
73
+ }
74
+
75
+ function getFixFunction(callExpression, functionInfo, context) {
76
+ const {sourceCode} = context;
77
+ const [callback] = callExpression.arguments;
78
+ const parameters = callback.params;
79
+ const iterableObject = callExpression.callee.object;
80
+ const {returnStatements} = functionInfo.get(callback);
81
+ const isOptionalObject = callExpression.callee.optional;
82
+ const ancestor = stripChainExpression(callExpression).parent;
83
+ const objectText = sourceCode.getText(iterableObject);
84
+
85
+ const getForOfLoopHeadText = () => {
86
+ const [elementText, indexText] = parameters.map(parameter => sourceCode.getText(parameter));
87
+ const shouldUseEntries = parameters.length === 2;
88
+
89
+ let text = 'for (';
90
+ text += isFunctionParameterVariableReassigned(callback, sourceCode) ? 'let' : 'const';
91
+ text += ' ';
92
+ text += shouldUseEntries ? `[${indexText}, ${elementText}]` : elementText;
93
+ text += ' of ';
94
+
95
+ const shouldAddParenthesesToObject
96
+ = isParenthesized(iterableObject, sourceCode)
97
+ || (
98
+ // `1?.forEach()` -> `(1).entries()`
99
+ isOptionalObject
100
+ && shouldUseEntries
101
+ && shouldAddParenthesesToMemberExpressionObject(iterableObject, sourceCode)
102
+ );
103
+
104
+ text += shouldAddParenthesesToObject ? `(${objectText})` : objectText;
105
+
106
+ if (shouldUseEntries) {
107
+ text += '.entries()';
108
+ }
109
+
110
+ text += ') ';
111
+
112
+ return text;
113
+ };
114
+
115
+ const getForOfLoopHeadRange = () => {
116
+ const [start] = callExpression.range;
117
+ const [end] = getParenthesizedRange(callback.body, sourceCode);
118
+ return [start, end];
119
+ };
120
+
121
+ function * replaceReturnStatement(returnStatement, fixer) {
122
+ const returnToken = sourceCode.getFirstToken(returnStatement);
123
+ assertToken(returnToken, {
124
+ expected: 'return',
125
+ ruleId: 'no-array-for-each',
126
+ });
127
+
128
+ if (!returnStatement.argument) {
129
+ yield fixer.replaceText(returnToken, 'continue');
130
+ return;
131
+ }
132
+
133
+ // Remove `return`
134
+ yield fixer.remove(returnToken);
135
+
136
+ const previousToken = sourceCode.getTokenBefore(returnToken);
137
+ const nextToken = sourceCode.getTokenAfter(returnToken);
138
+ let textBefore = '';
139
+ let textAfter = '';
140
+ const shouldAddParentheses
141
+ = !isParenthesized(returnStatement.argument, sourceCode)
142
+ && shouldAddParenthesesToExpressionStatementExpression(returnStatement.argument);
143
+ if (shouldAddParentheses) {
144
+ textBefore = `(${textBefore}`;
145
+ textAfter = `${textAfter})`;
146
+ }
147
+
148
+ const insertBraces = shouldSwitchReturnStatementToBlockStatement(returnStatement);
149
+ if (insertBraces) {
150
+ textBefore = `{ ${textBefore}`;
151
+ } else if (needsSemicolon(previousToken, sourceCode, shouldAddParentheses ? '(' : nextToken.value)) {
152
+ textBefore = `;${textBefore}`;
153
+ }
154
+
155
+ if (textBefore) {
156
+ yield fixer.insertTextBefore(nextToken, textBefore);
157
+ }
158
+
159
+ if (textAfter) {
160
+ yield fixer.insertTextAfter(returnStatement.argument, textAfter);
161
+ }
162
+
163
+ const returnStatementHasSemicolon = isSemicolonToken(sourceCode.getLastToken(returnStatement));
164
+ if (!returnStatementHasSemicolon) {
165
+ yield fixer.insertTextAfter(returnStatement, ';');
166
+ }
167
+
168
+ yield fixer.insertTextAfter(returnStatement, ' continue;');
169
+
170
+ if (insertBraces) {
171
+ yield fixer.insertTextAfter(returnStatement, ' }');
172
+ }
173
+ }
174
+
175
+ const shouldRemoveExpressionStatementLastToken = token => {
176
+ if (!isSemicolonToken(token)) {
177
+ return false;
178
+ }
179
+
180
+ if (callback.body.type !== 'BlockStatement') {
181
+ return false;
182
+ }
183
+
184
+ return true;
185
+ };
186
+
187
+ function * removeCallbackParentheses(fixer) {
188
+ // Opening parenthesis tokens already included in `getForOfLoopHeadRange`
189
+ const closingParenthesisTokens = getParentheses(callback, sourceCode)
190
+ .filter(token => isClosingParenToken(token));
191
+
192
+ for (const closingParenthesisToken of closingParenthesisTokens) {
193
+ yield fixer.remove(closingParenthesisToken);
194
+ }
195
+ }
196
+
197
+ return function * (fixer) {
198
+ // `(( foo.forEach(bar => bar) ))`
199
+ yield * removeParentheses(callExpression, fixer, sourceCode);
200
+
201
+ // Replace these with `for (const … of …) `
202
+ // foo.forEach(bar => bar)
203
+ // ^^^^^^^^^^^^^^^^^^^^^^
204
+ // foo.forEach(bar => (bar))
205
+ // ^^^^^^^^^^^^^^^^^^^^^^
206
+ // foo.forEach(bar => {})
207
+ // ^^^^^^^^^^^^^^^^^^^^^^
208
+ // foo.forEach(function(bar) {})
209
+ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
210
+ yield fixer.replaceTextRange(getForOfLoopHeadRange(), getForOfLoopHeadText());
211
+
212
+ // Parenthesized callback function
213
+ // foo.forEach( ((bar => {})) )
214
+ // ^^
215
+ yield * removeCallbackParentheses(fixer);
216
+
217
+ const [
218
+ penultimateToken,
219
+ lastToken,
220
+ ] = sourceCode.getLastTokens(callExpression, 2);
221
+
222
+ // The possible trailing comma token of `Array#forEach()` CallExpression
223
+ // foo.forEach(bar => {},)
224
+ // ^
225
+ if (isCommaToken(penultimateToken)) {
226
+ yield fixer.remove(penultimateToken);
227
+ }
228
+
229
+ // The closing parenthesis token of `Array#forEach()` CallExpression
230
+ // foo.forEach(bar => {})
231
+ // ^
232
+ yield fixer.remove(lastToken);
233
+
234
+ for (const returnStatement of returnStatements) {
235
+ yield * replaceReturnStatement(returnStatement, fixer);
236
+ }
237
+
238
+ if (ancestor.type === 'ExpressionStatement') {
239
+ const expressionStatementLastToken = sourceCode.getLastToken(ancestor);
240
+ // Remove semicolon if it's not needed anymore
241
+ // foo.forEach(bar => {});
242
+ // ^
243
+ if (shouldRemoveExpressionStatementLastToken(expressionStatementLastToken)) {
244
+ yield fixer.remove(expressionStatementLastToken, fixer);
245
+ }
246
+ } else if (ancestor.type === 'ArrowFunctionExpression') {
247
+ yield fixer.insertTextBefore(callExpression, '{ ');
248
+ yield fixer.insertTextAfter(callExpression, ' }');
249
+ }
250
+
251
+ yield * fixSpaceAroundKeyword(fixer, callExpression.parent, sourceCode);
252
+
253
+ if (isOptionalObject) {
254
+ yield fixer.insertTextBefore(callExpression, `if (${objectText}) `);
255
+ }
256
+
257
+ // Prevent possible variable conflicts
258
+ yield * extendFixRange(fixer, callExpression.parent.range);
259
+ };
260
+ }
261
+
262
+ const isChildScope = (child, parent) => {
263
+ for (let scope = child; scope; scope = scope.upper) {
264
+ if (scope === parent) {
265
+ return true;
266
+ }
267
+ }
268
+
269
+ return false;
270
+ };
271
+
272
+ function isFunctionParametersSafeToFix(callbackFunction, {sourceCode, scope, callExpression, allIdentifiers}) {
273
+ const variables = sourceCode.getDeclaredVariables(callbackFunction);
274
+
275
+ for (const variable of variables) {
276
+ if (variable.defs.length !== 1) {
277
+ return false;
278
+ }
279
+
280
+ const [definition] = variable.defs;
281
+ if (definition.type !== 'Parameter') {
282
+ continue;
283
+ }
284
+
285
+ const variableName = definition.name.name;
286
+ const [callExpressionStart, callExpressionEnd] = callExpression.range;
287
+ for (const identifier of allIdentifiers) {
288
+ const {name, range: [start, end]} = identifier;
289
+ if (
290
+ name !== variableName
291
+ || start < callExpressionStart
292
+ || end > callExpressionEnd
293
+ ) {
294
+ continue;
295
+ }
296
+
297
+ const variable = findVariable(scope, identifier);
298
+ if (!variable || variable.scope === scope || isChildScope(scope, variable.scope)) {
299
+ return false;
300
+ }
301
+ }
302
+ }
303
+
304
+ return true;
305
+ }
306
+
307
+ function isFunctionParameterVariableReassigned(callbackFunction, sourceCode) {
308
+ return sourceCode.getDeclaredVariables(callbackFunction)
309
+ .filter(variable => variable.defs[0].type === 'Parameter')
310
+ .some(variable =>
311
+ variable.references.some(reference => !reference.init && reference.isWrite()),
312
+ );
313
+ }
314
+
315
+ function isFixable(callExpression, {scope, functionInfo, allIdentifiers, sourceCode}) {
316
+ // Check `CallExpression`
317
+ if (callExpression.optional || callExpression.arguments.length !== 1) {
318
+ return false;
319
+ }
320
+
321
+ // Check ancestors, we only fix `ExpressionStatement`
322
+ const callOrChainExpression = stripChainExpression(callExpression);
323
+ if (
324
+ callOrChainExpression.parent.type !== 'ExpressionStatement'
325
+ && !isArrowFunctionBody(callOrChainExpression)
326
+ ) {
327
+ return false;
328
+ }
329
+
330
+ // Check `CallExpression.arguments[0]`;
331
+ const [callback] = callExpression.arguments;
332
+ if (
333
+ // Leave non-function type to `no-array-callback-reference` rule
334
+ (callback.type !== 'FunctionExpression' && callback.type !== 'ArrowFunctionExpression')
335
+ || callback.async
336
+ || callback.generator
337
+ ) {
338
+ return false;
339
+ }
340
+
341
+ // Check `callback.params`
342
+ const parameters = callback.params;
343
+ if (
344
+ !(parameters.length === 1 || parameters.length === 2)
345
+ // `array.forEach((element = defaultValue) => {})`
346
+ || (parameters.length === 1 && parameters[0].type === 'AssignmentPattern')
347
+ // https://github.com/sindresorhus/eslint-plugin-unicorn/issues/1814
348
+ || (parameters.length === 2 && parameters[1].type !== 'Identifier')
349
+ || parameters.some(({type, typeAnnotation}) => type === 'RestElement' || typeAnnotation)
350
+ || !isFunctionParametersSafeToFix(callback, {scope, callExpression, allIdentifiers, sourceCode})
351
+ ) {
352
+ return false;
353
+ }
354
+
355
+ // Check `ReturnStatement`s in `callback`
356
+ const {returnStatements, scope: callbackScope} = functionInfo.get(callback);
357
+ if (returnStatements.some(returnStatement => isReturnStatementInContinueAbleNodes(returnStatement, callback))) {
358
+ return false;
359
+ }
360
+
361
+ if (isFunctionSelfUsedInside(callback, callbackScope)) {
362
+ return false;
363
+ }
364
+
365
+ return true;
366
+ }
367
+
368
+ const ignoredObjects = [
369
+ 'React.Children',
370
+ 'Children',
371
+ 'R',
372
+ // https://www.npmjs.com/package/p-iteration
373
+ 'pIteration',
374
+ ];
375
+
376
+ /** @param {import('eslint').Rule.RuleContext} context */
377
+ const create = context => {
378
+ const functionStack = [];
379
+ const callExpressions = [];
380
+ const allIdentifiers = [];
381
+ const functionInfo = new Map();
382
+ const {sourceCode} = context;
383
+
384
+ context.on(functionTypes, node => {
385
+ functionStack.push(node);
386
+ functionInfo.set(node, {
387
+ returnStatements: [],
388
+ scope: sourceCode.getScope(node),
389
+ });
390
+ });
391
+
392
+ context.onExit(functionTypes, () => {
393
+ functionStack.pop();
394
+ });
395
+
396
+ context.on('Identifier', node => {
397
+ if (isReferenceIdentifier(node)) {
398
+ allIdentifiers.push(node);
399
+ }
400
+ });
401
+
402
+ context.on('ReturnStatement', node => {
403
+ const currentFunction = functionStack.at(-1);
404
+ if (!currentFunction) {
405
+ return;
406
+ }
407
+
408
+ const {returnStatements} = functionInfo.get(currentFunction);
409
+ returnStatements.push(node);
410
+ });
411
+
412
+ context.on('CallExpression', node => {
413
+ if (
414
+ !isMethodCall(node, {
415
+ method: 'forEach',
416
+ })
417
+ || isNodeMatches(node.callee.object, ignoredObjects)
418
+ ) {
419
+ return;
420
+ }
421
+
422
+ callExpressions.push({
423
+ node,
424
+ scope: sourceCode.getScope(node),
425
+ });
426
+ });
427
+
428
+ context.onExit('Program', function * () {
429
+ for (const {node, scope} of callExpressions) {
430
+ const iterable = node.callee;
431
+
432
+ const problem = {
433
+ node: iterable.property,
434
+ messageId: MESSAGE_ID_ERROR,
435
+ };
436
+
437
+ if (!isFixable(node, {scope, allIdentifiers, functionInfo, sourceCode})) {
438
+ yield problem;
439
+ continue;
440
+ }
441
+
442
+ const shouldUseSuggestion = iterable.optional && hasSideEffect(iterable, sourceCode);
443
+ const fix = getFixFunction(node, functionInfo, context);
444
+
445
+ if (shouldUseSuggestion) {
446
+ problem.suggest = [
447
+ {
448
+ messageId: MESSAGE_ID_SUGGESTION,
449
+ fix,
450
+ },
451
+ ];
452
+ } else {
453
+ problem.fix = fix;
454
+ }
455
+
456
+ yield problem;
457
+ }
458
+ });
459
+ };
460
+
461
+ /** @type {import('eslint').Rule.RuleModule} */
462
+ module.exports = {
463
+ create,
464
+ meta: {
465
+ type: 'suggestion',
466
+ docs: {
467
+ description: 'Prefer `for…of` over the `forEach` method.',
468
+ },
469
+ fixable: 'code',
470
+ hasSuggestions: true,
471
+ messages,
472
+ },
473
+ };
@@ -0,0 +1,188 @@
1
+ 'use strict';
2
+ const {hasSideEffect} = require('@eslint-community/eslint-utils');
3
+ const {removeArgument} = require('./fix/index.js');
4
+ const {getParentheses, getParenthesizedText} = require('./utils/parentheses.js');
5
+ const shouldAddParenthesesToMemberExpressionObject = require('./utils/should-add-parentheses-to-member-expression-object.js');
6
+ const {isNodeMatches} = require('./utils/is-node-matches.js');
7
+ const {isNodeValueNotFunction} = require('./utils/index.js');
8
+ const {isMethodCall} = require('./ast/index.js');
9
+
10
+ const ERROR = 'error';
11
+ const SUGGESTION_BIND = 'suggestion-bind';
12
+ const SUGGESTION_REMOVE = 'suggestion-remove';
13
+ const messages = {
14
+ [ERROR]: 'Do not use the `this` argument in `Array#{{method}}()`.',
15
+ [SUGGESTION_REMOVE]: 'Remove the second argument.',
16
+ [SUGGESTION_BIND]: 'Use a bound function.',
17
+ };
18
+
19
+ const ignored = [
20
+ 'lodash.every',
21
+ '_.every',
22
+ 'underscore.every',
23
+
24
+ 'lodash.filter',
25
+ '_.filter',
26
+ 'underscore.filter',
27
+ 'Vue.filter',
28
+ 'R.filter',
29
+
30
+ 'lodash.find',
31
+ '_.find',
32
+ 'underscore.find',
33
+ 'R.find',
34
+
35
+ 'lodash.findLast',
36
+ '_.findLast',
37
+ 'underscore.findLast',
38
+ 'R.findLast',
39
+
40
+ 'lodash.findIndex',
41
+ '_.findIndex',
42
+ 'underscore.findIndex',
43
+ 'R.findIndex',
44
+
45
+ 'lodash.findLastIndex',
46
+ '_.findLastIndex',
47
+ 'underscore.findLastIndex',
48
+ 'R.findLastIndex',
49
+
50
+ 'lodash.flatMap',
51
+ '_.flatMap',
52
+
53
+ 'lodash.forEach',
54
+ '_.forEach',
55
+ 'React.Children.forEach',
56
+ 'Children.forEach',
57
+ 'R.forEach',
58
+
59
+ 'lodash.map',
60
+ '_.map',
61
+ 'underscore.map',
62
+ 'React.Children.map',
63
+ 'Children.map',
64
+ 'jQuery.map',
65
+ '$.map',
66
+ 'R.map',
67
+
68
+ 'lodash.some',
69
+ '_.some',
70
+ 'underscore.some',
71
+ ];
72
+
73
+ function removeThisArgument(callExpression, sourceCode) {
74
+ return fixer => removeArgument(fixer, callExpression.arguments[1], sourceCode);
75
+ }
76
+
77
+ function useBoundFunction(callExpression, sourceCode) {
78
+ return function * (fixer) {
79
+ yield removeThisArgument(callExpression, sourceCode)(fixer);
80
+
81
+ const [callback, thisArgument] = callExpression.arguments;
82
+
83
+ const callbackParentheses = getParentheses(callback, sourceCode);
84
+ const isParenthesized = callbackParentheses.length > 0;
85
+ const callbackLastToken = isParenthesized
86
+ ? callbackParentheses.at(-1)
87
+ : callback;
88
+ if (
89
+ !isParenthesized
90
+ && shouldAddParenthesesToMemberExpressionObject(callback, sourceCode)
91
+ ) {
92
+ yield fixer.insertTextBefore(callbackLastToken, '(');
93
+ yield fixer.insertTextAfter(callbackLastToken, ')');
94
+ }
95
+
96
+ const thisArgumentText = getParenthesizedText(thisArgument, sourceCode);
97
+ // `thisArgument` was a argument, no need add extra parentheses
98
+ yield fixer.insertTextAfter(callbackLastToken, `.bind(${thisArgumentText})`);
99
+ };
100
+ }
101
+
102
+ /** @param {import('eslint').Rule.RuleContext} context */
103
+ const create = context => {
104
+ const {sourceCode} = context;
105
+
106
+ return {
107
+ CallExpression(callExpression) {
108
+ if (
109
+ !isMethodCall(callExpression, {
110
+ methods: [
111
+ 'every',
112
+ 'filter',
113
+ 'find',
114
+ 'findLast',
115
+ 'findIndex',
116
+ 'findLastIndex',
117
+ 'flatMap',
118
+ 'forEach',
119
+ 'map',
120
+ 'some',
121
+ ],
122
+ argumentsLength: 2,
123
+ optionalCall: false,
124
+ optionalMember: false,
125
+ })
126
+ || isNodeMatches(callExpression.callee, ignored)
127
+ || isNodeValueNotFunction(callExpression.arguments[0])
128
+ ) {
129
+ return;
130
+ }
131
+
132
+ const {callee} = callExpression;
133
+ const method = callee.property.name;
134
+ const [callback, thisArgument] = callExpression.arguments;
135
+
136
+ const problem = {
137
+ node: thisArgument,
138
+ messageId: ERROR,
139
+ data: {method},
140
+ };
141
+
142
+ const thisArgumentHasSideEffect = hasSideEffect(thisArgument, sourceCode);
143
+ const isArrowCallback = callback.type === 'ArrowFunctionExpression';
144
+
145
+ if (isArrowCallback) {
146
+ if (thisArgumentHasSideEffect) {
147
+ problem.suggest = [
148
+ {
149
+ messageId: SUGGESTION_REMOVE,
150
+ fix: removeThisArgument(callExpression, sourceCode),
151
+ },
152
+ ];
153
+ } else {
154
+ problem.fix = removeThisArgument(callExpression, sourceCode);
155
+ }
156
+
157
+ return problem;
158
+ }
159
+
160
+ problem.suggest = [
161
+ {
162
+ messageId: SUGGESTION_REMOVE,
163
+ fix: removeThisArgument(callExpression, sourceCode),
164
+ },
165
+ {
166
+ messageId: SUGGESTION_BIND,
167
+ fix: useBoundFunction(callExpression, sourceCode),
168
+ },
169
+ ];
170
+
171
+ return problem;
172
+ },
173
+ };
174
+ };
175
+
176
+ /** @type {import('eslint').Rule.RuleModule} */
177
+ module.exports = {
178
+ create,
179
+ meta: {
180
+ type: 'suggestion',
181
+ docs: {
182
+ description: 'Disallow using the `this` argument in array methods.',
183
+ },
184
+ fixable: 'code',
185
+ hasSuggestions: true,
186
+ messages,
187
+ },
188
+ };