eslint-plugin-unicorn 69.0.0 → 71.0.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.
Files changed (124) hide show
  1. package/package.json +26 -25
  2. package/readme.md +15 -1
  3. package/rules/ast/call-or-new-expression.js +2 -8
  4. package/rules/ast/is-member-expression.js +2 -8
  5. package/rules/comment-content.js +4 -4
  6. package/rules/consistent-boolean-name.js +727 -42
  7. package/rules/consistent-compound-words.js +1 -2
  8. package/rules/consistent-json-file-read.js +2 -5
  9. package/rules/dom-node-dataset.js +3 -7
  10. package/rules/escape-case.js +4 -4
  11. package/rules/expiring-todo-comments.js +25 -2
  12. package/rules/explicit-length-check.js +3 -3
  13. package/rules/filename-case.js +4 -4
  14. package/rules/fix/{add-parenthesizes-to-return-or-throw-expression.js → add-parentheses-to-return-or-throw-expression.js} +1 -1
  15. package/rules/fix/index.js +1 -1
  16. package/rules/fix/remove-argument.js +34 -8
  17. package/rules/fix/remove-statement.js +1 -3
  18. package/rules/fix/switch-new-expression-to-call-expression.js +2 -2
  19. package/rules/id-match.js +2 -2
  20. package/rules/index.js +10 -0
  21. package/rules/isolated-functions.js +203 -19
  22. package/rules/name-replacements.js +1 -2
  23. package/rules/no-anonymous-default-export.js +1 -3
  24. package/rules/no-array-callback-reference.js +2 -10
  25. package/rules/no-async-promise-finally.js +111 -0
  26. package/rules/no-chained-comparison.js +1 -1
  27. package/rules/no-collection-bracket-access.js +27 -4
  28. package/rules/no-computed-property-existence-check.js +1 -4
  29. package/rules/no-declarations-before-early-exit.js +6 -5
  30. package/rules/no-error-property-assignment.js +3 -3
  31. package/rules/no-for-loop.js +4 -4
  32. package/rules/no-keyword-prefix.js +2 -2
  33. package/rules/no-manually-wrapped-comments.js +5 -1
  34. package/rules/no-mismatched-map-key.js +13 -20
  35. package/rules/no-named-default.js +2 -5
  36. package/rules/no-negated-array-predicate.js +6 -13
  37. package/rules/no-negated-comparison.js +31 -71
  38. package/rules/no-negated-condition.js +2 -2
  39. package/rules/no-negation-in-equality-check.js +4 -7
  40. package/rules/no-non-function-verb-prefix.js +25 -2
  41. package/rules/no-process-exit.js +6 -6
  42. package/rules/no-return-array-push.js +9 -2
  43. package/rules/no-static-only-class.js +2 -8
  44. package/rules/no-thenable.js +9 -15
  45. package/rules/no-typeof-undefined.js +2 -2
  46. package/rules/no-undeclared-class-members.js +1 -5
  47. package/rules/no-unnecessary-array-flat-map.js +308 -0
  48. package/rules/no-unnecessary-await.js +2 -2
  49. package/rules/no-unnecessary-fetch-options.js +624 -0
  50. package/rules/no-unnecessary-global-this.js +5 -9
  51. package/rules/no-unsafe-promise-all-settled-values.js +865 -0
  52. package/rules/no-unused-properties.js +0 -1
  53. package/rules/no-useless-coercion.js +1 -2
  54. package/rules/no-useless-else.js +32 -1
  55. package/rules/no-useless-iterator-to-array.js +42 -16
  56. package/rules/no-useless-spread.js +2 -2
  57. package/rules/no-useless-undefined.js +2 -2
  58. package/rules/prefer-abort-signal-any.js +1391 -0
  59. package/rules/prefer-array-find.js +2 -5
  60. package/rules/prefer-array-flat-map.js +6 -11
  61. package/rules/prefer-array-from-range.js +98 -0
  62. package/rules/prefer-bigint-literals.js +1 -0
  63. package/rules/prefer-block-statement-over-iife.js +159 -0
  64. package/rules/prefer-boolean-return.js +20 -3
  65. package/rules/prefer-continue.js +6 -2
  66. package/rules/prefer-default-parameters.js +2 -2
  67. package/rules/prefer-early-return.js +3 -1
  68. package/rules/prefer-else-if.js +5 -9
  69. package/rules/prefer-group-by.js +576 -0
  70. package/rules/prefer-has-check.js +6 -3
  71. package/rules/prefer-https.js +3 -3
  72. package/rules/prefer-identifier-import-export-specifiers.js +1 -3
  73. package/rules/prefer-iterator-helpers.js +196 -0
  74. package/rules/prefer-minimal-ternary.js +4 -4
  75. package/rules/prefer-number-coercion.js +1 -1
  76. package/rules/prefer-object-define-properties.js +1 -3
  77. package/rules/prefer-object-iterable-methods.js +57 -14
  78. package/rules/prefer-observer-apis.js +2 -5
  79. package/rules/prefer-private-class-fields.js +1 -3
  80. package/rules/prefer-simple-sort-comparator.js +6 -6
  81. package/rules/prefer-simplified-conditions.js +584 -0
  82. package/rules/prefer-single-object-destructuring.js +2 -5
  83. package/rules/prefer-single-replace.js +2 -2
  84. package/rules/prefer-spread.js +25 -32
  85. package/rules/prefer-string-pad-start-end.js +1 -6
  86. package/rules/prefer-string-raw.js +2 -2
  87. package/rules/prefer-string-repeat.js +16 -9
  88. package/rules/prefer-string-replace-all.js +1 -1
  89. package/rules/prefer-ternary.js +5 -5
  90. package/rules/prefer-toggle-attribute.js +3 -3
  91. package/rules/prefer-top-level-await.js +6 -1
  92. package/rules/prefer-uint8array-base64.js +3 -1
  93. package/rules/require-proxy-trap-boolean-return.js +2 -2
  94. package/rules/rule/to-eslint-create.js +1 -2
  95. package/rules/rule/to-eslint-listener.js +1 -3
  96. package/rules/rule/to-eslint-problem.js +1 -3
  97. package/rules/rule/to-eslint-rule-fixer.js +1 -3
  98. package/rules/rule/to-eslint-rule.js +1 -3
  99. package/rules/rule/to-eslint-rules.js +1 -3
  100. package/rules/rule/unicorn-context.js +1 -3
  101. package/rules/rule/unicorn-listeners.js +1 -3
  102. package/rules/shared/array-range.js +66 -0
  103. package/rules/shared/iterator-helpers.js +43 -1
  104. package/rules/shared/no-array-mutate-rule.js +1 -3
  105. package/rules/shared/regexp-escape.js +4 -4
  106. package/rules/utils/can-tokens-be-adjacent.js +95 -0
  107. package/rules/utils/comparison.js +65 -0
  108. package/rules/utils/escape-string.js +2 -8
  109. package/rules/utils/get-available-variable-name.js +2 -13
  110. package/rules/utils/get-precedence.js +112 -0
  111. package/rules/utils/get-token-store.js +1 -3
  112. package/rules/utils/global-reference-tracker.js +3 -6
  113. package/rules/utils/index.js +4 -0
  114. package/rules/utils/is-boolean.js +103 -30
  115. package/rules/utils/is-identifier-name.js +5 -0
  116. package/rules/utils/is-promise-type.js +1 -3
  117. package/rules/utils/is-react-hook-name.js +5 -0
  118. package/rules/utils/parentheses/get-parent-syntax-opening-parenthesis.js +1 -3
  119. package/rules/utils/parentheses/iterate-surrounding-parentheses.js +1 -1
  120. package/rules/utils/should-add-parentheses-to-await-expression-argument.js +4 -9
  121. package/rules/utils/should-add-parentheses-to-call-expression-callee.js +6 -12
  122. package/rules/utils/should-add-parentheses-to-unary-expression.js +7 -14
  123. package/rules/utils/to-location.js +1 -3
  124. package/rules/utils/track-branch-exits.js +8 -7
@@ -179,11 +179,7 @@ const getDeclaredClassMemberNames = (classBody, sourceCode) => {
179
179
  const names = new Set(['constructor']);
180
180
 
181
181
  for (const member of classBody.body) {
182
- if (!(
183
- classMemberTypes.has(member.type)
184
- && member.static !== true
185
- && member.kind !== 'constructor'
186
- )) {
182
+ if (!classMemberTypes.has(member.type) || member.static === true || member.kind === 'constructor') {
187
183
  continue;
188
184
  }
189
185
 
@@ -0,0 +1,308 @@
1
+ import {hasSideEffect} from '@eslint-community/eslint-utils';
2
+ import {isEmptyArrayExpression, isMethodCall} from './ast/index.js';
3
+ import {
4
+ getParenthesizedText,
5
+ isKnownNonArray,
6
+ isParenthesized,
7
+ isSameIdentifier,
8
+ isTypeScriptFile,
9
+ shouldAddParenthesesToMemberExpressionObject,
10
+ wouldRemoveComments,
11
+ } from './utils/index.js';
12
+
13
+ const MESSAGE_ID = 'no-unnecessary-array-flat-map';
14
+ const SUGGESTION_ID_FILTER_MAP = 'no-unnecessary-array-flat-map/filter-map-suggestion';
15
+
16
+ const messages = {
17
+ [MESSAGE_ID]: 'Prefer `.{{method}}(…)` over `.flatMap(…)` for this single-item array callback.',
18
+ [SUGGESTION_ID_FILTER_MAP]: 'Replace `.flatMap(…)` with `.filter(…).map(…)`.',
19
+ };
20
+
21
+ const arrowBodyNeedsParenthesesTypes = new Set([
22
+ 'ObjectExpression',
23
+ 'SequenceExpression',
24
+ ]);
25
+
26
+ const typescriptExpressionWrapperTypes = new Set([
27
+ 'TSAsExpression',
28
+ 'TSNonNullExpression',
29
+ 'TSSatisfiesExpression',
30
+ 'TSTypeAssertion',
31
+ ]);
32
+
33
+ const hasTypeArguments = node => node.typeArguments || node.typeParameters;
34
+
35
+ const isFilterCallExpression = node => isMethodCall(node, {
36
+ method: 'filter',
37
+ argumentsLength: 1,
38
+ optionalCall: false,
39
+ optionalMember: false,
40
+ computed: false,
41
+ });
42
+
43
+ const isSimpleSingleParameterArrowCallback = node =>
44
+ node.type === 'ArrowFunctionExpression'
45
+ && !node.async
46
+ && !node.returnType
47
+ && !node.typeParameters
48
+ && node.params.length === 1
49
+ && node.params[0].type === 'Identifier'
50
+ && !node.params[0].optional
51
+ && node.body.type !== 'BlockStatement';
52
+
53
+ const getSingleArrayElement = node => {
54
+ if (
55
+ node.type !== 'ArrayExpression'
56
+ || node.elements.length !== 1
57
+ || !node.elements[0]
58
+ || node.elements[0].type === 'SpreadElement'
59
+ ) {
60
+ return;
61
+ }
62
+
63
+ return node.elements[0];
64
+ };
65
+
66
+ function shouldParenthesizeArrowBody(node, context) {
67
+ if (isParenthesized(node, context)) {
68
+ return false;
69
+ }
70
+
71
+ if (arrowBodyNeedsParenthesesTypes.has(node.type)) {
72
+ return true;
73
+ }
74
+
75
+ return typescriptExpressionWrapperTypes.has(node.type) && shouldParenthesizeArrowBody(node.expression, context);
76
+ }
77
+
78
+ function getCallbackResult(callback) {
79
+ const directElement = getSingleArrayElement(callback.body);
80
+ if (directElement) {
81
+ return {
82
+ type: 'map',
83
+ element: directElement,
84
+ arrayExpression: callback.body,
85
+ };
86
+ }
87
+
88
+ if (callback.body.type !== 'ConditionalExpression') {
89
+ return;
90
+ }
91
+
92
+ const {test, consequent, alternate} = callback.body;
93
+ const element = getSingleArrayElement(consequent);
94
+ if (!element || !isEmptyArrayExpression(alternate)) {
95
+ return;
96
+ }
97
+
98
+ return {
99
+ type: 'conditional',
100
+ test,
101
+ element,
102
+ arrayExpression: consequent,
103
+ };
104
+ }
105
+
106
+ function getArrowBodyText(node, context) {
107
+ if (isParenthesized(node, context)) {
108
+ return getParenthesizedText(node, context);
109
+ }
110
+
111
+ const text = context.sourceCode.getText(node);
112
+ return shouldParenthesizeArrowBody(node, context)
113
+ ? `(${text})`
114
+ : text;
115
+ }
116
+
117
+ function getMemberExpressionObjectText(node, context) {
118
+ if (node.type === 'Super') {
119
+ return 'super';
120
+ }
121
+
122
+ if (isParenthesized(node, context)) {
123
+ return getParenthesizedText(node, context);
124
+ }
125
+
126
+ const text = context.sourceCode.getText(node);
127
+ return shouldAddParenthesesToMemberExpressionObject(node, context) ? `(${text})` : text;
128
+ }
129
+
130
+ function getArrowParameterText(callback, context) {
131
+ const parameterText = context.sourceCode.getText(callback.params[0]);
132
+ return callback.params[0].typeAnnotation ? `(${parameterText})` : parameterText;
133
+ }
134
+
135
+ function getFilterMapSuggestion(flatMapCallExpression, callback, callbackResult, context) {
136
+ if (
137
+ hasTypeArguments(flatMapCallExpression)
138
+ || callback.params[0].typeAnnotation
139
+ || wouldRemoveComments(context, flatMapCallExpression, [
140
+ flatMapCallExpression.callee.object,
141
+ callbackResult.test,
142
+ callbackResult.element,
143
+ ])
144
+ || hasSideEffect(callbackResult.test, context.sourceCode)
145
+ || hasSideEffect(callbackResult.element, context.sourceCode)
146
+ ) {
147
+ return;
148
+ }
149
+
150
+ const arrayText = getMemberExpressionObjectText(flatMapCallExpression.callee.object, context);
151
+ const parameterText = getArrowParameterText(callback, context);
152
+ const testText = getArrowBodyText(callbackResult.test, context);
153
+ const elementText = getArrowBodyText(callbackResult.element, context);
154
+
155
+ return {
156
+ messageId: SUGGESTION_ID_FILTER_MAP,
157
+ fix: fixer => fixer.replaceText(
158
+ flatMapCallExpression,
159
+ `${arrayText}.filter(${parameterText} => ${testText}).map(${parameterText} => ${elementText})`,
160
+ ),
161
+ };
162
+ }
163
+
164
+ function getProblemForFilterFlatMap(flatMapCallExpression, callbackResult, context) {
165
+ const filterCallExpression = flatMapCallExpression.callee.object;
166
+ if (!isFilterCallExpression(filterCallExpression)) {
167
+ return;
168
+ }
169
+
170
+ const problem = {
171
+ node: flatMapCallExpression.callee.property,
172
+ messageId: MESSAGE_ID,
173
+ data: {method: 'map'},
174
+ };
175
+
176
+ if (
177
+ hasTypeArguments(filterCallExpression)
178
+ || hasTypeArguments(flatMapCallExpression)
179
+ || isKnownNonArray(filterCallExpression.callee.object, context)
180
+ || wouldRemoveComments(context, callbackResult.arrayExpression, [callbackResult.element])
181
+ ) {
182
+ return problem;
183
+ }
184
+
185
+ return {
186
+ ...problem,
187
+ * fix(fixer) {
188
+ yield fixer.replaceText(flatMapCallExpression.callee.property, 'map');
189
+ yield fixer.replaceText(callbackResult.arrayExpression, getArrowBodyText(callbackResult.element, context));
190
+ },
191
+ };
192
+ }
193
+
194
+ function getProblemForConditionalFlatMap(flatMapCallExpression, callback, callbackResult, context) {
195
+ if (isTypeScriptFile(context.physicalFilename)) {
196
+ return;
197
+ }
198
+
199
+ const method = isSameIdentifier(callbackResult.element, callback.params[0]) ? 'filter' : 'filter().map';
200
+ const problem = {
201
+ node: flatMapCallExpression.callee.property,
202
+ messageId: MESSAGE_ID,
203
+ data: {method},
204
+ };
205
+
206
+ if (method === 'filter') {
207
+ if (
208
+ hasTypeArguments(flatMapCallExpression)
209
+ || hasSideEffect(callbackResult.test, context.sourceCode)
210
+ || wouldRemoveComments(context, flatMapCallExpression, [
211
+ flatMapCallExpression.callee.object,
212
+ callback.params[0],
213
+ callbackResult.test,
214
+ ])
215
+ ) {
216
+ return problem;
217
+ }
218
+
219
+ const arrayText = getMemberExpressionObjectText(flatMapCallExpression.callee.object, context);
220
+ const parameterText = getArrowParameterText(callback, context);
221
+ const testText = getArrowBodyText(callbackResult.test, context);
222
+
223
+ return {
224
+ ...problem,
225
+ fix: fixer => fixer.replaceText(
226
+ flatMapCallExpression,
227
+ `${arrayText}.filter(${parameterText} => ${testText})`,
228
+ ),
229
+ };
230
+ }
231
+
232
+ const suggestion = getFilterMapSuggestion(flatMapCallExpression, callback, callbackResult, context);
233
+ return suggestion
234
+ ? {
235
+ ...problem,
236
+ suggest: [suggestion],
237
+ }
238
+ : problem;
239
+ }
240
+
241
+ function getProblem(flatMapCallExpression, context) {
242
+ if (
243
+ !isMethodCall(flatMapCallExpression, {
244
+ method: 'flatMap',
245
+ argumentsLength: 1,
246
+ optionalCall: false,
247
+ optionalMember: false,
248
+ computed: false,
249
+ })
250
+ || isKnownNonArray(flatMapCallExpression.callee.object, context)
251
+ ) {
252
+ return;
253
+ }
254
+
255
+ const filterCallExpression = flatMapCallExpression.callee.object;
256
+ if (
257
+ isFilterCallExpression(filterCallExpression)
258
+ && isKnownNonArray(filterCallExpression.callee.object, context)
259
+ ) {
260
+ return;
261
+ }
262
+
263
+ const [callback] = flatMapCallExpression.arguments;
264
+ if (!isSimpleSingleParameterArrowCallback(callback)) {
265
+ return;
266
+ }
267
+
268
+ const callbackResult = getCallbackResult(callback);
269
+ if (!callbackResult) {
270
+ return;
271
+ }
272
+
273
+ if (callbackResult.type === 'map') {
274
+ return getProblemForFilterFlatMap(flatMapCallExpression, callbackResult, context) ?? {
275
+ node: flatMapCallExpression.callee.property,
276
+ messageId: MESSAGE_ID,
277
+ data: {method: 'map'},
278
+ };
279
+ }
280
+
281
+ return getProblemForConditionalFlatMap(flatMapCallExpression, callback, callbackResult, context);
282
+ }
283
+
284
+ /** @param {import('eslint').Rule.RuleContext} context */
285
+ const create = context => {
286
+ context.on('CallExpression', callExpression => getProblem(callExpression, context));
287
+ };
288
+
289
+ /** @type {import('eslint').Rule.RuleModule} */
290
+ const config = {
291
+ create,
292
+ meta: {
293
+ type: 'suggestion',
294
+ docs: {
295
+ description: 'Disallow `Array#flatMap()` callbacks that only wrap a single item.',
296
+ recommended: true,
297
+ },
298
+ fixable: 'code',
299
+ hasSuggestions: true,
300
+ schema: [],
301
+ messages,
302
+ languages: [
303
+ 'js/js',
304
+ ],
305
+ },
306
+ };
307
+
308
+ export default config;
@@ -1,4 +1,4 @@
1
- import {addParenthesizesToReturnOrThrowExpression, removeSpacesAfter} from './fix/index.js';
1
+ import {addParenthesesToReturnOrThrowExpression, removeSpacesAfter} from './fix/index.js';
2
2
  import {isParenthesized, needsSemicolon, isOnSameLine} from './utils/index.js';
3
3
 
4
4
  const MESSAGE_ID = 'no-unnecessary-await';
@@ -74,7 +74,7 @@ const create = context => {
74
74
  !isOnSameLine(awaitToken, valueNode, context)
75
75
  && !isParenthesized(node, context)
76
76
  ) {
77
- yield addParenthesizesToReturnOrThrowExpression(fixer, node.parent, context);
77
+ yield addParenthesesToReturnOrThrowExpression(fixer, node.parent, context);
78
78
  }
79
79
 
80
80
  yield fixer.remove(awaitToken);