eslint-plugin-unicorn 67.0.0 → 69.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 (192) hide show
  1. package/index.js +4 -0
  2. package/package.json +8 -3
  3. package/readme.md +165 -16
  4. package/rules/ast/is-empty-node.js +1 -5
  5. package/rules/ast/is-empty-object-expression.js +2 -2
  6. package/rules/better-dom-traversing.js +17 -8
  7. package/rules/class-reference-in-static-methods.js +14 -2
  8. package/rules/comment-content.js +136 -18
  9. package/rules/consistent-assert.js +1 -1
  10. package/rules/consistent-boolean-name.js +44 -5
  11. package/rules/consistent-compound-words.js +7 -5
  12. package/rules/consistent-conditional-object-spread.js +360 -0
  13. package/rules/consistent-destructuring.js +5 -0
  14. package/rules/consistent-existence-index-check.js +1 -1
  15. package/rules/consistent-json-file-read.js +4 -16
  16. package/rules/consistent-optional-chaining.js +3 -20
  17. package/rules/consistent-tuple-labels.js +49 -0
  18. package/rules/custom-error-definition.js +26 -7
  19. package/rules/default-export-style.js +569 -0
  20. package/rules/expiring-todo-comments.js +1 -1
  21. package/rules/explicit-length-check.js +7 -32
  22. package/rules/import-style.js +8 -6
  23. package/rules/index.js +50 -1
  24. package/rules/logical-assignment-operators.js +17 -4
  25. package/rules/{prevent-abbreviations.js → name-replacements.js} +11 -9
  26. package/rules/new-for-builtins.js +39 -5
  27. package/rules/no-accessor-recursion.js +7 -0
  28. package/rules/no-accidental-bitwise-operator.js +97 -0
  29. package/rules/no-array-callback-reference.js +1 -7
  30. package/rules/no-array-concat-in-loop.js +45 -0
  31. package/rules/no-array-front-mutation.js +64 -0
  32. package/rules/no-array-sort-for-min-max.js +225 -0
  33. package/rules/no-array-splice.js +78 -64
  34. package/rules/no-boolean-sort-comparator.js +215 -0
  35. package/rules/no-break-in-nested-loop.js +33 -0
  36. package/rules/no-chained-comparison.js +116 -0
  37. package/rules/no-collection-bracket-access.js +165 -0
  38. package/rules/no-computed-property-existence-check.js +6 -0
  39. package/rules/no-confusing-array-splice.js +2 -21
  40. package/rules/no-confusing-array-with.js +2 -20
  41. package/rules/no-constant-zero-expression.js +91 -0
  42. package/rules/no-declarations-before-early-exit.js +29 -36
  43. package/rules/no-double-comparison.js +117 -0
  44. package/rules/no-duplicate-if-branches.js +143 -0
  45. package/rules/no-duplicate-logical-operands.js +151 -0
  46. package/rules/no-for-each.js +11 -12
  47. package/rules/no-for-loop.js +3 -15
  48. package/rules/no-impossible-length-comparison.js +166 -0
  49. package/rules/no-incorrect-query-selector.js +8 -4
  50. package/rules/no-incorrect-template-string-interpolation.js +106 -43
  51. package/rules/no-instanceof-builtins.js +73 -14
  52. package/rules/no-invalid-argument-count.js +110 -54
  53. package/rules/no-invalid-character-comparison.js +121 -0
  54. package/rules/no-invalid-fetch-options.js +3 -1
  55. package/rules/no-invalid-well-known-symbol-methods.js +205 -0
  56. package/rules/no-keyword-prefix.js +14 -4
  57. package/rules/no-late-current-target-access.js +13 -107
  58. package/rules/no-late-event-control.js +127 -0
  59. package/rules/no-loop-iterable-mutation.js +541 -0
  60. package/rules/no-manually-wrapped-comments.js +10 -2
  61. package/rules/no-misrefactored-assignment.js +102 -0
  62. package/rules/no-named-default.js +35 -2
  63. package/rules/no-nonstandard-builtin-properties.js +1383 -0
  64. package/rules/no-object-methods-with-collections.js +4 -289
  65. package/rules/no-process-exit.js +16 -12
  66. package/rules/no-redundant-comparison.js +5 -12
  67. package/rules/no-return-array-push.js +20 -5
  68. package/rules/no-selector-as-dom-name.js +150 -0
  69. package/rules/no-thenable.js +27 -11
  70. package/rules/no-top-level-side-effects.js +8 -1
  71. package/rules/no-uncalled-method.js +2 -8
  72. package/rules/no-undeclared-class-members.js +8 -6
  73. package/rules/no-unnecessary-boolean-comparison.js +131 -0
  74. package/rules/no-unnecessary-global-this.js +36 -1
  75. package/rules/no-unnecessary-splice.js +15 -20
  76. package/rules/no-unreadable-for-of-expression.js +2 -1
  77. package/rules/no-unreadable-object-destructuring.js +15 -0
  78. package/rules/no-unsafe-property-key.js +10 -15
  79. package/rules/no-unsafe-string-replacement.js +31 -0
  80. package/rules/no-unused-array-method-return.js +11 -2
  81. package/rules/no-unused-properties.js +4 -3
  82. package/rules/no-useless-boolean-cast.js +58 -1
  83. package/rules/no-useless-coercion.js +71 -15
  84. package/rules/no-useless-compound-assignment.js +110 -0
  85. package/rules/no-useless-concat.js +27 -3
  86. package/rules/no-useless-delete-check.js +353 -0
  87. package/rules/no-useless-else.js +27 -62
  88. package/rules/no-useless-error-capture-stack-trace.js +1 -1
  89. package/rules/no-useless-fallback-in-spread.js +7 -118
  90. package/rules/no-useless-iterator-to-array.js +165 -115
  91. package/rules/no-useless-logical-operand.js +248 -0
  92. package/rules/no-useless-undefined.js +11 -28
  93. package/rules/no-xor-as-exponentiation.js +59 -0
  94. package/rules/prefer-abort-signal-timeout.js +395 -0
  95. package/rules/prefer-aggregate-error.js +478 -0
  96. package/rules/prefer-array-find.js +95 -5
  97. package/rules/prefer-array-flat-map.js +9 -21
  98. package/rules/prefer-array-flat.js +1 -24
  99. package/rules/prefer-array-from-async.js +273 -0
  100. package/rules/prefer-array-iterable-methods.js +157 -0
  101. package/rules/prefer-array-some.js +56 -31
  102. package/rules/prefer-at.js +6 -30
  103. package/rules/prefer-await.js +29 -0
  104. package/rules/prefer-boolean-return.js +224 -0
  105. package/rules/prefer-code-point.js +58 -8
  106. package/rules/prefer-continue.js +377 -0
  107. package/rules/prefer-default-parameters.js +10 -6
  108. package/rules/prefer-dispose.js +1 -5
  109. package/rules/prefer-dom-node-replace-children.js +353 -0
  110. package/rules/prefer-early-return.js +160 -30
  111. package/rules/prefer-else-if.js +50 -53
  112. package/rules/prefer-error-is-error.js +181 -0
  113. package/rules/prefer-event-target.js +3 -13
  114. package/rules/prefer-flat-math-min-max.js +103 -0
  115. package/rules/prefer-global-this.js +1 -1
  116. package/rules/prefer-has-check.js +1 -4
  117. package/rules/prefer-hoisting-branch-code.js +424 -0
  118. package/rules/prefer-identifier-import-export-specifiers.js +6 -11
  119. package/rules/prefer-includes-over-repeated-comparisons.js +5 -1
  120. package/rules/prefer-includes.js +6 -0
  121. package/rules/prefer-iterator-concat.js +21 -1
  122. package/rules/prefer-logical-operator-over-ternary.js +12 -20
  123. package/rules/prefer-math-constants.js +103 -0
  124. package/rules/prefer-math-min-max.js +1 -5
  125. package/rules/prefer-minimal-ternary.js +41 -13
  126. package/rules/prefer-modern-dom-apis.js +1 -45
  127. package/rules/prefer-node-protocol.js +1 -0
  128. package/rules/prefer-number-properties.js +2 -5
  129. package/rules/prefer-object-from-entries.js +6 -1
  130. package/rules/prefer-object-iterable-methods.js +36 -11
  131. package/rules/prefer-observer-apis.js +498 -0
  132. package/rules/prefer-optional-catch-binding.js +30 -24
  133. package/rules/prefer-private-class-fields.js +2 -8
  134. package/rules/prefer-promise-try.js +238 -0
  135. package/rules/prefer-promise-with-resolvers.js +248 -0
  136. package/rules/prefer-query-selector.js +79 -17
  137. package/rules/prefer-queue-microtask.js +7 -9
  138. package/rules/prefer-regexp-escape.js +263 -0
  139. package/rules/prefer-set-methods.js +337 -0
  140. package/rules/prefer-set-size.js +10 -2
  141. package/rules/prefer-short-arrow-method.js +3 -3
  142. package/rules/prefer-single-replace.js +218 -0
  143. package/rules/prefer-spread.js +14 -21
  144. package/rules/prefer-string-replace-all.js +5 -0
  145. package/rules/prefer-structured-clone.js +2 -2
  146. package/rules/prefer-switch.js +1 -0
  147. package/rules/prefer-ternary.js +4 -2
  148. package/rules/prefer-toggle-attribute.js +293 -0
  149. package/rules/prefer-type-literal-last.js +22 -54
  150. package/rules/prefer-unary-minus.js +103 -0
  151. package/rules/prefer-url-can-parse.js +308 -0
  152. package/rules/prefer-url-search-parameters.js +411 -0
  153. package/rules/prefer-while-loop-condition.js +252 -0
  154. package/rules/require-passive-events.js +11 -4
  155. package/rules/require-proxy-trap-boolean-return.js +40 -126
  156. package/rules/rule/to-eslint-listener.js +5 -3
  157. package/rules/shared/array-concat-in-loop.js +151 -0
  158. package/rules/shared/identifier-checks.js +4 -16
  159. package/rules/shared/late-event-handler.js +82 -0
  160. package/rules/shared/{abbreviations.js → name-replacements.js} +63 -0
  161. package/rules/shared/no-array-mutate-rule.js +19 -2
  162. package/rules/shared/regexp-escape.js +81 -0
  163. package/rules/shared/splice-replacements.js +1 -18
  164. package/rules/string-content.js +11 -4
  165. package/rules/template-indent.js +2 -8
  166. package/rules/throw-new-error.js +7 -0
  167. package/rules/utils/block-scope.js +42 -0
  168. package/rules/utils/builtin-collection-type.js +267 -0
  169. package/rules/utils/builtins.js +50 -1
  170. package/rules/utils/comments.js +16 -0
  171. package/rules/utils/comparison.js +17 -0
  172. package/rules/utils/contains-suspension-point.js +35 -0
  173. package/rules/utils/get-const-variable-initializer.js +31 -0
  174. package/rules/utils/global-reference-tracker.js +14 -1
  175. package/rules/utils/has-optional-chain-element.js +36 -4
  176. package/rules/utils/index.js +38 -2
  177. package/rules/utils/is-boolean.js +26 -1
  178. package/rules/utils/is-event.js +127 -0
  179. package/rules/utils/is-function-self-used-inside.js +1 -5
  180. package/rules/utils/is-weak-map.js +21 -0
  181. package/rules/utils/is-weak-set.js +21 -0
  182. package/rules/utils/length-or-size.js +53 -0
  183. package/rules/utils/member-expression.js +18 -0
  184. package/rules/utils/needs-semicolon.js +1 -5
  185. package/rules/utils/numeric.js +20 -0
  186. package/rules/utils/should-add-parentheses-to-logical-expression-child.js +8 -14
  187. package/rules/utils/should-add-parentheses-to-member-expression-object.js +1 -5
  188. package/rules/utils/should-add-parentheses-to-unary-expression.js +8 -2
  189. package/rules/utils/should-report-replace-children-receiver.js +210 -0
  190. package/rules/utils/track-branch-exits.js +79 -0
  191. package/rules/utils/type-helpers.js +166 -8
  192. package/rules/utils/types.js +22 -0
@@ -0,0 +1,360 @@
1
+ import {
2
+ isEmptyObjectExpression,
3
+ isNullLiteral,
4
+ isUndefined,
5
+ } from './ast/index.js';
6
+ import {
7
+ getParenthesizedText,
8
+ isParenthesized,
9
+ isSameReference,
10
+ shouldAddParenthesesToConditionalExpressionChild,
11
+ shouldAddParenthesesToLogicalExpressionChild,
12
+ shouldAddParenthesesToUnaryExpressionArgument,
13
+ } from './utils/index.js';
14
+
15
+ const STYLE_LOGICAL = 'logical';
16
+ const STYLE_TERNARY = 'ternary';
17
+
18
+ const MESSAGE_ID = 'consistent-conditional-object-spread';
19
+ const messages = {
20
+ [MESSAGE_ID]: 'Prefer {{expectedStyle}} conditional object spreads.',
21
+ };
22
+ const nullishOperators = new Set(['==', '===']);
23
+ const nonNullishOperators = new Set(['!=', '!==']);
24
+
25
+ // `{...{}}`, `{...undefined}`, and `{...null}` all spread nothing.
26
+ const isEmptySpreadBranch = node =>
27
+ isEmptyObjectExpression(node)
28
+ || isUndefined(node)
29
+ || isNullLiteral(node);
30
+
31
+ const isObjectSpreadArgument = node => (
32
+ node.parent.type === 'SpreadElement'
33
+ && node.parent.argument === node
34
+ && node.parent.parent.type === 'ObjectExpression'
35
+ && node.parent.parent.properties.includes(node.parent)
36
+ );
37
+
38
+ function isSameNode(left, right, sourceCode) {
39
+ if (isSameReference(left, right)) {
40
+ return true;
41
+ }
42
+
43
+ if (left.type !== right.type) {
44
+ return false;
45
+ }
46
+
47
+ switch (left.type) {
48
+ case 'AwaitExpression': {
49
+ return isSameNode(left.argument, right.argument, sourceCode);
50
+ }
51
+
52
+ case 'LogicalExpression': {
53
+ return (
54
+ left.operator === right.operator
55
+ && isSameNode(left.left, right.left, sourceCode)
56
+ && isSameNode(left.right, right.right, sourceCode)
57
+ );
58
+ }
59
+
60
+ case 'UnaryExpression': {
61
+ return (
62
+ left.operator === right.operator
63
+ && left.prefix === right.prefix
64
+ && isSameNode(left.argument, right.argument, sourceCode)
65
+ );
66
+ }
67
+
68
+ case 'UpdateExpression': {
69
+ return false;
70
+ }
71
+
72
+ // No default
73
+ }
74
+
75
+ return sourceCode.getText(left) === sourceCode.getText(right);
76
+ }
77
+
78
+ function getNullishKind(node) {
79
+ if (isNullLiteral(node)) {
80
+ return 'null';
81
+ }
82
+
83
+ if (isUndefined(node)) {
84
+ return 'undefined';
85
+ }
86
+ }
87
+
88
+ function getNullishBinaryCheck(node) {
89
+ if (
90
+ node.type !== 'BinaryExpression'
91
+ || (!nullishOperators.has(node.operator) && !nonNullishOperators.has(node.operator))
92
+ ) {
93
+ return;
94
+ }
95
+
96
+ const leftKind = getNullishKind(node.left);
97
+ const rightKind = getNullishKind(node.right);
98
+
99
+ if (Boolean(leftKind) === Boolean(rightKind)) {
100
+ return;
101
+ }
102
+
103
+ return {
104
+ reference: leftKind ? node.right : node.left,
105
+ kind: node.operator.length === 2 ? 'nullish' : (leftKind ?? rightKind),
106
+ isTrueWhenNullish: nullishOperators.has(node.operator),
107
+ };
108
+ }
109
+
110
+ const checksNullAndUndefined = (left, right) =>
111
+ (left.kind === 'null' && right.kind === 'undefined')
112
+ || (left.kind === 'undefined' && right.kind === 'null');
113
+
114
+ function getNullishTest(node, sourceCode) {
115
+ const binaryCheck = getNullishBinaryCheck(node);
116
+
117
+ if (binaryCheck?.kind === 'nullish') {
118
+ return binaryCheck;
119
+ }
120
+
121
+ if (node.type !== 'LogicalExpression') {
122
+ return;
123
+ }
124
+
125
+ const left = getNullishBinaryCheck(node.left);
126
+ const right = getNullishBinaryCheck(node.right);
127
+
128
+ if (
129
+ !left
130
+ || !right
131
+ || !isSameNode(left.reference, right.reference, sourceCode)
132
+ ) {
133
+ return;
134
+ }
135
+
136
+ if (
137
+ node.operator === '||'
138
+ && left.isTrueWhenNullish
139
+ && right.isTrueWhenNullish
140
+ && checksNullAndUndefined(left, right)
141
+ ) {
142
+ return {
143
+ reference: left.reference,
144
+ isTrueWhenNullish: true,
145
+ };
146
+ }
147
+
148
+ if (
149
+ node.operator === '&&'
150
+ && !left.isTrueWhenNullish
151
+ && !right.isTrueWhenNullish
152
+ && checksNullAndUndefined(left, right)
153
+ ) {
154
+ return {
155
+ reference: left.reference,
156
+ isTrueWhenNullish: false,
157
+ };
158
+ }
159
+ }
160
+
161
+ // Render `node` as an operand of a `&&` expression, adding parentheses when precedence requires it.
162
+ function getLogicalOperandText(node, property, context) {
163
+ let text = getParenthesizedText(node, context);
164
+
165
+ if (
166
+ !isParenthesized(node, context)
167
+ && shouldAddParenthesesToLogicalExpressionChild(node, {operator: '&&', property})
168
+ ) {
169
+ text = `(${text})`;
170
+ }
171
+
172
+ return text;
173
+ }
174
+
175
+ // Render `!test` as the left operand of a `&&` expression, stripping a leading `!` when present.
176
+ function getNegatedTestText(test, context) {
177
+ if (
178
+ test.type === 'UnaryExpression'
179
+ && test.operator === '!'
180
+ && test.prefix
181
+ ) {
182
+ return getLogicalOperandText(test.argument, 'left', context);
183
+ }
184
+
185
+ let text = getParenthesizedText(test, context);
186
+
187
+ if (
188
+ !isParenthesized(test, context)
189
+ && shouldAddParenthesesToUnaryExpressionArgument(test, '!')
190
+ ) {
191
+ text = `(${text})`;
192
+ }
193
+
194
+ return `!${text}`;
195
+ }
196
+
197
+ function getConditionalExpressionChildText(node, context) {
198
+ let text = getParenthesizedText(node, context);
199
+
200
+ if (
201
+ !isParenthesized(node, context)
202
+ && shouldAddParenthesesToConditionalExpressionChild(node)
203
+ ) {
204
+ text = `(${text})`;
205
+ }
206
+
207
+ return text;
208
+ }
209
+
210
+ function getConditionalExpressionProblem(conditionalExpression, context) {
211
+ const {test, consequent, alternate} = conditionalExpression;
212
+ const isAlternateEmpty = isEmptySpreadBranch(alternate);
213
+ const isConsequentEmpty = isEmptySpreadBranch(consequent);
214
+
215
+ if (isAlternateEmpty === isConsequentEmpty) {
216
+ return;
217
+ }
218
+
219
+ const keptBranch = isAlternateEmpty ? consequent : alternate;
220
+ const nullishTest = getNullishTest(test, context.sourceCode);
221
+ const hasCommentsInside = context.sourceCode.getCommentsInside(conditionalExpression).length > 0;
222
+
223
+ if (
224
+ (
225
+ isAlternateEmpty
226
+ ? isSameNode(test, keptBranch, context.sourceCode)
227
+ : (
228
+ test.type === 'UnaryExpression'
229
+ && test.operator === '!'
230
+ && test.prefix
231
+ && isSameNode(test.argument, keptBranch, context.sourceCode)
232
+ )
233
+ )
234
+ || (
235
+ nullishTest
236
+ && !hasCommentsInside
237
+ && (nullishTest.isTrueWhenNullish ? !isAlternateEmpty : isAlternateEmpty)
238
+ && isSameNode(nullishTest.reference, keptBranch, context.sourceCode)
239
+ )
240
+ ) {
241
+ return;
242
+ }
243
+
244
+ const testText = isAlternateEmpty
245
+ ? getLogicalOperandText(test, 'left', context)
246
+ : getNegatedTestText(test, context);
247
+ const keptBranchText = getLogicalOperandText(keptBranch, 'right', context);
248
+
249
+ return {
250
+ node: conditionalExpression,
251
+ messageId: MESSAGE_ID,
252
+ data: {
253
+ expectedStyle: 'logical',
254
+ },
255
+ /** @param {import('eslint').Rule.RuleFixer} fixer */
256
+ * fix(fixer, {abort}) {
257
+ if (hasCommentsInside) {
258
+ return abort();
259
+ }
260
+
261
+ yield fixer.replaceText(conditionalExpression, `${testText} && ${keptBranchText}`);
262
+ },
263
+ };
264
+ }
265
+
266
+ function getLogicalExpressionProblem(logicalExpression, context) {
267
+ if (
268
+ logicalExpression.operator !== '&&'
269
+ || isEmptySpreadBranch(logicalExpression.right)
270
+ ) {
271
+ return;
272
+ }
273
+
274
+ if (isSameNode(logicalExpression.left, logicalExpression.right, context.sourceCode)) {
275
+ return;
276
+ }
277
+
278
+ const nullishTest = getNullishTest(logicalExpression.left, context.sourceCode);
279
+
280
+ if (
281
+ nullishTest
282
+ && !nullishTest.isTrueWhenNullish
283
+ && isSameNode(nullishTest.reference, logicalExpression.right, context.sourceCode)
284
+ ) {
285
+ return;
286
+ }
287
+
288
+ const testText = getConditionalExpressionChildText(logicalExpression.left, context);
289
+ const consequentText = getConditionalExpressionChildText(logicalExpression.right, context);
290
+
291
+ return {
292
+ node: logicalExpression,
293
+ messageId: MESSAGE_ID,
294
+ data: {
295
+ expectedStyle: 'ternary',
296
+ },
297
+ /** @param {import('eslint').Rule.RuleFixer} fixer */
298
+ * fix(fixer, {abort}) {
299
+ if (context.sourceCode.getCommentsInside(logicalExpression).length > 0) {
300
+ return abort();
301
+ }
302
+
303
+ yield fixer.replaceText(logicalExpression, `${testText} ? ${consequentText} : {}`);
304
+ },
305
+ };
306
+ }
307
+
308
+ /** @param {import('eslint').Rule.RuleContext} context */
309
+ const create = context => {
310
+ const style = context.options[0];
311
+
312
+ if (style === STYLE_TERNARY) {
313
+ context.on('LogicalExpression', logicalExpression => {
314
+ if (!isObjectSpreadArgument(logicalExpression)) {
315
+ return;
316
+ }
317
+
318
+ return getLogicalExpressionProblem(logicalExpression, context);
319
+ });
320
+
321
+ return;
322
+ }
323
+
324
+ context.on('ConditionalExpression', conditionalExpression => {
325
+ if (!isObjectSpreadArgument(conditionalExpression)) {
326
+ return;
327
+ }
328
+
329
+ return getConditionalExpressionProblem(conditionalExpression, context);
330
+ });
331
+ };
332
+
333
+ /** @type {import('eslint').Rule.RuleModule} */
334
+ const config = {
335
+ create,
336
+ meta: {
337
+ type: 'suggestion',
338
+ docs: {
339
+ description: 'Enforce consistent conditional object spread style.',
340
+ recommended: true,
341
+ },
342
+ fixable: 'code',
343
+ schema: [
344
+ {
345
+ description: 'The conditional object spread style to enforce.',
346
+ enum: [
347
+ STYLE_LOGICAL,
348
+ STYLE_TERNARY,
349
+ ],
350
+ },
351
+ ],
352
+ defaultOptions: [STYLE_LOGICAL],
353
+ messages,
354
+ languages: [
355
+ 'js/js',
356
+ ],
357
+ },
358
+ };
359
+
360
+ export default config;
@@ -66,6 +66,11 @@ const shouldIgnoreMemberExpression = node =>
66
66
  isCallOrNewExpression(node.parent)
67
67
  && node.parent.callee === node
68
68
  )
69
+ // Replacing the tag would change the `this` binding, like a method call.
70
+ || (
71
+ node.parent.type === 'TaggedTemplateExpression'
72
+ && node.parent.tag === node
73
+ )
69
74
  || isLeftHandSide(node);
70
75
 
71
76
  const isRootVariableReassigned = (declaration, memberExpressionNode, memberScope, sourceCode) => {
@@ -84,7 +84,7 @@ const create = context => {
84
84
  const replacement = getReplacement(binaryExpression);
85
85
 
86
86
  if (!replacement) {
87
- return;
87
+ continue;
88
88
  }
89
89
 
90
90
  const {left, operator, right} = binaryExpression;
@@ -92,14 +92,8 @@ function isUtf8Encoding(node, scope) {
92
92
  }
93
93
 
94
94
  const {value} = staticValue;
95
- if (
96
- isSingleEncodingOptionObject(value)
97
- && isUtf8EncodingString(value.encoding)
98
- ) {
99
- return true;
100
- }
101
-
102
- return false;
95
+ return Boolean(isSingleEncodingOptionObject(value)
96
+ && isUtf8EncodingString(value.encoding));
103
97
  }
104
98
 
105
99
  function isBufferEncoding(node, scope) {
@@ -113,14 +107,8 @@ function isBufferEncoding(node, scope) {
113
107
  return true;
114
108
  }
115
109
 
116
- if (
117
- isSingleEncodingOptionObject(value)
118
- && (value.encoding === undefined || value.encoding === null)
119
- ) {
120
- return true;
121
- }
122
-
123
- return false;
110
+ return Boolean(isSingleEncodingOptionObject(value)
111
+ && (value.encoding === undefined || value.encoding === null));
124
112
  }
125
113
 
126
114
  function isJsonReadFileCall(node, scope) {
@@ -1,5 +1,6 @@
1
1
  import {
2
- getParenthesizedRange,
2
+ getMemberAccessOperatorRange,
3
+ hasCommentInRange,
3
4
  isSameReference,
4
5
  unwrapTypeScriptExpression,
5
6
  } from './utils/index.js';
@@ -60,28 +61,10 @@ function isSupportedMemberBase(node) {
60
61
  return false;
61
62
  }
62
63
 
63
- function getMemberAccessOperatorRange(memberExpression, context) {
64
- const {sourceCode} = context;
65
- const [, start] = getParenthesizedRange(memberExpression.object, context);
66
- const end = memberExpression.computed
67
- ? sourceCode.getRange(sourceCode.getTokenBefore(memberExpression.property, token => token.value === '['))[1]
68
- : sourceCode.getRange(memberExpression.property)[0];
69
-
70
- return [start, end];
71
- }
72
-
73
- function hasCommentInRange(sourceCode, [start, end]) {
74
- return sourceCode.getAllComments().some(comment => {
75
- const [commentStart, commentEnd] = sourceCode.getRange(comment);
76
-
77
- return commentStart >= start && commentEnd <= end;
78
- });
79
- }
80
-
81
64
  function canReplaceMemberAccessOperator(memberExpression, context) {
82
65
  const range = getMemberAccessOperatorRange(memberExpression, context);
83
66
 
84
- return !hasCommentInRange(context.sourceCode, range);
67
+ return !hasCommentInRange(context, range);
85
68
  }
86
69
 
87
70
  function replaceMemberAccessOperator({memberExpression, context, fixer, replacement}) {
@@ -0,0 +1,49 @@
1
+ const MESSAGE_ID = 'consistent-tuple-labels';
2
+ const messages = {
3
+ [MESSAGE_ID]: 'This tuple element should have a label, just like the other elements.',
4
+ };
5
+
6
+ // A rest element keeps its label inside the `TSRestType` wrapper, e.g. `[...rest: number[]]`.
7
+ const isLabeledElement = element =>
8
+ element.type === 'TSNamedTupleMember'
9
+ || (element.type === 'TSRestType' && element.typeAnnotation.type === 'TSNamedTupleMember');
10
+
11
+ /** @param {import('eslint').Rule.RuleContext} context */
12
+ const create = context => {
13
+ context.on('TSTupleType', node => {
14
+ const elements = node.elementTypes;
15
+
16
+ // A tuple with fewer than two elements can't be inconsistent.
17
+ if (elements.length < 2) {
18
+ return;
19
+ }
20
+
21
+ const unlabeledElements = elements.filter(element => !isLabeledElement(element));
22
+
23
+ // All labeled or all unlabeled is consistent.
24
+ if (unlabeledElements.length === 0 || unlabeledElements.length === elements.length) {
25
+ return;
26
+ }
27
+
28
+ // At least one element is labeled, so flag every unlabeled element.
29
+ return unlabeledElements.map(element => ({node: element, messageId: MESSAGE_ID}));
30
+ });
31
+ };
32
+
33
+ /** @type {import('eslint').Rule.RuleModule} */
34
+ const config = {
35
+ create,
36
+ meta: {
37
+ type: 'suggestion',
38
+ docs: {
39
+ description: 'Enforce consistent labels on tuple type elements.',
40
+ recommended: true,
41
+ },
42
+ messages,
43
+ languages: [
44
+ 'js/js',
45
+ ],
46
+ },
47
+ };
48
+
49
+ export default config;
@@ -1,9 +1,13 @@
1
+ import {getPropertyName} from '@eslint-community/eslint-utils';
1
2
  import {
2
3
  upperFirst,
3
4
  getParenthesizedText,
4
5
  isNodeMatchesNameOrPath,
5
6
  } from './utils/index.js';
6
- import {isUndefined} from './ast/index.js';
7
+ import {
8
+ getStaticStringValue,
9
+ isUndefined,
10
+ } from './ast/index.js';
7
11
 
8
12
  const MESSAGE_ID_INVALID_EXPORT = 'invalidExport';
9
13
  const MESSAGE_ID_DO_NOT_PASS_MESSAGE_TO_SUPER = 'doNotPassMessageToSuper';
@@ -73,8 +77,7 @@ const isPropertyDefinition = (node, name) =>
73
77
  && node.key.name === name;
74
78
 
75
79
  const isValidNameProperty = (nameProperty, className) =>
76
- nameProperty?.value
77
- && nameProperty.value.value === className;
80
+ getStaticStringValue(nameProperty?.value) === className;
78
81
 
79
82
  const isMessageAccessor = (node, kind) =>
80
83
  node.type === 'MethodDefinition'
@@ -200,6 +203,20 @@ const isSameIdentifier = (node, identifier) =>
200
203
  node?.type === 'Identifier'
201
204
  && node.name === identifier.name;
202
205
 
206
+ // Whether `super()` already forwards the error options inline, e.g. `super('Fixed message', {cause})`.
207
+ const hasInlineErrorOptions = (superCallExpression, shouldPassMessageToSuper) => {
208
+ if (shouldPassMessageToSuper) {
209
+ return false;
210
+ }
211
+
212
+ const [messageArgument, optionsArgument] = superCallExpression.arguments;
213
+ return superCallExpression.arguments.length === 2
214
+ && getStaticStringValue(messageArgument) !== undefined
215
+ && optionsArgument.type === 'ObjectExpression'
216
+ && optionsArgument.properties.length === 1
217
+ && getPropertyName(optionsArgument.properties[0]) === 'cause';
218
+ };
219
+
203
220
  const getErrorOptionsProblem = (context, constructor, superExpression, hasMessageAccessor) => {
204
221
  const parameters = constructor.value.params;
205
222
  const firstParameter = parameters[0];
@@ -236,6 +253,11 @@ const getErrorOptionsProblem = (context, constructor, superExpression, hasMessag
236
253
  const messageArgumentText = shouldPassMessageToSuper ? firstParameterIdentifier.name : 'undefined';
237
254
 
238
255
  if (!optionsParameter) {
256
+ // When `options` is already forwarded to `super()` (e.g. `super('Fixed message', {cause})`), a dedicated `options` parameter isn't needed.
257
+ if (hasInlineErrorOptions(superCallExpression, shouldPassMessageToSuper)) {
258
+ return;
259
+ }
260
+
239
261
  return {
240
262
  node: firstParameter,
241
263
  messageId: MESSAGE_ID_MISSING_OPTIONS_PARAMETER,
@@ -282,10 +304,7 @@ function getInvalidErrorNameProblem(constructorBodyNode, constructorBody, errorD
282
304
  return;
283
305
  }
284
306
 
285
- if (
286
- nameExpression.expression.right.type !== 'Literal'
287
- || nameExpression.expression.right.value !== name
288
- ) {
307
+ if (getStaticStringValue(nameExpression.expression.right) !== name) {
289
308
  return createInvalidNameError(nameExpression.expression.right ?? constructorBodyNode, name);
290
309
  }
291
310
  }