eslint 3.14.0 → 3.16.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.
Files changed (119) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +1 -1
  3. package/conf/{eslint.json → eslint-recommended.js} +86 -71
  4. package/lib/ast-utils.js +192 -24
  5. package/lib/cli.js +2 -2
  6. package/lib/code-path-analysis/code-path-state.js +2 -2
  7. package/lib/config/autoconfig.js +3 -3
  8. package/lib/config/config-file.js +31 -24
  9. package/lib/config/config-initializer.js +1 -1
  10. package/lib/config/config-validator.js +6 -6
  11. package/lib/config.js +3 -2
  12. package/lib/eslint.js +18 -18
  13. package/lib/formatters/checkstyle.js +2 -2
  14. package/lib/formatters/compact.js +2 -2
  15. package/lib/formatters/junit.js +2 -2
  16. package/lib/formatters/tap.js +2 -2
  17. package/lib/formatters/unix.js +2 -2
  18. package/lib/formatters/visualstudio.js +2 -2
  19. package/lib/rules/arrow-body-style.js +7 -4
  20. package/lib/rules/arrow-spacing.js +7 -6
  21. package/lib/rules/block-spacing.js +2 -2
  22. package/lib/rules/brace-style.js +42 -22
  23. package/lib/rules/capitalized-comments.js +6 -6
  24. package/lib/rules/comma-spacing.js +16 -16
  25. package/lib/rules/consistent-return.js +1 -1
  26. package/lib/rules/constructor-super.js +3 -3
  27. package/lib/rules/curly.js +11 -7
  28. package/lib/rules/default-case.js +3 -3
  29. package/lib/rules/eqeqeq.js +15 -6
  30. package/lib/rules/func-call-spacing.js +10 -13
  31. package/lib/rules/func-name-matching.js +1 -1
  32. package/lib/rules/generator-star-spacing.js +18 -19
  33. package/lib/rules/global-require.js +2 -2
  34. package/lib/rules/id-blacklist.js +2 -2
  35. package/lib/rules/id-length.js +3 -3
  36. package/lib/rules/id-match.js +2 -2
  37. package/lib/rules/indent.js +21 -20
  38. package/lib/rules/key-spacing.js +20 -23
  39. package/lib/rules/keyword-spacing.js +2 -13
  40. package/lib/rules/line-comment-position.js +1 -1
  41. package/lib/rules/linebreak-style.js +7 -1
  42. package/lib/rules/lines-around-comment.js +4 -4
  43. package/lib/rules/lines-around-directive.js +3 -3
  44. package/lib/rules/max-lines.js +2 -2
  45. package/lib/rules/max-statements-per-line.js +7 -6
  46. package/lib/rules/new-cap.js +2 -2
  47. package/lib/rules/newline-after-var.js +7 -2
  48. package/lib/rules/newline-before-return.js +2 -2
  49. package/lib/rules/newline-per-chained-call.js +3 -1
  50. package/lib/rules/no-cond-assign.js +3 -3
  51. package/lib/rules/no-extend-native.js +3 -3
  52. package/lib/rules/no-extra-bind.js +3 -4
  53. package/lib/rules/no-extra-boolean-cast.js +8 -0
  54. package/lib/rules/no-extra-parens.js +29 -8
  55. package/lib/rules/no-inner-declarations.js +4 -4
  56. package/lib/rules/no-irregular-whitespace.js +7 -1
  57. package/lib/rules/no-lone-blocks.js +10 -10
  58. package/lib/rules/no-mixed-operators.js +1 -7
  59. package/lib/rules/no-mixed-requires.js +4 -4
  60. package/lib/rules/no-multi-spaces.js +4 -1
  61. package/lib/rules/no-multi-str.js +7 -3
  62. package/lib/rules/no-redeclare.js +7 -7
  63. package/lib/rules/no-return-assign.js +7 -14
  64. package/lib/rules/no-sequences.js +7 -6
  65. package/lib/rules/no-trailing-spaces.js +8 -2
  66. package/lib/rules/no-undefined.js +45 -6
  67. package/lib/rules/no-unexpected-multiline.js +9 -8
  68. package/lib/rules/no-unneeded-ternary.js +5 -1
  69. package/lib/rules/no-unused-labels.js +17 -2
  70. package/lib/rules/no-unused-vars.js +34 -19
  71. package/lib/rules/no-use-before-define.js +33 -29
  72. package/lib/rules/no-useless-computed-key.js +8 -3
  73. package/lib/rules/no-useless-concat.js +10 -7
  74. package/lib/rules/no-useless-escape.js +1 -1
  75. package/lib/rules/no-useless-return.js +1 -7
  76. package/lib/rules/no-var.js +11 -0
  77. package/lib/rules/no-whitespace-before-property.js +5 -16
  78. package/lib/rules/object-curly-newline.js +2 -2
  79. package/lib/rules/object-curly-spacing.js +7 -25
  80. package/lib/rules/object-property-newline.js +3 -3
  81. package/lib/rules/object-shorthand.js +10 -10
  82. package/lib/rules/operator-assignment.js +2 -2
  83. package/lib/rules/operator-linebreak.js +8 -10
  84. package/lib/rules/padded-blocks.js +7 -4
  85. package/lib/rules/prefer-spread.js +1 -1
  86. package/lib/rules/prefer-template.js +1 -1
  87. package/lib/rules/quotes.js +10 -6
  88. package/lib/rules/semi-spacing.js +4 -0
  89. package/lib/rules/sort-imports.js +4 -4
  90. package/lib/rules/sort-vars.js +2 -2
  91. package/lib/rules/space-before-function-paren.js +8 -5
  92. package/lib/rules/space-in-parens.js +8 -8
  93. package/lib/rules/spaced-comment.js +10 -10
  94. package/lib/rules/strict.js +2 -2
  95. package/lib/rules/template-tag-spacing.js +77 -0
  96. package/lib/rules/unicode-bom.js +1 -1
  97. package/lib/rules/wrap-iife.js +5 -5
  98. package/lib/rules/yoda.js +2 -7
  99. package/lib/rules.js +2 -2
  100. package/lib/testers/rule-tester.js +25 -18
  101. package/lib/token-store/backward-token-comment-cursor.js +57 -0
  102. package/lib/token-store/backward-token-cursor.js +56 -0
  103. package/lib/token-store/cursor.js +76 -0
  104. package/lib/token-store/cursors.js +92 -0
  105. package/lib/token-store/decorative-cursor.js +39 -0
  106. package/lib/token-store/filter-cursor.js +43 -0
  107. package/lib/token-store/forward-token-comment-cursor.js +57 -0
  108. package/lib/token-store/forward-token-cursor.js +61 -0
  109. package/lib/token-store/index.js +604 -0
  110. package/lib/token-store/limit-cursor.js +40 -0
  111. package/lib/token-store/padded-token-cursor.js +38 -0
  112. package/lib/token-store/skip-cursor.js +42 -0
  113. package/lib/token-store/utils.js +100 -0
  114. package/lib/util/glob.js +1 -1
  115. package/lib/util/source-code-fixer.js +46 -44
  116. package/lib/util/source-code.js +35 -19
  117. package/messages/extend-config-missing.txt +3 -0
  118. package/package.json +3 -3
  119. package/lib/token-store.js +0 -203
@@ -8,7 +8,7 @@
8
8
  // Requirements
9
9
  //------------------------------------------------------------------------------
10
10
 
11
- const getPropertyName = require("../ast-utils").getStaticPropertyName;
11
+ const astUtils = require("../ast-utils");
12
12
 
13
13
  //------------------------------------------------------------------------------
14
14
  // Rule Definition
@@ -44,8 +44,7 @@ module.exports = {
44
44
  loc: node.parent.property.loc.start,
45
45
  fix(fixer) {
46
46
  const firstTokenToRemove = context.getSourceCode()
47
- .getTokensBetween(node.parent.object, node.parent.property)
48
- .find(token => token.value !== ")");
47
+ .getFirstTokenBetween(node.parent.object, node.parent.property, astUtils.isNotClosingParenToken);
49
48
 
50
49
  return fixer.removeRange([firstTokenToRemove.range[0], node.parent.parent.range[1]]);
51
50
  }
@@ -73,7 +72,7 @@ module.exports = {
73
72
  grandparent.arguments.length === 1 &&
74
73
  parent.type === "MemberExpression" &&
75
74
  parent.object === node &&
76
- getPropertyName(parent) === "bind"
75
+ astUtils.getStaticPropertyName(parent) === "bind"
77
76
  );
78
77
  }
79
78
 
@@ -98,6 +98,14 @@ module.exports = {
98
98
  node,
99
99
  message: "Redundant Boolean call.",
100
100
  fix: fixer => {
101
+ if (!node.arguments.length) {
102
+ return fixer.replaceText(parent, "true");
103
+ }
104
+
105
+ if (node.arguments.length > 1 || node.arguments[0].type === "SpreadElement") {
106
+ return null;
107
+ }
108
+
101
109
  const argument = node.arguments[0];
102
110
 
103
111
  if (astUtils.getPrecedence(argument) < astUtils.getPrecedence(node.parent)) {
@@ -167,6 +167,19 @@ module.exports = {
167
167
  return false;
168
168
  }
169
169
 
170
+ /**
171
+ * Determines if a constructor function is newed-up with parens
172
+ * @param {ASTNode} newExpression - The NewExpression node to be checked.
173
+ * @returns {boolean} True if the constructor is called with parens.
174
+ * @private
175
+ */
176
+ function isNewExpressionWithParens(newExpression) {
177
+ const lastToken = sourceCode.getLastToken(newExpression);
178
+ const penultimateToken = sourceCode.getTokenBefore(lastToken);
179
+
180
+ return newExpression.arguments.length > 0 || penultimateToken.value === "(" && lastToken.value === ")";
181
+ }
182
+
170
183
  /**
171
184
  * Determines if a node is or contains an assignment expression
172
185
  * @param {ASTNode} node - The node to be checked.
@@ -202,9 +215,9 @@ module.exports = {
202
215
  return node.argument && containsAssignment(node.argument);
203
216
  } else if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {
204
217
  return containsAssignment(node.body);
205
- } else {
206
- return containsAssignment(node);
207
218
  }
219
+ return containsAssignment(node);
220
+
208
221
  }
209
222
 
210
223
  /**
@@ -353,6 +366,10 @@ module.exports = {
353
366
  * @private
354
367
  */
355
368
  function dryUnaryUpdate(node) {
369
+ if (node.type === "UnaryExpression" && node.argument.type === "BinaryExpression" && node.argument.operator === "**") {
370
+ return;
371
+ }
372
+
356
373
  if (hasExcessParens(node.argument) && precedence(node.argument) >= precedence(node)) {
357
374
  report(node.argument);
358
375
  }
@@ -367,7 +384,8 @@ module.exports = {
367
384
  function dryCallNew(node) {
368
385
  if (hasExcessParens(node.callee) && precedence(node.callee) >= precedence(node) && !(
369
386
  node.type === "CallExpression" &&
370
- node.callee.type === "FunctionExpression" &&
387
+ (node.callee.type === "FunctionExpression" ||
388
+ node.callee.type === "NewExpression" && !isNewExpressionWithParens(node.callee)) &&
371
389
 
372
390
  // One set of parentheses are allowed for a function expression
373
391
  !hasDoubleExcessParens(node.callee)
@@ -395,13 +413,17 @@ module.exports = {
395
413
  */
396
414
  function dryBinaryLogical(node) {
397
415
  const prec = precedence(node);
398
- const shouldSkipLeft = NESTED_BINARY && (node.left.type === "BinaryExpression" || node.left.type === "LogicalExpression");
416
+ const leftPrecedence = precedence(node.left);
417
+ const rightPrecedence = precedence(node.right);
418
+ const isExponentiation = node.operator === "**";
419
+ const shouldSkipLeft = (NESTED_BINARY && (node.left.type === "BinaryExpression" || node.left.type === "LogicalExpression")) ||
420
+ node.left.type === "UnaryExpression" && isExponentiation;
399
421
  const shouldSkipRight = NESTED_BINARY && (node.right.type === "BinaryExpression" || node.right.type === "LogicalExpression");
400
422
 
401
- if (!shouldSkipLeft && hasExcessParens(node.left) && precedence(node.left) >= prec) {
423
+ if (!shouldSkipLeft && hasExcessParens(node.left) && (leftPrecedence > prec || (leftPrecedence === prec && !isExponentiation))) {
402
424
  report(node.left);
403
425
  }
404
- if (!shouldSkipRight && hasExcessParens(node.right) && precedence(node.right) > prec) {
426
+ if (!shouldSkipRight && hasExcessParens(node.right) && (rightPrecedence > prec || (rightPrecedence === prec && isExponentiation))) {
405
427
  report(node.right);
406
428
  }
407
429
  }
@@ -535,8 +557,7 @@ module.exports = {
535
557
  !(
536
558
  (node.object.type === "Literal" &&
537
559
  typeof node.object.value === "number" &&
538
- astUtils.isDecimalInteger(node.object))
539
- ||
560
+ astUtils.isDecimalInteger(node.object)) ||
540
561
 
541
562
  // RegExp literal is allowed to have parens (#1589)
542
563
  (node.object.type === "Literal" && node.object.regex)
@@ -64,10 +64,10 @@ module.exports = {
64
64
 
65
65
  if (!valid) {
66
66
  context.report({ node, message: "Move {{type}} declaration to {{body}} root.", data: {
67
- type: (node.type === "FunctionDeclaration" ?
68
- "function" : "variable"),
69
- body: (body.type === "Program" ?
70
- "program" : "function body")
67
+ type: (node.type === "FunctionDeclaration"
68
+ ? "function" : "variable"),
69
+ body: (body.type === "Program"
70
+ ? "program" : "function body")
71
71
  } });
72
72
  }
73
73
  }
@@ -6,6 +6,12 @@
6
6
 
7
7
  "use strict";
8
8
 
9
+ //------------------------------------------------------------------------------
10
+ // Requirements
11
+ //------------------------------------------------------------------------------
12
+
13
+ const astUtils = require("../ast-utils");
14
+
9
15
  //------------------------------------------------------------------------------
10
16
  // Constants
11
17
  //------------------------------------------------------------------------------
@@ -13,7 +19,7 @@
13
19
  const ALL_IRREGULARS = /[\f\v\u0085\u00A0\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000\u2028\u2029]/;
14
20
  const IRREGULAR_WHITESPACE = /[\f\v\u0085\u00A0\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000]+/mg;
15
21
  const IRREGULAR_LINE_TERMINATORS = /[\u2028\u2029]/mg;
16
- const LINE_BREAK = /\r\n|\r|\n|\u2028|\u2029/g;
22
+ const LINE_BREAK = astUtils.createGlobalLinebreakMatcher();
17
23
 
18
24
  //------------------------------------------------------------------------------
19
25
  // Rule Definition
@@ -32,22 +32,22 @@ module.exports = {
32
32
  * @returns {void}
33
33
  */
34
34
  function report(node) {
35
- const parent = context.getAncestors().pop();
35
+ const message = node.parent.type === "BlockStatement" ? "Nested block is redundant." : "Block is redundant.";
36
36
 
37
- context.report({ node, message: parent.type === "Program" ?
38
- "Block is redundant." :
39
- "Nested block is redundant."
40
- });
37
+ context.report({ node, message });
41
38
  }
42
39
 
43
40
  /**
44
- * Checks for any ocurrence of BlockStatement > BlockStatement or Program > BlockStatement
45
- * @returns {boolean} True if the current node is a lone block.
41
+ * Checks for any ocurrence of a BlockStatement in a place where lists of statements can appear
42
+ * @param {ASTNode} node The node to check
43
+ * @returns {boolean} True if the node is a lone block.
46
44
  */
47
- function isLoneBlock() {
48
- const parent = context.getAncestors().pop();
45
+ function isLoneBlock(node) {
46
+ return node.parent.type === "BlockStatement" ||
47
+ node.parent.type === "Program" ||
49
48
 
50
- return parent.type === "BlockStatement" || parent.type === "Program";
49
+ // Don't report blocks in switch cases if the block is the only statement of the case.
50
+ node.parent.type === "SwitchCase" && !(node.parent.consequent[0] === node && node.parent.consequent.length === 1);
51
51
  }
52
52
 
53
53
  /**
@@ -148,13 +148,7 @@ module.exports = {
148
148
  * @returns {Token} The operator token of the node.
149
149
  */
150
150
  function getOperatorToken(node) {
151
- let token = sourceCode.getTokenAfter(node.left);
152
-
153
- while (token.value === ")") {
154
- token = sourceCode.getTokenAfter(token);
155
- }
156
-
157
- return token;
151
+ return sourceCode.getTokenAfter(node.left, astUtils.isNotClosingParenToken);
158
152
  }
159
153
 
160
154
  /**
@@ -153,11 +153,11 @@ module.exports = {
153
153
 
154
154
  // "var utils = require('./utils');"
155
155
  return REQ_FILE;
156
- } else {
157
-
158
- // "var async = require('async');"
159
- return REQ_MODULE;
160
156
  }
157
+
158
+ // "var async = require('async');"
159
+ return REQ_MODULE;
160
+
161
161
  }
162
162
 
163
163
  /**
@@ -5,6 +5,8 @@
5
5
 
6
6
  "use strict";
7
7
 
8
+ const astUtils = require("../ast-utils");
9
+
8
10
  //------------------------------------------------------------------------------
9
11
  // Rule Definition
10
12
  //------------------------------------------------------------------------------
@@ -93,7 +95,8 @@ module.exports = {
93
95
  const sourceCode = context.getSourceCode(),
94
96
  source = sourceCode.getText(),
95
97
  allComments = sourceCode.getAllComments(),
96
- pattern = /[^\n\r\u2028\u2029\t ].? {2,}/g; // note: repeating space
98
+ JOINED_LINEBEAKS = Array.from(astUtils.LINEBREAKS).join(""),
99
+ pattern = new RegExp(String.raw`[^ \t${JOINED_LINEBEAKS}].? {2,}`, "g"); // note: repeating space
97
100
  let parent;
98
101
 
99
102
 
@@ -5,6 +5,12 @@
5
5
 
6
6
  "use strict";
7
7
 
8
+ //------------------------------------------------------------------------------
9
+ // Requirements
10
+ //------------------------------------------------------------------------------
11
+
12
+ const astUtils = require("../ast-utils");
13
+
8
14
  //------------------------------------------------------------------------------
9
15
  // Rule Definition
10
16
  //------------------------------------------------------------------------------
@@ -39,9 +45,7 @@ module.exports = {
39
45
  return {
40
46
 
41
47
  Literal(node) {
42
- const lineBreak = /\n/;
43
-
44
- if (lineBreak.test(node.raw) && !isJSXElement(node.parent)) {
48
+ if (astUtils.LINEBREAK_MATCHER.test(node.raw) && !isJSXElement(node.parent)) {
45
49
  context.report({ node, message: "Multiline support is limited to browsers supporting ES5 only." });
46
50
  }
47
51
  }
@@ -89,13 +89,13 @@ module.exports = {
89
89
  BlockStatement: checkForBlock,
90
90
  SwitchStatement: checkForBlock
91
91
  };
92
- } else {
93
- return {
94
- Program: checkForGlobal,
95
- FunctionDeclaration: checkForBlock,
96
- FunctionExpression: checkForBlock,
97
- ArrowFunctionExpression: checkForBlock
98
- };
99
92
  }
93
+ return {
94
+ Program: checkForGlobal,
95
+ FunctionDeclaration: checkForBlock,
96
+ FunctionExpression: checkForBlock,
97
+ ArrowFunctionExpression: checkForBlock
98
+ };
99
+
100
100
  }
101
101
  };
@@ -5,23 +5,16 @@
5
5
  "use strict";
6
6
 
7
7
  //------------------------------------------------------------------------------
8
- // Helpers
8
+ // Requirements
9
9
  //------------------------------------------------------------------------------
10
10
 
11
- const SENTINEL_TYPE = /^(?:[a-zA-Z]+?Statement|ArrowFunctionExpression|FunctionExpression|ClassExpression)$/;
11
+ const astUtils = require("../ast-utils");
12
12
 
13
- /**
14
- * Checks whether or not a node is enclosed in parentheses.
15
- * @param {Node|null} node - A node to check.
16
- * @param {sourceCode} sourceCode - The ESLint SourceCode object.
17
- * @returns {boolean} Whether or not the node is enclosed in parentheses.
18
- */
19
- function isEnclosedInParens(node, sourceCode) {
20
- const prevToken = sourceCode.getTokenBefore(node);
21
- const nextToken = sourceCode.getTokenAfter(node);
13
+ //------------------------------------------------------------------------------
14
+ // Helpers
15
+ //------------------------------------------------------------------------------
22
16
 
23
- return prevToken && prevToken.value === "(" && nextToken && nextToken.value === ")";
24
- }
17
+ const SENTINEL_TYPE = /^(?:[a-zA-Z]+?Statement|ArrowFunctionExpression|FunctionExpression|ClassExpression)$/;
25
18
 
26
19
  //------------------------------------------------------------------------------
27
20
  // Rule Definition
@@ -48,7 +41,7 @@ module.exports = {
48
41
 
49
42
  return {
50
43
  AssignmentExpression(node) {
51
- if (!always && isEnclosedInParens(node, sourceCode)) {
44
+ if (!always && astUtils.isParenthesised(sourceCode, node)) {
52
45
  return;
53
46
  }
54
47
 
@@ -5,6 +5,12 @@
5
5
 
6
6
  "use strict";
7
7
 
8
+ //------------------------------------------------------------------------------
9
+ // Requirements
10
+ //------------------------------------------------------------------------------
11
+
12
+ const astUtils = require("../ast-utils");
13
+
8
14
  //------------------------------------------------------------------------------
9
15
  // Rule Definition
10
16
  //------------------------------------------------------------------------------
@@ -57,12 +63,7 @@ module.exports = {
57
63
  * @returns {boolean} True if the node has a paren on each side.
58
64
  */
59
65
  function isParenthesised(node) {
60
- const previousToken = sourceCode.getTokenBefore(node),
61
- nextToken = sourceCode.getTokenAfter(node);
62
-
63
- return previousToken && nextToken &&
64
- previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
65
- nextToken.value === ")" && nextToken.range[0] >= node.range[1];
66
+ return astUtils.isParenthesised(sourceCode, node);
66
67
  }
67
68
 
68
69
  /**
@@ -4,6 +4,12 @@
4
4
  */
5
5
  "use strict";
6
6
 
7
+ //------------------------------------------------------------------------------
8
+ // Requirements
9
+ //------------------------------------------------------------------------------
10
+
11
+ const astUtils = require("../ast-utils");
12
+
7
13
  //------------------------------------------------------------------------------
8
14
  // Rule Definition
9
15
  //------------------------------------------------------------------------------
@@ -34,7 +40,7 @@ module.exports = {
34
40
  create(context) {
35
41
  const sourceCode = context.getSourceCode();
36
42
 
37
- const BLANK_CLASS = "[ \t\u00a0\u2000-\u200b\u2028\u2029\u3000]",
43
+ const BLANK_CLASS = "[ \t\u00a0\u2000-\u200b\u3000]",
38
44
  SKIP_BLANK = `^${BLANK_CLASS}*$`,
39
45
  NONBLANK = `${BLANK_CLASS}+$`;
40
46
 
@@ -81,7 +87,7 @@ module.exports = {
81
87
  const re = new RegExp(NONBLANK),
82
88
  skipMatch = new RegExp(SKIP_BLANK),
83
89
  lines = sourceCode.lines,
84
- linebreaks = sourceCode.getText().match(/\r\n|\r|\n|\u2028|\u2029/g);
90
+ linebreaks = sourceCode.getText().match(astUtils.createGlobalLinebreakMatcher());
85
91
  let totalLength = 0,
86
92
  fixRange = [];
87
93
 
@@ -21,15 +21,54 @@ module.exports = {
21
21
 
22
22
  create(context) {
23
23
 
24
+ /**
25
+ * Report an invalid "undefined" identifier node.
26
+ * @param {ASTNode} node The node to report.
27
+ * @returns {void}
28
+ */
29
+ function report(node) {
30
+ context.report({
31
+ node,
32
+ message: "Unexpected use of undefined."
33
+ });
34
+ }
35
+
36
+ /**
37
+ * Checks the given scope for references to `undefined` and reports
38
+ * all references found.
39
+ * @param {escope.Scope} scope The scope to check.
40
+ * @returns {void}
41
+ */
42
+ function checkScope(scope) {
43
+ const undefinedVar = scope.set.get("undefined");
44
+
45
+ if (!undefinedVar) {
46
+ return;
47
+ }
48
+
49
+ const references = undefinedVar.references;
50
+
51
+ const defs = undefinedVar.defs;
52
+
53
+ // Report non-initializing references (those are covered in defs below)
54
+ references
55
+ .filter(ref => !ref.init)
56
+ .forEach(ref => report(ref.identifier));
57
+
58
+ defs.forEach(def => report(def.name));
59
+ }
60
+
24
61
  return {
62
+ "Program:exit"() {
63
+ const globalScope = context.getScope();
64
+
65
+ const stack = [globalScope];
25
66
 
26
- Identifier(node) {
27
- if (node.name === "undefined") {
28
- const parent = context.getAncestors().pop();
67
+ while (stack.length) {
68
+ const scope = stack.pop();
29
69
 
30
- if (!parent || parent.type !== "MemberExpression" || node !== parent.property || parent.computed) {
31
- context.report({ node, message: "Unexpected use of undefined." });
32
- }
70
+ stack.push.apply(stack, scope.childScopes);
71
+ checkScope(scope);
33
72
  }
34
73
  }
35
74
  };
@@ -4,9 +4,16 @@
4
4
  */
5
5
  "use strict";
6
6
 
7
+ //------------------------------------------------------------------------------
8
+ // Requirements
9
+ //------------------------------------------------------------------------------
10
+
11
+ const astUtils = require("../ast-utils");
12
+
7
13
  //------------------------------------------------------------------------------
8
14
  // Rule Definition
9
15
  //------------------------------------------------------------------------------
16
+
10
17
  module.exports = {
11
18
  meta: {
12
19
  docs: {
@@ -35,14 +42,8 @@ module.exports = {
35
42
  * @private
36
43
  */
37
44
  function checkForBreakAfter(node, msg) {
38
- let nodeExpressionEnd = node;
39
- let openParen = sourceCode.getTokenAfter(node);
40
-
41
- // Move along until the end of the wrapped expression
42
- while (openParen.value === ")") {
43
- nodeExpressionEnd = openParen;
44
- openParen = sourceCode.getTokenAfter(nodeExpressionEnd);
45
- }
45
+ const openParen = sourceCode.getTokenAfter(node, astUtils.isNotClosingParenToken);
46
+ const nodeExpressionEnd = sourceCode.getTokenBefore(openParen);
46
47
 
47
48
  if (openParen.loc.start.line !== nodeExpressionEnd.loc.end.line) {
48
49
  context.report({ node, loc: openParen.loc.start, message: msg, data: { char: openParen.value } });
@@ -67,7 +67,11 @@ module.exports = {
67
67
  */
68
68
  function invertExpression(node) {
69
69
  if (node.type === "BinaryExpression" && Object.prototype.hasOwnProperty.call(OPERATOR_INVERSES, node.operator)) {
70
- const operatorToken = sourceCode.getTokensBetween(node.left, node.right).find(token => token.value === node.operator);
70
+ const operatorToken = sourceCode.getFirstTokenBetween(
71
+ node.left,
72
+ node.right,
73
+ token => token.value === node.operator
74
+ );
71
75
 
72
76
  return sourceCode.getText().slice(node.range[0], operatorToken.range[0]) + OPERATOR_INVERSES[node.operator] + sourceCode.getText().slice(operatorToken.range[1], node.range[1]);
73
77
  }
@@ -17,10 +17,13 @@ module.exports = {
17
17
  recommended: true
18
18
  },
19
19
 
20
- schema: []
20
+ schema: [],
21
+
22
+ fixable: "code"
21
23
  },
22
24
 
23
25
  create(context) {
26
+ const sourceCode = context.getSourceCode();
24
27
  let scopeInfo = null;
25
28
 
26
29
  /**
@@ -49,7 +52,19 @@ module.exports = {
49
52
  context.report({
50
53
  node: node.label,
51
54
  message: "'{{name}}:' is defined but never used.",
52
- data: node.label
55
+ data: node.label,
56
+ fix(fixer) {
57
+
58
+ /*
59
+ * Only perform a fix if there are no comments between the label and the body. This will be the case
60
+ * when there is exactly one token/comment (the ":") between the label and the body.
61
+ */
62
+ if (sourceCode.getTokenAfter(node.label, { includeComments: true }) === sourceCode.getTokenBefore(node.body, { includeComments: true })) {
63
+ return fixer.removeRange([node.range[0], node.body.range[0]]);
64
+ }
65
+
66
+ return null;
67
+ }
53
68
  });
54
69
  }
55
70
 
@@ -42,6 +42,9 @@ module.exports = {
42
42
  args: {
43
43
  enum: ["all", "after-used", "none"]
44
44
  },
45
+ ignoreRestSiblings: {
46
+ type: "boolean"
47
+ },
45
48
  argsIgnorePattern: {
46
49
  type: "string"
47
50
  },
@@ -59,6 +62,7 @@ module.exports = {
59
62
  },
60
63
 
61
64
  create(context) {
65
+ const sourceCode = context.getSourceCode();
62
66
 
63
67
  const DEFINED_MESSAGE = "'{{name}}' is defined but never used.";
64
68
  const ASSIGNED_MESSAGE = "'{{name}}' is assigned a value but never used.";
@@ -66,6 +70,7 @@ module.exports = {
66
70
  const config = {
67
71
  vars: "all",
68
72
  args: "after-used",
73
+ ignoreRestSiblings: false,
69
74
  caughtErrors: "none"
70
75
  };
71
76
 
@@ -77,6 +82,7 @@ module.exports = {
77
82
  } else {
78
83
  config.vars = firstOption.vars || config.vars;
79
84
  config.args = firstOption.args || config.args;
85
+ config.ignoreRestSiblings = firstOption.ignoreRestSiblings || config.ignoreRestSiblings;
80
86
  config.caughtErrors = firstOption.caughtErrors || config.caughtErrors;
81
87
 
82
88
  if (firstOption.varsIgnorePattern) {
@@ -120,9 +126,33 @@ module.exports = {
120
126
  }
121
127
 
122
128
  return node.parent.type.indexOf("Export") === 0;
123
- } else {
124
- return false;
125
129
  }
130
+ return false;
131
+
132
+ }
133
+
134
+ /**
135
+ * Determines if a variable has a sibling rest property
136
+ * @param {Variable} variable - EScope variable object.
137
+ * @returns {boolean} True if the variable is exported, false if not.
138
+ * @private
139
+ */
140
+ function hasRestSpreadSibling(variable) {
141
+ if (config.ignoreRestSiblings) {
142
+ const restProperties = new Set(["ExperimentalRestProperty", "RestProperty"]);
143
+
144
+ return variable.defs
145
+ .filter(def => def.name.type === "Identifier")
146
+ .some(def => (
147
+ def.node.id &&
148
+ def.node.id.type === "ObjectPattern" &&
149
+ def.node.id.properties.length &&
150
+ restProperties.has(def.node.id.properties[def.node.id.properties.length - 1].type) && // last property is a rest property
151
+ !restProperties.has(def.name.parent.type) // variable is sibling of the rest property
152
+ ));
153
+ }
154
+
155
+ return false;
126
156
  }
127
157
 
128
158
  /**
@@ -495,7 +525,7 @@ module.exports = {
495
525
  }
496
526
  }
497
527
 
498
- if (!isUsedVariable(variable) && !isExported(variable)) {
528
+ if (!isUsedVariable(variable) && !isExported(variable) && !hasRestSpreadSibling(variable)) {
499
529
  unusedVars.push(variable);
500
530
  }
501
531
  }
@@ -537,23 +567,8 @@ module.exports = {
537
567
  */
538
568
  function getLocation(variable) {
539
569
  const comment = variable.eslintExplicitGlobalComment;
540
- const baseLoc = comment.loc.start;
541
- let column = getColumnInComment(variable, comment);
542
- const prefix = comment.value.slice(0, column);
543
- const lineInComment = (prefix.match(/\n/g) || []).length;
544
-
545
- if (lineInComment > 0) {
546
- column -= 1 + prefix.lastIndexOf("\n");
547
- } else {
548
-
549
- // 2 is for `/*`
550
- column += baseLoc.column + 2;
551
- }
552
570
 
553
- return {
554
- line: baseLoc.line + lineInComment,
555
- column
556
- };
571
+ return astUtils.getLocationFromRangeIndex(sourceCode, comment.range[0] + 2 + getColumnInComment(variable, comment));
557
572
  }
558
573
 
559
574
  //--------------------------------------------------------------------------