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
@@ -4,7 +4,12 @@ import {
4
4
  isMemberExpression,
5
5
  isNumericLiteral,
6
6
  } from './ast/index.js';
7
- import {escapeString, getParenthesizedText, isNodeValueNotDomNode} from './utils/index.js';
7
+ import {
8
+ escapeString,
9
+ getParenthesizedRange,
10
+ getParenthesizedText,
11
+ isNodeValueNotDomNode,
12
+ } from './utils/index.js';
8
13
 
9
14
  const MESSAGE_ID_FIRST_CHILD = 'first-child';
10
15
  const MESSAGE_ID_FIRST_ELEMENT_CHILD = 'first-element-child';
@@ -87,18 +92,24 @@ const isNestedIndexedDomCollection = node =>
87
92
  const hasCommentsInside = (node, sourceCode) =>
88
93
  sourceCode.getCommentsInside(node).length > 0;
89
94
 
90
- const getFirstChildSuggestion = (node, replacement, messageId, sourceCode) => {
95
+ const getFirstChildSuggestion = (node, replacement, messageId, context) => {
96
+ const {sourceCode} = context;
91
97
  if (hasCommentsInside(node, sourceCode)) {
92
98
  return;
93
99
  }
94
100
 
101
+ // Replace the property name (`childNodes`/`children`) and drop the `[index]` access
102
+ // separately, so parentheses around the collection (e.g. `(element.childNodes)[0]`) are preserved.
103
+ const [, objectEnd] = getParenthesizedRange(node.object, context);
95
104
  const [, end] = sourceCode.getRange(node);
96
- const [start] = sourceCode.getRange(node.object.property);
97
105
 
98
106
  return [
99
107
  {
100
108
  messageId,
101
- fix: fixer => fixer.replaceTextRange([start, end], replacement),
109
+ fix: fixer => [
110
+ fixer.replaceText(node.object.property, replacement),
111
+ fixer.removeRange([objectEnd, end]),
112
+ ],
102
113
  },
103
114
  ];
104
115
  };
@@ -222,8 +233,6 @@ const getMergeQuerySelectorSuggestion = (node, querySelectorChain, context) => {
222
233
 
223
234
  /** @param {import('eslint').Rule.RuleContext} context */
224
235
  const create = context => {
225
- const {sourceCode} = context;
226
-
227
236
  context.on('MemberExpression', node => {
228
237
  const collectionName = getIndexedDomCollectionName(node);
229
238
  if (
@@ -243,7 +252,7 @@ const create = context => {
243
252
  return {
244
253
  node,
245
254
  messageId: MESSAGE_ID_FIRST_CHILD,
246
- suggest: getFirstChildSuggestion(node, 'firstChild', SUGGESTION_ID_FIRST_CHILD, sourceCode),
255
+ suggest: getFirstChildSuggestion(node, 'firstChild', SUGGESTION_ID_FIRST_CHILD, context),
247
256
  };
248
257
  }
249
258
 
@@ -251,7 +260,7 @@ const create = context => {
251
260
  return {
252
261
  node,
253
262
  messageId: MESSAGE_ID_FIRST_ELEMENT_CHILD,
254
- suggest: getFirstChildSuggestion(node, 'firstElementChild', SUGGESTION_ID_FIRST_ELEMENT_CHILD, sourceCode),
263
+ suggest: getFirstChildSuggestion(node, 'firstElementChild', SUGGESTION_ID_FIRST_ELEMENT_CHILD, context),
255
264
  };
256
265
  }
257
266
 
@@ -92,6 +92,12 @@ const isSameVariable = (sourceCode, reference, binding) =>
92
92
  findVariable(sourceCode.getScope(reference), reference)
93
93
  === findVariable(sourceCode.getScope(binding), binding);
94
94
 
95
+ // Whether the class/superclass name resolves to the class binding at `node`'s location.
96
+ // If it's shadowed by a local variable, the name can't be used to reference the class.
97
+ const isReferenceNameAvailable = (sourceCode, node, referenceNode) =>
98
+ findVariable(sourceCode.getScope(node), referenceNode.name)
99
+ === findVariable(sourceCode.getScope(referenceNode), referenceNode);
100
+
95
101
  const isAssignmentTargetRoot = (parent, node) =>
96
102
  (
97
103
  parent.type === 'AssignmentExpression'
@@ -331,7 +337,10 @@ const create = context => {
331
337
  }
332
338
 
333
339
  const classReferenceNode = getClassReferenceNode(getClass(staticMethod));
334
- if (!classReferenceNode) {
340
+ if (
341
+ !classReferenceNode
342
+ || !isReferenceNameAvailable(sourceCode, node, classReferenceNode)
343
+ ) {
335
344
  return problemWithoutSuggestion(node, MESSAGE_ID_CLASS_UNAVAILABLE);
336
345
  }
337
346
 
@@ -355,7 +364,10 @@ const create = context => {
355
364
  }
356
365
 
357
366
  const superClassReferenceNode = getSimpleSuperClassReferenceNode(getClass(staticMethod));
358
- if (!superClassReferenceNode) {
367
+ if (
368
+ !superClassReferenceNode
369
+ || !isReferenceNameAvailable(sourceCode, node, superClassReferenceNode)
370
+ ) {
359
371
  return problemWithoutSuggestion(node, MESSAGE_ID_SUPER_CLASS_UNAVAILABLE);
360
372
  }
361
373
 
@@ -285,8 +285,11 @@ const listMarkerPatternSource = String.raw`(?:[*+\-]|\d+\.)`;
285
285
  const optionalListMarkerPrefixPatternSource = String.raw`${listMarkerPatternSource}?\s*`;
286
286
  const optionalLabelPrefixPatternSource = String.raw`(?:${identifierPatternSource}:\s*)?`;
287
287
  const bracketPropertyPatternSource = String.raw`\[[^\]]+\]`;
288
- const optionalAssignmentPrefixPatternSource = String.raw`(?:${identifierPatternSource}(?::|\s*=)\s*)?`;
289
288
  const memberCallPropertyPatternSource = String.raw`(?:(?:\?\.|\.)\s*${identifierPatternSource}|\??\.\s*${bracketPropertyPatternSource})`;
289
+ const memberAssignmentTargetPatternSource = String.raw`${identifierPatternSource}(?:\s*${memberCallPropertyPatternSource}|\s*${bracketPropertyPatternSource})+`;
290
+ const optionalDeclarationPrefixPatternSource = String.raw`(?:(?:const|let|var)\s+)?`;
291
+ const assignmentTargetPatternSource = `(?:${identifierPatternSource}|${memberAssignmentTargetPatternSource})`;
292
+ const optionalAssignmentPrefixPatternSource = String.raw`(?:(?:${identifierPatternSource}:|${optionalDeclarationPrefixPatternSource}${assignmentTargetPatternSource}\s*=)\s*)?`;
290
293
  const markdownLinkDestinationStartPatternSource = String.raw`(?:[A-Za-z][\d+\-.A-Za-z]*:\/\/|www\.|[\u{2D}0-9A-Za-z]+\.|[\w\-.]+\/|\/|#)`;
291
294
  const callArgumentsPatternSource = String.raw`\((?!\s*${markdownLinkDestinationStartPatternSource})[^\)]*\)`;
292
295
  const bracketMemberAccessLinePattern = new RegExp([
@@ -325,6 +328,14 @@ const memberCallLinePattern = new RegExp([
325
328
  String.raw`\s*${memberCallPropertyPatternSource}\s*(?:\?\.\s*)?\(`,
326
329
  String.raw`|${bracketPropertyPatternSource}\()`,
327
330
  ].join(''), 'v');
331
+ const bareCallLinePattern = new RegExp([
332
+ `^${optionalListMarkerPrefixPatternSource}`,
333
+ String.raw`(?:\(\s*)?`,
334
+ optionalAssignmentPrefixPatternSource,
335
+ String.raw`(?:new\s+)?`,
336
+ codeIdentifierPatternSource,
337
+ String.raw`(?:\(|\s+\(\s*$)`,
338
+ ].join(''), 'v');
328
339
  const shellPromptLinePattern = new RegExp(String.raw`^${optionalListMarkerPrefixPatternSource}\$\s+\S+`, 'v');
329
340
  const secondaryShellPromptLinePattern = new RegExp(String.raw`^${optionalListMarkerPrefixPatternSource}>\s*(?:bun|curl|deno|docker|git|node(?:js)?|npm|npx|pnpm|yarn)\b`, 'v');
330
341
  const listMarkerPattern = new RegExp(String.raw`^${listMarkerPatternSource}\s*`, 'v');
@@ -336,6 +347,55 @@ const packageSpecifierTerminatorCharacters = '"\'`()[]{}<>,';
336
347
  const domainLeadingPunctuation = '([{<';
337
348
  const domainTrailingPunctuation = '.,;:!?)]}>';
338
349
  const maskCharacter = '\uFFFF';
350
+ const openBrackets = '([{';
351
+ const closeBrackets = ')]}';
352
+ const quoteCharacters = '"\'`';
353
+
354
+ // Count the net change in unbalanced brackets across a line, ignoring brackets inside string literals so they do not miscount. Operates on raw text so it is independent of mask ordering.
355
+ function getBracketDepthDelta(text) {
356
+ let delta = 0;
357
+ let quote;
358
+
359
+ for (let index = 0; index < text.length; index++) {
360
+ const character = text[index];
361
+
362
+ if (quote) {
363
+ if (character === '\\') {
364
+ index++;
365
+ continue;
366
+ }
367
+
368
+ if (character === quote) {
369
+ quote = undefined;
370
+ }
371
+
372
+ continue;
373
+ }
374
+
375
+ if (character === '/' && text[index + 1] === '/') {
376
+ break;
377
+ }
378
+
379
+ if (character === '/' && text[index + 1] === '*') {
380
+ const endIndex = text.indexOf('*/', index + 2);
381
+ index = endIndex === -1 ? text.length : endIndex + 1;
382
+ continue;
383
+ }
384
+
385
+ if (quoteCharacters.includes(character)) {
386
+ quote = character;
387
+ continue;
388
+ }
389
+
390
+ if (openBrackets.includes(character)) {
391
+ delta++;
392
+ } else if (closeBrackets.includes(character)) {
393
+ delta--;
394
+ }
395
+ }
396
+
397
+ return delta;
398
+ }
339
399
 
340
400
  function isIdentifierLikeCharacter(character) {
341
401
  return Boolean(character) && /[\p{Letter}\p{Number}_]/v.test(character);
@@ -716,14 +776,48 @@ function isIgnoredCommentLine(line) {
716
776
  || memberCallLinePattern.test(removeMarkdownInlineLinks(line));
717
777
  }
718
778
 
779
+ function getBracketContinuationOpeningDepth(line) {
780
+ line = cleanCommentLine(line);
781
+ line = removeMarkdownInlineLinks(line);
782
+
783
+ if (!bareCallLinePattern.test(line) && !memberCallLinePattern.test(line)) {
784
+ return 0;
785
+ }
786
+
787
+ return Math.max(0, getBracketDepthDelta(line));
788
+ }
789
+
790
+ // Advance the running bracket depth for one line. A line counts as code if depth is already open (a continuation line) or it looks like code on its own; otherwise depth resets to zero.
791
+ function getNextBracketDepth(depth, line) {
792
+ if (depth > 0) {
793
+ return Math.max(0, depth + getBracketDepthDelta(line));
794
+ }
795
+
796
+ return getBracketContinuationOpeningDepth(line);
797
+ }
798
+
719
799
  function maskIgnoredLines(characters, text) {
720
800
  let start = 0;
801
+ let depth = 0;
721
802
 
722
803
  while (start < text.length) {
723
804
  const end = getLineEndIndex(text, start);
805
+ const line = text.slice(start, end);
806
+
807
+ if (characters.slice(start, end).includes(maskCharacter)) {
808
+ depth = 0;
809
+ start = end + 1;
810
+ continue;
811
+ }
724
812
 
725
- if (isIgnoredCommentLine(text.slice(start, end))) {
813
+ const openingDepth = getBracketContinuationOpeningDepth(line);
814
+
815
+ // A line is masked if it looks like code on its own, or if it is a continuation line inside brackets opened by an earlier code-like line.
816
+ if (depth > 0 || isIgnoredCommentLine(line) || openingDepth > 0) {
726
817
  maskRange(characters, start, end);
818
+ depth = depth > 0
819
+ ? Math.max(0, depth + getBracketDepthDelta(line))
820
+ : openingDepth;
727
821
  }
728
822
 
729
823
  start = end + 1;
@@ -980,29 +1074,53 @@ const create = context => {
980
1074
  const {sourceCode} = context;
981
1075
  const comments = getRuleComments(context);
982
1076
 
1077
+ // Carry bracket depth across adjacent line comments so that a commented-out multi-line code construct spanning several `//` comments is not rewritten on its continuation lines.
1078
+ let continuationDepth = 0;
1079
+ let previousLineCommentRangeEnd;
1080
+
983
1081
  for (const comment of comments) {
984
1082
  if (comment.type === 'Shebang' || isEslintDirective(context, comment)) {
1083
+ continuationDepth = 0;
1084
+ previousLineCommentRangeEnd = undefined;
985
1085
  continue;
986
1086
  }
987
1087
 
988
- const problem = getReplacementProblem(comment, sourceCode, replacements, checkUniformCase);
989
- if (!problem) {
990
- continue;
1088
+ const range = sourceCode.getRange(comment);
1089
+ const isAdjacentLineComment = comment.type === 'Line'
1090
+ && previousLineCommentRangeEnd !== undefined
1091
+ && /^[^\S\n]*\n[^\S\n]*$/v.test(sourceCode.text.slice(previousLineCommentRangeEnd, range[0]));
1092
+
1093
+ if (!isAdjacentLineComment) {
1094
+ continuationDepth = 0;
991
1095
  }
992
1096
 
993
- yield {
994
- node,
995
- loc: {
996
- start: sourceCode.getLocFromIndex(problem.replacementRange[0]),
997
- end: sourceCode.getLocFromIndex(problem.replacementRange[1]),
998
- },
999
- messageId: MESSAGE_ID,
1000
- data: {
1001
- value: problem.value,
1002
- replacement: problem.replacement,
1003
- },
1004
- fix: fixer => fixer.replaceTextRange(problem.replacementRange, problem.replacement),
1005
- };
1097
+ if (continuationDepth <= 0) {
1098
+ const problem = getReplacementProblem(comment, sourceCode, replacements, checkUniformCase);
1099
+ if (problem) {
1100
+ yield {
1101
+ node,
1102
+ loc: {
1103
+ start: sourceCode.getLocFromIndex(problem.replacementRange[0]),
1104
+ end: sourceCode.getLocFromIndex(problem.replacementRange[1]),
1105
+ },
1106
+ messageId: MESSAGE_ID,
1107
+ data: {
1108
+ value: problem.value,
1109
+ replacement: problem.replacement,
1110
+ },
1111
+ fix: fixer => fixer.replaceTextRange(problem.replacementRange, problem.replacement),
1112
+ };
1113
+ }
1114
+ }
1115
+
1116
+ // Update continuation state for the next adjacent line comment.
1117
+ if (comment.type === 'Line') {
1118
+ continuationDepth = getNextBracketDepth(continuationDepth, comment.value);
1119
+ previousLineCommentRangeEnd = range[1];
1120
+ } else {
1121
+ continuationDepth = 0;
1122
+ previousLineCommentRangeEnd = undefined;
1123
+ }
1006
1124
  }
1007
1125
  });
1008
1126
  };
@@ -60,7 +60,7 @@ const create = context => {
60
60
  const variables = context.sourceCode.getDeclaredVariables(specifier);
61
61
 
62
62
  /* c8 ignore next 3 */
63
- if (!Array.isArray(variables) && variables.length === 1) {
63
+ if (!Array.isArray(variables) || variables.length !== 1) {
64
64
  continue;
65
65
  }
66
66
 
@@ -1,3 +1,4 @@
1
+ import {isRegExp} from 'node:util/types';
1
2
  import {getPropertyName} from '@eslint-community/eslint-utils';
2
3
  import {renameVariable} from './fix/index.js';
3
4
  import {
@@ -23,10 +24,13 @@ const messages = {
23
24
 
24
25
  const defaultPrefixes = {
25
26
  is: true,
27
+ are: true,
26
28
  has: true,
29
+ have: true,
27
30
  can: true,
28
31
  should: true,
29
32
  was: true,
33
+ were: true,
30
34
  did: true,
31
35
  will: true,
32
36
  };
@@ -83,11 +87,22 @@ function findParameter(parameters, identifier) {
83
87
  const prepareOptions = ({
84
88
  checkProperties = false,
85
89
  prefixes = {},
90
+ ignore = [],
86
91
  } = {}) => ({
87
92
  checkProperties,
88
93
  prefixes: getEnabledPrefixes({prefixes}),
94
+ ignore: ignore.map(pattern => isRegExp(pattern) ? pattern : new RegExp(pattern, 'u')),
89
95
  });
90
96
 
97
+ function isIgnoredName(name, ignore) {
98
+ return ignore.some(regexp => {
99
+ regexp.lastIndex = 0;
100
+ const isIgnored = regexp.test(name);
101
+ regexp.lastIndex = 0;
102
+ return isIgnored;
103
+ });
104
+ }
105
+
91
106
  function hasBooleanPrefix(name, prefixes) {
92
107
  name = stripLeadingUnderscores(name);
93
108
 
@@ -198,6 +213,16 @@ const isBooleanVariable = (variable, context) => {
198
213
  return false;
199
214
  }
200
215
 
216
+ // Destructuring patterns (`const {completed} = task`, `const [enabled] = list`) are intentionally not checked, since renaming a destructured binding is more involved than renaming a plain identifier.
217
+ if (definition.type === 'Variable' && definition.node.id.type !== 'Identifier') {
218
+ return false;
219
+ }
220
+
221
+ // A setter parameter's name is positional and dictated by the accessor, not chosen freely.
222
+ if (definition.type === 'Parameter' && definition.node.parent?.kind === 'set') {
223
+ return false;
224
+ }
225
+
201
226
  const scope = sourceCode.getScope(name);
202
227
 
203
228
  if (name.typeAnnotation) {
@@ -206,6 +231,11 @@ const isBooleanVariable = (variable, context) => {
206
231
  }
207
232
 
208
233
  if (definition.type === 'Parameter') {
234
+ // Some parsers (such as Svelte's `{#each}` bindings) create `Parameter` definitions whose owner is not a function and has no `params` list.
235
+ if (!isFunction(definition.node)) {
236
+ return false;
237
+ }
238
+
209
239
  const parameter = findParameter(definition.node.params, name);
210
240
 
211
241
  return parameter?.type === 'AssignmentPattern'
@@ -377,16 +407,19 @@ function getAutofix(variable, prefixes, context, suggestions) {
377
407
 
378
408
  /** @param {import('eslint').Rule.RuleContext} context */
379
409
  const create = context => {
380
- const {checkProperties, prefixes} = prepareOptions(context.options[0]);
410
+ const {checkProperties, prefixes, ignore} = prepareOptions(context.options[0]);
381
411
 
382
412
  if (prefixes.length === 0) {
383
413
  return;
384
414
  }
385
415
 
386
416
  const checkVariable = variable => {
417
+ // `hasBooleanPrefix` and `ignore` are cheap string checks, so run them before
418
+ // the expensive `isBooleanVariable` analysis.
387
419
  if (
388
- !isBooleanVariable(variable, context)
389
- || hasBooleanPrefix(variable.name, prefixes)
420
+ hasBooleanPrefix(variable.name, prefixes)
421
+ || isIgnoredName(variable.name, ignore)
422
+ || !isBooleanVariable(variable, context)
390
423
  ) {
391
424
  return;
392
425
  }
@@ -423,8 +456,9 @@ const create = context => {
423
456
 
424
457
  if (
425
458
  !name
426
- || !isBooleanProperty(node, context)
427
459
  || hasBooleanPrefix(name, prefixes)
460
+ || isIgnoredName(name, ignore)
461
+ || !isBooleanProperty(node, context)
428
462
  ) {
429
463
  return;
430
464
  }
@@ -482,10 +516,15 @@ const config = {
482
516
  pattern: '^[a-z][a-zA-Z0-9]*$',
483
517
  },
484
518
  },
519
+ ignore: {
520
+ type: 'array',
521
+ uniqueItems: true,
522
+ description: 'Patterns to ignore.',
523
+ },
485
524
  },
486
525
  },
487
526
  ],
488
- defaultOptions: [{}],
527
+ defaultOptions: [{ignore: []}],
489
528
  messages,
490
529
  },
491
530
  };
@@ -218,6 +218,13 @@ const create = context => {
218
218
  return;
219
219
  }
220
220
 
221
+ // The combined-regex gate is cheap and rejects most names, so run it before the
222
+ // import/shorthand checks.
223
+ const replacement = getNameReplacement(variable.name, options);
224
+ if (!replacement) {
225
+ return;
226
+ }
227
+
221
228
  const [definition] = variable.defs;
222
229
 
223
230
  if (!shouldCheckDefaultOrNamespaceImportName(definition, options)) {
@@ -235,11 +242,6 @@ const create = context => {
235
242
  return;
236
243
  }
237
244
 
238
- const replacement = getNameReplacement(variable.name, options);
239
- if (!replacement) {
240
- return;
241
- }
242
-
243
245
  const scopes = [
244
246
  ...variable.references.map(reference => reference.from),
245
247
  variable.scope,