eslint 7.0.0 → 7.3.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 (70) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +14 -10
  3. package/lib/cli-engine/cli-engine.js +2 -2
  4. package/lib/cli-engine/config-array-factory.js +34 -2
  5. package/lib/init/config-initializer.js +92 -74
  6. package/lib/linter/code-path-analysis/code-path-analyzer.js +2 -2
  7. package/lib/linter/code-path-analysis/code-path-state.js +34 -12
  8. package/lib/linter/config-comment-parser.js +1 -1
  9. package/lib/linter/linter.js +2 -1
  10. package/lib/rule-tester/rule-tester.js +9 -0
  11. package/lib/rules/accessor-pairs.js +1 -1
  12. package/lib/rules/arrow-parens.js +19 -3
  13. package/lib/rules/block-spacing.js +19 -2
  14. package/lib/rules/callback-return.js +1 -1
  15. package/lib/rules/comma-dangle.js +2 -1
  16. package/lib/rules/curly.js +8 -1
  17. package/lib/rules/func-call-spacing.js +18 -3
  18. package/lib/rules/global-require.js +1 -1
  19. package/lib/rules/handle-callback-err.js +1 -1
  20. package/lib/rules/id-match.js +2 -1
  21. package/lib/rules/index.js +3 -0
  22. package/lib/rules/key-spacing.js +6 -2
  23. package/lib/rules/keyword-spacing.js +9 -2
  24. package/lib/rules/linebreak-style.js +8 -2
  25. package/lib/rules/max-lines-per-function.js +1 -1
  26. package/lib/rules/max-lines.js +34 -8
  27. package/lib/rules/multiline-ternary.js +44 -25
  28. package/lib/rules/no-buffer-constructor.js +1 -1
  29. package/lib/rules/no-control-regex.js +1 -1
  30. package/lib/rules/no-extra-boolean-cast.js +3 -0
  31. package/lib/rules/no-extra-parens.js +30 -2
  32. package/lib/rules/no-invalid-regexp.js +1 -1
  33. package/lib/rules/no-loss-of-precision.js +198 -0
  34. package/lib/rules/no-misleading-character-class.js +1 -1
  35. package/lib/rules/no-mixed-operators.js +3 -2
  36. package/lib/rules/no-mixed-requires.js +1 -1
  37. package/lib/rules/no-mixed-spaces-and-tabs.js +14 -6
  38. package/lib/rules/no-new-func.js +22 -19
  39. package/lib/rules/no-new-require.js +1 -1
  40. package/lib/rules/no-new-symbol.js +2 -1
  41. package/lib/rules/no-path-concat.js +1 -1
  42. package/lib/rules/no-process-env.js +1 -1
  43. package/lib/rules/no-process-exit.js +1 -1
  44. package/lib/rules/no-promise-executor-return.js +121 -0
  45. package/lib/rules/no-regex-spaces.js +1 -1
  46. package/lib/rules/no-restricted-exports.js +6 -0
  47. package/lib/rules/no-restricted-modules.js +1 -1
  48. package/lib/rules/no-sync.js +1 -1
  49. package/lib/rules/no-unneeded-ternary.js +6 -4
  50. package/lib/rules/no-unreachable-loop.js +150 -0
  51. package/lib/rules/no-unused-expressions.js +1 -1
  52. package/lib/rules/no-unused-vars.js +5 -2
  53. package/lib/rules/no-useless-backreference.js +1 -1
  54. package/lib/rules/object-property-newline.js +1 -1
  55. package/lib/rules/one-var-declaration-per-line.js +1 -1
  56. package/lib/rules/operator-linebreak.js +2 -5
  57. package/lib/rules/padded-blocks.js +19 -5
  58. package/lib/rules/prefer-named-capture-group.js +1 -1
  59. package/lib/rules/quote-props.js +2 -2
  60. package/lib/rules/rest-spread-spacing.js +3 -6
  61. package/lib/rules/semi-spacing.js +33 -8
  62. package/lib/rules/template-tag-spacing.js +8 -2
  63. package/lib/rules/utils/ast-utils.js +106 -9
  64. package/lib/source-code/source-code.js +1 -0
  65. package/messages/extend-config-missing.txt +1 -1
  66. package/messages/no-config-found.txt +1 -1
  67. package/messages/plugin-conflict.txt +1 -1
  68. package/messages/plugin-missing.txt +1 -1
  69. package/messages/whitespace-found.txt +1 -1
  70. package/package.json +6 -6
@@ -201,6 +201,7 @@ function finalizeTestSegmentsOfFor(context, choiceContext, head) {
201
201
  if (!choiceContext.processed) {
202
202
  choiceContext.trueForkContext.add(head);
203
203
  choiceContext.falseForkContext.add(head);
204
+ choiceContext.qqForkContext.add(head);
204
205
  }
205
206
 
206
207
  if (context.test !== true) {
@@ -351,6 +352,7 @@ class CodePathState {
351
352
  isForkingAsResult,
352
353
  trueForkContext: ForkContext.newEmpty(this.forkContext),
353
354
  falseForkContext: ForkContext.newEmpty(this.forkContext),
355
+ qqForkContext: ForkContext.newEmpty(this.forkContext),
354
356
  processed: false
355
357
  };
356
358
  }
@@ -370,6 +372,7 @@ class CodePathState {
370
372
  switch (context.kind) {
371
373
  case "&&":
372
374
  case "||":
375
+ case "??":
373
376
 
374
377
  /*
375
378
  * If any result were not transferred from child contexts,
@@ -379,6 +382,7 @@ class CodePathState {
379
382
  if (!context.processed) {
380
383
  context.trueForkContext.add(headSegments);
381
384
  context.falseForkContext.add(headSegments);
385
+ context.qqForkContext.add(headSegments);
382
386
  }
383
387
 
384
388
  /*
@@ -390,6 +394,7 @@ class CodePathState {
390
394
 
391
395
  parentContext.trueForkContext.addAll(context.trueForkContext);
392
396
  parentContext.falseForkContext.addAll(context.falseForkContext);
397
+ parentContext.qqForkContext.addAll(context.qqForkContext);
393
398
  parentContext.processed = true;
394
399
 
395
400
  return context;
@@ -456,13 +461,24 @@ class CodePathState {
456
461
  * This got segments already from the child choice context.
457
462
  * Creates the next path from own true/false fork context.
458
463
  */
459
- const prevForkContext =
460
- context.kind === "&&" ? context.trueForkContext
461
- /* kind === "||" */ : context.falseForkContext;
464
+ let prevForkContext;
465
+
466
+ switch (context.kind) {
467
+ case "&&": // if true then go to the right-hand side.
468
+ prevForkContext = context.trueForkContext;
469
+ break;
470
+ case "||": // if false then go to the right-hand side.
471
+ prevForkContext = context.falseForkContext;
472
+ break;
473
+ case "??": // Both true/false can short-circuit, so needs the third path to go to the right-hand side. That's qqForkContext.
474
+ prevForkContext = context.qqForkContext;
475
+ break;
476
+ default:
477
+ throw new Error("unreachable");
478
+ }
462
479
 
463
480
  forkContext.replaceHead(prevForkContext.makeNext(0, -1));
464
481
  prevForkContext.clear();
465
-
466
482
  context.processed = false;
467
483
  } else {
468
484
 
@@ -471,14 +487,19 @@ class CodePathState {
471
487
  * So addresses the head segments.
472
488
  * The head segments are the path of the left-hand operand.
473
489
  */
474
- if (context.kind === "&&") {
475
-
476
- // The path does short-circuit if false.
477
- context.falseForkContext.add(forkContext.head);
478
- } else {
479
-
480
- // The path does short-circuit if true.
481
- context.trueForkContext.add(forkContext.head);
490
+ switch (context.kind) {
491
+ case "&&": // the false path can short-circuit.
492
+ context.falseForkContext.add(forkContext.head);
493
+ break;
494
+ case "||": // the true path can short-circuit.
495
+ context.trueForkContext.add(forkContext.head);
496
+ break;
497
+ case "??": // both can short-circuit.
498
+ context.trueForkContext.add(forkContext.head);
499
+ context.falseForkContext.add(forkContext.head);
500
+ break;
501
+ default:
502
+ throw new Error("unreachable");
482
503
  }
483
504
 
484
505
  forkContext.replaceHead(forkContext.makeNext(-1, -1));
@@ -501,6 +522,7 @@ class CodePathState {
501
522
  if (!context.processed) {
502
523
  context.trueForkContext.add(forkContext.head);
503
524
  context.falseForkContext.add(forkContext.head);
525
+ context.qqForkContext.add(forkContext.head);
504
526
  }
505
527
 
506
528
  context.processed = false;
@@ -78,7 +78,7 @@ module.exports = class ConfigCommentParser {
78
78
  config: items
79
79
  };
80
80
  }
81
- } catch (ex) {
81
+ } catch {
82
82
 
83
83
  debug("Levn parsing failed; falling back to manual parsing.");
84
84
 
@@ -938,7 +938,8 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parser
938
938
  });
939
939
  });
940
940
 
941
- const eventGenerator = new CodePathAnalyzer(new NodeEventGenerator(emitter));
941
+ // only run code path analyzer if the top level node is "Program", skip otherwise
942
+ const eventGenerator = nodeQueue[0].node.type === "Program" ? new CodePathAnalyzer(new NodeEventGenerator(emitter)) : new NodeEventGenerator(emitter);
942
943
 
943
944
  nodeQueue.forEach(traversalInfo => {
944
945
  currentNode = traversalInfo.node;
@@ -644,6 +644,10 @@ class RuleTester {
644
644
  assert.ok(item.errors || item.errors === 0,
645
645
  `Did not specify errors for an invalid test of ${ruleName}`);
646
646
 
647
+ if (Array.isArray(item.errors) && item.errors.length === 0) {
648
+ assert.fail("Invalid cases must have at least one error");
649
+ }
650
+
647
651
  const ruleHasMetaMessages = hasOwnProperty(rule, "meta") && hasOwnProperty(rule.meta, "messages");
648
652
  const friendlyIDList = ruleHasMetaMessages ? `[${Object.keys(rule.meta.messages).map(key => `'${key}'`).join(", ")}]` : null;
649
653
 
@@ -651,6 +655,11 @@ class RuleTester {
651
655
  const messages = result.messages;
652
656
 
653
657
  if (typeof item.errors === "number") {
658
+
659
+ if (item.errors === 0) {
660
+ assert.fail("Invalid cases must have 'error' value greater than 0");
661
+ }
662
+
654
663
  assert.strictEqual(messages.length, item.errors, util.format("Should have %d error%s but had %d: %s",
655
664
  item.errors, item.errors === 1 ? "" : "s", messages.length, util.inspect(messages)));
656
665
  } else {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @fileoverview Rule to flag wrapping non-iife in parens
2
+ * @fileoverview Rule to enforce getter and setter pairs in objects and classes.
3
3
  * @author Gyandeep Singh
4
4
  */
5
5
 
@@ -105,10 +105,27 @@ module.exports = {
105
105
  ], `${shouldAddSpaceForAsync ? " " : ""}${paramToken.value}`);
106
106
  }
107
107
 
108
+ /**
109
+ * Checks whether there are comments inside the params or not.
110
+ * @returns {boolean} `true` if there are comments inside of parens, else `false`
111
+ */
112
+ function hasCommentsInParens() {
113
+ if (astUtils.isOpeningParenToken(firstTokenOfParam)) {
114
+ const closingParenToken = sourceCode.getTokenAfter(node.params[0], astUtils.isClosingParenToken);
115
+
116
+ return closingParenToken && sourceCode.commentsExistBetween(firstTokenOfParam, closingParenToken);
117
+ }
118
+ return false;
119
+
120
+ }
121
+
122
+ if (hasCommentsInParens()) {
123
+ return;
124
+ }
125
+
108
126
  // "as-needed", { "requireForBlockBody": true }: x => x
109
127
  if (
110
128
  requireForBlockBody &&
111
- node.params.length === 1 &&
112
129
  node.params[0].type === "Identifier" &&
113
130
  !node.params[0].typeAnnotation &&
114
131
  node.body.type !== "BlockStatement" &&
@@ -144,7 +161,6 @@ module.exports = {
144
161
 
145
162
  // "as-needed": x => x
146
163
  if (asNeeded &&
147
- node.params.length === 1 &&
148
164
  node.params[0].type === "Identifier" &&
149
165
  !node.params[0].typeAnnotation &&
150
166
  !node.returnType
@@ -178,7 +194,7 @@ module.exports = {
178
194
  }
179
195
 
180
196
  return {
181
- ArrowFunctionExpression: parens
197
+ "ArrowFunctionExpression[params.length=1]": parens
182
198
  };
183
199
  }
184
200
  };
@@ -102,9 +102,18 @@ module.exports = {
102
102
 
103
103
  // Check.
104
104
  if (!isValid(openBrace, firstToken)) {
105
+ let loc = openBrace.loc;
106
+
107
+ if (messageId === "extra") {
108
+ loc = {
109
+ start: openBrace.loc.end,
110
+ end: firstToken.loc.start
111
+ };
112
+ }
113
+
105
114
  context.report({
106
115
  node,
107
- loc: openBrace.loc.start,
116
+ loc,
108
117
  messageId,
109
118
  data: {
110
119
  location: "after",
@@ -120,9 +129,17 @@ module.exports = {
120
129
  });
121
130
  }
122
131
  if (!isValid(lastToken, closeBrace)) {
132
+ let loc = closeBrace.loc;
133
+
134
+ if (messageId === "extra") {
135
+ loc = {
136
+ start: lastToken.loc.end,
137
+ end: closeBrace.loc.start
138
+ };
139
+ }
123
140
  context.report({
124
141
  node,
125
- loc: closeBrace.loc.start,
142
+ loc,
126
143
  messageId,
127
144
  data: {
128
145
  location: "before",
@@ -12,7 +12,7 @@ module.exports = {
12
12
  meta: {
13
13
  deprecated: true,
14
14
 
15
- replacedBy: ["node/callback-return"],
15
+ replacedBy: [],
16
16
 
17
17
  type: "suggestion",
18
18
 
@@ -124,7 +124,8 @@ module.exports = {
124
124
  }
125
125
  ]
126
126
  }
127
- ]
127
+ ],
128
+ additionalItems: false
128
129
  },
129
130
 
130
131
  messages: {
@@ -457,11 +457,18 @@ module.exports = {
457
457
 
458
458
  return {
459
459
  IfStatement(node) {
460
- if (node.parent.type !== "IfStatement") {
460
+ const parent = node.parent;
461
+ const isElseIf = parent.type === "IfStatement" && parent.alternate === node;
462
+
463
+ if (!isElseIf) {
464
+
465
+ // This is a top `if`, check the whole `if-else-if` chain
461
466
  prepareIfChecks(node).forEach(preparedCheck => {
462
467
  preparedCheck.check();
463
468
  });
464
469
  }
470
+
471
+ // Skip `else if`, it's already checked (when the top `if` was visited)
465
472
  },
466
473
 
467
474
  WhileStatement(node) {
@@ -116,7 +116,13 @@ module.exports = {
116
116
  if (never && hasWhitespace) {
117
117
  context.report({
118
118
  node,
119
- loc: leftToken.loc.start,
119
+ loc: {
120
+ start: leftToken.loc.end,
121
+ end: {
122
+ line: rightToken.loc.start.line,
123
+ column: rightToken.loc.start.column - 1
124
+ }
125
+ },
120
126
  messageId: "unexpectedWhitespace",
121
127
  fix(fixer) {
122
128
 
@@ -134,7 +140,13 @@ module.exports = {
134
140
  } else if (!never && !hasWhitespace) {
135
141
  context.report({
136
142
  node,
137
- loc: leftToken.loc.start,
143
+ loc: {
144
+ start: {
145
+ line: leftToken.loc.end.line,
146
+ column: leftToken.loc.end.column - 1
147
+ },
148
+ end: rightToken.loc.start
149
+ },
138
150
  messageId: "missing",
139
151
  fix(fixer) {
140
152
  return fixer.insertTextBefore(rightToken, " ");
@@ -143,7 +155,10 @@ module.exports = {
143
155
  } else if (!never && !allowNewlines && hasNewline) {
144
156
  context.report({
145
157
  node,
146
- loc: leftToken.loc.start,
158
+ loc: {
159
+ start: leftToken.loc.end,
160
+ end: rightToken.loc.start
161
+ },
147
162
  messageId: "unexpectedNewline",
148
163
  fix(fixer) {
149
164
  return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], " ");
@@ -50,7 +50,7 @@ module.exports = {
50
50
  meta: {
51
51
  deprecated: true,
52
52
 
53
- replacedBy: ["node/global-require"],
53
+ replacedBy: [],
54
54
 
55
55
  type: "suggestion",
56
56
 
@@ -13,7 +13,7 @@ module.exports = {
13
13
  meta: {
14
14
  deprecated: true,
15
15
 
16
- replacedBy: ["node/handle-callback-err"],
16
+ replacedBy: [],
17
17
 
18
18
  type: "suggestion",
19
19
 
@@ -39,7 +39,8 @@ module.exports = {
39
39
  type: "boolean",
40
40
  default: false
41
41
  }
42
- }
42
+ },
43
+ additionalProperties: false
43
44
  }
44
45
  ],
45
46
  messages: {
@@ -148,6 +148,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
148
148
  "no-lone-blocks": () => require("./no-lone-blocks"),
149
149
  "no-lonely-if": () => require("./no-lonely-if"),
150
150
  "no-loop-func": () => require("./no-loop-func"),
151
+ "no-loss-of-precision": () => require("./no-loss-of-precision"),
151
152
  "no-magic-numbers": () => require("./no-magic-numbers"),
152
153
  "no-misleading-character-class": () => require("./no-misleading-character-class"),
153
154
  "no-mixed-operators": () => require("./no-mixed-operators"),
@@ -175,6 +176,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
175
176
  "no-plusplus": () => require("./no-plusplus"),
176
177
  "no-process-env": () => require("./no-process-env"),
177
178
  "no-process-exit": () => require("./no-process-exit"),
179
+ "no-promise-executor-return": () => require("./no-promise-executor-return"),
178
180
  "no-proto": () => require("./no-proto"),
179
181
  "no-prototype-builtins": () => require("./no-prototype-builtins"),
180
182
  "no-redeclare": () => require("./no-redeclare"),
@@ -211,6 +213,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
211
213
  "no-unmodified-loop-condition": () => require("./no-unmodified-loop-condition"),
212
214
  "no-unneeded-ternary": () => require("./no-unneeded-ternary"),
213
215
  "no-unreachable": () => require("./no-unreachable"),
216
+ "no-unreachable-loop": () => require("./no-unreachable-loop"),
214
217
  "no-unsafe-finally": () => require("./no-unsafe-finally"),
215
218
  "no-unsafe-negation": () => require("./no-unsafe-negation"),
216
219
  "no-unused-expressions": () => require("./no-unused-expressions"),
@@ -433,11 +433,15 @@ module.exports = {
433
433
  tokenBeforeColon = sourceCode.getTokenBefore(nextColon, { includeComments: true }),
434
434
  tokenAfterColon = sourceCode.getTokenAfter(nextColon, { includeComments: true }),
435
435
  isKeySide = side === "key",
436
- locStart = isKeySide ? tokenBeforeColon.loc.start : tokenAfterColon.loc.start,
437
436
  isExtra = diff > 0,
438
437
  diffAbs = Math.abs(diff),
439
438
  spaces = Array(diffAbs + 1).join(" ");
440
439
 
440
+ const locStart = isKeySide ? tokenBeforeColon.loc.end : nextColon.loc.start;
441
+ const locEnd = isKeySide ? nextColon.loc.start : tokenAfterColon.loc.start;
442
+ const missingLoc = isKeySide ? tokenBeforeColon.loc : tokenAfterColon.loc;
443
+ const loc = isExtra ? { start: locStart, end: locEnd } : missingLoc;
444
+
441
445
  if ((
442
446
  diff && mode === "strict" ||
443
447
  diff < 0 && mode === "minimum" ||
@@ -482,7 +486,7 @@ module.exports = {
482
486
 
483
487
  context.report({
484
488
  node: property[side],
485
- loc: locStart,
489
+ loc,
486
490
  messageId,
487
491
  data: {
488
492
  computed: property.computed ? "computed " : "",
@@ -152,7 +152,7 @@ module.exports = {
152
152
  sourceCode.isSpaceBetweenTokens(prevToken, token)
153
153
  ) {
154
154
  context.report({
155
- loc: token.loc.start,
155
+ loc: { start: prevToken.loc.end, end: token.loc.start },
156
156
  messageId: "unexpectedBefore",
157
157
  data: token,
158
158
  fix(fixer) {
@@ -203,8 +203,9 @@ module.exports = {
203
203
  astUtils.isTokenOnSameLine(token, nextToken) &&
204
204
  sourceCode.isSpaceBetweenTokens(token, nextToken)
205
205
  ) {
206
+
206
207
  context.report({
207
- loc: token.loc.start,
208
+ loc: { start: token.loc.end, end: nextToken.loc.start },
208
209
  messageId: "unexpectedAfter",
209
210
  data: token,
210
211
  fix(fixer) {
@@ -442,6 +443,12 @@ module.exports = {
442
443
  checkSpacingAround(sourceCode.getTokenAfter(firstToken));
443
444
  }
444
445
 
446
+ if (node.type === "ExportAllDeclaration" && node.exported) {
447
+ const asToken = sourceCode.getTokenBefore(node.exported);
448
+
449
+ checkSpacingBefore(asToken, PREV_TOKEN_M);
450
+ }
451
+
445
452
  if (node.source) {
446
453
  const fromToken = sourceCode.getTokenBefore(node.source);
447
454
 
@@ -86,8 +86,14 @@ module.exports = {
86
86
  context.report({
87
87
  node,
88
88
  loc: {
89
- line: i,
90
- column: sourceCode.lines[i - 1].length
89
+ start: {
90
+ line: i,
91
+ column: sourceCode.lines[i - 1].length
92
+ },
93
+ end: {
94
+ line: i + 1,
95
+ column: 0
96
+ }
91
97
  },
92
98
  messageId: expectedLF ? "expectedLF" : "expectedCRLF",
93
99
  fix: createFix(range, expectedLFChars)
@@ -134,7 +134,7 @@ module.exports = {
134
134
  * @returns {boolean} True if it's an IIFE
135
135
  */
136
136
  function isIIFE(node) {
137
- return node.type === "FunctionExpression" && node.parent && node.parent.type === "CallExpression" && node.parent.callee === node;
137
+ return (node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") && node.parent && node.parent.type === "CallExpression" && node.parent.callee === node;
138
138
  }
139
139
 
140
140
  /**
@@ -53,7 +53,8 @@ module.exports = {
53
53
  }
54
54
  ],
55
55
  messages: {
56
- exceed: "File has too many lines ({{actual}}). Maximum allowed is {{max}}."
56
+ exceed:
57
+ "File has too many lines ({{actual}}). Maximum allowed is {{max}}."
57
58
  }
58
59
  },
59
60
 
@@ -61,7 +62,10 @@ module.exports = {
61
62
  const option = context.options[0];
62
63
  let max = 300;
63
64
 
64
- if (typeof option === "object" && Object.prototype.hasOwnProperty.call(option, "max")) {
65
+ if (
66
+ typeof option === "object" &&
67
+ Object.prototype.hasOwnProperty.call(option, "max")
68
+ ) {
65
69
  max = option.max;
66
70
  } else if (typeof option === "number") {
67
71
  max = option;
@@ -94,7 +98,9 @@ module.exports = {
94
98
 
95
99
  token = comment;
96
100
  do {
97
- token = sourceCode.getTokenBefore(token, { includeComments: true });
101
+ token = sourceCode.getTokenBefore(token, {
102
+ includeComments: true
103
+ });
98
104
  } while (isCommentNodeType(token));
99
105
 
100
106
  if (token && astUtils.isTokenOnSameLine(token, comment)) {
@@ -103,7 +109,9 @@ module.exports = {
103
109
 
104
110
  token = comment;
105
111
  do {
106
- token = sourceCode.getTokenAfter(token, { includeComments: true });
112
+ token = sourceCode.getTokenAfter(token, {
113
+ includeComments: true
114
+ });
107
115
  } while (isCommentNodeType(token));
108
116
 
109
117
  if (token && astUtils.isTokenOnSameLine(comment, token)) {
@@ -118,7 +126,10 @@ module.exports = {
118
126
 
119
127
  return {
120
128
  "Program:exit"() {
121
- let lines = sourceCode.lines.map((text, i) => ({ lineNumber: i + 1, text }));
129
+ let lines = sourceCode.lines.map((text, i) => ({
130
+ lineNumber: i + 1,
131
+ text
132
+ }));
122
133
 
123
134
  if (skipBlankLines) {
124
135
  lines = lines.filter(l => l.text.trim() !== "");
@@ -127,14 +138,29 @@ module.exports = {
127
138
  if (skipComments) {
128
139
  const comments = sourceCode.getAllComments();
129
140
 
130
- const commentLines = lodash.flatten(comments.map(comment => getLinesWithoutCode(comment)));
141
+ const commentLines = lodash.flatten(
142
+ comments.map(comment => getLinesWithoutCode(comment))
143
+ );
131
144
 
132
- lines = lines.filter(l => !lodash.includes(commentLines, l.lineNumber));
145
+ lines = lines.filter(
146
+ l => !lodash.includes(commentLines, l.lineNumber)
147
+ );
133
148
  }
134
149
 
135
150
  if (lines.length > max) {
151
+ const loc = {
152
+ start: {
153
+ line: lines[max].lineNumber,
154
+ column: 0
155
+ },
156
+ end: {
157
+ line: sourceCode.lines.length,
158
+ column: lodash.last(sourceCode.lines).length
159
+ }
160
+ };
161
+
136
162
  context.report({
137
- loc: { line: 1, column: 0 },
163
+ loc,
138
164
  messageId: "exceed",
139
165
  data: {
140
166
  max,
@@ -39,25 +39,7 @@ module.exports = {
39
39
  const option = context.options[0];
40
40
  const multiline = option !== "never";
41
41
  const allowSingleLine = option === "always-multiline";
42
-
43
- //--------------------------------------------------------------------------
44
- // Helpers
45
- //--------------------------------------------------------------------------
46
-
47
- /**
48
- * Tests whether node is preceded by supplied tokens
49
- * @param {ASTNode} node node to check
50
- * @param {ASTNode} parentNode parent of node to report
51
- * @param {boolean} expected whether newline was expected or not
52
- * @returns {void}
53
- * @private
54
- */
55
- function reportError(node, parentNode, expected) {
56
- context.report({
57
- node,
58
- messageId: `${expected ? "expected" : "unexpected"}${node === parentNode.test ? "TestCons" : "ConsAlt"}`
59
- });
60
- }
42
+ const sourceCode = context.getSourceCode();
61
43
 
62
44
  //--------------------------------------------------------------------------
63
45
  // Public
@@ -65,16 +47,39 @@ module.exports = {
65
47
 
66
48
  return {
67
49
  ConditionalExpression(node) {
68
- const areTestAndConsequentOnSameLine = astUtils.isTokenOnSameLine(node.test, node.consequent);
69
- const areConsequentAndAlternateOnSameLine = astUtils.isTokenOnSameLine(node.consequent, node.alternate);
50
+ const questionToken = sourceCode.getTokenAfter(node.test, astUtils.isNotClosingParenToken);
51
+ const colonToken = sourceCode.getTokenAfter(node.consequent, astUtils.isNotClosingParenToken);
52
+
53
+ const firstTokenOfTest = sourceCode.getFirstToken(node);
54
+ const lastTokenOfTest = sourceCode.getTokenBefore(questionToken);
55
+ const firstTokenOfConsequent = sourceCode.getTokenAfter(questionToken);
56
+ const lastTokenOfConsequent = sourceCode.getTokenBefore(colonToken);
57
+ const firstTokenOfAlternate = sourceCode.getTokenAfter(colonToken);
58
+
59
+ const areTestAndConsequentOnSameLine = astUtils.isTokenOnSameLine(lastTokenOfTest, firstTokenOfConsequent);
60
+ const areConsequentAndAlternateOnSameLine = astUtils.isTokenOnSameLine(lastTokenOfConsequent, firstTokenOfAlternate);
70
61
 
71
62
  if (!multiline) {
72
63
  if (!areTestAndConsequentOnSameLine) {
73
- reportError(node.test, node, false);
64
+ context.report({
65
+ node: node.test,
66
+ loc: {
67
+ start: firstTokenOfTest.loc.start,
68
+ end: lastTokenOfTest.loc.end
69
+ },
70
+ messageId: "unexpectedTestCons"
71
+ });
74
72
  }
75
73
 
76
74
  if (!areConsequentAndAlternateOnSameLine) {
77
- reportError(node.consequent, node, false);
75
+ context.report({
76
+ node: node.consequent,
77
+ loc: {
78
+ start: firstTokenOfConsequent.loc.start,
79
+ end: lastTokenOfConsequent.loc.end
80
+ },
81
+ messageId: "unexpectedConsAlt"
82
+ });
78
83
  }
79
84
  } else {
80
85
  if (allowSingleLine && node.loc.start.line === node.loc.end.line) {
@@ -82,11 +87,25 @@ module.exports = {
82
87
  }
83
88
 
84
89
  if (areTestAndConsequentOnSameLine) {
85
- reportError(node.test, node, true);
90
+ context.report({
91
+ node: node.test,
92
+ loc: {
93
+ start: firstTokenOfTest.loc.start,
94
+ end: lastTokenOfTest.loc.end
95
+ },
96
+ messageId: "expectedTestCons"
97
+ });
86
98
  }
87
99
 
88
100
  if (areConsequentAndAlternateOnSameLine) {
89
- reportError(node.consequent, node, true);
101
+ context.report({
102
+ node: node.consequent,
103
+ loc: {
104
+ start: firstTokenOfConsequent.loc.start,
105
+ end: lastTokenOfConsequent.loc.end
106
+ },
107
+ messageId: "expectedConsAlt"
108
+ });
90
109
  }
91
110
  }
92
111
  }