eslint 3.9.0 → 3.10.2

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 (112) hide show
  1. package/CHANGELOG.md +60 -1
  2. package/LICENSE +1 -1
  3. package/README.md +1 -1
  4. package/bin/eslint.js +5 -5
  5. package/conf/eslint.json +1 -0
  6. package/lib/ast-utils.js +1 -3
  7. package/lib/cli-engine.js +9 -11
  8. package/lib/code-path-analysis/debug-helpers.js +4 -4
  9. package/lib/config/autoconfig.js +23 -35
  10. package/lib/config/config-file.js +1 -1
  11. package/lib/config/config-initializer.js +12 -20
  12. package/lib/config/config-ops.js +7 -9
  13. package/lib/config/config-rule.js +14 -18
  14. package/lib/config/config-validator.js +3 -3
  15. package/lib/config/environments.js +1 -1
  16. package/lib/config.js +2 -2
  17. package/lib/eslint.js +21 -23
  18. package/lib/file-finder.js +1 -1
  19. package/lib/formatters/checkstyle.js +2 -2
  20. package/lib/formatters/compact.js +2 -2
  21. package/lib/formatters/html.js +9 -11
  22. package/lib/formatters/jslint-xml.js +2 -2
  23. package/lib/formatters/junit.js +2 -2
  24. package/lib/formatters/stylish.js +3 -7
  25. package/lib/formatters/table.js +4 -6
  26. package/lib/formatters/tap.js +2 -2
  27. package/lib/formatters/unix.js +2 -2
  28. package/lib/formatters/visualstudio.js +2 -2
  29. package/lib/load-rules.js +1 -1
  30. package/lib/rules/arrow-body-style.js +1 -1
  31. package/lib/rules/arrow-parens.js +9 -2
  32. package/lib/rules/brace-style.js +2 -2
  33. package/lib/rules/comma-spacing.js +2 -2
  34. package/lib/rules/comma-style.js +51 -4
  35. package/lib/rules/consistent-this.js +5 -9
  36. package/lib/rules/constructor-super.js +1 -1
  37. package/lib/rules/curly.js +10 -7
  38. package/lib/rules/default-case.js +1 -3
  39. package/lib/rules/eqeqeq.js +19 -7
  40. package/lib/rules/func-names.js +23 -4
  41. package/lib/rules/global-require.js +3 -7
  42. package/lib/rules/handle-callback-err.js +1 -3
  43. package/lib/rules/id-length.js +1 -1
  44. package/lib/rules/indent.js +10 -25
  45. package/lib/rules/key-spacing.js +4 -4
  46. package/lib/rules/keyword-spacing.js +1 -1
  47. package/lib/rules/lines-around-comment.js +5 -11
  48. package/lib/rules/lines-around-directive.js +23 -4
  49. package/lib/rules/max-len.js +5 -11
  50. package/lib/rules/max-lines.js +4 -12
  51. package/lib/rules/max-statements.js +11 -2
  52. package/lib/rules/newline-after-var.js +1 -1
  53. package/lib/rules/newline-before-return.js +2 -4
  54. package/lib/rules/no-alert.js +2 -4
  55. package/lib/rules/no-class-assign.js +1 -1
  56. package/lib/rules/no-const-assign.js +1 -1
  57. package/lib/rules/no-control-regex.js +2 -2
  58. package/lib/rules/no-duplicate-case.js +1 -1
  59. package/lib/rules/no-ex-assign.js +1 -1
  60. package/lib/rules/no-extend-native.js +3 -7
  61. package/lib/rules/no-extra-boolean-cast.js +14 -3
  62. package/lib/rules/no-extra-parens.js +4 -4
  63. package/lib/rules/no-func-assign.js +1 -1
  64. package/lib/rules/no-implicit-globals.js +4 -4
  65. package/lib/rules/no-irregular-whitespace.js +3 -3
  66. package/lib/rules/no-mixed-operators.js +1 -3
  67. package/lib/rules/no-mixed-requires.js +2 -2
  68. package/lib/rules/no-mixed-spaces-and-tabs.js +3 -3
  69. package/lib/rules/no-multi-spaces.js +1 -1
  70. package/lib/rules/no-new-symbol.js +1 -1
  71. package/lib/rules/no-redeclare.js +2 -4
  72. package/lib/rules/no-restricted-globals.js +2 -2
  73. package/lib/rules/no-restricted-imports.js +45 -11
  74. package/lib/rules/no-restricted-modules.js +53 -36
  75. package/lib/rules/no-restricted-syntax.js +2 -4
  76. package/lib/rules/no-return-await.js +77 -0
  77. package/lib/rules/no-tabs.js +1 -1
  78. package/lib/rules/no-this-before-super.js +2 -2
  79. package/lib/rules/no-undef.js +1 -1
  80. package/lib/rules/no-underscore-dangle.js +1 -3
  81. package/lib/rules/no-unused-vars.js +2 -6
  82. package/lib/rules/no-use-before-define.js +1 -1
  83. package/lib/rules/no-useless-escape.js +8 -54
  84. package/lib/rules/no-useless-return.js +6 -0
  85. package/lib/rules/no-warning-comments.js +2 -2
  86. package/lib/rules/object-shorthand.js +21 -10
  87. package/lib/rules/one-var-declaration-per-line.js +1 -1
  88. package/lib/rules/prefer-const.js +1 -3
  89. package/lib/rules/prefer-reflect.js +2 -1
  90. package/lib/rules/quote-props.js +1 -1
  91. package/lib/rules/quotes.js +1 -1
  92. package/lib/rules/radix.js +2 -2
  93. package/lib/rules/require-jsdoc.js +8 -0
  94. package/lib/rules/sort-vars.js +1 -1
  95. package/lib/rules/space-in-parens.js +1 -1
  96. package/lib/rules/space-infix-ops.js +1 -1
  97. package/lib/rules/spaced-comment.js +1 -1
  98. package/lib/rules/symbol-description.js +1 -1
  99. package/lib/rules/valid-jsdoc.js +3 -3
  100. package/lib/rules.js +2 -2
  101. package/lib/testers/event-generator-tester.js +5 -5
  102. package/lib/testers/rule-tester.js +11 -13
  103. package/lib/timing.js +11 -13
  104. package/lib/util/comment-event-generator.js +1 -1
  105. package/lib/util/glob-util.js +3 -5
  106. package/lib/util/npm-util.js +1 -1
  107. package/lib/util/source-code-fixer.js +3 -5
  108. package/lib/util/source-code-util.js +4 -4
  109. package/lib/util/source-code.js +3 -3
  110. package/lib/util/traverser.js +1 -3
  111. package/lib/util/xml-escape.js +1 -1
  112. package/package.json +3 -3
@@ -141,7 +141,7 @@ module.exports = {
141
141
  function addNullElementsToIgnoreList(node) {
142
142
  let previousToken = sourceCode.getFirstToken(node);
143
143
 
144
- node.elements.forEach(function(element) {
144
+ node.elements.forEach(element => {
145
145
  let token;
146
146
 
147
147
  if (element === null) {
@@ -164,7 +164,7 @@ module.exports = {
164
164
 
165
165
  return {
166
166
  "Program:exit"() {
167
- tokensAndComments.forEach(function(token, i) {
167
+ tokensAndComments.forEach((token, i) => {
168
168
 
169
169
  if (!isComma(token)) {
170
170
  return;
@@ -41,10 +41,22 @@ module.exports = {
41
41
  create(context) {
42
42
  const style = context.options[0] || "last",
43
43
  sourceCode = context.getSourceCode();
44
- let exceptions = {};
44
+ const exceptions = {
45
+ ArrayPattern: true,
46
+ ArrowFunctionExpression: true,
47
+ CallExpression: true,
48
+ FunctionDeclaration: true,
49
+ FunctionExpression: true,
50
+ ImportDeclaration: true,
51
+ ObjectPattern: true,
52
+ };
45
53
 
46
54
  if (context.options.length === 2 && context.options[1].hasOwnProperty("exceptions")) {
47
- exceptions = context.options[1].exceptions;
55
+ const keys = Object.keys(context.options[1].exceptions);
56
+
57
+ for (let i = 0; i < keys.length; i++) {
58
+ exceptions[keys[i]] = context.options[1].exceptions[keys[i]];
59
+ }
48
60
  }
49
61
 
50
62
  //--------------------------------------------------------------------------
@@ -166,14 +178,14 @@ module.exports = {
166
178
  */
167
179
  function validateComma(node, property) {
168
180
  const items = node[property],
169
- arrayLiteral = (node.type === "ArrayExpression");
181
+ arrayLiteral = (node.type === "ArrayExpression" || node.type === "ArrayPattern");
170
182
 
171
183
  if (items.length > 1 || arrayLiteral) {
172
184
 
173
185
  // seed as opening [
174
186
  let previousItemToken = sourceCode.getFirstToken(node);
175
187
 
176
- items.forEach(function(item) {
188
+ items.forEach(item => {
177
189
  const commaToken = item ? sourceCode.getTokenBefore(item) : previousItemToken,
178
190
  currentItemToken = item ? sourceCode.getFirstToken(item) : sourceCode.getTokenAfter(commaToken),
179
191
  reportItem = item || currentItemToken,
@@ -245,11 +257,46 @@ module.exports = {
245
257
  validateComma(node, "properties");
246
258
  };
247
259
  }
260
+ if (!exceptions.ObjectPattern) {
261
+ nodes.ObjectPattern = function(node) {
262
+ validateComma(node, "properties");
263
+ };
264
+ }
248
265
  if (!exceptions.ArrayExpression) {
249
266
  nodes.ArrayExpression = function(node) {
250
267
  validateComma(node, "elements");
251
268
  };
252
269
  }
270
+ if (!exceptions.ArrayPattern) {
271
+ nodes.ArrayPattern = function(node) {
272
+ validateComma(node, "elements");
273
+ };
274
+ }
275
+ if (!exceptions.FunctionDeclaration) {
276
+ nodes.FunctionDeclaration = function(node) {
277
+ validateComma(node, "params");
278
+ };
279
+ }
280
+ if (!exceptions.FunctionExpression) {
281
+ nodes.FunctionExpression = function(node) {
282
+ validateComma(node, "params");
283
+ };
284
+ }
285
+ if (!exceptions.ArrowFunctionExpression) {
286
+ nodes.ArrowFunctionExpression = function(node) {
287
+ validateComma(node, "params");
288
+ };
289
+ }
290
+ if (!exceptions.CallExpression) {
291
+ nodes.CallExpression = function(node) {
292
+ validateComma(node, "arguments");
293
+ };
294
+ }
295
+ if (!exceptions.ImportDeclaration) {
296
+ nodes.ImportDeclaration = function(node) {
297
+ validateComma(node, "specifiers");
298
+ };
299
+ }
253
300
 
254
301
  return nodes;
255
302
  }
@@ -84,16 +84,14 @@ module.exports = {
84
84
  return;
85
85
  }
86
86
 
87
- if (variable.defs.some(function(def) {
88
- return def.node.type === "VariableDeclarator" &&
89
- def.node.init !== null;
90
- })) {
87
+ if (variable.defs.some(def => def.node.type === "VariableDeclarator" &&
88
+ def.node.init !== null)) {
91
89
  return;
92
90
  }
93
91
 
94
92
  // The alias has been declared and not assigned: check it was
95
93
  // assigned later in the same scope.
96
- if (!variable.references.some(function(reference) {
94
+ if (!variable.references.some(reference => {
97
95
  const write = reference.writeExpr;
98
96
 
99
97
  return (
@@ -102,9 +100,7 @@ module.exports = {
102
100
  write.parent.operator === "="
103
101
  );
104
102
  })) {
105
- variable.defs.map(function(def) {
106
- return def.node;
107
- }).forEach(function(node) {
103
+ variable.defs.map(def => def.node).forEach(node => {
108
104
  reportBadAssignment(node, alias);
109
105
  });
110
106
  }
@@ -117,7 +113,7 @@ module.exports = {
117
113
  function ensureWasAssigned() {
118
114
  const scope = context.getScope();
119
115
 
120
- aliases.forEach(function(alias) {
116
+ aliases.forEach(alias => {
121
117
  checkWasAssigned(alias, scope);
122
118
  });
123
119
  }
@@ -262,7 +262,7 @@ module.exports = {
262
262
 
263
263
  funcInfo.codePath.traverseSegments(
264
264
  {first: toSegment, last: fromSegment},
265
- function(segment) {
265
+ segment => {
266
266
  const info = segInfoMap[segment.id];
267
267
  const prevSegments = segment.prevSegments;
268
268
 
@@ -74,10 +74,11 @@ module.exports = {
74
74
  * @private
75
75
  */
76
76
  function isCollapsedOneLiner(node) {
77
- const before = sourceCode.getTokenBefore(node),
78
- last = sourceCode.getLastToken(node);
77
+ const before = sourceCode.getTokenBefore(node);
78
+ const last = sourceCode.getLastToken(node);
79
+ const lastExcludingSemicolon = last.type === "Punctuator" && last.value === ";" ? sourceCode.getTokenBefore(last) : last;
79
80
 
80
- return before.loc.start.line === last.loc.end.line;
81
+ return before.loc.start.line === lastExcludingSemicolon.loc.end.line;
81
82
  }
82
83
 
83
84
  /**
@@ -289,7 +290,9 @@ module.exports = {
289
290
  }
290
291
  } else if (multiOrNest) {
291
292
  if (hasBlock && body.body.length === 1 && isOneLiner(body.body[0])) {
292
- expected = false;
293
+ const leadingComments = sourceCode.getComments(body.body[0]).leading;
294
+
295
+ expected = leadingComments.length > 0;
293
296
  } else if (!isOneLiner(body)) {
294
297
  expected = true;
295
298
  }
@@ -337,14 +340,14 @@ module.exports = {
337
340
  * all have braces.
338
341
  * If all nodes shouldn't have braces, make sure they don't.
339
342
  */
340
- const expected = preparedChecks.some(function(preparedCheck) {
343
+ const expected = preparedChecks.some(preparedCheck => {
341
344
  if (preparedCheck.expected !== null) {
342
345
  return preparedCheck.expected;
343
346
  }
344
347
  return preparedCheck.actual;
345
348
  });
346
349
 
347
- preparedChecks.forEach(function(preparedCheck) {
350
+ preparedChecks.forEach(preparedCheck => {
348
351
  preparedCheck.expected = expected;
349
352
  });
350
353
  }
@@ -359,7 +362,7 @@ module.exports = {
359
362
  return {
360
363
  IfStatement(node) {
361
364
  if (node.parent.type !== "IfStatement") {
362
- prepareIfChecks(node).forEach(function(preparedCheck) {
365
+ prepareIfChecks(node).forEach(preparedCheck => {
363
366
  preparedCheck.check();
364
367
  });
365
368
  }
@@ -67,9 +67,7 @@ module.exports = {
67
67
  return;
68
68
  }
69
69
 
70
- const hasDefault = node.cases.some(function(v) {
71
- return v.test === null;
72
- });
70
+ const hasDefault = node.cases.some(v => v.test === null);
73
71
 
74
72
  if (!hasDefault) {
75
73
 
@@ -47,7 +47,9 @@ module.exports = {
47
47
  additionalItems: false
48
48
  }
49
49
  ]
50
- }
50
+ },
51
+
52
+ fixable: "code"
51
53
  },
52
54
 
53
55
  create(context) {
@@ -118,16 +120,26 @@ module.exports = {
118
120
  /**
119
121
  * Reports a message for this rule.
120
122
  * @param {ASTNode} node The binary expression node that was checked
121
- * @param {string} message The message to report
123
+ * @param {string} expectedOperator The operator that was expected (either '==', '!=', '===', or '!==')
122
124
  * @returns {void}
123
125
  * @private
124
126
  */
125
- function report(node, message) {
127
+ function report(node, expectedOperator) {
126
128
  context.report({
127
129
  node,
128
130
  loc: getOperatorLocation(node),
129
- message,
130
- data: { op: node.operator.charAt(0) }
131
+ message: "Expected '{{expectedOperator}}' and instead saw '{{actualOperator}}'.",
132
+ data: {expectedOperator, actualOperator: node.operator},
133
+ fix(fixer) {
134
+
135
+ // If the comparison is a `typeof` comparison or both sides are literals with the same type, then it's safe to fix.
136
+ if (isTypeOfBinary(node) || areLiteralsAndSameType(node)) {
137
+ const operatorToken = sourceCode.getTokensBetween(node.left, node.right).find(token => token.value === node.operator);
138
+
139
+ return fixer.replaceText(operatorToken, expectedOperator);
140
+ }
141
+ return null;
142
+ }
131
143
  });
132
144
  }
133
145
 
@@ -137,7 +149,7 @@ module.exports = {
137
149
 
138
150
  if (node.operator !== "==" && node.operator !== "!=") {
139
151
  if (enforceInverseRuleForNull && isNull) {
140
- report(node, "Expected '{{op}}=' and instead saw '{{op}}=='.");
152
+ report(node, node.operator.slice(0, -1));
141
153
  }
142
154
  return;
143
155
  }
@@ -151,7 +163,7 @@ module.exports = {
151
163
  return;
152
164
  }
153
165
 
154
- report(node, "Expected '{{op}}==' and instead saw '{{op}}='.");
166
+ report(node, `${node.operator}=`);
155
167
  }
156
168
  };
157
169
 
@@ -28,21 +28,23 @@ module.exports = {
28
28
 
29
29
  schema: [
30
30
  {
31
- enum: ["always", "never"]
31
+ enum: ["always", "as-needed", "never"]
32
32
  }
33
33
  ]
34
34
  },
35
35
 
36
36
  create(context) {
37
37
  const never = context.options[0] === "never";
38
+ const asNeeded = context.options[0] === "as-needed";
38
39
 
39
40
  /**
40
41
  * Determines whether the current FunctionExpression node is a get, set, or
41
42
  * shorthand method in an object literal or a class.
43
+ * @param {ASTNode} node - A node to check.
42
44
  * @returns {boolean} True if the node is a get, set, or shorthand method.
43
45
  */
44
- function isObjectOrClassMethod() {
45
- const parent = context.getAncestors().pop();
46
+ function isObjectOrClassMethod(node) {
47
+ const parent = node.parent;
46
48
 
47
49
  return (parent.type === "MethodDefinition" || (
48
50
  parent.type === "Property" && (
@@ -53,6 +55,23 @@ module.exports = {
53
55
  ));
54
56
  }
55
57
 
58
+ /**
59
+ * Determines whether the current FunctionExpression node has a name that would be
60
+ * inferred from context in a conforming ES6 environment.
61
+ * @param {ASTNode} node - A node to check.
62
+ * @returns {boolean} True if the node would have a name assigned automatically.
63
+ */
64
+ function hasInferredName(node) {
65
+ const parent = node.parent;
66
+
67
+ return isObjectOrClassMethod(node) ||
68
+ (parent.type === "VariableDeclarator" && parent.id.type === "Identifier" && parent.init === node) ||
69
+ (parent.type === "Property" && parent.value === node) ||
70
+ (parent.type === "AssignmentExpression" && parent.left.type === "Identifier" && parent.right === node) ||
71
+ (parent.type === "ExportDefaultDeclaration" && parent.declaration === node) ||
72
+ (parent.type === "AssignmentPattern" && parent.right === node);
73
+ }
74
+
56
75
  return {
57
76
  "FunctionExpression:exit"(node) {
58
77
 
@@ -70,7 +89,7 @@ module.exports = {
70
89
  context.report(node, "Unexpected function expression name.");
71
90
  }
72
91
  } else {
73
- if (!name && !isObjectOrClassMethod()) {
92
+ if (!name && (asNeeded ? !hasInferredName(node) : !isObjectOrClassMethod(node))) {
74
93
  context.report(node, "Missing function expression name.");
75
94
  }
76
95
  }
@@ -23,10 +23,8 @@ const ACCEPTABLE_PARENTS = [
23
23
  * @returns {Reference|null} Returns the found reference or null if none were found.
24
24
  */
25
25
  function findReference(scope, node) {
26
- const references = scope.references.filter(function(reference) {
27
- return reference.identifier.range[0] === node.range[0] &&
28
- reference.identifier.range[1] === node.range[1];
29
- });
26
+ const references = scope.references.filter(reference => reference.identifier.range[0] === node.range[0] &&
27
+ reference.identifier.range[1] === node.range[1]);
30
28
 
31
29
  /* istanbul ignore else: correctly returns null */
32
30
  if (references.length === 1) {
@@ -65,9 +63,7 @@ module.exports = {
65
63
  const currentScope = context.getScope();
66
64
 
67
65
  if (node.callee.name === "require" && !isShadowed(currentScope, node.callee)) {
68
- const isGoodRequire = context.getAncestors().every(function(parent) {
69
- return ACCEPTABLE_PARENTS.indexOf(parent.type) > -1;
70
- });
66
+ const isGoodRequire = context.getAncestors().every(parent => ACCEPTABLE_PARENTS.indexOf(parent.type) > -1);
71
67
 
72
68
  if (!isGoodRequire) {
73
69
  context.report(node, "Unexpected require().");
@@ -59,9 +59,7 @@ module.exports = {
59
59
  * @returns {array} All parameters of the given scope.
60
60
  */
61
61
  function getParameters(scope) {
62
- return scope.variables.filter(function(variable) {
63
- return variable.defs[0] && variable.defs[0].type === "Parameter";
64
- });
62
+ return scope.variables.filter(variable => variable.defs[0] && variable.defs[0].type === "Parameter");
65
63
  }
66
64
 
67
65
  /**
@@ -50,7 +50,7 @@ module.exports = {
50
50
  const maxLength = typeof options.max !== "undefined" ? options.max : Infinity;
51
51
  const properties = options.properties !== "never";
52
52
  const exceptions = (options.exceptions ? options.exceptions : [])
53
- .reduce(function(obj, item) {
53
+ .reduce((obj, item) => {
54
54
  obj[item] = true;
55
55
 
56
56
  return obj;
@@ -255,20 +255,18 @@ module.exports = {
255
255
  * @param {int} lastNodeCheckEndOffset Number of charecters to skip from the end
256
256
  * @returns {void}
257
257
  */
258
- function report(node, needed, gottenSpaces, gottenTabs, loc, isLastNodeCheck, lastNodeCheckEndOffset) {
258
+ function report(node, needed, gottenSpaces, gottenTabs, loc, isLastNodeCheck) {
259
259
  if (gottenSpaces && gottenTabs) {
260
260
 
261
261
  // To avoid conflicts with `no-mixed-spaces-and-tabs`, don't report lines that have both spaces and tabs.
262
262
  return;
263
263
  }
264
264
 
265
- lastNodeCheckEndOffset = lastNodeCheckEndOffset || 0;
266
-
267
- const desiredIndent = (indentType === "space" ? " " : "\t").repeat(needed - lastNodeCheckEndOffset);
265
+ const desiredIndent = (indentType === "space" ? " " : "\t").repeat(needed);
268
266
 
269
267
  const textRange = isLastNodeCheck
270
- ? [node.range[1] - gottenSpaces - gottenTabs - 1, node.range[1] - 1 - lastNodeCheckEndOffset]
271
- : [node.range[0] - gottenSpaces - gottenTabs, node.range[0]];
268
+ ? [node.range[1] - node.loc.end.column, node.range[1] - node.loc.end.column + gottenSpaces + gottenTabs]
269
+ : [node.range[0] - node.loc.start.column, node.range[0] - node.loc.start.column + gottenSpaces + gottenTabs];
272
270
 
273
271
  context.report({
274
272
  node,
@@ -406,13 +404,11 @@ module.exports = {
406
404
  */
407
405
  function checkLastReturnStatementLineIndent(node, firstLineIndent) {
408
406
  const nodeLastToken = sourceCode.getLastToken(node);
409
- let lastNodeCheckEndOffset = 0;
410
407
  let lastToken = nodeLastToken;
411
408
 
412
409
  // in case if return statement ends with ');' we have traverse back to ')'
413
410
  // otherwise we'll measure indent for ';' and replace ')'
414
411
  while (lastToken.value !== ")") {
415
- lastNodeCheckEndOffset++;
416
412
  lastToken = sourceCode.getTokenBefore(lastToken);
417
413
  }
418
414
 
@@ -433,8 +429,7 @@ module.exports = {
433
429
  endIndent.space,
434
430
  endIndent.tab,
435
431
  { line: lastToken.loc.start.line, column: lastToken.loc.start.column },
436
- true,
437
- lastNodeCheckEndOffset
432
+ true
438
433
  );
439
434
  }
440
435
  }
@@ -684,9 +679,7 @@ module.exports = {
684
679
  let elements = (node.type === "ArrayExpression") ? node.elements : node.properties;
685
680
 
686
681
  // filter out empty elements example would be [ , 2] so remove first element as espree considers it as null
687
- elements = elements.filter(function(elem) {
688
- return elem !== null;
689
- });
682
+ elements = elements.filter(elem => elem !== null);
690
683
 
691
684
  let nodeIndent;
692
685
  let elementsIndent;
@@ -695,19 +688,11 @@ module.exports = {
695
688
  // TODO - come up with a better strategy in future
696
689
  if (isNodeFirstInLine(node)) {
697
690
  const parent = node.parent;
698
- let effectiveParent = parent;
699
691
 
700
- if (parent.type === "MemberExpression") {
701
- if (isNodeFirstInLine(parent)) {
702
- effectiveParent = parent.parent.parent;
703
- } else {
704
- effectiveParent = parent.parent;
705
- }
706
- }
707
- nodeIndent = getNodeIndent(effectiveParent).goodChar;
692
+ nodeIndent = getNodeIndent(parent).goodChar;
708
693
  if (parentVarNode && parentVarNode.loc.start.line !== node.loc.start.line) {
709
694
  if (parent.type !== "VariableDeclarator" || parentVarNode === parentVarNode.parent.declarations[0]) {
710
- if (parent.type === "VariableDeclarator" && parentVarNode.loc.start.line === effectiveParent.loc.start.line) {
695
+ if (parent.type === "VariableDeclarator" && parentVarNode.loc.start.line === parent.loc.start.line) {
711
696
  nodeIndent = nodeIndent + (indentSize * options.VariableDeclarator[parentVarNode.parent.kind]);
712
697
  } else if (
713
698
  parent.type === "ObjectExpression" ||
@@ -720,7 +705,7 @@ module.exports = {
720
705
  nodeIndent = nodeIndent + indentSize;
721
706
  }
722
707
  }
723
- } else if (!parentVarNode && !isFirstArrayElementOnSameLine(parent) && effectiveParent.type !== "MemberExpression" && effectiveParent.type !== "ExpressionStatement" && effectiveParent.type !== "AssignmentExpression" && effectiveParent.type !== "Property") {
708
+ } else if (!parentVarNode && !isFirstArrayElementOnSameLine(parent) && parent.type !== "MemberExpression" && parent.type !== "ExpressionStatement" && parent.type !== "AssignmentExpression" && parent.type !== "Property") {
724
709
  nodeIndent = nodeIndent + indentSize;
725
710
  }
726
711
 
@@ -827,7 +812,7 @@ module.exports = {
827
812
  * @returns {ASTNode[]} Filtered elements
828
813
  */
829
814
  function filterOutSameLineVars(node) {
830
- return node.declarations.reduce(function(finalCollection, elem) {
815
+ return node.declarations.reduce((finalCollection, elem) => {
831
816
  const lastElem = finalCollection[finalCollection.length - 1];
832
817
 
833
818
  if ((elem.loc.start.line !== node.loc.start.line && !lastElem) ||
@@ -417,8 +417,8 @@ module.exports = {
417
417
  function report(property, side, whitespace, expected, mode) {
418
418
  const diff = whitespace.length - expected,
419
419
  nextColon = getNextColon(property.key),
420
- tokenBeforeColon = sourceCode.getTokenBefore(nextColon),
421
- tokenAfterColon = sourceCode.getTokenAfter(nextColon),
420
+ tokenBeforeColon = sourceCode.getTokenOrCommentBefore(nextColon),
421
+ tokenAfterColon = sourceCode.getTokenOrCommentAfter(nextColon),
422
422
  isKeySide = side === "key",
423
423
  locStart = isKeySide ? tokenBeforeColon.loc.start : tokenAfterColon.loc.start,
424
424
  isExtra = diff > 0,
@@ -514,7 +514,7 @@ module.exports = {
514
514
  return [node.properties];
515
515
  }
516
516
 
517
- return node.properties.reduce(function(groups, property) {
517
+ return node.properties.reduce((groups, property) => {
518
518
  const currentGroup = last(groups),
519
519
  prev = last(currentGroup);
520
520
 
@@ -579,7 +579,7 @@ module.exports = {
579
579
  * @returns {void}
580
580
  */
581
581
  function verifyAlignment(node) {
582
- createGroups(node).forEach(function(group) {
582
+ createGroups(node).forEach(group => {
583
583
  verifyGroupAlignment(group.filter(isKeyValueProperty));
584
584
  });
585
585
  }
@@ -81,7 +81,7 @@ module.exports = {
81
81
  after: {type: "boolean"},
82
82
  overrides: {
83
83
  type: "object",
84
- properties: KEYS.reduce(function(retv, key) {
84
+ properties: KEYS.reduce((retv, key) => {
85
85
  retv[key] = {
86
86
  type: "object",
87
87
  properties: {
@@ -21,16 +21,10 @@ const lodash = require("lodash"),
21
21
  * @returns {Array} An array of line numbers.
22
22
  */
23
23
  function getEmptyLineNums(lines) {
24
- const emptyLines = lines.map(function(line, i) {
25
- return {
26
- code: line.trim(),
27
- num: i + 1
28
- };
29
- }).filter(function(line) {
30
- return !line.code;
31
- }).map(function(line) {
32
- return line.num;
33
- });
24
+ const emptyLines = lines.map((line, i) => ({
25
+ code: line.trim(),
26
+ num: i + 1
27
+ })).filter(line => !line.code).map(line => line.num);
34
28
 
35
29
  return emptyLines;
36
30
  }
@@ -43,7 +37,7 @@ function getEmptyLineNums(lines) {
43
37
  function getCommentLineNums(comments) {
44
38
  const lines = [];
45
39
 
46
- comments.forEach(function(token) {
40
+ comments.forEach(token => {
47
41
  const start = token.loc.start.line;
48
42
  const end = token.loc.end.line;
49
43
 
@@ -63,15 +63,32 @@ module.exports = {
63
63
  return node.loc.start.line - tokenLineBefore >= 2;
64
64
  }
65
65
 
66
+ /**
67
+ * Gets the last token of a node that is on the same line as the rest of the node.
68
+ * This will usually be the last token of the node, but it will be the second-to-last token if the node has a trailing
69
+ * semicolon on a different line.
70
+ * @param {ASTNode} node A directive node
71
+ * @returns {Token} The last token of the node on the line
72
+ */
73
+ function getLastTokenOnLine(node) {
74
+ const lastToken = sourceCode.getLastToken(node);
75
+ const secondToLastToken = sourceCode.getTokenBefore(lastToken);
76
+
77
+ return lastToken.type === "Punctuator" && lastToken.value === ";" && lastToken.loc.start.line > secondToLastToken.loc.end.line
78
+ ? secondToLastToken
79
+ : lastToken;
80
+ }
81
+
66
82
  /**
67
83
  * Check if node is followed by a blank newline.
68
84
  * @param {ASTNode} node Node to check.
69
85
  * @returns {boolean} Whether or not the passed in node is followed by a blank newline.
70
86
  */
71
87
  function hasNewlineAfter(node) {
72
- const tokenAfter = sourceCode.getTokenOrCommentAfter(node);
88
+ const lastToken = getLastTokenOnLine(node);
89
+ const tokenAfter = sourceCode.getTokenOrCommentAfter(lastToken);
73
90
 
74
- return tokenAfter.loc.start.line - node.loc.end.line >= 2;
91
+ return tokenAfter.loc.start.line - lastToken.loc.end.line >= 2;
75
92
  }
76
93
 
77
94
  /**
@@ -91,10 +108,12 @@ module.exports = {
91
108
  location
92
109
  },
93
110
  fix(fixer) {
111
+ const lastToken = getLastTokenOnLine(node);
112
+
94
113
  if (expected) {
95
- return location === "before" ? fixer.insertTextBefore(node, "\n") : fixer.insertTextAfter(node, "\n");
114
+ return location === "before" ? fixer.insertTextBefore(node, "\n") : fixer.insertTextAfter(lastToken, "\n");
96
115
  }
97
- return fixer.removeRange(location === "before" ? [node.range[0] - 1, node.range[0]] : [node.range[1], node.range[1] + 1]);
116
+ return fixer.removeRange(location === "before" ? [node.range[0] - 1, node.range[0]] : [lastToken.range[1], lastToken.range[1] + 1]);
98
117
  }
99
118
  });
100
119
  }
@@ -103,7 +103,7 @@ module.exports = {
103
103
  function computeLineLength(line, tabWidth) {
104
104
  let extraCharacterCount = 0;
105
105
 
106
- line.replace(/\t/g, function(match, offset) {
106
+ line.replace(/\t/g, (match, offset) => {
107
107
  const totalOffset = offset + extraCharacterCount,
108
108
  previousTabStopOffset = tabWidth ? totalOffset % tabWidth : 0,
109
109
  spaceCount = tabWidth - previousTabStopOffset;
@@ -213,9 +213,7 @@ module.exports = {
213
213
  * @returns {ASTNode[]} An array of string nodes.
214
214
  */
215
215
  function getAllStrings() {
216
- return sourceCode.ast.tokens.filter(function(token) {
217
- return token.type === "String";
218
- });
216
+ return sourceCode.ast.tokens.filter(token => token.type === "String");
219
217
  }
220
218
 
221
219
  /**
@@ -224,9 +222,7 @@ module.exports = {
224
222
  * @returns {ASTNode[]} An array of template literal nodes.
225
223
  */
226
224
  function getAllTemplateLiterals() {
227
- return sourceCode.ast.tokens.filter(function(token) {
228
- return token.type === "Template";
229
- });
225
+ return sourceCode.ast.tokens.filter(token => token.type === "Template");
230
226
  }
231
227
 
232
228
 
@@ -236,9 +232,7 @@ module.exports = {
236
232
  * @returns {ASTNode[]} An array of RegExp literal nodes.
237
233
  */
238
234
  function getAllRegExpLiterals() {
239
- return sourceCode.ast.tokens.filter(function(token) {
240
- return token.type === "RegularExpression";
241
- });
235
+ return sourceCode.ast.tokens.filter(token => token.type === "RegularExpression");
242
236
  }
243
237
 
244
238
 
@@ -283,7 +277,7 @@ module.exports = {
283
277
  const regExpLiterals = getAllRegExpLiterals(sourceCode);
284
278
  const regExpLiteralsByLine = regExpLiterals.reduce(groupByLineNumber, {});
285
279
 
286
- lines.forEach(function(line, i) {
280
+ lines.forEach((line, i) => {
287
281
 
288
282
  // i is zero-indexed, line numbers are one-indexed
289
283
  const lineNumber = i + 1;