eslint-plugin-unicorn 63.0.0 → 65.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 +26 -18
- package/package.json +32 -30
- package/readme.md +183 -147
- package/rules/ast/call-or-new-expression.js +3 -5
- package/rules/ast/literal.js +1 -6
- package/rules/better-dom-traversing.js +291 -0
- package/rules/catch-error-name.js +3 -3
- package/rules/consistent-compound-words.js +452 -0
- package/rules/consistent-destructuring.js +234 -22
- package/rules/consistent-function-scoping.js +35 -11
- package/rules/consistent-json-file-read.js +245 -0
- package/rules/consistent-template-literal-escape.js +52 -0
- package/rules/custom-error-definition.js +176 -71
- package/rules/dom-node-dataset.js +431 -0
- package/rules/escape-case.js +2 -1
- package/rules/expiring-todo-comments.js +56 -102
- package/rules/explicit-length-check.js +145 -33
- package/rules/filename-case.js +97 -13
- package/rules/fix/append-argument.js +1 -1
- package/rules/fix/remove-expression-statement.js +1 -0
- package/rules/import-style.js +30 -10
- package/rules/index.js +37 -3
- package/rules/isolated-functions.js +11 -5
- package/rules/no-accessor-recursion.js +1 -2
- package/rules/no-anonymous-default-export.js +9 -11
- package/rules/no-array-callback-reference.js +148 -22
- package/rules/no-array-fill-with-reference-type.js +127 -0
- package/rules/no-array-for-each.js +18 -3
- package/rules/no-array-from-fill.js +76 -0
- package/rules/no-array-method-this-argument.js +1 -1
- package/rules/no-array-reduce.js +509 -2
- package/rules/no-blob-to-file.js +299 -0
- package/rules/no-canvas-to-image.js +152 -0
- package/rules/no-confusing-array-splice.js +164 -0
- package/rules/no-duplicate-set-values.js +57 -0
- package/rules/no-empty-file.js +31 -2
- package/rules/no-exports-in-scripts.js +34 -0
- package/rules/no-for-loop.js +1 -5
- package/rules/no-hex-escape.js +1 -1
- package/rules/no-incorrect-query-selector.js +347 -0
- package/rules/no-instanceof-builtins.js +4 -6
- package/rules/no-invalid-fetch-options.js +1 -1
- package/rules/no-invalid-file-input-accept.js +475 -0
- package/rules/no-keyword-prefix.js +23 -5
- package/rules/no-late-current-target-access.js +166 -0
- package/rules/no-lonely-if.js +152 -52
- package/rules/no-manually-wrapped-comments.js +151 -0
- package/rules/no-null.js +16 -5
- package/rules/no-process-exit.js +1 -1
- package/rules/no-this-outside-of-class.js +75 -0
- package/rules/no-typeof-undefined.js +7 -4
- package/rules/no-unnecessary-nested-ternary.js +132 -0
- package/rules/no-unnecessary-polyfills.js +253 -24
- package/rules/no-unreadable-iife.js +33 -3
- package/rules/no-unused-array-method-return.js +301 -0
- package/rules/no-unused-properties.js +13 -19
- package/rules/no-useless-iterator-to-array.js +249 -0
- package/rules/no-useless-length-check.js +5 -11
- package/rules/no-useless-promise-resolve-reject.js +5 -5
- package/rules/no-useless-spread.js +53 -4
- package/rules/no-useless-undefined.js +62 -46
- package/rules/numeric-separators-style.js +17 -6
- package/rules/prefer-add-event-listener.js +1 -0
- package/rules/prefer-array-find.js +1 -0
- package/rules/prefer-array-flat.js +76 -8
- package/rules/prefer-array-last-methods.js +93 -0
- package/rules/prefer-array-some.js +74 -8
- package/rules/prefer-at.js +118 -57
- package/rules/prefer-class-fields.js +2 -4
- package/rules/prefer-default-parameters.js +1 -2
- package/rules/prefer-dom-node-append.js +2 -2
- package/rules/prefer-dom-node-remove.js +24 -18
- package/rules/prefer-export-from.js +8 -7
- package/rules/prefer-get-or-insert-computed.js +159 -0
- package/rules/prefer-global-this.js +33 -29
- package/rules/prefer-https.js +83 -0
- package/rules/prefer-includes-over-repeated-comparisons.js +199 -0
- package/rules/prefer-iterator-concat.js +174 -0
- package/rules/prefer-iterator-to-array-at-end.js +123 -0
- package/rules/prefer-keyboard-event-key.js +202 -76
- package/rules/prefer-logical-operator-over-ternary.js +1 -1
- package/rules/prefer-math-abs.js +397 -0
- package/rules/prefer-math-min-max.js +54 -26
- package/rules/prefer-module.js +5 -7
- package/rules/prefer-native-coercion-functions.js +8 -1
- package/rules/prefer-node-protocol.js +7 -4
- package/rules/prefer-number-properties.js +2 -0
- package/rules/prefer-object-from-entries.js +12 -7
- package/rules/prefer-prototype-methods.js +1 -3
- package/rules/prefer-query-selector.js +119 -7
- package/rules/prefer-queue-microtask.js +204 -0
- package/rules/prefer-regexp-test.js +166 -20
- package/rules/prefer-set-has.js +474 -47
- package/rules/prefer-simple-condition-first.js +282 -0
- package/rules/prefer-single-call.js +129 -38
- package/rules/prefer-split-limit.js +107 -0
- package/rules/prefer-spread.js +150 -19
- package/rules/prefer-string-match-all.js +352 -0
- package/rules/prefer-string-pad-start-end.js +380 -0
- package/rules/prefer-string-raw.js +90 -19
- package/rules/prefer-string-repeat.js +173 -0
- package/rules/prefer-string-replace-all.js +180 -12
- package/rules/prefer-string-slice.js +55 -4
- package/rules/prefer-string-starts-ends-with.js +2 -2
- package/rules/prefer-structured-clone.js +1 -0
- package/rules/prefer-switch.js +2 -0
- package/rules/prefer-ternary.js +3 -2
- package/rules/prefer-top-level-await.js +168 -6
- package/rules/prevent-abbreviations.js +144 -158
- package/rules/relative-url-style.js +1 -0
- package/rules/require-css-escape.js +141 -0
- package/rules/require-module-attributes.js +16 -18
- package/rules/require-module-specifiers.js +62 -0
- package/rules/require-passive-events.js +292 -0
- package/rules/rule/to-eslint-rule.js +1 -1
- package/rules/rule/to-eslint-rules.js +4 -6
- package/rules/shared/abbreviations.js +1 -0
- package/rules/shared/dom-events.js +7 -7
- package/rules/shared/identifier-checks.js +205 -0
- package/rules/shared/package-json.js +1 -1
- package/rules/string-content.js +35 -5
- package/rules/switch-case-braces.js +2 -3
- package/rules/switch-case-break-position.js +138 -0
- package/rules/template-indent.js +59 -3
- package/rules/text-encoding-identifier-case.js +23 -5
- package/rules/throw-new-error.js +4 -0
- package/rules/try-complexity.js +184 -0
- package/rules/utils/assert-token.js +3 -5
- package/rules/utils/boolean.js +37 -28
- package/rules/utils/create-deprecated-rules.js +25 -27
- package/rules/utils/get-call-expression-arguments-text.js +3 -5
- package/rules/utils/get-duplicate-array-elements.js +77 -0
- package/rules/utils/index.js +4 -1
- package/rules/utils/is-node-contains-lexical-this.js +65 -0
- package/rules/utils/is-number.js +2 -2
- package/rules/utils/is-same-reference.js +25 -17
- package/rules/utils/should-add-parentheses-to-await-expression-argument.js +9 -9
- package/rules/utils/should-add-parentheses-to-call-expression-callee.js +12 -10
- package/rules/utils/should-add-parentheses-to-conditional-expression-child.js +9 -5
- package/rules/utils/should-add-parentheses-to-expression-statement-expression.js +1 -1
- package/rules/utils/should-add-parentheses-to-logical-expression-child.js +12 -8
- package/rules/utils/should-add-parentheses-to-member-expression-object.js +1 -1
- package/rules/utils/should-add-parentheses-to-unary-expression.js +10 -10
- package/rules/better-regex.js +0 -145
- package/rules/prefer-dom-node-dataset.js +0 -135
- package/rules/prefer-json-parse-buffer.js +0 -161
|
@@ -256,20 +256,22 @@ function tryToCoerceVersion(rawVersion) {
|
|
|
256
256
|
}
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
-
function
|
|
260
|
-
return {
|
|
261
|
-
'>': semver.gt,
|
|
262
|
-
'>=': semver.gte,
|
|
263
|
-
}[operator];
|
|
259
|
+
function satisfiesRange(version, condition, range) {
|
|
260
|
+
return semver.satisfies(version, `${condition}${range}`, {includePrerelease: true});
|
|
264
261
|
}
|
|
265
262
|
|
|
266
263
|
const DEFAULT_OPTIONS = {
|
|
267
264
|
terms: ['todo', 'fixme', 'xxx'],
|
|
268
265
|
ignore: [],
|
|
269
|
-
|
|
266
|
+
checkDates: false,
|
|
267
|
+
checkDatesOnPullRequests: false,
|
|
270
268
|
allowWarningComments: true,
|
|
271
269
|
};
|
|
272
270
|
|
|
271
|
+
function getAllComments(sourceCode) {
|
|
272
|
+
return sourceCode.getAllComments?.() ?? sourceCode.comments ?? [];
|
|
273
|
+
}
|
|
274
|
+
|
|
273
275
|
/** @param {import('eslint').Rule.RuleContext} context */
|
|
274
276
|
const create = context => {
|
|
275
277
|
const options = {
|
|
@@ -277,15 +279,13 @@ const create = context => {
|
|
|
277
279
|
...context.options[0],
|
|
278
280
|
};
|
|
279
281
|
|
|
280
|
-
const ignoreRegexes = options.ignore.map(
|
|
281
|
-
pattern => isRegExp(pattern) ? pattern : new RegExp(pattern, 'u'),
|
|
282
|
-
);
|
|
282
|
+
const ignoreRegexes = options.ignore.map(pattern => isRegExp(pattern) ? pattern : new RegExp(pattern, 'u'));
|
|
283
283
|
|
|
284
284
|
const dirname = path.dirname(context.filename);
|
|
285
285
|
const {packageJson, packageDependencies, parseArgument, parseTodoMessage, parseTodoWithArguments} = getPackageHelpers(dirname);
|
|
286
286
|
|
|
287
287
|
const {sourceCode} = context;
|
|
288
|
-
const comments =
|
|
288
|
+
const comments = getAllComments(sourceCode);
|
|
289
289
|
const unusedComments = comments
|
|
290
290
|
.filter(comment => comment.type !== 'Shebang' && !isEslintDisableOrEnableDirective(context, comment))
|
|
291
291
|
// Block comments come as one.
|
|
@@ -299,8 +299,7 @@ const create = context => {
|
|
|
299
299
|
comment.value.split('\n').map(line => ({
|
|
300
300
|
...comment,
|
|
301
301
|
value: line,
|
|
302
|
-
}))
|
|
303
|
-
).filter(comment => processComment(comment));
|
|
302
|
+
}))).filter(comment => processComment(comment));
|
|
304
303
|
|
|
305
304
|
// This is highly dependable on ESLint's `no-warning-comments` implementation.
|
|
306
305
|
// What we do is patch the parts we know the rule will use, `getAllComments`.
|
|
@@ -342,63 +341,48 @@ const create = context => {
|
|
|
342
341
|
engines = [],
|
|
343
342
|
unknowns = [],
|
|
344
343
|
} = parsed;
|
|
344
|
+
const todoMessage = parseTodoMessage(comment.value);
|
|
345
345
|
|
|
346
|
-
|
|
347
|
-
uses++;
|
|
346
|
+
function report(messageId, data = {}) {
|
|
348
347
|
context.report({
|
|
349
|
-
|
|
350
|
-
messageId
|
|
348
|
+
node: comment,
|
|
349
|
+
messageId,
|
|
351
350
|
data: {
|
|
352
|
-
|
|
353
|
-
message:
|
|
351
|
+
...data,
|
|
352
|
+
message: todoMessage,
|
|
354
353
|
},
|
|
355
354
|
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (dates.length > 1) {
|
|
358
|
+
uses++;
|
|
359
|
+
report(MESSAGE_ID_AVOID_MULTIPLE_DATES, {
|
|
360
|
+
expirationDates: dates.join(', '),
|
|
361
|
+
});
|
|
356
362
|
} else if (dates.length === 1) {
|
|
357
363
|
uses++;
|
|
358
364
|
const [expirationDate] = dates;
|
|
359
365
|
|
|
360
|
-
const
|
|
361
|
-
if (
|
|
362
|
-
|
|
363
|
-
loc: sourceCode.getLoc(comment),
|
|
364
|
-
messageId: MESSAGE_ID_EXPIRED_TODO,
|
|
365
|
-
data: {
|
|
366
|
-
expirationDate,
|
|
367
|
-
message: parseTodoMessage(comment.value),
|
|
368
|
-
},
|
|
369
|
-
});
|
|
366
|
+
const shouldCheckDate = options.checkDates && (options.checkDatesOnPullRequests || !ci.isPR);
|
|
367
|
+
if (shouldCheckDate && reachedDate(expirationDate, options.date)) {
|
|
368
|
+
report(MESSAGE_ID_EXPIRED_TODO, {expirationDate});
|
|
370
369
|
}
|
|
371
370
|
}
|
|
372
371
|
|
|
373
372
|
if (packageVersions.length > 1) {
|
|
374
373
|
uses++;
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
versions: packageVersions
|
|
380
|
-
.map(({condition, version}) => `${condition}${version}`)
|
|
381
|
-
.join(', '),
|
|
382
|
-
message: parseTodoMessage(comment.value),
|
|
383
|
-
},
|
|
374
|
+
report(MESSAGE_ID_AVOID_MULTIPLE_PACKAGE_VERSIONS, {
|
|
375
|
+
versions: packageVersions
|
|
376
|
+
.map(({condition, version}) => `${condition}${version}`)
|
|
377
|
+
.join(', '),
|
|
384
378
|
});
|
|
385
379
|
} else if (packageVersions.length === 1) {
|
|
386
380
|
uses++;
|
|
387
381
|
const [{condition, version}] = packageVersions;
|
|
388
382
|
|
|
389
383
|
const packageVersion = tryToCoerceVersion(packageJson.version);
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
const compare = semverComparisonForOperator(condition);
|
|
393
|
-
if (packageVersion && compare(packageVersion, decidedPackageVersion)) {
|
|
394
|
-
context.report({
|
|
395
|
-
loc: sourceCode.getLoc(comment),
|
|
396
|
-
messageId: MESSAGE_ID_REACHED_PACKAGE_VERSION,
|
|
397
|
-
data: {
|
|
398
|
-
comparison: `${condition}${version}`,
|
|
399
|
-
message: parseTodoMessage(comment.value),
|
|
400
|
-
},
|
|
401
|
-
});
|
|
384
|
+
if (packageVersion && satisfiesRange(packageVersion, condition, version)) {
|
|
385
|
+
report(MESSAGE_ID_REACHED_PACKAGE_VERSION, {comparison: `${condition}${version}`});
|
|
402
386
|
}
|
|
403
387
|
}
|
|
404
388
|
|
|
@@ -417,20 +401,12 @@ const create = context => {
|
|
|
417
401
|
: [!hasTargetPackage, MESSAGE_ID_DONT_HAVE_PACKAGE];
|
|
418
402
|
|
|
419
403
|
if (trigger) {
|
|
420
|
-
|
|
421
|
-
loc: sourceCode.getLoc(comment),
|
|
422
|
-
messageId,
|
|
423
|
-
data: {
|
|
424
|
-
package: dependency.name,
|
|
425
|
-
message: parseTodoMessage(comment.value),
|
|
426
|
-
},
|
|
427
|
-
});
|
|
404
|
+
report(messageId, {package: dependency.name});
|
|
428
405
|
}
|
|
429
406
|
|
|
430
407
|
continue;
|
|
431
408
|
}
|
|
432
409
|
|
|
433
|
-
const todoVersion = tryToCoerceVersion(dependency.version);
|
|
434
410
|
const targetPackageVersion = tryToCoerceVersion(targetPackageRawVersion);
|
|
435
411
|
|
|
436
412
|
/* c8 ignore start */
|
|
@@ -440,17 +416,8 @@ const create = context => {
|
|
|
440
416
|
}
|
|
441
417
|
/* c8 ignore end */
|
|
442
418
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
if (compare(targetPackageVersion, todoVersion)) {
|
|
446
|
-
context.report({
|
|
447
|
-
loc: sourceCode.getLoc(comment),
|
|
448
|
-
messageId: MESSAGE_ID_VERSION_MATCHES,
|
|
449
|
-
data: {
|
|
450
|
-
comparison: `${dependency.name} ${dependency.condition} ${dependency.version}`,
|
|
451
|
-
message: parseTodoMessage(comment.value),
|
|
452
|
-
},
|
|
453
|
-
});
|
|
419
|
+
if (satisfiesRange(targetPackageVersion, dependency.condition, dependency.version)) {
|
|
420
|
+
report(MESSAGE_ID_VERSION_MATCHES, {comparison: `${dependency.name} ${dependency.condition} ${dependency.version}`});
|
|
454
421
|
}
|
|
455
422
|
}
|
|
456
423
|
|
|
@@ -467,22 +434,10 @@ const create = context => {
|
|
|
467
434
|
continue;
|
|
468
435
|
}
|
|
469
436
|
|
|
470
|
-
const
|
|
471
|
-
const targetPackageEngineVersion = tryToCoerceVersion(
|
|
472
|
-
targetPackageRawEngineVersion,
|
|
473
|
-
);
|
|
474
|
-
|
|
475
|
-
const compare = semverComparisonForOperator(engine.condition);
|
|
437
|
+
const targetPackageEngineVersion = tryToCoerceVersion(targetPackageRawEngineVersion);
|
|
476
438
|
|
|
477
|
-
if (
|
|
478
|
-
|
|
479
|
-
loc: sourceCode.getLoc(comment),
|
|
480
|
-
messageId: MESSAGE_ID_ENGINE_MATCHES,
|
|
481
|
-
data: {
|
|
482
|
-
comparison: `node${engine.condition}${engine.version}`,
|
|
483
|
-
message: parseTodoMessage(comment.value),
|
|
484
|
-
},
|
|
485
|
-
});
|
|
439
|
+
if (targetPackageEngineVersion && satisfiesRange(targetPackageEngineVersion, engine.condition, engine.version)) {
|
|
440
|
+
report(MESSAGE_ID_ENGINE_MATCHES, {comparison: `node${engine.condition}${engine.version}`});
|
|
486
441
|
}
|
|
487
442
|
}
|
|
488
443
|
|
|
@@ -499,14 +454,9 @@ const create = context => {
|
|
|
499
454
|
|
|
500
455
|
if (parseArgument(testString).type !== 'unknowns') {
|
|
501
456
|
uses++;
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
data: {
|
|
506
|
-
original: unknown,
|
|
507
|
-
fix: testString,
|
|
508
|
-
message: parseTodoMessage(comment.value),
|
|
509
|
-
},
|
|
457
|
+
report(MESSAGE_ID_MISSING_AT_SYMBOL, {
|
|
458
|
+
original: unknown,
|
|
459
|
+
fix: testString,
|
|
510
460
|
});
|
|
511
461
|
continue;
|
|
512
462
|
}
|
|
@@ -516,14 +466,9 @@ const create = context => {
|
|
|
516
466
|
|
|
517
467
|
if (parseArgument(withoutWhitespace).type !== 'unknowns') {
|
|
518
468
|
uses++;
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
data: {
|
|
523
|
-
original: unknown,
|
|
524
|
-
fix: withoutWhitespace,
|
|
525
|
-
message: parseTodoMessage(comment.value),
|
|
526
|
-
},
|
|
469
|
+
report(MESSAGE_ID_REMOVE_WHITESPACE, {
|
|
470
|
+
original: unknown,
|
|
471
|
+
fix: withoutWhitespace,
|
|
527
472
|
});
|
|
528
473
|
continue;
|
|
529
474
|
}
|
|
@@ -532,7 +477,7 @@ const create = context => {
|
|
|
532
477
|
return uses === 0;
|
|
533
478
|
}
|
|
534
479
|
|
|
535
|
-
context.on('Program', () => {
|
|
480
|
+
context.on(['Program', 'StyleSheet'], () => {
|
|
536
481
|
rules.Program(); // eslint-disable-line new-cap
|
|
537
482
|
});
|
|
538
483
|
};
|
|
@@ -547,20 +492,29 @@ const schema = [
|
|
|
547
492
|
items: {
|
|
548
493
|
type: 'string',
|
|
549
494
|
},
|
|
495
|
+
description: 'Comment terms to check.',
|
|
550
496
|
},
|
|
551
497
|
ignore: {
|
|
552
498
|
type: 'array',
|
|
553
499
|
uniqueItems: true,
|
|
500
|
+
description: 'Patterns to ignore.',
|
|
501
|
+
},
|
|
502
|
+
checkDates: {
|
|
503
|
+
type: 'boolean',
|
|
504
|
+
description: 'Whether to check expiration dates.',
|
|
554
505
|
},
|
|
555
|
-
|
|
506
|
+
checkDatesOnPullRequests: {
|
|
556
507
|
type: 'boolean',
|
|
508
|
+
description: 'Whether to check expiration dates on pull requests.',
|
|
557
509
|
},
|
|
558
510
|
allowWarningComments: {
|
|
559
511
|
type: 'boolean',
|
|
512
|
+
description: 'Whether to allow warning comments.',
|
|
560
513
|
},
|
|
561
514
|
date: {
|
|
562
515
|
type: 'string',
|
|
563
516
|
format: 'date',
|
|
517
|
+
description: 'The reference date.',
|
|
564
518
|
},
|
|
565
519
|
},
|
|
566
520
|
},
|
|
@@ -3,11 +3,13 @@ import {
|
|
|
3
3
|
isParenthesized,
|
|
4
4
|
checkVueTemplate,
|
|
5
5
|
isLogicalExpression,
|
|
6
|
-
|
|
6
|
+
isBooleanExpression,
|
|
7
|
+
isControlFlowTest,
|
|
7
8
|
getBooleanAncestor,
|
|
9
|
+
isSameReference,
|
|
8
10
|
} from './utils/index.js';
|
|
9
11
|
import {fixSpaceAroundKeyword} from './fix/index.js';
|
|
10
|
-
import {isLiteral, isMemberExpression
|
|
12
|
+
import {isLiteral, isMemberExpression} from './ast/index.js';
|
|
11
13
|
|
|
12
14
|
const TYPE_NON_ZERO = 'non-zero';
|
|
13
15
|
const TYPE_ZERO = 'zero';
|
|
@@ -47,8 +49,107 @@ const zeroStyle = {
|
|
|
47
49
|
test: node => isCompareRight(node, '===', 0),
|
|
48
50
|
};
|
|
49
51
|
|
|
50
|
-
|
|
52
|
+
const shapeProperties = new Set(['depth', 'height', 'width']);
|
|
53
|
+
|
|
54
|
+
function isLengthOrSizeMemberExpression(node) {
|
|
55
|
+
return isMemberExpression(node, {
|
|
56
|
+
properties: ['length', 'size'],
|
|
57
|
+
optional: false,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isTypeScriptExpression(node) {
|
|
62
|
+
return node?.type === 'TSAsExpression'
|
|
63
|
+
|| node?.type === 'TSSatisfiesExpression'
|
|
64
|
+
|| node?.type === 'TSTypeAssertion'
|
|
65
|
+
|| node?.type === 'TSNonNullExpression';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function unwrapTypeScriptExpression(node) {
|
|
69
|
+
if (isTypeScriptExpression(node)) {
|
|
70
|
+
return unwrapTypeScriptExpression(node.expression);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return node;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function getLengthCheckParent(node, allowTypeScriptExpression) {
|
|
51
77
|
node = node.parent;
|
|
78
|
+
if (allowTypeScriptExpression) {
|
|
79
|
+
while (isTypeScriptExpression(node)) {
|
|
80
|
+
node = node.parent;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return node;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function getLogicalExpressionRoot(node) {
|
|
88
|
+
while (
|
|
89
|
+
isLogicalExpression(node.parent)
|
|
90
|
+
&& node.parent.operator === '&&'
|
|
91
|
+
) {
|
|
92
|
+
node = node.parent;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return node;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function getLogicalExpressionOperands(node) {
|
|
99
|
+
return [node.left, node.right].flatMap(child =>
|
|
100
|
+
child.type === 'LogicalExpression' && child.operator === node.operator
|
|
101
|
+
? getLogicalExpressionOperands(child)
|
|
102
|
+
: [child]);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function hasSameObjectShapePropertyCheck({node, lengthNode}) {
|
|
106
|
+
const root = getLogicalExpressionRoot(node);
|
|
107
|
+
if (
|
|
108
|
+
root.type !== 'LogicalExpression'
|
|
109
|
+
|| root.operator !== '&&'
|
|
110
|
+
) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return getLogicalExpressionOperands(root).some(operand =>
|
|
115
|
+
operand !== node
|
|
116
|
+
&& isMemberExpression(operand, {computed: false, optional: false})
|
|
117
|
+
&& operand.property.type === 'Identifier'
|
|
118
|
+
&& shapeProperties.has(operand.property.name)
|
|
119
|
+
&& isSameReference(operand.object, lengthNode.object));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function getLengthCheckMemberExpression(node) {
|
|
123
|
+
if (node.type === 'UnaryExpression' && node.operator === '!') {
|
|
124
|
+
return getLengthCheckMemberExpression(node.argument);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (
|
|
128
|
+
node.type === 'CallExpression'
|
|
129
|
+
&& node.callee.type === 'Identifier'
|
|
130
|
+
&& node.callee.name === 'Boolean'
|
|
131
|
+
&& node.arguments.length === 1
|
|
132
|
+
) {
|
|
133
|
+
return getLengthCheckMemberExpression(node.arguments[0]);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (node.type !== 'BinaryExpression') {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const left = unwrapTypeScriptExpression(node.left);
|
|
141
|
+
if (isLengthOrSizeMemberExpression(left)) {
|
|
142
|
+
return left;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const right = unwrapTypeScriptExpression(node.right);
|
|
146
|
+
if (isLengthOrSizeMemberExpression(right)) {
|
|
147
|
+
return right;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function getLengthCheckNode(node, {allowTypeScriptExpression = false} = {}) {
|
|
152
|
+
node = getLengthCheckParent(node, allowTypeScriptExpression);
|
|
52
153
|
|
|
53
154
|
// Zero length check
|
|
54
155
|
if (
|
|
@@ -80,7 +181,7 @@ function getLengthCheckNode(node) {
|
|
|
80
181
|
|| isCompareRight(node, '>=', 1)
|
|
81
182
|
// `0 !== foo.length`
|
|
82
183
|
|| isCompareLeft(node, '!==', 0)
|
|
83
|
-
// `0
|
|
184
|
+
// `0 != foo.length`
|
|
84
185
|
|| isCompareLeft(node, '!=', 0)
|
|
85
186
|
// `0 < foo.length`
|
|
86
187
|
|| isCompareLeft(node, '<', 0)
|
|
@@ -93,26 +194,33 @@ function getLengthCheckNode(node) {
|
|
|
93
194
|
return {};
|
|
94
195
|
}
|
|
95
196
|
|
|
96
|
-
function
|
|
97
|
-
|
|
98
|
-
|
|
197
|
+
function isSameLengthNonZeroCheck(node, lengthNode) {
|
|
198
|
+
const comparisonLengthNode = getLengthCheckMemberExpression(node);
|
|
199
|
+
if (!comparisonLengthNode || !isSameReference(comparisonLengthNode, lengthNode)) {
|
|
200
|
+
return false;
|
|
99
201
|
}
|
|
100
202
|
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
function isNodeValueString(node, context) {
|
|
106
|
-
if (node.type === 'Literal' && typeof node.value === 'string') {
|
|
107
|
-
return true;
|
|
203
|
+
const {isZeroLengthCheck, node: lengthCheckNode} = getLengthCheckNode(comparisonLengthNode, {allowTypeScriptExpression: true});
|
|
204
|
+
if (!lengthCheckNode) {
|
|
205
|
+
return false;
|
|
108
206
|
}
|
|
109
207
|
|
|
110
|
-
|
|
111
|
-
|
|
208
|
+
const {isNegative, node: ancestor} = getBooleanAncestor(lengthCheckNode);
|
|
209
|
+
return ancestor === node && isNegative === isZeroLengthCheck;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function isLengthGuardedByNonZeroCheck(lengthNode) {
|
|
213
|
+
const root = getLogicalExpressionRoot(lengthNode);
|
|
214
|
+
if (
|
|
215
|
+
root.type !== 'LogicalExpression'
|
|
216
|
+
|| root.operator !== '&&'
|
|
217
|
+
) {
|
|
218
|
+
return false;
|
|
112
219
|
}
|
|
113
220
|
|
|
114
|
-
|
|
115
|
-
|
|
221
|
+
return getLogicalExpressionOperands(root).some(operand =>
|
|
222
|
+
operand !== lengthNode
|
|
223
|
+
&& isSameLengthNonZeroCheck(operand, lengthNode));
|
|
116
224
|
}
|
|
117
225
|
|
|
118
226
|
function create(context) {
|
|
@@ -120,7 +228,7 @@ function create(context) {
|
|
|
120
228
|
const nonZeroStyle = nonZeroStyles.get(options['non-zero']);
|
|
121
229
|
const {sourceCode} = context;
|
|
122
230
|
|
|
123
|
-
function getProblem({node, isZeroLengthCheck, lengthNode, autoFix}) {
|
|
231
|
+
function getProblem({node, isZeroLengthCheck, lengthNode, autoFix, shouldSuggest = true}) {
|
|
124
232
|
const {code, test} = isZeroLengthCheck ? zeroStyle : nonZeroStyle;
|
|
125
233
|
if (test(node)) {
|
|
126
234
|
return;
|
|
@@ -148,7 +256,7 @@ function create(context) {
|
|
|
148
256
|
|
|
149
257
|
if (autoFix) {
|
|
150
258
|
problem.fix = fix;
|
|
151
|
-
} else {
|
|
259
|
+
} else if (shouldSuggest) {
|
|
152
260
|
problem.suggest = [
|
|
153
261
|
{
|
|
154
262
|
messageId: MESSAGE_ID_SUGGESTION,
|
|
@@ -162,10 +270,7 @@ function create(context) {
|
|
|
162
270
|
|
|
163
271
|
context.on('MemberExpression', memberExpression => {
|
|
164
272
|
if (
|
|
165
|
-
!
|
|
166
|
-
properties: ['length', 'size'],
|
|
167
|
-
optional: false,
|
|
168
|
-
})
|
|
273
|
+
!isLengthOrSizeMemberExpression(memberExpression)
|
|
169
274
|
|| memberExpression.object.type === 'ThisExpression'
|
|
170
275
|
) {
|
|
171
276
|
return;
|
|
@@ -189,16 +294,10 @@ function create(context) {
|
|
|
189
294
|
}
|
|
190
295
|
} else {
|
|
191
296
|
const {isNegative, node: ancestor} = getBooleanAncestor(lengthNode);
|
|
192
|
-
if (
|
|
297
|
+
if (isBooleanExpression(ancestor) || isControlFlowTest(ancestor)) {
|
|
193
298
|
isZeroLengthCheck = isNegative;
|
|
194
299
|
node = ancestor;
|
|
195
|
-
} else if (
|
|
196
|
-
isLogicalExpression(lengthNode.parent)
|
|
197
|
-
&& !(
|
|
198
|
-
lengthNode.parent.operator === '||'
|
|
199
|
-
&& (isNodeValueNumber(lengthNode.parent.right, context) || isNodeValueString(lengthNode.parent.right, context))
|
|
200
|
-
)
|
|
201
|
-
) {
|
|
300
|
+
} else if (isLogicalExpression(lengthNode.parent) && lengthNode.parent.operator === '&&') {
|
|
202
301
|
isZeroLengthCheck = isNegative;
|
|
203
302
|
node = lengthNode;
|
|
204
303
|
autoFix = false;
|
|
@@ -206,11 +305,24 @@ function create(context) {
|
|
|
206
305
|
}
|
|
207
306
|
|
|
208
307
|
if (node) {
|
|
308
|
+
if (
|
|
309
|
+
(node === lengthNode && isLengthGuardedByNonZeroCheck(lengthNode))
|
|
310
|
+
|| hasSameObjectShapePropertyCheck({node, lengthNode})
|
|
311
|
+
) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const isUnsafeNegationInBinaryExpression = node.type === 'UnaryExpression'
|
|
316
|
+
&& node.operator === '!'
|
|
317
|
+
&& node.parent.type === 'BinaryExpression'
|
|
318
|
+
&& node.parent.left === node;
|
|
319
|
+
|
|
209
320
|
return getProblem({
|
|
210
321
|
node,
|
|
211
322
|
isZeroLengthCheck,
|
|
212
323
|
lengthNode,
|
|
213
|
-
autoFix,
|
|
324
|
+
autoFix: autoFix && !isUnsafeNegationInBinaryExpression,
|
|
325
|
+
shouldSuggest: !isUnsafeNegationInBinaryExpression,
|
|
214
326
|
});
|
|
215
327
|
}
|
|
216
328
|
});
|