eslint 7.1.0 → 7.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ v7.2.0 - June 5, 2020
2
+
3
+ * [`b735a48`](https://github.com/eslint/eslint/commit/b735a485e77bcc791e4c4c6b8716801d94e98b2c) Update: add enforceForFunctionPrototypeMethods option to no-extra-parens (#12895) (Milos Djermanovic)
4
+ * [`27ef73f`](https://github.com/eslint/eslint/commit/27ef73ffb7428d5eff792d443186a2313e417bda) Update: reporter locr of func-call-spacing (refs #12334) (#13311) (Anix)
5
+ * [`353bfe9`](https://github.com/eslint/eslint/commit/353bfe9760ec640f470859855d4018df084a4e88) Update: handle parentheses in multiline-ternary (fixes #13195) (#13367) (Milos Djermanovic)
6
+ * [`a7fd343`](https://github.com/eslint/eslint/commit/a7fd343991cde99d8a219e3b25616db5792fe9a9) Update: keyword-spacing unexpected space loc improve (refs #12334) (#13377) (Anix)
7
+ * [`e49732e`](https://github.com/eslint/eslint/commit/e49732eb41bff6347ca7718c3c5ca1d13f1cd2d3) Fix: Ignore import expressions in no-unused-expressions rule (#13387) (Veniamin Krol)
8
+ * [`220349f`](https://github.com/eslint/eslint/commit/220349f5404060effe02fb5ec176a92e1383c3b5) Chore: Remove duplicate health files (#13380) (Nicholas C. Zakas)
9
+ * [`dd949ae`](https://github.com/eslint/eslint/commit/dd949aedb81fa772e10568920156daf075d25ea2) Update: support `??` operator, import.meta, and `export * as ns` (#13196) (Toru Nagashima)
10
+ * [`d5fce9f`](https://github.com/eslint/eslint/commit/d5fce9fa07e37ce61010a1fbb65964f1f7aefd82) Update: enable es2020 environment in --init (#13357) (Milos Djermanovic)
11
+ * [`21b1583`](https://github.com/eslint/eslint/commit/21b15832e326f96d349c063cd7e85e72c3abb670) Docs: fixed broken hash link for working-with-rules.md (#13386) (Yosuke Ota)
12
+ * [`b76aef7`](https://github.com/eslint/eslint/commit/b76aef778befb32afe7ad249934b132dc49713d2) Update: Improve report location for template-tag-spacing (refs #12334) (#13203) (Milos Djermanovic)
13
+ * [`578efad`](https://github.com/eslint/eslint/commit/578efad331b797e28c0f5f1547ce4769d2ea23ee) Chore: update no-unused-vars caughtErrors in eslint-config-eslint (#13351) (Milos Djermanovic)
14
+ * [`426088c`](https://github.com/eslint/eslint/commit/426088c966dc79dc338b33100f3adf827b147d69) Fix: no-unused-vars updated location to last reference (fixes #13181) (#13354) (Anix)
15
+ * [`cb50b69`](https://github.com/eslint/eslint/commit/cb50b69c08d4393e32d5c42c537d769c51dd34d8) Update: Improve location for no-mixed-spaces-and-tabs (refs #12334) (#13365) (Milos Djermanovic)
16
+ * [`f858f2a`](https://github.com/eslint/eslint/commit/f858f2a8f83232484491bd90b0bc5001b5056ad0) Chore: Add Tidelift to funding.yml (#13371) (Nicholas C. Zakas)
17
+ * [`ee30e5d`](https://github.com/eslint/eslint/commit/ee30e5d8bb1a4c82a2a3fbe1b9ee9f979b55c5c4) Sponsors: Sync README with website (ESLint Jenkins)
18
+ * [`c29bd9f`](https://github.com/eslint/eslint/commit/c29bd9f75582e5b1a403a8ffd0aafd1ffc8c58e1) Chore: Add breaking/core change link to issue templates (#13344) (Kai Cataldo)
19
+ * [`d55490f`](https://github.com/eslint/eslint/commit/d55490fa73ff69416de375e4c1cd67b6edba531c) Sponsors: Sync README with website (ESLint Jenkins)
1
20
  v7.1.0 - May 22, 2020
2
21
 
3
22
  * [`a93083a`](https://github.com/eslint/eslint/commit/a93083af89c6f9714dcdd4a7f27c8655a0b0dba6) Fix: astUtils.getNextLocation returns invalid location after CRLF (#13275) (Milos Djermanovic)
package/README.md CHANGED
@@ -250,8 +250,8 @@ The following companies, organizations, and individuals support ESLint's ongoing
250
250
  <!--sponsorsstart-->
251
251
  <h3>Gold Sponsors</h3>
252
252
  <p><a href="https://www.shopify.com"><img src="https://images.opencollective.com/shopify/e780cd4/logo.png" alt="Shopify" height="96"></a> <a href="https://www.salesforce.com"><img src="https://images.opencollective.com/salesforce/ca8f997/logo.png" alt="Salesforce" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="96"></a></p><h3>Silver Sponsors</h3>
253
- <p><a href="https://www.ampproject.org/"><img src="https://images.opencollective.com/amp/c8a3b25/logo.png" alt="AMP Project" height="64"></a></p><h3>Bronze Sponsors</h3>
254
- <p><a href="https://bruce.agency"><img src="https://images.opencollective.com/brucemade/0c70c59/logo.png" alt="Bruce" height="32"></a> <a href="https://edubirdie.com/"><img src="https://images.opencollective.com/edubirdie2/b1d51ab/logo.png" alt="EduBirdie" height="32"></a> <a href="https://www.casinotop.com/"><img src="https://images.opencollective.com/casinotop-com/10fd95b/logo.png" alt="CasinoTop.com" height="32"></a> <a href="https://www.casinotopp.net/"><img src="https://images.opencollective.com/casino-topp/1dd399a/logo.png" alt="Casino Topp" height="32"></a> <a href="https://writersperhour.com/urgent-essay-writing-service"><img src="https://images.opencollective.com/writersperhour/5787d4b/logo.png" alt="Writers Per Hour" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://cooltechzone.com/netflix-vpn"><img src="https://images.opencollective.com/vpn-netflix/4850160/logo.png" alt="vpn netflix" height="32"></a> <a href="https://www.kasinot.fi"><img src="https://images.opencollective.com/kasinot-fi/e09aa2e/logo.png" alt="Kasinot.fi" height="32"></a> <a href="https://www.pelisivut.com"><img src="https://images.opencollective.com/pelisivut/04f08f2/logo.png" alt="Pelisivut" height="32"></a> <a href="https://www.nettikasinot.org"><img src="https://images.opencollective.com/nettikasinot-org/bbd887f/logo.png" alt="Nettikasinot.org" height="32"></a> <a href="https://www.bonus.com.de/freispiele"><img src="https://images.opencollective.com/bonusfinder-deutschland/646169e/logo.png" alt="BonusFinder Deutschland" height="32"></a> <a href="https://www.bugsnag.com/platforms?utm_source=Open Collective&utm_medium=Website&utm_content=open-source&utm_campaign=2019-community&utm_term="><img src="https://images.opencollective.com/bugsnag-stability-monitoring/c2cef36/logo.png" alt="Bugsnag Stability Monitoring" height="32"></a> <a href="https://mixpanel.com"><img src="https://images.opencollective.com/mixpanel/cd682f7/logo.png" alt="Mixpanel" height="32"></a> <a href="https://www.vpsserver.com"><img src="https://images.opencollective.com/vpsservercom/logo.png" alt="VPS Server" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/0b37d14/logo.png" alt="Free Icons by Icons8" height="32"></a> <a href="https://discordapp.com"><img src="https://images.opencollective.com/discordapp/7e3d9a9/logo.png" alt="Discord" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://tekhattan.com"><img src="https://images.opencollective.com/tekhattan/bc73c28/logo.png" alt="TekHattan" height="32"></a> <a href="https://www.marfeel.com/"><img src="https://images.opencollective.com/marfeel/4b88e30/logo.png" alt="Marfeel" height="32"></a> <a href="http://www.firesticktricks.com"><img src="https://images.opencollective.com/fire-stick-tricks/b8fbe2c/logo.png" alt="Fire Stick Tricks" height="32"></a></p>
253
+ <p><a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/5c4fa84/logo.png" alt="Liftoff" height="64"></a> <a href="https://www.ampproject.org/"><img src="https://images.opencollective.com/amp/c8a3b25/logo.png" alt="AMP Project" height="64"></a></p><h3>Bronze Sponsors</h3>
254
+ <p><a href="https://bruce.agency"><img src="https://images.opencollective.com/brucemade/0c70c59/logo.png" alt="Bruce" height="32"></a> <a href="https://edubirdie.com/"><img src="https://images.opencollective.com/edubirdie2/b1d51ab/logo.png" alt="EduBirdie" height="32"></a> <a href="https://www.casinotop.com/"><img src="https://images.opencollective.com/casinotop-com/10fd95b/logo.png" alt="CasinoTop.com" height="32"></a> <a href="https://www.casinotopp.net/"><img src="https://images.opencollective.com/casino-topp/1dd399a/logo.png" alt="Casino Topp" height="32"></a> <a href="https://writersperhour.com/write-my-essay"><img src="https://images.opencollective.com/writersperhour/5787d4b/logo.png" alt="Writers Per Hour" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://cooltechzone.com/netflix-vpn"><img src="https://images.opencollective.com/vpn-netflix/4850160/logo.png" alt="vpn netflix" height="32"></a> <a href="https://www.kasinot.fi"><img src="https://images.opencollective.com/kasinot-fi/e09aa2e/logo.png" alt="Kasinot.fi" height="32"></a> <a href="https://www.pelisivut.com"><img src="https://images.opencollective.com/pelisivut/04f08f2/logo.png" alt="Pelisivut" height="32"></a> <a href="https://www.nettikasinot.org"><img src="https://images.opencollective.com/nettikasinot-org/bbd887f/logo.png" alt="Nettikasinot.org" height="32"></a> <a href="https://www.bonus.com.de/freispiele"><img src="https://images.opencollective.com/bonusfinder-deutschland/646169e/logo.png" alt="BonusFinder Deutschland" height="32"></a> <a href="https://www.bugsnag.com/platforms?utm_source=Open Collective&utm_medium=Website&utm_content=open-source&utm_campaign=2019-community&utm_term="><img src="https://images.opencollective.com/bugsnag-stability-monitoring/c2cef36/logo.png" alt="Bugsnag Stability Monitoring" height="32"></a> <a href="https://mixpanel.com"><img src="https://images.opencollective.com/mixpanel/cd682f7/logo.png" alt="Mixpanel" height="32"></a> <a href="https://www.vpsserver.com"><img src="https://images.opencollective.com/vpsservercom/logo.png" alt="VPS Server" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/0b37d14/logo.png" alt="Free Icons by Icons8" height="32"></a> <a href="https://discordapp.com"><img src="https://images.opencollective.com/discordapp/7e3d9a9/logo.png" alt="Discord" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://tekhattan.com"><img src="https://images.opencollective.com/tekhattan/bc73c28/logo.png" alt="TekHattan" height="32"></a> <a href="https://www.marfeel.com/"><img src="https://images.opencollective.com/marfeel/4b88e30/logo.png" alt="Marfeel" height="32"></a> <a href="http://www.firesticktricks.com"><img src="https://images.opencollective.com/fire-stick-tricks/b8fbe2c/logo.png" alt="Fire Stick Tricks" height="32"></a></p>
255
255
  <!--sponsorsend-->
256
256
 
257
257
  ## <a name="technology-sponsors"></a>Technology Sponsors
@@ -403,7 +403,7 @@ function getCacheFile(cacheFile, cwd) {
403
403
 
404
404
  try {
405
405
  fileStats = fs.lstatSync(resolvedCacheFile);
406
- } catch (ex) {
406
+ } catch {
407
407
  fileStats = null;
408
408
  }
409
409
 
@@ -991,7 +991,7 @@ class CLIEngine {
991
991
  const npmFormat = naming.normalizePackageName(normalizedFormatName, "eslint-formatter");
992
992
 
993
993
  formatterPath = ModuleResolver.resolve(npmFormat, path.join(cwd, "__placeholder__.js"));
994
- } catch (e) {
994
+ } catch {
995
995
  formatterPath = path.resolve(__dirname, "formatters", normalizedFormatName);
996
996
  }
997
997
  }
@@ -517,7 +517,7 @@ class ConfigArrayFactory {
517
517
  try {
518
518
  loadPackageJSONConfigFile(filePath);
519
519
  return filePath;
520
- } catch (error) { /* ignore */ }
520
+ } catch { /* ignore */ }
521
521
  } else {
522
522
  return filePath;
523
523
  }
@@ -265,11 +265,7 @@ function processAnswers(answers) {
265
265
  };
266
266
 
267
267
  config.parserOptions.ecmaVersion = espree.latestEcmaVersion;
268
- config.env.es6 = true;
269
- config.globals = {
270
- Atomics: "readonly",
271
- SharedArrayBuffer: "readonly"
272
- };
268
+ config.env.es2020 = true;
273
269
 
274
270
  // set the module type
275
271
  if (answers.moduleType === "esm") {
@@ -350,7 +346,7 @@ function getLocalESLintVersion() {
350
346
  const eslint = require(eslintPath);
351
347
 
352
348
  return eslint.linter.version || null;
353
- } catch (_err) {
349
+ } catch {
354
350
  return null;
355
351
  }
356
352
  }
@@ -33,10 +33,10 @@ function isCaseNode(node) {
33
33
  * Checks whether the given logical operator is taken into account for the code
34
34
  * path analysis.
35
35
  * @param {string} operator The operator found in the LogicalExpression node
36
- * @returns {boolean} `true` if the operator is "&&" or "||"
36
+ * @returns {boolean} `true` if the operator is "&&" or "||" or "??"
37
37
  */
38
38
  function isHandledLogicalOperator(operator) {
39
- return operator === "&&" || operator === "||";
39
+ return operator === "&&" || operator === "||" || operator === "??";
40
40
  }
41
41
 
42
42
  /**
@@ -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
 
@@ -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]], " ");
@@ -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
 
@@ -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
  }
@@ -35,7 +35,7 @@ const collector = new (class {
35
35
  try {
36
36
  this._source = regexpStr;
37
37
  this._validator.validatePattern(regexpStr); // Call onCharacter hook
38
- } catch (err) {
38
+ } catch {
39
39
 
40
40
  // Ignore syntax errors in RegExp.
41
41
  }
@@ -172,6 +172,9 @@ module.exports = {
172
172
  case "UnaryExpression":
173
173
  return precedence(node) < precedence(parent);
174
174
  case "LogicalExpression":
175
+ if (astUtils.isMixedLogicalAndCoalesceExpressions(node, parent)) {
176
+ return true;
177
+ }
175
178
  if (previousNode === parent.left) {
176
179
  return precedence(node) < precedence(parent);
177
180
  }
@@ -51,7 +51,8 @@ module.exports = {
51
51
  ignoreJSX: { enum: ["none", "all", "single-line", "multi-line"] },
52
52
  enforceForArrowConditionals: { type: "boolean" },
53
53
  enforceForSequenceExpressions: { type: "boolean" },
54
- enforceForNewInMemberExpressions: { type: "boolean" }
54
+ enforceForNewInMemberExpressions: { type: "boolean" },
55
+ enforceForFunctionPrototypeMethods: { type: "boolean" }
55
56
  },
56
57
  additionalProperties: false
57
58
  }
@@ -83,12 +84,28 @@ module.exports = {
83
84
  context.options[1].enforceForSequenceExpressions === false;
84
85
  const IGNORE_NEW_IN_MEMBER_EXPR = ALL_NODES && context.options[1] &&
85
86
  context.options[1].enforceForNewInMemberExpressions === false;
87
+ const IGNORE_FUNCTION_PROTOTYPE_METHODS = ALL_NODES && context.options[1] &&
88
+ context.options[1].enforceForFunctionPrototypeMethods === false;
86
89
 
87
90
  const PRECEDENCE_OF_ASSIGNMENT_EXPR = precedence({ type: "AssignmentExpression" });
88
91
  const PRECEDENCE_OF_UPDATE_EXPR = precedence({ type: "UpdateExpression" });
89
92
 
90
93
  let reportsBuffer;
91
94
 
95
+ /**
96
+ * Determines whether the given node is a `call` or `apply` method call, invoked directly on a `FunctionExpression` node.
97
+ * Example: function(){}.call()
98
+ * @param {ASTNode} node The node to be checked.
99
+ * @returns {boolean} True if the node is an immediate `call` or `apply` method call.
100
+ * @private
101
+ */
102
+ function isImmediateFunctionPrototypeMethodCall(node) {
103
+ return node.type === "CallExpression" &&
104
+ node.callee.type === "MemberExpression" &&
105
+ node.callee.object.type === "FunctionExpression" &&
106
+ ["call", "apply"].includes(astUtils.getStaticPropertyName(node.callee));
107
+ }
108
+
92
109
  /**
93
110
  * Determines if this rule should be enforced for a node given the current configuration.
94
111
  * @param {ASTNode} node The node to be checked.
@@ -125,6 +142,10 @@ module.exports = {
125
142
  return false;
126
143
  }
127
144
 
145
+ if (isImmediateFunctionPrototypeMethodCall(node) && IGNORE_FUNCTION_PROTOTYPE_METHODS) {
146
+ return false;
147
+ }
148
+
128
149
  return ALL_NODES || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
129
150
  }
130
151
 
@@ -478,6 +499,7 @@ module.exports = {
478
499
  if (!shouldSkipLeft && hasExcessParens(node.left)) {
479
500
  if (
480
501
  !(node.left.type === "UnaryExpression" && isExponentiation) &&
502
+ !astUtils.isMixedLogicalAndCoalesceExpressions(node.left, node) &&
481
503
  (leftPrecedence > prec || (leftPrecedence === prec && !isExponentiation)) ||
482
504
  isParenthesisedTwice(node.left)
483
505
  ) {
@@ -487,6 +509,7 @@ module.exports = {
487
509
 
488
510
  if (!shouldSkipRight && hasExcessParens(node.right)) {
489
511
  if (
512
+ !astUtils.isMixedLogicalAndCoalesceExpressions(node.right, node) &&
490
513
  (rightPrecedence > prec || (rightPrecedence === prec && isExponentiation)) ||
491
514
  isParenthesisedTwice(node.right)
492
515
  ) {
@@ -927,7 +950,12 @@ module.exports = {
927
950
  LogicalExpression: checkBinaryLogical,
928
951
 
929
952
  MemberExpression(node) {
930
- const nodeObjHasExcessParens = hasExcessParens(node.object);
953
+ const nodeObjHasExcessParens = hasExcessParens(node.object) &&
954
+ !(
955
+ isImmediateFunctionPrototypeMethodCall(node.parent) &&
956
+ node.parent.callee === node &&
957
+ IGNORE_FUNCTION_PROTOTYPE_METHODS
958
+ );
931
959
 
932
960
  if (
933
961
  nodeObjHasExcessParens &&
@@ -93,7 +93,7 @@ module.exports = {
93
93
  try {
94
94
  validator.validateFlags(flags);
95
95
  return null;
96
- } catch (err) {
96
+ } catch {
97
97
  return `Invalid flags supplied to RegExp constructor '${flags}'`;
98
98
  }
99
99
  }
@@ -147,7 +147,7 @@ module.exports = {
147
147
  pattern.length,
148
148
  flags.includes("u")
149
149
  );
150
- } catch (e) {
150
+ } catch {
151
151
 
152
152
  // Ignore regular expressions with syntax errors
153
153
  return;
@@ -21,13 +21,15 @@ const COMPARISON_OPERATORS = ["==", "!=", "===", "!==", ">", ">=", "<", "<="];
21
21
  const LOGICAL_OPERATORS = ["&&", "||"];
22
22
  const RELATIONAL_OPERATORS = ["in", "instanceof"];
23
23
  const TERNARY_OPERATOR = ["?:"];
24
+ const COALESCE_OPERATOR = ["??"];
24
25
  const ALL_OPERATORS = [].concat(
25
26
  ARITHMETIC_OPERATORS,
26
27
  BITWISE_OPERATORS,
27
28
  COMPARISON_OPERATORS,
28
29
  LOGICAL_OPERATORS,
29
30
  RELATIONAL_OPERATORS,
30
- TERNARY_OPERATOR
31
+ TERNARY_OPERATOR,
32
+ COALESCE_OPERATOR
31
33
  );
32
34
  const DEFAULT_GROUPS = [
33
35
  ARITHMETIC_OPERATORS,
@@ -236,7 +238,6 @@ module.exports = {
236
238
  return {
237
239
  BinaryExpression: check,
238
240
  LogicalExpression: check
239
-
240
241
  };
241
242
  }
242
243
  };
@@ -67,7 +67,7 @@ module.exports = {
67
67
  * or the reverse before non-tab/-space
68
68
  * characters begin.
69
69
  */
70
- let regex = /^(?=[\t ]*(\t | \t))/u;
70
+ let regex = /^(?=( +|\t+))\1(?:\t| )/u;
71
71
 
72
72
  if (smartTabs) {
73
73
 
@@ -75,19 +75,27 @@ module.exports = {
75
75
  * At least one space followed by a tab
76
76
  * before non-tab/-space characters begin.
77
77
  */
78
- regex = /^(?=[\t ]* \t)/u;
78
+ regex = /^(?=(\t*))\1(?=( +))\2\t/u;
79
79
  }
80
80
 
81
81
  lines.forEach((line, i) => {
82
82
  const match = regex.exec(line);
83
83
 
84
84
  if (match) {
85
- const lineNumber = i + 1,
86
- column = match.index + 1,
87
- loc = { line: lineNumber, column };
85
+ const lineNumber = i + 1;
86
+ const loc = {
87
+ start: {
88
+ line: lineNumber,
89
+ column: match[0].length - 2
90
+ },
91
+ end: {
92
+ line: lineNumber,
93
+ column: match[0].length
94
+ }
95
+ };
88
96
 
89
97
  if (!ignoredCommentLines.has(lineNumber)) {
90
- const containingNode = sourceCode.getNodeByRangeIndex(sourceCode.getIndexFromLoc(loc));
98
+ const containingNode = sourceCode.getNodeByRangeIndex(sourceCode.getIndexFromLoc(loc.start));
91
99
 
92
100
  if (!(containingNode && ["Literal", "TemplateElement"].includes(containingNode.type))) {
93
101
  context.report({
@@ -76,7 +76,7 @@ module.exports = {
76
76
 
77
77
  try {
78
78
  regExpAST = regExpParser.parsePattern(pattern, 0, pattern.length, flags.includes("u"));
79
- } catch (e) {
79
+ } catch {
80
80
 
81
81
  // Ignore regular expressions with syntax errors
82
82
  return;
@@ -61,6 +61,12 @@ module.exports = {
61
61
  }
62
62
 
63
63
  return {
64
+ ExportAllDeclaration(node) {
65
+ if (node.exported) {
66
+ checkExportedName(node.exported);
67
+ }
68
+ },
69
+
64
70
  ExportNamedDeclaration(node) {
65
71
  const declaration = node.declaration;
66
72
 
@@ -147,10 +147,12 @@ module.exports = {
147
147
  loc: node.consequent.loc.start,
148
148
  messageId: "unnecessaryConditionalAssignment",
149
149
  fix: fixer => {
150
- const shouldParenthesizeAlternate = (
151
- astUtils.getPrecedence(node.alternate) < OR_PRECEDENCE &&
152
- !astUtils.isParenthesised(sourceCode, node.alternate)
153
- );
150
+ const shouldParenthesizeAlternate =
151
+ (
152
+ astUtils.getPrecedence(node.alternate) < OR_PRECEDENCE ||
153
+ astUtils.isCoalesceExpression(node.alternate)
154
+ ) &&
155
+ !astUtils.isParenthesised(sourceCode, node.alternate);
154
156
  const alternateText = shouldParenthesizeAlternate
155
157
  ? `(${sourceCode.getText(node.alternate)})`
156
158
  : astUtils.getParenthesisedText(sourceCode, node.alternate);
@@ -124,7 +124,7 @@ module.exports = {
124
124
  return true;
125
125
  }
126
126
 
127
- return /^(?:Assignment|Call|New|Update|Yield|Await)Expression$/u.test(node.type) ||
127
+ return /^(?:Assignment|Call|New|Update|Yield|Await|Import)Expression$/u.test(node.type) ||
128
128
  (node.type === "UnaryExpression" && ["delete", "void"].indexOf(node.operator) >= 0);
129
129
  }
130
130
 
@@ -619,7 +619,9 @@ module.exports = {
619
619
  // Report the first declaration.
620
620
  if (unusedVar.defs.length > 0) {
621
621
  context.report({
622
- node: unusedVar.identifiers[0],
622
+ node: unusedVar.references.length ? unusedVar.references[
623
+ unusedVar.references.length - 1
624
+ ].identifier : unusedVar.identifiers[0],
623
625
  messageId: "unusedVar",
624
626
  data: unusedVar.references.some(ref => ref.isWrite())
625
627
  ? getAssignedMessageData(unusedVar)
@@ -95,7 +95,7 @@ module.exports = {
95
95
 
96
96
  try {
97
97
  regExpAST = parser.parsePattern(pattern, 0, pattern.length, flags.includes("u"));
98
- } catch (e) {
98
+ } catch {
99
99
 
100
100
  // Ignore regular expressions with syntax errors
101
101
  return;
@@ -59,7 +59,7 @@ module.exports = {
59
59
 
60
60
  try {
61
61
  ast = parser.parsePattern(pattern, 0, pattern.length, uFlag);
62
- } catch (_) {
62
+ } catch {
63
63
 
64
64
  // ignore regex syntax errors
65
65
  return;
@@ -154,7 +154,7 @@ module.exports = {
154
154
 
155
155
  try {
156
156
  tokens = espree.tokenize(key.value);
157
- } catch (e) {
157
+ } catch {
158
158
  return;
159
159
  }
160
160
 
@@ -239,7 +239,7 @@ module.exports = {
239
239
 
240
240
  try {
241
241
  tokens = espree.tokenize(key.value);
242
- } catch (e) {
242
+ } catch {
243
243
  necessaryQuotes = true;
244
244
  return;
245
245
  }
@@ -49,7 +49,10 @@ module.exports = {
49
49
  if (never && hasWhitespace) {
50
50
  context.report({
51
51
  node,
52
- loc: tagToken.loc.start,
52
+ loc: {
53
+ start: tagToken.loc.end,
54
+ end: literalToken.loc.start
55
+ },
53
56
  messageId: "unexpected",
54
57
  fix(fixer) {
55
58
  const comments = sourceCode.getCommentsBefore(node.quasi);
@@ -68,7 +71,10 @@ module.exports = {
68
71
  } else if (!never && !hasWhitespace) {
69
72
  context.report({
70
73
  node,
71
- loc: tagToken.loc.start,
74
+ loc: {
75
+ start: node.loc.start,
76
+ end: literalToken.loc.start
77
+ },
72
78
  messageId: "missing",
73
79
  fix(fixer) {
74
80
  return fixer.insertTextAfter(tagToken, " ");
@@ -416,6 +416,53 @@ function equalTokens(left, right, sourceCode) {
416
416
  return true;
417
417
  }
418
418
 
419
+ /**
420
+ * Check if the given node is a true logical expression or not.
421
+ *
422
+ * The three binary expressions logical-or (`||`), logical-and (`&&`), and
423
+ * coalesce (`??`) are known as `ShortCircuitExpression`.
424
+ * But ESTree represents those by `LogicalExpression` node.
425
+ *
426
+ * This function rejects coalesce expressions of `LogicalExpression` node.
427
+ * @param {ASTNode} node The node to check.
428
+ * @returns {boolean} `true` if the node is `&&` or `||`.
429
+ * @see https://tc39.es/ecma262/#prod-ShortCircuitExpression
430
+ */
431
+ function isLogicalExpression(node) {
432
+ return (
433
+ node.type === "LogicalExpression" &&
434
+ (node.operator === "&&" || node.operator === "||")
435
+ );
436
+ }
437
+
438
+ /**
439
+ * Check if the given node is a nullish coalescing expression or not.
440
+ *
441
+ * The three binary expressions logical-or (`||`), logical-and (`&&`), and
442
+ * coalesce (`??`) are known as `ShortCircuitExpression`.
443
+ * But ESTree represents those by `LogicalExpression` node.
444
+ *
445
+ * This function finds only coalesce expressions of `LogicalExpression` node.
446
+ * @param {ASTNode} node The node to check.
447
+ * @returns {boolean} `true` if the node is `??`.
448
+ */
449
+ function isCoalesceExpression(node) {
450
+ return node.type === "LogicalExpression" && node.operator === "??";
451
+ }
452
+
453
+ /**
454
+ * Check if given two nodes are the pair of a logical expression and a coalesce expression.
455
+ * @param {ASTNode} left A node to check.
456
+ * @param {ASTNode} right Another node to check.
457
+ * @returns {boolean} `true` if the two nodes are the pair of a logical expression and a coalesce expression.
458
+ */
459
+ function isMixedLogicalAndCoalesceExpressions(left, right) {
460
+ return (
461
+ (isLogicalExpression(left) && isCoalesceExpression(right)) ||
462
+ (isCoalesceExpression(left) && isLogicalExpression(right))
463
+ );
464
+ }
465
+
419
466
  //------------------------------------------------------------------------------
420
467
  // Public Interface
421
468
  //------------------------------------------------------------------------------
@@ -779,6 +826,7 @@ module.exports = {
779
826
  case "LogicalExpression":
780
827
  switch (node.operator) {
781
828
  case "||":
829
+ case "??":
782
830
  return 4;
783
831
  case "&&":
784
832
  return 5;
@@ -1415,7 +1463,7 @@ module.exports = {
1415
1463
 
1416
1464
  try {
1417
1465
  tokens = espree.tokenize(leftValue, espreeOptions);
1418
- } catch (e) {
1466
+ } catch {
1419
1467
  return false;
1420
1468
  }
1421
1469
 
@@ -1444,7 +1492,7 @@ module.exports = {
1444
1492
 
1445
1493
  try {
1446
1494
  tokens = espree.tokenize(rightValue, espreeOptions);
1447
- } catch (e) {
1495
+ } catch {
1448
1496
  return false;
1449
1497
  }
1450
1498
 
@@ -1538,5 +1586,9 @@ module.exports = {
1538
1586
  */
1539
1587
  hasOctalEscapeSequence(rawString) {
1540
1588
  return OCTAL_ESCAPE_PATTERN.test(rawString);
1541
- }
1589
+ },
1590
+
1591
+ isLogicalExpression,
1592
+ isCoalesceExpression,
1593
+ isMixedLogicalAndCoalesceExpressions
1542
1594
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint",
3
- "version": "7.1.0",
3
+ "version": "7.2.0",
4
4
  "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
5
5
  "description": "An AST-based pattern checker for JavaScript.",
6
6
  "bin": {
@@ -52,10 +52,10 @@
52
52
  "cross-spawn": "^7.0.2",
53
53
  "debug": "^4.0.1",
54
54
  "doctrine": "^3.0.0",
55
- "eslint-scope": "^5.0.0",
55
+ "eslint-scope": "^5.1.0",
56
56
  "eslint-utils": "^2.0.0",
57
- "eslint-visitor-keys": "^1.1.0",
58
- "espree": "^7.0.0",
57
+ "eslint-visitor-keys": "^1.2.0",
58
+ "espree": "^7.1.0",
59
59
  "esquery": "^1.2.0",
60
60
  "esutils": "^2.0.2",
61
61
  "file-entry-cache": "^5.0.1",
@@ -86,7 +86,7 @@
86
86
  "devDependencies": {
87
87
  "@babel/core": "^7.4.3",
88
88
  "@babel/preset-env": "^7.4.3",
89
- "acorn": "^7.1.1",
89
+ "acorn": "^7.2.0",
90
90
  "babel-loader": "^8.0.5",
91
91
  "chai": "^4.0.1",
92
92
  "cheerio": "^0.22.0",