eslint-plugin-unicorn 68.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 (53) hide show
  1. package/package.json +3 -1
  2. package/readme.md +12 -0
  3. package/rules/ast/is-empty-object-expression.js +2 -2
  4. package/rules/comment-content.js +136 -18
  5. package/rules/consistent-boolean-name.js +31 -4
  6. package/rules/consistent-conditional-object-spread.js +9 -3
  7. package/rules/consistent-tuple-labels.js +49 -0
  8. package/rules/custom-error-definition.js +26 -7
  9. package/rules/index.js +12 -0
  10. package/rules/no-chained-comparison.js +1 -1
  11. package/rules/no-computed-property-existence-check.js +6 -0
  12. package/rules/no-incorrect-template-string-interpolation.js +82 -30
  13. package/rules/no-instanceof-builtins.js +59 -13
  14. package/rules/no-invalid-well-known-symbol-methods.js +205 -0
  15. package/rules/no-late-current-target-access.js +9 -71
  16. package/rules/no-late-event-control.js +127 -0
  17. package/rules/no-nonstandard-builtin-properties.js +2 -0
  18. package/rules/no-object-methods-with-collections.js +4 -289
  19. package/rules/no-return-array-push.js +20 -5
  20. package/rules/no-top-level-side-effects.js +8 -1
  21. package/rules/no-unnecessary-global-this.js +36 -1
  22. package/rules/no-unreadable-object-destructuring.js +15 -0
  23. package/rules/no-unsafe-string-replacement.js +31 -0
  24. package/rules/no-unused-array-method-return.js +11 -2
  25. package/rules/no-useless-boolean-cast.js +58 -1
  26. package/rules/no-useless-concat.js +27 -3
  27. package/rules/no-useless-else.js +16 -2
  28. package/rules/no-useless-iterator-to-array.js +165 -115
  29. package/rules/prefer-abort-signal-timeout.js +395 -0
  30. package/rules/prefer-aggregate-error.js +478 -0
  31. package/rules/prefer-at.js +5 -6
  32. package/rules/prefer-code-point.js +58 -8
  33. package/rules/prefer-continue.js +16 -0
  34. package/rules/prefer-dom-node-replace-children.js +353 -0
  35. package/rules/prefer-error-is-error.js +181 -0
  36. package/rules/prefer-has-check.js +1 -4
  37. package/rules/prefer-hoisting-branch-code.js +9 -7
  38. package/rules/prefer-iterator-concat.js +21 -1
  39. package/rules/prefer-minimal-ternary.js +15 -11
  40. package/rules/prefer-modern-dom-apis.js +1 -45
  41. package/rules/prefer-observer-apis.js +498 -0
  42. package/rules/prefer-promise-try.js +238 -0
  43. package/rules/prefer-set-methods.js +337 -0
  44. package/rules/prefer-toggle-attribute.js +293 -0
  45. package/rules/prefer-url-search-parameters.js +411 -0
  46. package/rules/shared/late-event-handler.js +82 -0
  47. package/rules/shared/name-replacements.js +6 -0
  48. package/rules/utils/builtin-collection-type.js +267 -0
  49. package/rules/utils/index.js +6 -0
  50. package/rules/utils/is-boolean.js +3 -1
  51. package/rules/utils/is-event.js +127 -0
  52. package/rules/utils/should-report-replace-children-receiver.js +210 -0
  53. package/rules/utils/type-helpers.js +166 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-unicorn",
3
- "version": "68.0.0",
3
+ "version": "69.0.0",
4
4
  "description": "More than 300 powerful ESLint rules",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -113,6 +113,8 @@
113
113
  "open-editor": "^6.0.0",
114
114
  "outdent": "^0.8.0",
115
115
  "pretty-ms": "^9.3.0",
116
+ "svelte": "^5.56.3",
117
+ "svelte-eslint-parser": "^1.8.0",
116
118
  "typescript": "^6.0.3",
117
119
  "vue-eslint-parser": "^10.4.1",
118
120
  "yaml": "^2.9.0"
package/readme.md CHANGED
@@ -110,6 +110,7 @@ export default defineConfig([
110
110
  | [consistent-json-file-read](docs/rules/consistent-json-file-read.md) | Enforce consistent JSON file reads before `JSON.parse()`. | ✅ | 🔧 | | |
111
111
  | [consistent-optional-chaining](docs/rules/consistent-optional-chaining.md) | Enforce consistent optional chaining for same-base member access. | ✅ ☑️ | | 💡 | |
112
112
  | [consistent-template-literal-escape](docs/rules/consistent-template-literal-escape.md) | Enforce consistent style for escaping `${` in template literals. | ✅ | 🔧 | | |
113
+ | [consistent-tuple-labels](docs/rules/consistent-tuple-labels.md) | Enforce consistent labels on tuple type elements. | ✅ | | | |
113
114
  | [custom-error-definition](docs/rules/custom-error-definition.md) | Enforce correct `Error` subclassing. | | 🔧 | | |
114
115
  | [default-export-style](docs/rules/default-export-style.md) | Enforce consistent default export declarations. | ✅ | 🔧 | 💡 | |
115
116
  | [dom-node-dataset](docs/rules/dom-node-dataset.md) | Enforce consistent style for DOM element dataset access. | ✅ ☑️ | 🔧 | | |
@@ -179,8 +180,10 @@ export default defineConfig([
179
180
  | [no-invalid-fetch-options](docs/rules/no-invalid-fetch-options.md) | Disallow invalid options in `fetch()` and `new Request()`. | ✅ ☑️ | | | |
180
181
  | [no-invalid-file-input-accept](docs/rules/no-invalid-file-input-accept.md) | Disallow invalid `accept` values on file inputs. | | 🔧 | | |
181
182
  | [no-invalid-remove-event-listener](docs/rules/no-invalid-remove-event-listener.md) | Prevent calling `EventTarget#removeEventListener()` with the result of an expression. | ✅ ☑️ | | | |
183
+ | [no-invalid-well-known-symbol-methods](docs/rules/no-invalid-well-known-symbol-methods.md) | Disallow invalid implementations of well-known symbol methods. | ✅ ☑️ | 🔧 | 💡 | |
182
184
  | [no-keyword-prefix](docs/rules/no-keyword-prefix.md) | Disallow identifiers starting with `new` or `class`. | | | | |
183
185
  | [no-late-current-target-access](docs/rules/no-late-current-target-access.md) | Disallow accessing `event.currentTarget` after the synchronous event dispatch has finished. | ✅ | | | |
186
+ | [no-late-event-control](docs/rules/no-late-event-control.md) | Disallow event-control method calls after the synchronous event dispatch has finished. | ✅ | | | |
184
187
  | [no-lonely-if](docs/rules/no-lonely-if.md) | Disallow `if` statements as the only statement in `if` blocks without `else`. | ✅ ☑️ | 🔧 | | |
185
188
  | [no-loop-iterable-mutation](docs/rules/no-loop-iterable-mutation.md) | Disallow mutating a loop iterable during iteration. | ✅ | | | |
186
189
  | [no-magic-array-flat-depth](docs/rules/no-magic-array-flat-depth.md) | Disallow a magic number as the `depth` argument in `Array#flat(…).` | ✅ ☑️ | | | |
@@ -261,8 +264,10 @@ export default defineConfig([
261
264
  | [number-literal-case](docs/rules/number-literal-case.md) | Enforce proper case for numeric literals. | ✅ ☑️ | 🔧 | | |
262
265
  | [numeric-separators-style](docs/rules/numeric-separators-style.md) | Enforce the style of numeric separators by correctly grouping digits. | ✅ ☑️ | 🔧 | | |
263
266
  | [operator-assignment](docs/rules/operator-assignment.md) | Require assignment operator shorthand where possible. | ✅ | 🔧 | 💡 | |
267
+ | [prefer-abort-signal-timeout](docs/rules/prefer-abort-signal-timeout.md) | Prefer `AbortSignal.timeout()` over manually aborting an `AbortController` with `setTimeout()`. | ✅ | | 💡 | |
264
268
  | [prefer-add-event-listener](docs/rules/prefer-add-event-listener.md) | Prefer `.addEventListener()` and `.removeEventListener()` over `on`-functions. | ✅ ☑️ | 🔧 | | |
265
269
  | [prefer-add-event-listener-options](docs/rules/prefer-add-event-listener-options.md) | Prefer an options object over a boolean in `.addEventListener()`. | ✅ ☑️ | 🔧 | | |
270
+ | [prefer-aggregate-error](docs/rules/prefer-aggregate-error.md) | Prefer `AggregateError` when throwing collected errors. | ✅ ☑️ | 🔧 | | |
266
271
  | [prefer-array-find](docs/rules/prefer-array-find.md) | Prefer `.find(…)` and `.findLast(…)` over the first or last element from `.filter(…)`. | ✅ ☑️ | 🔧 | 💡 | |
267
272
  | [prefer-array-flat](docs/rules/prefer-array-flat.md) | Prefer `Array#flat()` over legacy techniques to flatten arrays. | ✅ ☑️ | 🔧 | | |
268
273
  | [prefer-array-flat-map](docs/rules/prefer-array-flat-map.md) | Prefer `.flatMap(…)` over `.map(…).flat()` and `.filter(…).flatMap(…)`. | ✅ ☑️ | 🔧 | 💡 | |
@@ -289,9 +294,11 @@ export default defineConfig([
289
294
  | [prefer-dom-node-append](docs/rules/prefer-dom-node-append.md) | Prefer `Element#append()` over `Node#appendChild()`. | ✅ ☑️ | 🔧 | | |
290
295
  | [prefer-dom-node-html-methods](docs/rules/prefer-dom-node-html-methods.md) | Prefer `.getHTML()` and `.setHTML()` over `.innerHTML`. | | 🔧 | 💡 | |
291
296
  | [prefer-dom-node-remove](docs/rules/prefer-dom-node-remove.md) | Prefer `childNode.remove()` over `parentNode.removeChild(childNode)`. | ✅ ☑️ | 🔧 | 💡 | |
297
+ | [prefer-dom-node-replace-children](docs/rules/prefer-dom-node-replace-children.md) | Prefer `.replaceChildren()` when emptying DOM children. | ✅ ☑️ | 🔧 | | |
292
298
  | [prefer-dom-node-text-content](docs/rules/prefer-dom-node-text-content.md) | Prefer `.textContent` over `.innerText`. | ✅ ☑️ | | 💡 | |
293
299
  | [prefer-early-return](docs/rules/prefer-early-return.md) | Prefer early returns over full-function conditional wrapping. | ✅ ☑️ | 🔧 | 💡 | |
294
300
  | [prefer-else-if](docs/rules/prefer-else-if.md) | Prefer `else if` over adjacent `if` statements with related conditions. | ✅ | 🔧 | 💡 | |
301
+ | [prefer-error-is-error](docs/rules/prefer-error-is-error.md) | Prefer `Error.isError()` when checking for errors. | | 🔧 | | |
295
302
  | [prefer-event-target](docs/rules/prefer-event-target.md) | Prefer `EventTarget` over `EventEmitter`. | ✅ ☑️ | | | |
296
303
  | [prefer-export-from](docs/rules/prefer-export-from.md) | Prefer `export…from` when re-exporting. | ✅ | 🔧 | 💡 | |
297
304
  | [prefer-flat-math-min-max](docs/rules/prefer-flat-math-min-max.md) | Prefer flat `Math.min()` and `Math.max()` calls over nested calls. | ✅ ☑️ | 🔧 | | |
@@ -331,9 +338,11 @@ export default defineConfig([
331
338
  | [prefer-object-destructuring-defaults](docs/rules/prefer-object-destructuring-defaults.md) | Prefer object destructuring defaults over default object literals with spread. | ✅ | | 💡 | |
332
339
  | [prefer-object-from-entries](docs/rules/prefer-object-from-entries.md) | Prefer using `Object.fromEntries(…)` to transform a list of key-value pairs into an object. | ✅ ☑️ | 🔧 | | |
333
340
  | [prefer-object-iterable-methods](docs/rules/prefer-object-iterable-methods.md) | Prefer the most specific `Object` iterable method. | ✅ ☑️ | 🔧 | | |
341
+ | [prefer-observer-apis](docs/rules/prefer-observer-apis.md) | Prefer observer APIs over resize and scroll listeners with layout reads. | ✅ | | | |
334
342
  | [prefer-optional-catch-binding](docs/rules/prefer-optional-catch-binding.md) | Prefer omitting the `catch` binding parameter. | ✅ ☑️ | 🔧 | | |
335
343
  | [prefer-path2d](docs/rules/prefer-path2d.md) | Prefer `Path2D` for repeatedly drawn canvas paths. | ✅ ☑️ | | | |
336
344
  | [prefer-private-class-fields](docs/rules/prefer-private-class-fields.md) | Prefer private class fields over the underscore-prefix convention. | ✅ | 🔧 | | |
345
+ | [prefer-promise-try](docs/rules/prefer-promise-try.md) | Prefer `Promise.try()` over promise-wrapping boilerplate. | ✅ | 🔧 | | |
337
346
  | [prefer-promise-with-resolvers](docs/rules/prefer-promise-with-resolvers.md) | Prefer `Promise.withResolvers()` when extracting resolver functions from `new Promise()`. | ✅ ☑️ | 🔧 | | |
338
347
  | [prefer-prototype-methods](docs/rules/prefer-prototype-methods.md) | Prefer borrowing methods from the prototype instead of the instance. | ✅ ☑️ | 🔧 | | |
339
348
  | [prefer-query-selector](docs/rules/prefer-query-selector.md) | Prefer `.querySelector()` and `.querySelectorAll()` over older DOM query methods. | ✅ | 🔧 | | |
@@ -344,6 +353,7 @@ export default defineConfig([
344
353
  | [prefer-response-static-json](docs/rules/prefer-response-static-json.md) | Prefer `Response.json()` over `new Response(JSON.stringify())`. | ✅ ☑️ | 🔧 | | |
345
354
  | [prefer-scoped-selector](docs/rules/prefer-scoped-selector.md) | Prefer `:scope` when using element query selector methods. | ✅ | | 💡 | |
346
355
  | [prefer-set-has](docs/rules/prefer-set-has.md) | Prefer `Set#has()` over `Array#includes()` when checking for existence or non-existence. | ✅ ☑️ | 🔧 | 💡 | |
356
+ | [prefer-set-methods](docs/rules/prefer-set-methods.md) | Prefer `Set` methods for Set operations. | ✅ | 🔧 | 💡 | |
347
357
  | [prefer-set-size](docs/rules/prefer-set-size.md) | Prefer using `Set#size` instead of `Array#length`. | ✅ ☑️ | 🔧 | | |
348
358
  | [prefer-short-arrow-method](docs/rules/prefer-short-arrow-method.md) | Prefer arrow function properties over methods with a single return. | | 🔧 | | |
349
359
  | [prefer-simple-condition-first](docs/rules/prefer-simple-condition-first.md) | Prefer simple conditions first in logical expressions. | ✅ ☑️ | 🔧 | 💡 | |
@@ -367,6 +377,7 @@ export default defineConfig([
367
377
  | [prefer-switch](docs/rules/prefer-switch.md) | Prefer `switch` over multiple `else-if`. | ✅ ☑️ | 🔧 | | |
368
378
  | [prefer-temporal](docs/rules/prefer-temporal.md) | Prefer `Temporal` over `Date`. | | 🔧 | 💡 | |
369
379
  | [prefer-ternary](docs/rules/prefer-ternary.md) | Prefer ternary expressions over simple `if` statements that return or assign values. | ✅ ☑️ | 🔧 | 💡 | |
380
+ | [prefer-toggle-attribute](docs/rules/prefer-toggle-attribute.md) | Prefer using `Element#toggleAttribute()` to toggle attributes. | ✅ ☑️ | 🔧 | 💡 | |
370
381
  | [prefer-top-level-await](docs/rules/prefer-top-level-await.md) | Prefer top-level await over top-level promises and async function calls. | ✅ ☑️ | | 💡 | |
371
382
  | [prefer-type-error](docs/rules/prefer-type-error.md) | Enforce throwing `TypeError` in type checking conditions. | ✅ ☑️ | 🔧 | | |
372
383
  | [prefer-type-literal-last](docs/rules/prefer-type-literal-last.md) | Require type literals to be last in union types. | ✅ | 🔧 | | |
@@ -375,6 +386,7 @@ export default defineConfig([
375
386
  | [prefer-unicode-code-point-escapes](docs/rules/prefer-unicode-code-point-escapes.md) | Prefer Unicode code point escapes over legacy escape sequences. | ✅ ☑️ | 🔧 | 💡 | |
376
387
  | [prefer-url-can-parse](docs/rules/prefer-url-can-parse.md) | Prefer `URL.canParse()` over constructing a `URL` in a try/catch for validation. | ✅ ☑️ | 🔧 | | |
377
388
  | [prefer-url-href](docs/rules/prefer-url-href.md) | Prefer `URL#href` over stringifying a `URL`. | ✅ ☑️ | 🔧 | | |
389
+ | [prefer-url-search-parameters](docs/rules/prefer-url-search-parameters.md) | Prefer `URLSearchParams` over manually splitting query strings. | ✅ ☑️ | | 💡 | |
378
390
  | [prefer-while-loop-condition](docs/rules/prefer-while-loop-condition.md) | Prefer putting the condition in the while statement. | ✅ ☑️ | 🔧 | | |
379
391
  | [relative-url-style](docs/rules/relative-url-style.md) | Enforce consistent relative URL style. | ✅ ☑️ | 🔧 | 💡 | |
380
392
  | [require-array-join-separator](docs/rules/require-array-join-separator.md) | Enforce using the separator argument with `Array#join()`. | ✅ ☑️ | 🔧 | | |
@@ -1,5 +1,5 @@
1
- const isEmptyArrayExpression = node =>
1
+ const isEmptyObjectExpression = node =>
2
2
  node.type === 'ObjectExpression'
3
3
  && node.properties.length === 0;
4
4
 
5
- export default isEmptyArrayExpression;
5
+ export default isEmptyObjectExpression;
@@ -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
  };
@@ -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
 
@@ -216,6 +231,11 @@ const isBooleanVariable = (variable, context) => {
216
231
  }
217
232
 
218
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
+
219
239
  const parameter = findParameter(definition.node.params, name);
220
240
 
221
241
  return parameter?.type === 'AssignmentPattern'
@@ -387,17 +407,18 @@ function getAutofix(variable, prefixes, context, suggestions) {
387
407
 
388
408
  /** @param {import('eslint').Rule.RuleContext} context */
389
409
  const create = context => {
390
- const {checkProperties, prefixes} = prepareOptions(context.options[0]);
410
+ const {checkProperties, prefixes, ignore} = prepareOptions(context.options[0]);
391
411
 
392
412
  if (prefixes.length === 0) {
393
413
  return;
394
414
  }
395
415
 
396
416
  const checkVariable = variable => {
397
- // `hasBooleanPrefix` is a cheap string check, so run it before the expensive
398
- // `isBooleanVariable` analysis.
417
+ // `hasBooleanPrefix` and `ignore` are cheap string checks, so run them before
418
+ // the expensive `isBooleanVariable` analysis.
399
419
  if (
400
420
  hasBooleanPrefix(variable.name, prefixes)
421
+ || isIgnoredName(variable.name, ignore)
401
422
  || !isBooleanVariable(variable, context)
402
423
  ) {
403
424
  return;
@@ -436,6 +457,7 @@ const create = context => {
436
457
  if (
437
458
  !name
438
459
  || hasBooleanPrefix(name, prefixes)
460
+ || isIgnoredName(name, ignore)
439
461
  || !isBooleanProperty(node, context)
440
462
  ) {
441
463
  return;
@@ -494,10 +516,15 @@ const config = {
494
516
  pattern: '^[a-z][a-zA-Z0-9]*$',
495
517
  },
496
518
  },
519
+ ignore: {
520
+ type: 'array',
521
+ uniqueItems: true,
522
+ description: 'Patterns to ignore.',
523
+ },
497
524
  },
498
525
  },
499
526
  ],
500
- defaultOptions: [{}],
527
+ defaultOptions: [{ignore: []}],
501
528
  messages,
502
529
  },
503
530
  };
@@ -22,6 +22,12 @@ const messages = {
22
22
  const nullishOperators = new Set(['==', '===']);
23
23
  const nonNullishOperators = new Set(['!=', '!==']);
24
24
 
25
+ // `{...{}}`, `{...undefined}`, and `{...null}` all spread nothing.
26
+ const isEmptySpreadBranch = node =>
27
+ isEmptyObjectExpression(node)
28
+ || isUndefined(node)
29
+ || isNullLiteral(node);
30
+
25
31
  const isObjectSpreadArgument = node => (
26
32
  node.parent.type === 'SpreadElement'
27
33
  && node.parent.argument === node
@@ -203,8 +209,8 @@ function getConditionalExpressionChildText(node, context) {
203
209
 
204
210
  function getConditionalExpressionProblem(conditionalExpression, context) {
205
211
  const {test, consequent, alternate} = conditionalExpression;
206
- const isAlternateEmpty = isEmptyObjectExpression(alternate);
207
- const isConsequentEmpty = isEmptyObjectExpression(consequent);
212
+ const isAlternateEmpty = isEmptySpreadBranch(alternate);
213
+ const isConsequentEmpty = isEmptySpreadBranch(consequent);
208
214
 
209
215
  if (isAlternateEmpty === isConsequentEmpty) {
210
216
  return;
@@ -260,7 +266,7 @@ function getConditionalExpressionProblem(conditionalExpression, context) {
260
266
  function getLogicalExpressionProblem(logicalExpression, context) {
261
267
  if (
262
268
  logicalExpression.operator !== '&&'
263
- || isEmptyObjectExpression(logicalExpression.right)
269
+ || isEmptySpreadBranch(logicalExpression.right)
264
270
  ) {
265
271
  return;
266
272
  }
@@ -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
  }
package/rules/index.js CHANGED
@@ -19,6 +19,7 @@ export {default as 'consistent-function-style'} from './consistent-function-styl
19
19
  export {default as 'consistent-json-file-read'} from './consistent-json-file-read.js';
20
20
  export {default as 'consistent-optional-chaining'} from './consistent-optional-chaining.js';
21
21
  export {default as 'consistent-template-literal-escape'} from './consistent-template-literal-escape.js';
22
+ export {default as 'consistent-tuple-labels'} from './consistent-tuple-labels.js';
22
23
  export {default as 'custom-error-definition'} from './custom-error-definition.js';
23
24
  export {default as 'default-export-style'} from './default-export-style.js';
24
25
  export {default as 'dom-node-dataset'} from './dom-node-dataset.js';
@@ -88,8 +89,10 @@ export {default as 'no-invalid-character-comparison'} from './no-invalid-charact
88
89
  export {default as 'no-invalid-fetch-options'} from './no-invalid-fetch-options.js';
89
90
  export {default as 'no-invalid-file-input-accept'} from './no-invalid-file-input-accept.js';
90
91
  export {default as 'no-invalid-remove-event-listener'} from './no-invalid-remove-event-listener.js';
92
+ export {default as 'no-invalid-well-known-symbol-methods'} from './no-invalid-well-known-symbol-methods.js';
91
93
  export {default as 'no-keyword-prefix'} from './no-keyword-prefix.js';
92
94
  export {default as 'no-late-current-target-access'} from './no-late-current-target-access.js';
95
+ export {default as 'no-late-event-control'} from './no-late-event-control.js';
93
96
  export {default as 'no-lonely-if'} from './no-lonely-if.js';
94
97
  export {default as 'no-loop-iterable-mutation'} from './no-loop-iterable-mutation.js';
95
98
  export {default as 'no-magic-array-flat-depth'} from './no-magic-array-flat-depth.js';
@@ -170,8 +173,10 @@ export {default as 'no-zero-fractions'} from './no-zero-fractions.js';
170
173
  export {default as 'number-literal-case'} from './number-literal-case.js';
171
174
  export {default as 'numeric-separators-style'} from './numeric-separators-style.js';
172
175
  export {default as 'operator-assignment'} from './operator-assignment.js';
176
+ export {default as 'prefer-abort-signal-timeout'} from './prefer-abort-signal-timeout.js';
173
177
  export {default as 'prefer-add-event-listener-options'} from './prefer-add-event-listener-options.js';
174
178
  export {default as 'prefer-add-event-listener'} from './prefer-add-event-listener.js';
179
+ export {default as 'prefer-aggregate-error'} from './prefer-aggregate-error.js';
175
180
  export {default as 'prefer-array-find'} from './prefer-array-find.js';
176
181
  export {default as 'prefer-array-flat-map'} from './prefer-array-flat-map.js';
177
182
  export {default as 'prefer-array-flat'} from './prefer-array-flat.js';
@@ -198,9 +203,11 @@ export {default as 'prefer-dispose'} from './prefer-dispose.js';
198
203
  export {default as 'prefer-dom-node-append'} from './prefer-dom-node-append.js';
199
204
  export {default as 'prefer-dom-node-html-methods'} from './prefer-dom-node-html-methods.js';
200
205
  export {default as 'prefer-dom-node-remove'} from './prefer-dom-node-remove.js';
206
+ export {default as 'prefer-dom-node-replace-children'} from './prefer-dom-node-replace-children.js';
201
207
  export {default as 'prefer-dom-node-text-content'} from './prefer-dom-node-text-content.js';
202
208
  export {default as 'prefer-early-return'} from './prefer-early-return.js';
203
209
  export {default as 'prefer-else-if'} from './prefer-else-if.js';
210
+ export {default as 'prefer-error-is-error'} from './prefer-error-is-error.js';
204
211
  export {default as 'prefer-event-target'} from './prefer-event-target.js';
205
212
  export {default as 'prefer-export-from'} from './prefer-export-from.js';
206
213
  export {default as 'prefer-flat-math-min-max'} from './prefer-flat-math-min-max.js';
@@ -240,9 +247,11 @@ export {default as 'prefer-object-define-properties'} from './prefer-object-defi
240
247
  export {default as 'prefer-object-destructuring-defaults'} from './prefer-object-destructuring-defaults.js';
241
248
  export {default as 'prefer-object-from-entries'} from './prefer-object-from-entries.js';
242
249
  export {default as 'prefer-object-iterable-methods'} from './prefer-object-iterable-methods.js';
250
+ export {default as 'prefer-observer-apis'} from './prefer-observer-apis.js';
243
251
  export {default as 'prefer-optional-catch-binding'} from './prefer-optional-catch-binding.js';
244
252
  export {default as 'prefer-path2d'} from './prefer-path2d.js';
245
253
  export {default as 'prefer-private-class-fields'} from './prefer-private-class-fields.js';
254
+ export {default as 'prefer-promise-try'} from './prefer-promise-try.js';
246
255
  export {default as 'prefer-promise-with-resolvers'} from './prefer-promise-with-resolvers.js';
247
256
  export {default as 'prefer-prototype-methods'} from './prefer-prototype-methods.js';
248
257
  export {default as 'prefer-query-selector'} from './prefer-query-selector.js';
@@ -253,6 +262,7 @@ export {default as 'prefer-regexp-test'} from './prefer-regexp-test.js';
253
262
  export {default as 'prefer-response-static-json'} from './prefer-response-static-json.js';
254
263
  export {default as 'prefer-scoped-selector'} from './prefer-scoped-selector.js';
255
264
  export {default as 'prefer-set-has'} from './prefer-set-has.js';
265
+ export {default as 'prefer-set-methods'} from './prefer-set-methods.js';
256
266
  export {default as 'prefer-set-size'} from './prefer-set-size.js';
257
267
  export {default as 'prefer-short-arrow-method'} from './prefer-short-arrow-method.js';
258
268
  export {default as 'prefer-simple-condition-first'} from './prefer-simple-condition-first.js';
@@ -276,6 +286,7 @@ export {default as 'prefer-structured-clone'} from './prefer-structured-clone.js
276
286
  export {default as 'prefer-switch'} from './prefer-switch.js';
277
287
  export {default as 'prefer-temporal'} from './prefer-temporal.js';
278
288
  export {default as 'prefer-ternary'} from './prefer-ternary.js';
289
+ export {default as 'prefer-toggle-attribute'} from './prefer-toggle-attribute.js';
279
290
  export {default as 'prefer-top-level-await'} from './prefer-top-level-await.js';
280
291
  export {default as 'prefer-type-error'} from './prefer-type-error.js';
281
292
  export {default as 'prefer-type-literal-last'} from './prefer-type-literal-last.js';
@@ -284,6 +295,7 @@ export {default as 'prefer-unary-minus'} from './prefer-unary-minus.js';
284
295
  export {default as 'prefer-unicode-code-point-escapes'} from './prefer-unicode-code-point-escapes.js';
285
296
  export {default as 'prefer-url-can-parse'} from './prefer-url-can-parse.js';
286
297
  export {default as 'prefer-url-href'} from './prefer-url-href.js';
298
+ export {default as 'prefer-url-search-parameters'} from './prefer-url-search-parameters.js';
287
299
  export {default as 'prefer-while-loop-condition'} from './prefer-while-loop-condition.js';
288
300
  export {default as 'relative-url-style'} from './relative-url-style.js';
289
301
  export {default as 'require-array-join-separator'} from './require-array-join-separator.js';