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.
- package/index.js +4 -0
- package/package.json +8 -3
- package/readme.md +165 -16
- package/rules/ast/is-empty-node.js +1 -5
- package/rules/ast/is-empty-object-expression.js +2 -2
- package/rules/better-dom-traversing.js +17 -8
- package/rules/class-reference-in-static-methods.js +14 -2
- package/rules/comment-content.js +136 -18
- package/rules/consistent-assert.js +1 -1
- package/rules/consistent-boolean-name.js +44 -5
- package/rules/consistent-compound-words.js +7 -5
- package/rules/consistent-conditional-object-spread.js +360 -0
- package/rules/consistent-destructuring.js +5 -0
- package/rules/consistent-existence-index-check.js +1 -1
- package/rules/consistent-json-file-read.js +4 -16
- package/rules/consistent-optional-chaining.js +3 -20
- package/rules/consistent-tuple-labels.js +49 -0
- package/rules/custom-error-definition.js +26 -7
- package/rules/default-export-style.js +569 -0
- package/rules/expiring-todo-comments.js +1 -1
- package/rules/explicit-length-check.js +7 -32
- package/rules/import-style.js +8 -6
- package/rules/index.js +50 -1
- package/rules/logical-assignment-operators.js +17 -4
- package/rules/{prevent-abbreviations.js → name-replacements.js} +11 -9
- package/rules/new-for-builtins.js +39 -5
- package/rules/no-accessor-recursion.js +7 -0
- package/rules/no-accidental-bitwise-operator.js +97 -0
- package/rules/no-array-callback-reference.js +1 -7
- package/rules/no-array-concat-in-loop.js +45 -0
- package/rules/no-array-front-mutation.js +64 -0
- package/rules/no-array-sort-for-min-max.js +225 -0
- package/rules/no-array-splice.js +78 -64
- package/rules/no-boolean-sort-comparator.js +215 -0
- package/rules/no-break-in-nested-loop.js +33 -0
- package/rules/no-chained-comparison.js +116 -0
- package/rules/no-collection-bracket-access.js +165 -0
- package/rules/no-computed-property-existence-check.js +6 -0
- package/rules/no-confusing-array-splice.js +2 -21
- package/rules/no-confusing-array-with.js +2 -20
- package/rules/no-constant-zero-expression.js +91 -0
- package/rules/no-declarations-before-early-exit.js +29 -36
- package/rules/no-double-comparison.js +117 -0
- package/rules/no-duplicate-if-branches.js +143 -0
- package/rules/no-duplicate-logical-operands.js +151 -0
- package/rules/no-for-each.js +11 -12
- package/rules/no-for-loop.js +3 -15
- package/rules/no-impossible-length-comparison.js +166 -0
- package/rules/no-incorrect-query-selector.js +8 -4
- package/rules/no-incorrect-template-string-interpolation.js +106 -43
- package/rules/no-instanceof-builtins.js +73 -14
- package/rules/no-invalid-argument-count.js +110 -54
- package/rules/no-invalid-character-comparison.js +121 -0
- package/rules/no-invalid-fetch-options.js +3 -1
- package/rules/no-invalid-well-known-symbol-methods.js +205 -0
- package/rules/no-keyword-prefix.js +14 -4
- package/rules/no-late-current-target-access.js +13 -107
- package/rules/no-late-event-control.js +127 -0
- package/rules/no-loop-iterable-mutation.js +541 -0
- package/rules/no-manually-wrapped-comments.js +10 -2
- package/rules/no-misrefactored-assignment.js +102 -0
- package/rules/no-named-default.js +35 -2
- package/rules/no-nonstandard-builtin-properties.js +1383 -0
- package/rules/no-object-methods-with-collections.js +4 -289
- package/rules/no-process-exit.js +16 -12
- package/rules/no-redundant-comparison.js +5 -12
- package/rules/no-return-array-push.js +20 -5
- package/rules/no-selector-as-dom-name.js +150 -0
- package/rules/no-thenable.js +27 -11
- package/rules/no-top-level-side-effects.js +8 -1
- package/rules/no-uncalled-method.js +2 -8
- package/rules/no-undeclared-class-members.js +8 -6
- package/rules/no-unnecessary-boolean-comparison.js +131 -0
- package/rules/no-unnecessary-global-this.js +36 -1
- package/rules/no-unnecessary-splice.js +15 -20
- package/rules/no-unreadable-for-of-expression.js +2 -1
- package/rules/no-unreadable-object-destructuring.js +15 -0
- package/rules/no-unsafe-property-key.js +10 -15
- package/rules/no-unsafe-string-replacement.js +31 -0
- package/rules/no-unused-array-method-return.js +11 -2
- package/rules/no-unused-properties.js +4 -3
- package/rules/no-useless-boolean-cast.js +58 -1
- package/rules/no-useless-coercion.js +71 -15
- package/rules/no-useless-compound-assignment.js +110 -0
- package/rules/no-useless-concat.js +27 -3
- package/rules/no-useless-delete-check.js +353 -0
- package/rules/no-useless-else.js +27 -62
- package/rules/no-useless-error-capture-stack-trace.js +1 -1
- package/rules/no-useless-fallback-in-spread.js +7 -118
- package/rules/no-useless-iterator-to-array.js +165 -115
- package/rules/no-useless-logical-operand.js +248 -0
- package/rules/no-useless-undefined.js +11 -28
- package/rules/no-xor-as-exponentiation.js +59 -0
- package/rules/prefer-abort-signal-timeout.js +395 -0
- package/rules/prefer-aggregate-error.js +478 -0
- package/rules/prefer-array-find.js +95 -5
- package/rules/prefer-array-flat-map.js +9 -21
- package/rules/prefer-array-flat.js +1 -24
- package/rules/prefer-array-from-async.js +273 -0
- package/rules/prefer-array-iterable-methods.js +157 -0
- package/rules/prefer-array-some.js +56 -31
- package/rules/prefer-at.js +6 -30
- package/rules/prefer-await.js +29 -0
- package/rules/prefer-boolean-return.js +224 -0
- package/rules/prefer-code-point.js +58 -8
- package/rules/prefer-continue.js +377 -0
- package/rules/prefer-default-parameters.js +10 -6
- package/rules/prefer-dispose.js +1 -5
- package/rules/prefer-dom-node-replace-children.js +353 -0
- package/rules/prefer-early-return.js +160 -30
- package/rules/prefer-else-if.js +50 -53
- package/rules/prefer-error-is-error.js +181 -0
- package/rules/prefer-event-target.js +3 -13
- package/rules/prefer-flat-math-min-max.js +103 -0
- package/rules/prefer-global-this.js +1 -1
- package/rules/prefer-has-check.js +1 -4
- package/rules/prefer-hoisting-branch-code.js +424 -0
- package/rules/prefer-identifier-import-export-specifiers.js +6 -11
- package/rules/prefer-includes-over-repeated-comparisons.js +5 -1
- package/rules/prefer-includes.js +6 -0
- package/rules/prefer-iterator-concat.js +21 -1
- package/rules/prefer-logical-operator-over-ternary.js +12 -20
- package/rules/prefer-math-constants.js +103 -0
- package/rules/prefer-math-min-max.js +1 -5
- package/rules/prefer-minimal-ternary.js +41 -13
- package/rules/prefer-modern-dom-apis.js +1 -45
- package/rules/prefer-node-protocol.js +1 -0
- package/rules/prefer-number-properties.js +2 -5
- package/rules/prefer-object-from-entries.js +6 -1
- package/rules/prefer-object-iterable-methods.js +36 -11
- package/rules/prefer-observer-apis.js +498 -0
- package/rules/prefer-optional-catch-binding.js +30 -24
- package/rules/prefer-private-class-fields.js +2 -8
- package/rules/prefer-promise-try.js +238 -0
- package/rules/prefer-promise-with-resolvers.js +248 -0
- package/rules/prefer-query-selector.js +79 -17
- package/rules/prefer-queue-microtask.js +7 -9
- package/rules/prefer-regexp-escape.js +263 -0
- package/rules/prefer-set-methods.js +337 -0
- package/rules/prefer-set-size.js +10 -2
- package/rules/prefer-short-arrow-method.js +3 -3
- package/rules/prefer-single-replace.js +218 -0
- package/rules/prefer-spread.js +14 -21
- package/rules/prefer-string-replace-all.js +5 -0
- package/rules/prefer-structured-clone.js +2 -2
- package/rules/prefer-switch.js +1 -0
- package/rules/prefer-ternary.js +4 -2
- package/rules/prefer-toggle-attribute.js +293 -0
- package/rules/prefer-type-literal-last.js +22 -54
- package/rules/prefer-unary-minus.js +103 -0
- package/rules/prefer-url-can-parse.js +308 -0
- package/rules/prefer-url-search-parameters.js +411 -0
- package/rules/prefer-while-loop-condition.js +252 -0
- package/rules/require-passive-events.js +11 -4
- package/rules/require-proxy-trap-boolean-return.js +40 -126
- package/rules/rule/to-eslint-listener.js +5 -3
- package/rules/shared/array-concat-in-loop.js +151 -0
- package/rules/shared/identifier-checks.js +4 -16
- package/rules/shared/late-event-handler.js +82 -0
- package/rules/shared/{abbreviations.js → name-replacements.js} +63 -0
- package/rules/shared/no-array-mutate-rule.js +19 -2
- package/rules/shared/regexp-escape.js +81 -0
- package/rules/shared/splice-replacements.js +1 -18
- package/rules/string-content.js +11 -4
- package/rules/template-indent.js +2 -8
- package/rules/throw-new-error.js +7 -0
- package/rules/utils/block-scope.js +42 -0
- package/rules/utils/builtin-collection-type.js +267 -0
- package/rules/utils/builtins.js +50 -1
- package/rules/utils/comments.js +16 -0
- package/rules/utils/comparison.js +17 -0
- package/rules/utils/contains-suspension-point.js +35 -0
- package/rules/utils/get-const-variable-initializer.js +31 -0
- package/rules/utils/global-reference-tracker.js +14 -1
- package/rules/utils/has-optional-chain-element.js +36 -4
- package/rules/utils/index.js +38 -2
- package/rules/utils/is-boolean.js +26 -1
- package/rules/utils/is-event.js +127 -0
- package/rules/utils/is-function-self-used-inside.js +1 -5
- package/rules/utils/is-weak-map.js +21 -0
- package/rules/utils/is-weak-set.js +21 -0
- package/rules/utils/length-or-size.js +53 -0
- package/rules/utils/member-expression.js +18 -0
- package/rules/utils/needs-semicolon.js +1 -5
- package/rules/utils/numeric.js +20 -0
- package/rules/utils/should-add-parentheses-to-logical-expression-child.js +8 -14
- package/rules/utils/should-add-parentheses-to-member-expression-object.js +1 -5
- package/rules/utils/should-add-parentheses-to-unary-expression.js +8 -2
- package/rules/utils/should-report-replace-children-receiver.js +210 -0
- package/rules/utils/track-branch-exits.js +79 -0
- package/rules/utils/type-helpers.js +166 -8
- 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 {
|
|
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,
|
|
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 =>
|
|
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,
|
|
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,
|
|
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 (
|
|
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 (
|
|
367
|
+
if (
|
|
368
|
+
!superClassReferenceNode
|
|
369
|
+
|| !isReferenceNameAvailable(sourceCode, node, superClassReferenceNode)
|
|
370
|
+
) {
|
|
359
371
|
return problemWithoutSuggestion(node, MESSAGE_ID_SUPER_CLASS_UNAVAILABLE);
|
|
360
372
|
}
|
|
361
373
|
|
package/rules/comment-content.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
989
|
-
|
|
990
|
-
|
|
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
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
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)
|
|
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
|
-
|
|
389
|
-
||
|
|
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,
|