eslint 6.0.0 → 6.2.1
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/CHANGELOG.md +80 -0
- package/README.md +3 -5
- package/bin/eslint.js +4 -1
- package/conf/config-schema.js +1 -0
- package/conf/environments.js +72 -15
- package/lib/cli-engine/cascading-config-array-factory.js +15 -3
- package/lib/cli-engine/cli-engine.js +13 -3
- package/lib/cli-engine/config-array/config-array.js +7 -0
- package/lib/cli-engine/config-array/extracted-config.js +16 -1
- package/lib/cli-engine/config-array-factory.js +9 -6
- package/lib/cli-engine/file-enumerator.js +5 -13
- package/lib/init/config-initializer.js +19 -9
- package/lib/init/npm-utils.js +2 -2
- package/lib/linter/code-path-analysis/code-path-analyzer.js +1 -0
- package/lib/linter/linter.js +49 -16
- package/lib/rule-tester/rule-tester.js +1 -1
- package/lib/rules/accessor-pairs.js +195 -35
- package/lib/rules/arrow-body-style.js +2 -2
- package/lib/rules/class-methods-use-this.js +10 -3
- package/lib/rules/dot-location.js +21 -17
- package/lib/rules/dot-notation.js +6 -2
- package/lib/rules/func-call-spacing.js +30 -20
- package/lib/rules/func-names.js +4 -0
- package/lib/rules/function-call-argument-newline.js +120 -0
- package/lib/rules/function-paren-newline.js +34 -22
- package/lib/rules/indent.js +13 -2
- package/lib/rules/index.js +1 -0
- package/lib/rules/new-cap.js +2 -1
- package/lib/rules/no-dupe-keys.js +1 -1
- package/lib/rules/no-duplicate-case.js +10 -8
- package/lib/rules/no-extra-bind.js +1 -0
- package/lib/rules/no-extra-boolean-cast.js +44 -5
- package/lib/rules/no-extra-parens.js +295 -39
- package/lib/rules/no-mixed-operators.js +48 -13
- package/lib/rules/no-param-reassign.js +12 -1
- package/lib/rules/no-restricted-syntax.js +2 -2
- package/lib/rules/no-unused-vars.js +1 -1
- package/lib/rules/prefer-const.js +9 -3
- package/lib/rules/prefer-template.js +1 -10
- package/lib/rules/sort-keys.js +11 -3
- package/lib/rules/utils/ast-utils.js +45 -3
- package/lib/rules/yoda.js +1 -1
- package/lib/{cli-engine → shared}/naming.js +0 -0
- package/lib/shared/types.js +2 -0
- package/messages/extend-config-missing.txt +2 -0
- package/messages/print-config-with-directory-path.txt +2 -0
- package/package.json +22 -21
@@ -8,6 +8,7 @@
|
|
8
8
|
// Rule Definition
|
9
9
|
//------------------------------------------------------------------------------
|
10
10
|
|
11
|
+
const { isParenthesized: isParenthesizedRaw } = require("eslint-utils");
|
11
12
|
const astUtils = require("./utils/ast-utils.js");
|
12
13
|
|
13
14
|
module.exports = {
|
@@ -68,7 +69,6 @@ module.exports = {
|
|
68
69
|
const sourceCode = context.getSourceCode();
|
69
70
|
|
70
71
|
const tokensToIgnore = new WeakSet();
|
71
|
-
const isParenthesised = astUtils.isParenthesised.bind(astUtils, sourceCode);
|
72
72
|
const precedence = astUtils.getPrecedence;
|
73
73
|
const ALL_NODES = context.options[0] !== "functions";
|
74
74
|
const EXCEPT_COND_ASSIGN = ALL_NODES && context.options[1] && context.options[1].conditionalAssign === false;
|
@@ -81,6 +81,8 @@ module.exports = {
|
|
81
81
|
const PRECEDENCE_OF_ASSIGNMENT_EXPR = precedence({ type: "AssignmentExpression" });
|
82
82
|
const PRECEDENCE_OF_UPDATE_EXPR = precedence({ type: "UpdateExpression" });
|
83
83
|
|
84
|
+
let reportsBuffer;
|
85
|
+
|
84
86
|
/**
|
85
87
|
* Determines if this rule should be enforced for a node given the current configuration.
|
86
88
|
* @param {ASTNode} node - The node to be checked.
|
@@ -116,6 +118,16 @@ module.exports = {
|
|
116
118
|
return ALL_NODES || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
|
117
119
|
}
|
118
120
|
|
121
|
+
/**
|
122
|
+
* Determines if a node is surrounded by parentheses.
|
123
|
+
* @param {ASTNode} node - The node to be checked.
|
124
|
+
* @returns {boolean} True if the node is parenthesised.
|
125
|
+
* @private
|
126
|
+
*/
|
127
|
+
function isParenthesised(node) {
|
128
|
+
return isParenthesizedRaw(1, node, sourceCode);
|
129
|
+
}
|
130
|
+
|
119
131
|
/**
|
120
132
|
* Determines if a node is surrounded by parentheses twice.
|
121
133
|
* @param {ASTNode} node - The node to be checked.
|
@@ -123,12 +135,7 @@ module.exports = {
|
|
123
135
|
* @private
|
124
136
|
*/
|
125
137
|
function isParenthesisedTwice(node) {
|
126
|
-
|
127
|
-
nextToken = sourceCode.getTokenAfter(node, 1);
|
128
|
-
|
129
|
-
return isParenthesised(node) && previousToken && nextToken &&
|
130
|
-
astUtils.isOpeningParenToken(previousToken) && previousToken.range[1] <= node.range[0] &&
|
131
|
-
astUtils.isClosingParenToken(nextToken) && nextToken.range[0] >= node.range[1];
|
138
|
+
return isParenthesizedRaw(2, node, sourceCode);
|
132
139
|
}
|
133
140
|
|
134
141
|
/**
|
@@ -316,19 +323,33 @@ module.exports = {
|
|
316
323
|
}
|
317
324
|
}
|
318
325
|
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
326
|
+
/**
|
327
|
+
* Finishes reporting
|
328
|
+
* @returns {void}
|
329
|
+
* @private
|
330
|
+
*/
|
331
|
+
function finishReport() {
|
332
|
+
context.report({
|
333
|
+
node,
|
334
|
+
loc: leftParenToken.loc.start,
|
335
|
+
messageId: "unexpected",
|
336
|
+
fix(fixer) {
|
337
|
+
const parenthesizedSource = sourceCode.text.slice(leftParenToken.range[1], rightParenToken.range[0]);
|
338
|
+
|
339
|
+
return fixer.replaceTextRange([
|
340
|
+
leftParenToken.range[0],
|
341
|
+
rightParenToken.range[1]
|
342
|
+
], (requiresLeadingSpace(node) ? " " : "") + parenthesizedSource + (requiresTrailingSpace(node) ? " " : ""));
|
343
|
+
}
|
344
|
+
});
|
345
|
+
}
|
325
346
|
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
347
|
+
if (reportsBuffer) {
|
348
|
+
reportsBuffer.reports.push({ node, finishReport });
|
349
|
+
return;
|
350
|
+
}
|
351
|
+
|
352
|
+
finishReport();
|
332
353
|
}
|
333
354
|
|
334
355
|
/**
|
@@ -390,15 +411,9 @@ module.exports = {
|
|
390
411
|
report(node.callee);
|
391
412
|
}
|
392
413
|
}
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
}
|
397
|
-
} else {
|
398
|
-
node.arguments
|
399
|
-
.filter(arg => hasExcessParens(arg) && precedence(arg) >= PRECEDENCE_OF_ASSIGNMENT_EXPR)
|
400
|
-
.forEach(report);
|
401
|
-
}
|
414
|
+
node.arguments
|
415
|
+
.filter(arg => hasExcessParens(arg) && precedence(arg) >= PRECEDENCE_OF_ASSIGNMENT_EXPR)
|
416
|
+
.forEach(report);
|
402
417
|
}
|
403
418
|
|
404
419
|
/**
|
@@ -498,6 +513,126 @@ module.exports = {
|
|
498
513
|
}
|
499
514
|
}
|
500
515
|
|
516
|
+
/**
|
517
|
+
* Finds the path from the given node to the specified ancestor.
|
518
|
+
* @param {ASTNode} node First node in the path.
|
519
|
+
* @param {ASTNode} ancestor Last node in the path.
|
520
|
+
* @returns {ASTNode[]} Path, including both nodes.
|
521
|
+
* @throws {Error} If the given node does not have the specified ancestor.
|
522
|
+
*/
|
523
|
+
function pathToAncestor(node, ancestor) {
|
524
|
+
const path = [node];
|
525
|
+
let currentNode = node;
|
526
|
+
|
527
|
+
while (currentNode !== ancestor) {
|
528
|
+
|
529
|
+
currentNode = currentNode.parent;
|
530
|
+
|
531
|
+
/* istanbul ignore if */
|
532
|
+
if (currentNode === null) {
|
533
|
+
throw new Error("Nodes are not in the ancestor-descendant relationship.");
|
534
|
+
}
|
535
|
+
|
536
|
+
path.push(currentNode);
|
537
|
+
}
|
538
|
+
|
539
|
+
return path;
|
540
|
+
}
|
541
|
+
|
542
|
+
/**
|
543
|
+
* Finds the path from the given node to the specified descendant.
|
544
|
+
* @param {ASTNode} node First node in the path.
|
545
|
+
* @param {ASTNode} descendant Last node in the path.
|
546
|
+
* @returns {ASTNode[]} Path, including both nodes.
|
547
|
+
* @throws {Error} If the given node does not have the specified descendant.
|
548
|
+
*/
|
549
|
+
function pathToDescendant(node, descendant) {
|
550
|
+
return pathToAncestor(descendant, node).reverse();
|
551
|
+
}
|
552
|
+
|
553
|
+
/**
|
554
|
+
* Checks whether the syntax of the given ancestor of an 'in' expression inside a for-loop initializer
|
555
|
+
* is preventing the 'in' keyword from being interpreted as a part of an ill-formed for-in loop.
|
556
|
+
*
|
557
|
+
* @param {ASTNode} node Ancestor of an 'in' expression.
|
558
|
+
* @param {ASTNode} child Child of the node, ancestor of the same 'in' expression or the 'in' expression itself.
|
559
|
+
* @returns {boolean} True if the keyword 'in' would be interpreted as the 'in' operator, without any parenthesis.
|
560
|
+
*/
|
561
|
+
function isSafelyEnclosingInExpression(node, child) {
|
562
|
+
switch (node.type) {
|
563
|
+
case "ArrayExpression":
|
564
|
+
case "ArrayPattern":
|
565
|
+
case "BlockStatement":
|
566
|
+
case "ObjectExpression":
|
567
|
+
case "ObjectPattern":
|
568
|
+
case "TemplateLiteral":
|
569
|
+
return true;
|
570
|
+
case "ArrowFunctionExpression":
|
571
|
+
case "FunctionExpression":
|
572
|
+
return node.params.includes(child);
|
573
|
+
case "CallExpression":
|
574
|
+
case "NewExpression":
|
575
|
+
return node.arguments.includes(child);
|
576
|
+
case "MemberExpression":
|
577
|
+
return node.computed && node.property === child;
|
578
|
+
case "ConditionalExpression":
|
579
|
+
return node.consequent === child;
|
580
|
+
default:
|
581
|
+
return false;
|
582
|
+
}
|
583
|
+
}
|
584
|
+
|
585
|
+
/**
|
586
|
+
* Starts a new reports buffering. Warnings will be stored in a buffer instead of being reported immediately.
|
587
|
+
* An additional logic that requires multiple nodes (e.g. a whole subtree) may dismiss some of the stored warnings.
|
588
|
+
*
|
589
|
+
* @returns {void}
|
590
|
+
*/
|
591
|
+
function startNewReportsBuffering() {
|
592
|
+
reportsBuffer = {
|
593
|
+
upper: reportsBuffer,
|
594
|
+
inExpressionNodes: [],
|
595
|
+
reports: []
|
596
|
+
};
|
597
|
+
}
|
598
|
+
|
599
|
+
/**
|
600
|
+
* Ends the current reports buffering.
|
601
|
+
* @returns {void}
|
602
|
+
*/
|
603
|
+
function endCurrentReportsBuffering() {
|
604
|
+
const { upper, inExpressionNodes, reports } = reportsBuffer;
|
605
|
+
|
606
|
+
if (upper) {
|
607
|
+
upper.inExpressionNodes.push(...inExpressionNodes);
|
608
|
+
upper.reports.push(...reports);
|
609
|
+
} else {
|
610
|
+
|
611
|
+
// flush remaining reports
|
612
|
+
reports.forEach(({ finishReport }) => finishReport());
|
613
|
+
}
|
614
|
+
|
615
|
+
reportsBuffer = upper;
|
616
|
+
}
|
617
|
+
|
618
|
+
/**
|
619
|
+
* Checks whether the given node is in the current reports buffer.
|
620
|
+
* @param {ASTNode} node Node to check.
|
621
|
+
* @returns {boolean} True if the node is in the current buffer, false otherwise.
|
622
|
+
*/
|
623
|
+
function isInCurrentReportsBuffer(node) {
|
624
|
+
return reportsBuffer.reports.some(r => r.node === node);
|
625
|
+
}
|
626
|
+
|
627
|
+
/**
|
628
|
+
* Removes the given node from the current reports buffer.
|
629
|
+
* @param {ASTNode} node Node to remove.
|
630
|
+
* @returns {void}
|
631
|
+
*/
|
632
|
+
function removeFromCurrentReportsBuffer(node) {
|
633
|
+
reportsBuffer.reports = reportsBuffer.reports.filter(r => r.node !== node);
|
634
|
+
}
|
635
|
+
|
501
636
|
return {
|
502
637
|
ArrayExpression(node) {
|
503
638
|
node.elements
|
@@ -540,9 +675,23 @@ module.exports = {
|
|
540
675
|
}
|
541
676
|
},
|
542
677
|
|
543
|
-
BinaryExpression
|
678
|
+
BinaryExpression(node) {
|
679
|
+
if (reportsBuffer && node.operator === "in") {
|
680
|
+
reportsBuffer.inExpressionNodes.push(node);
|
681
|
+
}
|
682
|
+
|
683
|
+
checkBinaryLogical(node);
|
684
|
+
},
|
685
|
+
|
544
686
|
CallExpression: checkCallNew,
|
545
687
|
|
688
|
+
ClassBody(node) {
|
689
|
+
node.body
|
690
|
+
.filter(member => member.type === "MethodDefinition" && member.computed &&
|
691
|
+
member.key && hasExcessParens(member.key) && precedence(member.key) >= PRECEDENCE_OF_ASSIGNMENT_EXPR)
|
692
|
+
.forEach(member => report(member.key));
|
693
|
+
},
|
694
|
+
|
546
695
|
ConditionalExpression(node) {
|
547
696
|
if (isReturnAssignException(node)) {
|
548
697
|
return;
|
@@ -562,7 +711,7 @@ module.exports = {
|
|
562
711
|
},
|
563
712
|
|
564
713
|
DoWhileStatement(node) {
|
565
|
-
if (
|
714
|
+
if (hasExcessParens(node.test) && !isCondAssignException(node)) {
|
566
715
|
report(node.test);
|
567
716
|
}
|
568
717
|
},
|
@@ -602,10 +751,6 @@ module.exports = {
|
|
602
751
|
},
|
603
752
|
|
604
753
|
ForStatement(node) {
|
605
|
-
if (node.init && hasExcessParens(node.init)) {
|
606
|
-
report(node.init);
|
607
|
-
}
|
608
|
-
|
609
754
|
if (node.test && hasExcessParens(node.test) && !isCondAssignException(node)) {
|
610
755
|
report(node.test);
|
611
756
|
}
|
@@ -613,14 +758,101 @@ module.exports = {
|
|
613
758
|
if (node.update && hasExcessParens(node.update)) {
|
614
759
|
report(node.update);
|
615
760
|
}
|
761
|
+
|
762
|
+
if (node.init) {
|
763
|
+
startNewReportsBuffering();
|
764
|
+
|
765
|
+
if (hasExcessParens(node.init)) {
|
766
|
+
report(node.init);
|
767
|
+
}
|
768
|
+
}
|
769
|
+
},
|
770
|
+
|
771
|
+
"ForStatement > *.init:exit"(node) {
|
772
|
+
|
773
|
+
/*
|
774
|
+
* Removing parentheses around `in` expressions might change semantics and cause errors.
|
775
|
+
*
|
776
|
+
* For example, this valid for loop:
|
777
|
+
* for (let a = (b in c); ;);
|
778
|
+
* after removing parentheses would be treated as an invalid for-in loop:
|
779
|
+
* for (let a = b in c; ;);
|
780
|
+
*/
|
781
|
+
|
782
|
+
if (reportsBuffer.reports.length) {
|
783
|
+
reportsBuffer.inExpressionNodes.forEach(inExpressionNode => {
|
784
|
+
const path = pathToDescendant(node, inExpressionNode);
|
785
|
+
let nodeToExclude;
|
786
|
+
|
787
|
+
for (let i = 0; i < path.length; i++) {
|
788
|
+
const pathNode = path[i];
|
789
|
+
|
790
|
+
if (i < path.length - 1) {
|
791
|
+
const nextPathNode = path[i + 1];
|
792
|
+
|
793
|
+
if (isSafelyEnclosingInExpression(pathNode, nextPathNode)) {
|
794
|
+
|
795
|
+
// The 'in' expression in safely enclosed by the syntax of its ancestor nodes (e.g. by '{}' or '[]').
|
796
|
+
return;
|
797
|
+
}
|
798
|
+
}
|
799
|
+
|
800
|
+
if (isParenthesised(pathNode)) {
|
801
|
+
if (isInCurrentReportsBuffer(pathNode)) {
|
802
|
+
|
803
|
+
// This node was supposed to be reported, but parentheses might be necessary.
|
804
|
+
|
805
|
+
if (isParenthesisedTwice(pathNode)) {
|
806
|
+
|
807
|
+
/*
|
808
|
+
* This node is parenthesised twice, it certainly has at least one pair of `extra` parentheses.
|
809
|
+
* If the --fix option is on, the current fixing iteration will remove only one pair of parentheses.
|
810
|
+
* The remaining pair is safely enclosing the 'in' expression.
|
811
|
+
*/
|
812
|
+
return;
|
813
|
+
}
|
814
|
+
|
815
|
+
// Exclude the outermost node only.
|
816
|
+
if (!nodeToExclude) {
|
817
|
+
nodeToExclude = pathNode;
|
818
|
+
}
|
819
|
+
|
820
|
+
// Don't break the loop here, there might be some safe nodes or parentheses that will stay inside.
|
821
|
+
|
822
|
+
} else {
|
823
|
+
|
824
|
+
// This node will stay parenthesised, the 'in' expression in safely enclosed by '()'.
|
825
|
+
return;
|
826
|
+
}
|
827
|
+
}
|
828
|
+
}
|
829
|
+
|
830
|
+
// Exclude the node from the list (i.e. treat parentheses as necessary)
|
831
|
+
removeFromCurrentReportsBuffer(nodeToExclude);
|
832
|
+
});
|
833
|
+
}
|
834
|
+
|
835
|
+
endCurrentReportsBuffering();
|
616
836
|
},
|
617
837
|
|
618
838
|
IfStatement(node) {
|
619
|
-
if (
|
839
|
+
if (hasExcessParens(node.test) && !isCondAssignException(node)) {
|
620
840
|
report(node.test);
|
621
841
|
}
|
622
842
|
},
|
623
843
|
|
844
|
+
ImportExpression(node) {
|
845
|
+
const { source } = node;
|
846
|
+
|
847
|
+
if (source.type === "SequenceExpression") {
|
848
|
+
if (hasDoubleExcessParens(source)) {
|
849
|
+
report(source);
|
850
|
+
}
|
851
|
+
} else if (hasExcessParens(source)) {
|
852
|
+
report(source);
|
853
|
+
}
|
854
|
+
},
|
855
|
+
|
624
856
|
LogicalExpression: checkBinaryLogical,
|
625
857
|
|
626
858
|
MemberExpression(node) {
|
@@ -664,6 +896,16 @@ module.exports = {
|
|
664
896
|
}).forEach(property => report(property.value));
|
665
897
|
},
|
666
898
|
|
899
|
+
Property(node) {
|
900
|
+
if (node.computed) {
|
901
|
+
const { key } = node;
|
902
|
+
|
903
|
+
if (key && hasExcessParens(key) && precedence(key) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
|
904
|
+
report(key);
|
905
|
+
}
|
906
|
+
}
|
907
|
+
},
|
908
|
+
|
667
909
|
ReturnStatement(node) {
|
668
910
|
const returnToken = sourceCode.getFirstToken(node);
|
669
911
|
|
@@ -693,7 +935,7 @@ module.exports = {
|
|
693
935
|
},
|
694
936
|
|
695
937
|
SwitchStatement(node) {
|
696
|
-
if (
|
938
|
+
if (hasExcessParens(node.discriminant)) {
|
697
939
|
report(node.discriminant);
|
698
940
|
}
|
699
941
|
},
|
@@ -721,13 +963,13 @@ module.exports = {
|
|
721
963
|
},
|
722
964
|
|
723
965
|
WhileStatement(node) {
|
724
|
-
if (
|
966
|
+
if (hasExcessParens(node.test) && !isCondAssignException(node)) {
|
725
967
|
report(node.test);
|
726
968
|
}
|
727
969
|
},
|
728
970
|
|
729
971
|
WithStatement(node) {
|
730
|
-
if (
|
972
|
+
if (hasExcessParens(node.object)) {
|
731
973
|
report(node.object);
|
732
974
|
}
|
733
975
|
},
|
@@ -749,7 +991,21 @@ module.exports = {
|
|
749
991
|
|
750
992
|
SpreadElement: checkSpreadOperator,
|
751
993
|
SpreadProperty: checkSpreadOperator,
|
752
|
-
ExperimentalSpreadProperty: checkSpreadOperator
|
994
|
+
ExperimentalSpreadProperty: checkSpreadOperator,
|
995
|
+
|
996
|
+
TemplateLiteral(node) {
|
997
|
+
node.expressions
|
998
|
+
.filter(e => e && hasExcessParens(e))
|
999
|
+
.forEach(report);
|
1000
|
+
},
|
1001
|
+
|
1002
|
+
AssignmentPattern(node) {
|
1003
|
+
const { right } = node;
|
1004
|
+
|
1005
|
+
if (right && hasExcessParens(right) && precedence(right) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
|
1006
|
+
report(right);
|
1007
|
+
}
|
1008
|
+
}
|
753
1009
|
};
|
754
1010
|
|
755
1011
|
}
|
@@ -20,12 +20,14 @@ const BITWISE_OPERATORS = ["&", "|", "^", "~", "<<", ">>", ">>>"];
|
|
20
20
|
const COMPARISON_OPERATORS = ["==", "!=", "===", "!==", ">", ">=", "<", "<="];
|
21
21
|
const LOGICAL_OPERATORS = ["&&", "||"];
|
22
22
|
const RELATIONAL_OPERATORS = ["in", "instanceof"];
|
23
|
+
const TERNARY_OPERATOR = ["?:"];
|
23
24
|
const ALL_OPERATORS = [].concat(
|
24
25
|
ARITHMETIC_OPERATORS,
|
25
26
|
BITWISE_OPERATORS,
|
26
27
|
COMPARISON_OPERATORS,
|
27
28
|
LOGICAL_OPERATORS,
|
28
|
-
RELATIONAL_OPERATORS
|
29
|
+
RELATIONAL_OPERATORS,
|
30
|
+
TERNARY_OPERATOR
|
29
31
|
);
|
30
32
|
const DEFAULT_GROUPS = [
|
31
33
|
ARITHMETIC_OPERATORS,
|
@@ -34,7 +36,7 @@ const DEFAULT_GROUPS = [
|
|
34
36
|
LOGICAL_OPERATORS,
|
35
37
|
RELATIONAL_OPERATORS
|
36
38
|
];
|
37
|
-
const TARGET_NODE_TYPE = /^(?:Binary|Logical)Expression$/u;
|
39
|
+
const TARGET_NODE_TYPE = /^(?:Binary|Logical|Conditional)Expression$/u;
|
38
40
|
|
39
41
|
/**
|
40
42
|
* Normalizes options.
|
@@ -65,6 +67,18 @@ function includesBothInAGroup(groups, left, right) {
|
|
65
67
|
return groups.some(group => group.indexOf(left) !== -1 && group.indexOf(right) !== -1);
|
66
68
|
}
|
67
69
|
|
70
|
+
/**
|
71
|
+
* Checks whether the given node is a conditional expression and returns the test node else the left node.
|
72
|
+
*
|
73
|
+
* @param {ASTNode} node - A node which can be a BinaryExpression or a LogicalExpression node.
|
74
|
+
* This parent node can be BinaryExpression, LogicalExpression
|
75
|
+
* , or a ConditionalExpression node
|
76
|
+
* @returns {ASTNode} node the appropriate node(left or test).
|
77
|
+
*/
|
78
|
+
function getChildNode(node) {
|
79
|
+
return node.type === "ConditionalExpression" ? node.test : node.left;
|
80
|
+
}
|
81
|
+
|
68
82
|
//------------------------------------------------------------------------------
|
69
83
|
// Rule Definition
|
70
84
|
//------------------------------------------------------------------------------
|
@@ -121,7 +135,7 @@ module.exports = {
|
|
121
135
|
const b = node.parent;
|
122
136
|
|
123
137
|
return (
|
124
|
-
!includesBothInAGroup(options.groups, a.operator, b.operator) ||
|
138
|
+
!includesBothInAGroup(options.groups, a.operator, b.type === "ConditionalExpression" ? "?:" : b.operator) ||
|
125
139
|
(
|
126
140
|
options.allowSamePrecedence &&
|
127
141
|
astUtils.getPrecedence(a) === astUtils.getPrecedence(b)
|
@@ -139,12 +153,25 @@ module.exports = {
|
|
139
153
|
* @returns {boolean} `true` if the node was mixed.
|
140
154
|
*/
|
141
155
|
function isMixedWithParent(node) {
|
156
|
+
|
142
157
|
return (
|
143
158
|
node.operator !== node.parent.operator &&
|
144
159
|
!astUtils.isParenthesised(sourceCode, node)
|
145
160
|
);
|
146
161
|
}
|
147
162
|
|
163
|
+
/**
|
164
|
+
* Checks whether the operator of a given node is mixed with a
|
165
|
+
* conditional expression.
|
166
|
+
*
|
167
|
+
* @param {ASTNode} node - A node to check. This is a conditional
|
168
|
+
* expression node
|
169
|
+
* @returns {boolean} `true` if the node was mixed.
|
170
|
+
*/
|
171
|
+
function isMixedWithConditionalParent(node) {
|
172
|
+
return !astUtils.isParenthesised(sourceCode, node) && !astUtils.isParenthesised(sourceCode, node.test);
|
173
|
+
}
|
174
|
+
|
148
175
|
/**
|
149
176
|
* Gets the operator token of a given node.
|
150
177
|
*
|
@@ -153,7 +180,7 @@ module.exports = {
|
|
153
180
|
* @returns {Token} The operator token of the node.
|
154
181
|
*/
|
155
182
|
function getOperatorToken(node) {
|
156
|
-
return sourceCode.getTokenAfter(node
|
183
|
+
return sourceCode.getTokenAfter(getChildNode(node), astUtils.isNotClosingParenToken);
|
157
184
|
}
|
158
185
|
|
159
186
|
/**
|
@@ -167,13 +194,13 @@ module.exports = {
|
|
167
194
|
*/
|
168
195
|
function reportBothOperators(node) {
|
169
196
|
const parent = node.parent;
|
170
|
-
const left = (parent
|
171
|
-
const right = (parent
|
197
|
+
const left = (getChildNode(parent) === node) ? node : parent;
|
198
|
+
const right = (getChildNode(parent) !== node) ? node : parent;
|
172
199
|
const message =
|
173
200
|
"Unexpected mix of '{{leftOperator}}' and '{{rightOperator}}'.";
|
174
201
|
const data = {
|
175
|
-
leftOperator: left.operator,
|
176
|
-
rightOperator: right.operator
|
202
|
+
leftOperator: left.operator || "?:",
|
203
|
+
rightOperator: right.operator || "?:"
|
177
204
|
};
|
178
205
|
|
179
206
|
context.report({
|
@@ -198,17 +225,25 @@ module.exports = {
|
|
198
225
|
* @returns {void}
|
199
226
|
*/
|
200
227
|
function check(node) {
|
201
|
-
if (TARGET_NODE_TYPE.test(node.parent.type)
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
228
|
+
if (TARGET_NODE_TYPE.test(node.parent.type)) {
|
229
|
+
if (node.parent.type === "ConditionalExpression" && !shouldIgnore(node) && isMixedWithConditionalParent(node.parent)) {
|
230
|
+
reportBothOperators(node);
|
231
|
+
} else {
|
232
|
+
if (TARGET_NODE_TYPE.test(node.parent.type) &&
|
233
|
+
isMixedWithParent(node) &&
|
234
|
+
!shouldIgnore(node)
|
235
|
+
) {
|
236
|
+
reportBothOperators(node);
|
237
|
+
}
|
238
|
+
}
|
206
239
|
}
|
240
|
+
|
207
241
|
}
|
208
242
|
|
209
243
|
return {
|
210
244
|
BinaryExpression: check,
|
211
245
|
LogicalExpression: check
|
246
|
+
|
212
247
|
};
|
213
248
|
}
|
214
249
|
};
|
@@ -67,7 +67,8 @@ module.exports = {
|
|
67
67
|
let node = reference.identifier;
|
68
68
|
let parent = node.parent;
|
69
69
|
|
70
|
-
while (parent && !stopNodePattern.test(parent.type)
|
70
|
+
while (parent && (!stopNodePattern.test(parent.type) ||
|
71
|
+
parent.type === "ForInStatement" || parent.type === "ForOfStatement")) {
|
71
72
|
switch (parent.type) {
|
72
73
|
|
73
74
|
// e.g. foo.a = 0;
|
@@ -85,6 +86,16 @@ module.exports = {
|
|
85
86
|
}
|
86
87
|
break;
|
87
88
|
|
89
|
+
// e.g. for (foo.a in b) {}
|
90
|
+
case "ForInStatement":
|
91
|
+
case "ForOfStatement":
|
92
|
+
if (parent.left === node) {
|
93
|
+
return true;
|
94
|
+
}
|
95
|
+
|
96
|
+
// this is a stop node for parent.right and parent.body
|
97
|
+
return false;
|
98
|
+
|
88
99
|
// EXCLUDES: e.g. cache.get(foo.a).b = 0;
|
89
100
|
case "CallExpression":
|
90
101
|
if (parent.callee !== node) {
|
@@ -21,7 +21,7 @@ module.exports = {
|
|
21
21
|
|
22
22
|
schema: {
|
23
23
|
type: "array",
|
24
|
-
items:
|
24
|
+
items: {
|
25
25
|
oneOf: [
|
26
26
|
{
|
27
27
|
type: "string"
|
@@ -36,7 +36,7 @@ module.exports = {
|
|
36
36
|
additionalProperties: false
|
37
37
|
}
|
38
38
|
]
|
39
|
-
}
|
39
|
+
},
|
40
40
|
uniqueItems: true,
|
41
41
|
minItems: 0
|
42
42
|
}
|
@@ -507,7 +507,7 @@ module.exports = {
|
|
507
507
|
const childScopes = scope.childScopes;
|
508
508
|
let i, l;
|
509
509
|
|
510
|
-
if (scope.type !== "
|
510
|
+
if (scope.type !== "global" || config.vars === "all") {
|
511
511
|
for (i = 0, l = variables.length; i < l; ++i) {
|
512
512
|
const variable = variables[i];
|
513
513
|
|
@@ -420,8 +420,9 @@ module.exports = {
|
|
420
420
|
|
421
421
|
let shouldFix = varDeclParent &&
|
422
422
|
|
423
|
-
// Don't do a fix unless the
|
424
|
-
(varDeclParent.parent.type === "ForInStatement" || varDeclParent.parent.type === "ForOfStatement" ||
|
423
|
+
// Don't do a fix unless all variables in the declarations are initialized (or it's in a for-in or for-of loop)
|
424
|
+
(varDeclParent.parent.type === "ForInStatement" || varDeclParent.parent.type === "ForOfStatement" ||
|
425
|
+
varDeclParent.declarations.every(declaration => declaration.init)) &&
|
425
426
|
|
426
427
|
/*
|
427
428
|
* If options.destructuring is "all", then this warning will not occur unless
|
@@ -450,7 +451,12 @@ module.exports = {
|
|
450
451
|
node,
|
451
452
|
messageId: "useConst",
|
452
453
|
data: node,
|
453
|
-
fix: shouldFix
|
454
|
+
fix: shouldFix
|
455
|
+
? fixer => fixer.replaceText(
|
456
|
+
sourceCode.getFirstToken(varDeclParent, t => t.value === varDeclParent.kind),
|
457
|
+
"const"
|
458
|
+
)
|
459
|
+
: null
|
454
460
|
});
|
455
461
|
});
|
456
462
|
}
|