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.
- package/configs/all.js +6 -0
- package/configs/flat-config-base.js +10 -0
- package/configs/legacy-config-base.js +10 -0
- package/configs/recommended.js +117 -0
- package/index.js +91 -0
- package/license +9 -0
- package/package.json +186 -4
- package/readme.md +356 -0
- package/rules/ast/call-or-new-expression.js +127 -0
- package/rules/ast/function-types.js +5 -0
- package/rules/ast/index.js +39 -0
- package/rules/ast/is-arrow-function-body.js +7 -0
- package/rules/ast/is-empty-node.js +20 -0
- package/rules/ast/is-expression-statement.js +11 -0
- package/rules/ast/is-function.js +8 -0
- package/rules/ast/is-member-expression.js +101 -0
- package/rules/ast/is-method-call.js +65 -0
- package/rules/ast/is-reference-identifier.js +156 -0
- package/rules/ast/is-static-require.js +14 -0
- package/rules/ast/is-undefined.js +7 -0
- package/rules/ast/literal.js +29 -0
- package/rules/better-regex.js +144 -0
- package/rules/catch-error-name.js +136 -0
- package/rules/consistent-destructuring.js +168 -0
- package/rules/consistent-function-scoping.js +223 -0
- package/rules/custom-error-definition.js +215 -0
- package/rules/empty-brace-spaces.js +72 -0
- package/rules/error-message.js +104 -0
- package/rules/escape-case.js +63 -0
- package/rules/expiring-todo-comments.js +580 -0
- package/rules/explicit-length-check.js +229 -0
- package/rules/filename-case.js +258 -0
- package/rules/fix/add-parenthesizes-to-return-or-throw-expression.js +21 -0
- package/rules/fix/append-argument.js +20 -0
- package/rules/fix/extend-fix-range.js +15 -0
- package/rules/fix/fix-space-around-keywords.js +35 -0
- package/rules/fix/index.js +23 -0
- package/rules/fix/remove-argument.js +32 -0
- package/rules/fix/remove-member-expression-property.js +11 -0
- package/rules/fix/remove-method-call.js +20 -0
- package/rules/fix/remove-parentheses.js +11 -0
- package/rules/fix/remove-spaces-after.js +14 -0
- package/rules/fix/rename-variable.js +9 -0
- package/rules/fix/replace-argument.js +8 -0
- package/rules/fix/replace-node-or-token-and-spaces-before.js +21 -0
- package/rules/fix/replace-reference-identifier.js +35 -0
- package/rules/fix/replace-string-literal.js +11 -0
- package/rules/fix/replace-string-raw.js +14 -0
- package/rules/fix/replace-template-element.js +11 -0
- package/rules/fix/switch-call-expression-to-new-expression.js +18 -0
- package/rules/fix/switch-new-expression-to-call-expression.js +34 -0
- package/rules/import-style.js +364 -0
- package/rules/new-for-builtins.js +85 -0
- package/rules/no-abusive-eslint-disable.js +48 -0
- package/rules/no-array-callback-reference.js +256 -0
- package/rules/no-array-for-each.js +473 -0
- package/rules/no-array-method-this-argument.js +188 -0
- package/rules/no-array-push-push.js +144 -0
- package/rules/no-array-reduce.js +126 -0
- package/rules/no-await-expression-member.js +90 -0
- package/rules/no-console-spaces.js +86 -0
- package/rules/no-document-cookie.js +25 -0
- package/rules/no-empty-file.js +57 -0
- package/rules/no-for-loop.js +427 -0
- package/rules/no-hex-escape.js +46 -0
- package/rules/no-instanceof-array.js +65 -0
- package/rules/no-invalid-remove-event-listener.js +60 -0
- package/rules/no-keyword-prefix.js +199 -0
- package/rules/no-lonely-if.js +151 -0
- package/rules/no-negated-condition.js +144 -0
- package/rules/no-nested-ternary.js +58 -0
- package/rules/no-new-array.js +104 -0
- package/rules/no-new-buffer.js +98 -0
- package/rules/no-null.js +153 -0
- package/rules/no-object-as-default-parameter.js +50 -0
- package/rules/no-process-exit.js +104 -0
- package/rules/no-static-only-class.js +224 -0
- package/rules/no-thenable.js +198 -0
- package/rules/no-this-assignment.js +38 -0
- package/rules/no-typeof-undefined.js +143 -0
- package/rules/no-unnecessary-await.js +107 -0
- package/rules/no-unnecessary-polyfills.js +176 -0
- package/rules/no-unreadable-array-destructuring.js +83 -0
- package/rules/no-unreadable-iife.js +45 -0
- package/rules/no-unused-properties.js +238 -0
- package/rules/no-useless-fallback-in-spread.js +68 -0
- package/rules/no-useless-length-check.js +152 -0
- package/rules/no-useless-promise-resolve-reject.js +212 -0
- package/rules/no-useless-spread.js +381 -0
- package/rules/no-useless-switch-case.js +71 -0
- package/rules/no-useless-undefined.js +301 -0
- package/rules/no-zero-fractions.js +79 -0
- package/rules/number-literal-case.js +52 -0
- package/rules/numeric-separators-style.js +181 -0
- package/rules/prefer-add-event-listener.js +188 -0
- package/rules/prefer-array-find.js +423 -0
- package/rules/prefer-array-flat-map.js +82 -0
- package/rules/prefer-array-flat.js +279 -0
- package/rules/prefer-array-index-of.js +32 -0
- package/rules/prefer-array-some.js +157 -0
- package/rules/prefer-at.js +374 -0
- package/rules/prefer-blob-reading-methods.js +45 -0
- package/rules/prefer-code-point.js +67 -0
- package/rules/prefer-date-now.js +135 -0
- package/rules/prefer-default-parameters.js +219 -0
- package/rules/prefer-dom-node-append.js +48 -0
- package/rules/prefer-dom-node-dataset.js +120 -0
- package/rules/prefer-dom-node-remove.js +122 -0
- package/rules/prefer-dom-node-text-content.js +75 -0
- package/rules/prefer-event-target.js +117 -0
- package/rules/prefer-export-from.js +413 -0
- package/rules/prefer-includes.js +98 -0
- package/rules/prefer-json-parse-buffer.js +159 -0
- package/rules/prefer-keyboard-event-key.js +186 -0
- package/rules/prefer-logical-operator-over-ternary.js +159 -0
- package/rules/prefer-math-trunc.js +109 -0
- package/rules/prefer-modern-dom-apis.js +141 -0
- package/rules/prefer-modern-math-apis.js +212 -0
- package/rules/prefer-module.js +349 -0
- package/rules/prefer-native-coercion-functions.js +185 -0
- package/rules/prefer-negative-index.js +213 -0
- package/rules/prefer-node-protocol.js +61 -0
- package/rules/prefer-number-properties.js +126 -0
- package/rules/prefer-object-from-entries.js +252 -0
- package/rules/prefer-optional-catch-binding.js +75 -0
- package/rules/prefer-prototype-methods.js +88 -0
- package/rules/prefer-query-selector.js +135 -0
- package/rules/prefer-reflect-apply.js +97 -0
- package/rules/prefer-regexp-test.js +156 -0
- package/rules/prefer-set-has.js +186 -0
- package/rules/prefer-set-size.js +103 -0
- package/rules/prefer-spread.js +529 -0
- package/rules/prefer-string-replace-all.js +145 -0
- package/rules/prefer-string-slice.js +182 -0
- package/rules/prefer-string-starts-ends-with.js +199 -0
- package/rules/prefer-string-trim-start-end.js +44 -0
- package/rules/prefer-switch.js +344 -0
- package/rules/prefer-ternary.js +282 -0
- package/rules/prefer-top-level-await.js +152 -0
- package/rules/prefer-type-error.js +151 -0
- package/rules/prevent-abbreviations.js +645 -0
- package/rules/relative-url-style.js +168 -0
- package/rules/require-array-join-separator.js +63 -0
- package/rules/require-number-to-fixed-digits-argument.js +54 -0
- package/rules/require-post-message-target-origin.js +71 -0
- package/rules/shared/abbreviations.js +262 -0
- package/rules/shared/dom-events.js +275 -0
- package/rules/shared/event-keys.js +52 -0
- package/rules/shared/negative-index.js +46 -0
- package/rules/shared/simple-array-search-rule.js +128 -0
- package/rules/shared/typed-array.js +16 -0
- package/rules/string-content.js +187 -0
- package/rules/switch-case-braces.js +109 -0
- package/rules/template-indent.js +219 -0
- package/rules/text-encoding-identifier-case.js +108 -0
- package/rules/throw-new-error.js +53 -0
- package/rules/utils/array-or-object-prototype-property.js +63 -0
- package/rules/utils/assert-token.js +32 -0
- package/rules/utils/avoid-capture.js +146 -0
- package/rules/utils/boolean.js +92 -0
- package/rules/utils/builtins.js +36 -0
- package/rules/utils/cartesian-product-samples.js +24 -0
- package/rules/utils/create-deprecated-rules.js +25 -0
- package/rules/utils/escape-string.js +26 -0
- package/rules/utils/escape-template-element-raw.js +6 -0
- package/rules/utils/get-ancestor.js +20 -0
- package/rules/utils/get-builtin-rule.js +7 -0
- package/rules/utils/get-call-expression-arguments-text.js +21 -0
- package/rules/utils/get-class-head-location.js +22 -0
- package/rules/utils/get-documentation-url.js +10 -0
- package/rules/utils/get-indent-string.js +11 -0
- package/rules/utils/get-previous-node.js +24 -0
- package/rules/utils/get-references.js +9 -0
- package/rules/utils/get-scopes.js +14 -0
- package/rules/utils/get-switch-case-head-location.js +21 -0
- package/rules/utils/get-variable-identifiers.js +7 -0
- package/rules/utils/global-reference-tracker.js +72 -0
- package/rules/utils/has-optional-chain-element.js +21 -0
- package/rules/utils/has-same-range.js +7 -0
- package/rules/utils/index.js +53 -0
- package/rules/utils/is-function-self-used-inside.js +43 -0
- package/rules/utils/is-left-hand-side.js +22 -0
- package/rules/utils/is-logical-expression.js +16 -0
- package/rules/utils/is-method-named.js +9 -0
- package/rules/utils/is-new-expression-with-parentheses.js +26 -0
- package/rules/utils/is-node-matches.js +53 -0
- package/rules/utils/is-node-value-not-dom-node.js +21 -0
- package/rules/utils/is-node-value-not-function.js +42 -0
- package/rules/utils/is-number.js +224 -0
- package/rules/utils/is-object-method.js +11 -0
- package/rules/utils/is-on-same-line.js +7 -0
- package/rules/utils/is-same-identifier.js +8 -0
- package/rules/utils/is-same-reference.js +173 -0
- package/rules/utils/is-shadowed.js +33 -0
- package/rules/utils/is-shorthand-export-local.js +9 -0
- package/rules/utils/is-shorthand-import-local.js +9 -0
- package/rules/utils/is-shorthand-property-assignment-pattern-left.js +10 -0
- package/rules/utils/is-shorthand-property-value.js +8 -0
- package/rules/utils/is-value-not-usable.js +5 -0
- package/rules/utils/lodash.js +1589 -0
- package/rules/utils/needs-semicolon.js +114 -0
- package/rules/utils/numeric.js +53 -0
- package/rules/utils/parentheses.js +73 -0
- package/rules/utils/resolve-variable-name.js +20 -0
- package/rules/utils/rule.js +190 -0
- package/rules/utils/should-add-parentheses-to-conditional-expression-child.js +17 -0
- package/rules/utils/should-add-parentheses-to-expression-statement-expression.js +26 -0
- package/rules/utils/should-add-parentheses-to-logical-expression-child.js +47 -0
- package/rules/utils/should-add-parentheses-to-member-expression-object.js +47 -0
- package/rules/utils/should-add-parentheses-to-new-expression-callee.js +32 -0
- package/rules/utils/should-add-parentheses-to-spread-element-argument.js +22 -0
- package/rules/utils/singular.js +18 -0
- package/rules/utils/to-location.js +21 -0
- 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
|
+
};
|