eslint 7.4.0 → 7.5.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +3 -1
  3. package/lib/linter/code-path-analysis/code-path-analyzer.js +38 -0
  4. package/lib/linter/code-path-analysis/code-path-segment.js +0 -1
  5. package/lib/linter/code-path-analysis/code-path-state.js +59 -0
  6. package/lib/linter/code-path-analysis/debug-helpers.js +26 -19
  7. package/lib/rules/accessor-pairs.js +1 -14
  8. package/lib/rules/array-callback-return.js +5 -7
  9. package/lib/rules/arrow-body-style.js +41 -6
  10. package/lib/rules/consistent-return.js +1 -12
  11. package/lib/rules/constructor-super.js +1 -0
  12. package/lib/rules/dot-location.js +20 -14
  13. package/lib/rules/dot-notation.js +36 -33
  14. package/lib/rules/func-call-spacing.js +42 -6
  15. package/lib/rules/func-name-matching.js +1 -4
  16. package/lib/rules/global-require.js +2 -1
  17. package/lib/rules/id-blacklist.js +233 -0
  18. package/lib/rules/indent.js +19 -0
  19. package/lib/rules/index.js +1 -3
  20. package/lib/rules/keyword-spacing.js +2 -2
  21. package/lib/rules/max-len.js +13 -2
  22. package/lib/rules/new-cap.js +10 -14
  23. package/lib/rules/newline-per-chained-call.js +15 -5
  24. package/lib/rules/no-alert.js +10 -3
  25. package/lib/rules/no-eval.js +8 -38
  26. package/lib/rules/no-extend-native.js +37 -40
  27. package/lib/rules/no-extra-bind.js +57 -17
  28. package/lib/rules/no-extra-boolean-cast.js +7 -0
  29. package/lib/rules/no-extra-parens.js +27 -7
  30. package/lib/rules/no-implicit-coercion.js +11 -6
  31. package/lib/rules/no-implied-eval.js +7 -28
  32. package/lib/rules/no-import-assign.js +33 -32
  33. package/lib/rules/no-irregular-whitespace.js +22 -12
  34. package/lib/rules/no-magic-numbers.js +4 -8
  35. package/lib/rules/no-obj-calls.js +7 -4
  36. package/lib/rules/no-prototype-builtins.js +13 -3
  37. package/lib/rules/no-self-assign.js +3 -53
  38. package/lib/rules/no-setter-return.js +5 -8
  39. package/lib/rules/no-unexpected-multiline.js +2 -2
  40. package/lib/rules/no-unneeded-ternary.js +0 -2
  41. package/lib/rules/no-unused-expressions.js +55 -23
  42. package/lib/rules/no-useless-call.js +10 -7
  43. package/lib/rules/no-whitespace-before-property.js +16 -4
  44. package/lib/rules/object-curly-newline.js +4 -4
  45. package/lib/rules/operator-assignment.js +3 -42
  46. package/lib/rules/padding-line-between-statements.js +2 -2
  47. package/lib/rules/prefer-arrow-callback.js +90 -25
  48. package/lib/rules/prefer-exponentiation-operator.js +1 -1
  49. package/lib/rules/prefer-numeric-literals.js +4 -13
  50. package/lib/rules/prefer-promise-reject-errors.js +1 -3
  51. package/lib/rules/prefer-regex-literals.js +2 -5
  52. package/lib/rules/prefer-spread.js +2 -6
  53. package/lib/rules/radix.js +5 -2
  54. package/lib/rules/sort-imports.js +28 -0
  55. package/lib/rules/use-isnan.js +1 -1
  56. package/lib/rules/utils/ast-utils.js +317 -153
  57. package/lib/rules/wrap-iife.js +9 -2
  58. package/lib/rules/yoda.js +2 -55
  59. package/package.json +6 -6
package/CHANGELOG.md CHANGED
@@ -1,3 +1,28 @@
1
+ v7.5.0 - July 18, 2020
2
+
3
+ * [`6ea3178`](https://github.com/eslint/eslint/commit/6ea3178776eae0e40c3f5498893e8aab0e23686b) Update: optional chaining support (fixes #12642) (#13416) (Toru Nagashima)
4
+ * [`540b1af`](https://github.com/eslint/eslint/commit/540b1af77278ae649b621aa8d4bf8d6de03c3155) Chore: enable consistent-meta-messages internal rule (#13487) (Milos Djermanovic)
5
+ * [`885a145`](https://github.com/eslint/eslint/commit/885a1455691265db88dc0befe9b48a69d69e8b9c) Docs: clarify behavior if `meta.fixable` is omitted (refs #13349) (#13493) (Milos Djermanovic)
6
+ * [`1a01b42`](https://github.com/eslint/eslint/commit/1a01b420eaab0de03dab5cc190a9f2a860c21a84) Docs: Update technology sponsors in README (#13478) (Nicholas C. Zakas)
7
+ * [`6ed9e8e`](https://github.com/eslint/eslint/commit/6ed9e8e4ff038c0259b0e7fe7ab7f4fd4ec55801) Upgrade: lodash@4.17.19 (#13499) (Yohan Siguret)
8
+ * [`45cdf00`](https://github.com/eslint/eslint/commit/45cdf00da6aeff3d584d37b0710fc8d6ad9456d6) Sponsors: Sync README with website (ESLint Jenkins)
9
+ * [`f1cc725`](https://github.com/eslint/eslint/commit/f1cc725ba1b8646dcf06a83716d96ad9bb726172) Docs: fix linebreaks between versions in changelog (#13488) (Milos Djermanovic)
10
+ * [`f4d7b9e`](https://github.com/eslint/eslint/commit/f4d7b9e1a599346b2f21ff9de003b311b51411e6) Update: deprecate id-blacklist rule (#13465) (Dimitri Mitropoulos)
11
+ * [`e14a645`](https://github.com/eslint/eslint/commit/e14a645aa495558081490f990ba221e21aa6b27c) Chore: use espree.latestEcmaVersion in fuzzer (#13484) (Milos Djermanovic)
12
+ * [`61097fe`](https://github.com/eslint/eslint/commit/61097fe5cc275d414a0c8e19b31c6060cb5568b7) Docs: Update int rule level to string (#13483) (Brandon Mills)
13
+ * [`c8f9c82`](https://github.com/eslint/eslint/commit/c8f9c8210cf4b9da8f07922093d7b219abad9f10) Update: Improve report location no-irregular-whitespace (refs #12334) (#13462) (Milos Djermanovic)
14
+ * [`f2e68ec`](https://github.com/eslint/eslint/commit/f2e68ec1d6cee6299e8a5cdf76c522c11d3008dd) Build: update webpack resolve.mainFields to match website config (#13457) (Milos Djermanovic)
15
+ * [`a96bc5e`](https://github.com/eslint/eslint/commit/a96bc5ec06f3a48bfe458bccd68d4d3b2a280ed9) Fix: arrow-body-style fixer for `in` wrap (fixes #11849) (#13228) (Anix)
16
+ * [`748734f`](https://github.com/eslint/eslint/commit/748734fdd497fbf61f3a616ff4a09169135b9396) Upgrade: Updated puppeteer version to v4.0.0 (#13444) (odidev)
17
+ * [`e951457`](https://github.com/eslint/eslint/commit/e951457b7aaa1b12b135588d36e3f4db4d7b8463) Docs: fix wording in configuring.md (#13469) (Piper)
18
+ * [`0af1d28`](https://github.com/eslint/eslint/commit/0af1d2828d27885483737867653ba1659af72005) Update: add allowSeparatedGroups option to sort-imports (fixes #12951) (#13455) (Milos Djermanovic)
19
+ * [`1050ee7`](https://github.com/eslint/eslint/commit/1050ee78a95da9484ff333dc1c74dac64c05da6f) Update: Improve report location for no-unneeded-ternary (refs #12334) (#13456) (Milos Djermanovic)
20
+ * [`b77b420`](https://github.com/eslint/eslint/commit/b77b4202bd1d5d1306f6f645e88d7a41a51715db) Update: Improve report location for max-len (refs #12334) (#13458) (Milos Djermanovic)
21
+ * [`095194c`](https://github.com/eslint/eslint/commit/095194c0fc0eb02aa69fde6b4280696e0e4de214) Fix: add end location to reports in object-curly-newline (refs #12334) (#13460) (Milos Djermanovic)
22
+ * [`10251bb`](https://github.com/eslint/eslint/commit/10251bbaeba80ac15244f385fc42cf2f2a30e5d2) Fix: add end location to reports in keyword-spacing (refs #12334) (#13461) (Milos Djermanovic)
23
+ * [`2ea7ee5`](https://github.com/eslint/eslint/commit/2ea7ee51a4e05ee76a6dae5954c3b6263b0970a3) Sponsors: Sync README with website (ESLint Jenkins)
24
+ * [`b55fd3b`](https://github.com/eslint/eslint/commit/b55fd3b8c05a29a465a794a524b06c1a28cddf0c) Sponsors: Sync README with website (ESLint Jenkins)
25
+
1
26
  v7.4.0 - July 3, 2020
2
27
 
3
28
  * [`f21bad2`](https://github.com/eslint/eslint/commit/f21bad2680406a2671b877f8dba47f4475d0cc64) Docs: fix description for `never` in multiline-ternary (fixes #13368) (#13452) (Milos Djermanovic)
@@ -18,9 +43,11 @@ v7.4.0 - July 3, 2020
18
43
  * [`0655f66`](https://github.com/eslint/eslint/commit/0655f66525d167ca1288167b79a77087cfc8fcf6) Update: improve report location in arrow-body-style (refs #12334) (#13424) (YeonJuan)
19
44
  * [`d53d69a`](https://github.com/eslint/eslint/commit/d53d69af08cfe55f42e0a0ca725b1014dabccc21) Update: prefer-regex-literal detect regex literals (fixes #12840) (#12842) (Mathias Schreck)
20
45
  * [`004adae`](https://github.com/eslint/eslint/commit/004adae3f959414f56e44e5884f6221e9dcda142) Update: rename id-blacklist to id-denylist (fixes #13407) (#13408) (Kai Cataldo)
46
+
21
47
  v7.3.1 - June 22, 2020
22
48
 
23
49
  * [`de77c11`](https://github.com/eslint/eslint/commit/de77c11e7515f2097ff355ddc0d7b6db9c83c892) Fix: Replace Infinity with Number.MAX_SAFE_INTEGER (fixes #13427) (#13435) (Nicholas C. Zakas)
50
+
24
51
  v7.3.0 - June 19, 2020
25
52
 
26
53
  * [`638a6d6`](https://github.com/eslint/eslint/commit/638a6d6be18b4a37cfdc7223e1f5acd3718694be) Update: add missing `additionalProperties: false` to some rules' schema (#13198) (Milos Djermanovic)
@@ -45,6 +72,7 @@ v7.3.0 - June 19, 2020
45
72
  * [`0f1f5ed`](https://github.com/eslint/eslint/commit/0f1f5ed2a20b8fb575d4360316861cf4c2b9b7bc) Docs: Add security policy link to README (#13403) (Nicholas C. Zakas)
46
73
  * [`9e9ba89`](https://github.com/eslint/eslint/commit/9e9ba897c566601cfe90522099c635ea316b235f) Sponsors: Sync README with website (ESLint Jenkins)
47
74
  * [`ca59fb9`](https://github.com/eslint/eslint/commit/ca59fb95a395c0a02ed23768a70e086480ab1f6d) Sponsors: Sync README with website (ESLint Jenkins)
75
+
48
76
  v7.2.0 - June 5, 2020
49
77
 
50
78
  * [`b735a48`](https://github.com/eslint/eslint/commit/b735a485e77bcc791e4c4c6b8716801d94e98b2c) Update: add enforceForFunctionPrototypeMethods option to no-extra-parens (#12895) (Milos Djermanovic)
package/README.md CHANGED
@@ -254,7 +254,7 @@ The following companies, organizations, and individuals support ESLint's ongoing
254
254
  <!-- NOTE: This section is autogenerated. Do not manually edit.-->
255
255
  <!--sponsorsstart-->
256
256
  <h3>Gold Sponsors</h3>
257
- <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>
257
+ <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> <a href="https://aka.ms/microsoftfossfund"><img src="https://avatars1.githubusercontent.com/u/67931232?u=7fddc652a464d7151b97e8f108392af7d54fa3e8&v=4" alt="Microsoft FOSS Fund Sponsorships" height="96"></a></p><h3>Silver Sponsors</h3>
258
258
  <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>
259
259
  <p><a href="https://mytruemedia.com/"><img src="https://images.opencollective.com/my-true-media/03e2168/logo.png" alt="My True Media" height="32"></a> <a href="https://www.norgekasino.com"><img src="https://images.opencollective.com/norgekasino/ecfd57a/logo.png" alt="Norgekasino" height="32"></a> <a href="https://www.japanesecasino.com/"><img src="https://images.opencollective.com/japanesecasino/b0ffe3c/logo.png" alt="Japanesecasino" height="32"></a> <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://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/53a4b44/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/6e889f6/logo.png" alt="Icons8: free icons, photos, illustrations, and music" 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>
260
260
  <!--sponsorsend-->
@@ -262,3 +262,5 @@ The following companies, organizations, and individuals support ESLint's ongoing
262
262
  ## <a name="technology-sponsors"></a>Technology Sponsors
263
263
 
264
264
  * Site search ([eslint.org](https://eslint.org)) is sponsored by [Algolia](https://www.algolia.com)
265
+ * Hosting for ([eslint.org](https://eslint.org)) is sponsored by [Netlify](https://www.netlify.com)
266
+ * Password management is sponsored by [1Password](https://www.1password.com)
@@ -244,6 +244,19 @@ function preprocess(analyzer, node) {
244
244
  const parent = node.parent;
245
245
 
246
246
  switch (parent.type) {
247
+
248
+ // The `arguments.length == 0` case is in `postprocess` function.
249
+ case "CallExpression":
250
+ if (parent.optional === true && parent.arguments.length >= 1 && parent.arguments[0] === node) {
251
+ state.makeOptionalRight();
252
+ }
253
+ break;
254
+ case "MemberExpression":
255
+ if (parent.optional === true && parent.property === node) {
256
+ state.makeOptionalRight();
257
+ }
258
+ break;
259
+
247
260
  case "LogicalExpression":
248
261
  if (
249
262
  parent.right === node &&
@@ -377,6 +390,20 @@ function processCodePathToEnter(analyzer, node) {
377
390
  analyzer.emitter.emit("onCodePathStart", codePath, node);
378
391
  break;
379
392
 
393
+ case "ChainExpression":
394
+ state.pushChainContext();
395
+ break;
396
+ case "CallExpression":
397
+ if (node.optional === true) {
398
+ state.makeOptionalNode();
399
+ }
400
+ break;
401
+ case "MemberExpression":
402
+ if (node.optional === true) {
403
+ state.makeOptionalNode();
404
+ }
405
+ break;
406
+
380
407
  case "LogicalExpression":
381
408
  if (isHandledLogicalOperator(node.operator)) {
382
409
  state.pushChoiceContext(
@@ -449,6 +476,10 @@ function processCodePathToExit(analyzer, node) {
449
476
  let dontForward = false;
450
477
 
451
478
  switch (node.type) {
479
+ case "ChainExpression":
480
+ state.popChainContext();
481
+ break;
482
+
452
483
  case "IfStatement":
453
484
  case "ConditionalExpression":
454
485
  state.popChoiceContext();
@@ -583,6 +614,13 @@ function postprocess(analyzer, node) {
583
614
  break;
584
615
  }
585
616
 
617
+ // The `arguments.length >= 1` case is in `preprocess` function.
618
+ case "CallExpression":
619
+ if (node.optional === true && node.arguments.length === 0) {
620
+ CodePath.getState(analyzer.codePath).makeOptionalRight();
621
+ }
622
+ break;
623
+
586
624
  default:
587
625
  break;
588
626
  }
@@ -92,7 +92,6 @@ class CodePathSegment {
92
92
  /* istanbul ignore if */
93
93
  if (debug.enabled) {
94
94
  this.internal.nodes = [];
95
- this.internal.exitNodes = [];
96
95
  }
97
96
  }
98
97
 
@@ -234,6 +234,7 @@ class CodePathState {
234
234
  this.tryContext = null;
235
235
  this.loopContext = null;
236
236
  this.breakContext = null;
237
+ this.chainContext = null;
237
238
 
238
239
  this.currentSegments = [];
239
240
  this.initialSegment = this.forkContext.head[0];
@@ -555,6 +556,64 @@ class CodePathState {
555
556
  );
556
557
  }
557
558
 
559
+ //--------------------------------------------------------------------------
560
+ // ChainExpression
561
+ //--------------------------------------------------------------------------
562
+
563
+ /**
564
+ * Push a new `ChainExpression` context to the stack.
565
+ * This method is called on entering to each `ChainExpression` node.
566
+ * This context is used to count forking in the optional chain then merge them on the exiting from the `ChainExpression` node.
567
+ * @returns {void}
568
+ */
569
+ pushChainContext() {
570
+ this.chainContext = {
571
+ upper: this.chainContext,
572
+ countChoiceContexts: 0
573
+ };
574
+ }
575
+
576
+ /**
577
+ * Pop a `ChainExpression` context from the stack.
578
+ * This method is called on exiting from each `ChainExpression` node.
579
+ * This merges all forks of the last optional chaining.
580
+ * @returns {void}
581
+ */
582
+ popChainContext() {
583
+ const context = this.chainContext;
584
+
585
+ this.chainContext = context.upper;
586
+
587
+ // pop all choice contexts of this.
588
+ for (let i = context.countChoiceContexts; i > 0; --i) {
589
+ this.popChoiceContext();
590
+ }
591
+ }
592
+
593
+ /**
594
+ * Create a choice context for optional access.
595
+ * This method is called on entering to each `(Call|Member)Expression[optional=true]` node.
596
+ * This creates a choice context as similar to `LogicalExpression[operator="??"]` node.
597
+ * @returns {void}
598
+ */
599
+ makeOptionalNode() {
600
+ if (this.chainContext) {
601
+ this.chainContext.countChoiceContexts += 1;
602
+ this.pushChoiceContext("??", false);
603
+ }
604
+ }
605
+
606
+ /**
607
+ * Create a fork.
608
+ * This method is called on entering to the `arguments|property` property of each `(Call|Member)Expression` node.
609
+ * @returns {void}
610
+ */
611
+ makeOptionalRight() {
612
+ if (this.chainContext) {
613
+ this.makeLogicalRight();
614
+ }
615
+ }
616
+
558
617
  //--------------------------------------------------------------------------
559
618
  // SwitchStatement
560
619
  //--------------------------------------------------------------------------
@@ -25,6 +25,22 @@ function getId(segment) { // eslint-disable-line jsdoc/require-jsdoc
25
25
  return segment.id + (segment.reachable ? "" : "!");
26
26
  }
27
27
 
28
+ /**
29
+ * Get string for the given node and operation.
30
+ * @param {ASTNode} node The node to convert.
31
+ * @param {"enter" | "exit" | undefined} label The operation label.
32
+ * @returns {string} The string representation.
33
+ */
34
+ function nodeToString(node, label) {
35
+ const suffix = label ? `:${label}` : "";
36
+
37
+ switch (node.type) {
38
+ case "Identifier": return `${node.type}${suffix} (${node.name})`;
39
+ case "Literal": return `${node.type}${suffix} (${node.value})`;
40
+ default: return `${node.type}${suffix}`;
41
+ }
42
+ }
43
+
28
44
  //------------------------------------------------------------------------------
29
45
  // Public Interface
30
46
  //------------------------------------------------------------------------------
@@ -56,9 +72,15 @@ module.exports = {
56
72
  const segInternal = state.currentSegments[i].internal;
57
73
 
58
74
  if (leaving) {
59
- segInternal.exitNodes.push(node);
75
+ const last = segInternal.nodes.length - 1;
76
+
77
+ if (last >= 0 && segInternal.nodes[last] === nodeToString(node, "enter")) {
78
+ segInternal.nodes[last] = nodeToString(node, void 0);
79
+ } else {
80
+ segInternal.nodes.push(nodeToString(node, "exit"));
81
+ }
60
82
  } else {
61
- segInternal.nodes.push(node);
83
+ segInternal.nodes.push(nodeToString(node, "enter"));
62
84
  }
63
85
  }
64
86
 
@@ -104,23 +126,8 @@ module.exports = {
104
126
  text += "style=\"rounded,dashed,filled\",fillcolor=\"#FF9800\",label=\"<<unreachable>>\\n";
105
127
  }
106
128
 
107
- if (segment.internal.nodes.length > 0 || segment.internal.exitNodes.length > 0) {
108
- text += [].concat(
109
- segment.internal.nodes.map(node => {
110
- switch (node.type) {
111
- case "Identifier": return `${node.type} (${node.name})`;
112
- case "Literal": return `${node.type} (${node.value})`;
113
- default: return node.type;
114
- }
115
- }),
116
- segment.internal.exitNodes.map(node => {
117
- switch (node.type) {
118
- case "Identifier": return `${node.type}:exit (${node.name})`;
119
- case "Literal": return `${node.type}:exit (${node.value})`;
120
- default: return `${node.type}:exit`;
121
- }
122
- })
123
- ).join("\\n");
129
+ if (segment.internal.nodes.length > 0) {
130
+ text += segment.internal.nodes.join("\\n");
124
131
  } else {
125
132
  text += "????";
126
133
  }
@@ -86,16 +86,6 @@ function isAccessorKind(node) {
86
86
  return node.kind === "get" || node.kind === "set";
87
87
  }
88
88
 
89
- /**
90
- * Checks whether or not a given node is an `Identifier` node which was named a given name.
91
- * @param {ASTNode} node A node to check.
92
- * @param {string} name An expected name of the node.
93
- * @returns {boolean} `true` if the node is an `Identifier` node which was named as expected.
94
- */
95
- function isIdentifier(node, name) {
96
- return node.type === "Identifier" && node.name === name;
97
- }
98
-
99
89
  /**
100
90
  * Checks whether or not a given node is an argument of a specified method call.
101
91
  * @param {ASTNode} node A node to check.
@@ -109,10 +99,7 @@ function isArgumentOfMethodCall(node, index, object, property) {
109
99
 
110
100
  return (
111
101
  parent.type === "CallExpression" &&
112
- parent.callee.type === "MemberExpression" &&
113
- parent.callee.computed === false &&
114
- isIdentifier(parent.callee.object, object) &&
115
- isIdentifier(parent.callee.property, property) &&
102
+ astUtils.isSpecificMemberAccess(parent.callee, object, property) &&
116
103
  parent.arguments[index] === node
117
104
  );
118
105
  }
@@ -28,17 +28,14 @@ function isReachable(segment) {
28
28
  }
29
29
 
30
30
  /**
31
- * Checks a given node is a MemberExpression node which has the specified name's
31
+ * Checks a given node is a member access which has the specified name's
32
32
  * property.
33
33
  * @param {ASTNode} node A node to check.
34
- * @returns {boolean} `true` if the node is a MemberExpression node which has
35
- * the specified name's property
34
+ * @returns {boolean} `true` if the node is a member access which has
35
+ * the specified name's property. The node may be a `(Chain|Member)Expression` node.
36
36
  */
37
37
  function isTargetMethod(node) {
38
- return (
39
- node.type === "MemberExpression" &&
40
- TARGET_METHODS.test(astUtils.getStaticPropertyName(node) || "")
41
- );
38
+ return astUtils.isSpecificMemberAccess(node, null, TARGET_METHODS);
42
39
  }
43
40
 
44
41
  /**
@@ -76,6 +73,7 @@ function getArrayMethodName(node) {
76
73
  */
77
74
  case "LogicalExpression":
78
75
  case "ConditionalExpression":
76
+ case "ChainExpression":
79
77
  currentNode = parent;
80
78
  break;
81
79
 
@@ -75,6 +75,7 @@ module.exports = {
75
75
  const never = options[0] === "never";
76
76
  const requireReturnForObjectLiteral = options[1] && options[1].requireReturnForObjectLiteral;
77
77
  const sourceCode = context.getSourceCode();
78
+ let funcInfo = null;
78
79
 
79
80
  /**
80
81
  * Checks whether the given node has ASI problem or not.
@@ -99,6 +100,21 @@ module.exports = {
99
100
  return sourceCode.getTokenAfter(node);
100
101
  }
101
102
 
103
+ /**
104
+ * Check whether the node is inside of a for loop's init
105
+ * @param {ASTNode} node node is inside for loop
106
+ * @returns {boolean} `true` if the node is inside of a for loop, else `false`
107
+ */
108
+ function isInsideForLoopInitializer(node) {
109
+ if (node && node.parent) {
110
+ if (node.parent.type === "ForStatement" && node.parent.init === node) {
111
+ return true;
112
+ }
113
+ return isInsideForLoopInitializer(node.parent);
114
+ }
115
+ return false;
116
+ }
117
+
102
118
  /**
103
119
  * Determines whether a arrow function body needs braces
104
120
  * @param {ASTNode} node The arrow function node.
@@ -178,11 +194,13 @@ module.exports = {
178
194
  * If the first token of the reutrn value is `{` or the return value is a sequence expression,
179
195
  * enclose the return value by parentheses to avoid syntax error.
180
196
  */
181
- if (astUtils.isOpeningBraceToken(firstValueToken) || blockBody[0].argument.type === "SequenceExpression") {
182
- fixes.push(
183
- fixer.insertTextBefore(firstValueToken, "("),
184
- fixer.insertTextAfter(lastValueToken, ")")
185
- );
197
+ if (astUtils.isOpeningBraceToken(firstValueToken) || blockBody[0].argument.type === "SequenceExpression" || (funcInfo.hasInOperator && isInsideForLoopInitializer(node))) {
198
+ if (!astUtils.isParenthesised(sourceCode, blockBody[0].argument)) {
199
+ fixes.push(
200
+ fixer.insertTextBefore(firstValueToken, "("),
201
+ fixer.insertTextAfter(lastValueToken, ")")
202
+ );
203
+ }
186
204
  }
187
205
 
188
206
  /*
@@ -245,7 +263,24 @@ module.exports = {
245
263
  }
246
264
 
247
265
  return {
248
- "ArrowFunctionExpression:exit": validate
266
+ "BinaryExpression[operator='in']"() {
267
+ let info = funcInfo;
268
+
269
+ while (info) {
270
+ info.hasInOperator = true;
271
+ info = info.upper;
272
+ }
273
+ },
274
+ ArrowFunctionExpression() {
275
+ funcInfo = {
276
+ upper: funcInfo,
277
+ hasInOperator: false
278
+ };
279
+ },
280
+ "ArrowFunctionExpression:exit"(node) {
281
+ validate(node);
282
+ funcInfo = funcInfo.upper;
283
+ }
249
284
  };
250
285
  }
251
286
  };
@@ -9,23 +9,12 @@
9
9
  //------------------------------------------------------------------------------
10
10
 
11
11
  const lodash = require("lodash");
12
-
13
12
  const astUtils = require("./utils/ast-utils");
14
13
 
15
14
  //------------------------------------------------------------------------------
16
15
  // Helpers
17
16
  //------------------------------------------------------------------------------
18
17
 
19
- /**
20
- * Checks whether or not a given node is an `Identifier` node which was named a given name.
21
- * @param {ASTNode} node A node to check.
22
- * @param {string} name An expected name of the node.
23
- * @returns {boolean} `true` if the node is an `Identifier` node which was named as expected.
24
- */
25
- function isIdentifier(node, name) {
26
- return node.type === "Identifier" && node.name === name;
27
- }
28
-
29
18
  /**
30
19
  * Checks whether or not a given code path segment is unreachable.
31
20
  * @param {CodePathSegment} segment A CodePathSegment to check.
@@ -165,7 +154,7 @@ module.exports = {
165
154
  let hasReturnValue = Boolean(argument);
166
155
 
167
156
  if (treatUndefinedAsUnspecified && hasReturnValue) {
168
- hasReturnValue = !isIdentifier(argument, "undefined") && argument.operator !== "void";
157
+ hasReturnValue = !astUtils.isSpecificId(argument, "undefined") && argument.operator !== "void";
169
158
  }
170
159
 
171
160
  if (!funcInfo.hasReturn) {
@@ -50,6 +50,7 @@ function isPossibleConstructor(node) {
50
50
  case "MemberExpression":
51
51
  case "CallExpression":
52
52
  case "NewExpression":
53
+ case "ChainExpression":
53
54
  case "YieldExpression":
54
55
  case "TaggedTemplateExpression":
55
56
  case "MetaProperty":
@@ -52,31 +52,37 @@ module.exports = {
52
52
  */
53
53
  function checkDotLocation(node) {
54
54
  const property = node.property;
55
- const dot = sourceCode.getTokenBefore(property);
56
-
57
- // `obj` expression can be parenthesized, but those paren tokens are not a part of the `obj` node.
58
- const tokenBeforeDot = sourceCode.getTokenBefore(dot);
59
-
60
- const textBeforeDot = sourceCode.getText().slice(tokenBeforeDot.range[1], dot.range[0]);
61
- const textAfterDot = sourceCode.getText().slice(dot.range[1], property.range[0]);
55
+ const dotToken = sourceCode.getTokenBefore(property);
62
56
 
63
57
  if (onObject) {
64
- if (!astUtils.isTokenOnSameLine(tokenBeforeDot, dot)) {
65
- const neededTextAfterToken = astUtils.isDecimalIntegerNumericToken(tokenBeforeDot) ? " " : "";
66
58
 
59
+ // `obj` expression can be parenthesized, but those paren tokens are not a part of the `obj` node.
60
+ const tokenBeforeDot = sourceCode.getTokenBefore(dotToken);
61
+
62
+ if (!astUtils.isTokenOnSameLine(tokenBeforeDot, dotToken)) {
67
63
  context.report({
68
64
  node,
69
- loc: dot.loc,
65
+ loc: dotToken.loc,
70
66
  messageId: "expectedDotAfterObject",
71
- fix: fixer => fixer.replaceTextRange([tokenBeforeDot.range[1], property.range[0]], `${neededTextAfterToken}.${textBeforeDot}${textAfterDot}`)
67
+ *fix(fixer) {
68
+ if (dotToken.value.startsWith(".") && astUtils.isDecimalIntegerNumericToken(tokenBeforeDot)) {
69
+ yield fixer.insertTextAfter(tokenBeforeDot, ` ${dotToken.value}`);
70
+ } else {
71
+ yield fixer.insertTextAfter(tokenBeforeDot, dotToken.value);
72
+ }
73
+ yield fixer.remove(dotToken);
74
+ }
72
75
  });
73
76
  }
74
- } else if (!astUtils.isTokenOnSameLine(dot, property)) {
77
+ } else if (!astUtils.isTokenOnSameLine(dotToken, property)) {
75
78
  context.report({
76
79
  node,
77
- loc: dot.loc,
80
+ loc: dotToken.loc,
78
81
  messageId: "expectedDotBeforeProperty",
79
- fix: fixer => fixer.replaceTextRange([tokenBeforeDot.range[1], property.range[0]], `${textBeforeDot}${textAfterDot}.`)
82
+ *fix(fixer) {
83
+ yield fixer.remove(dotToken);
84
+ yield fixer.insertTextBefore(property, dotToken.value);
85
+ }
80
86
  });
81
87
  }
82
88
  }
@@ -87,28 +87,36 @@ module.exports = {
87
87
  data: {
88
88
  key: formattedValue
89
89
  },
90
- fix(fixer) {
90
+ *fix(fixer) {
91
91
  const leftBracket = sourceCode.getTokenAfter(node.object, astUtils.isOpeningBracketToken);
92
92
  const rightBracket = sourceCode.getLastToken(node);
93
+ const nextToken = sourceCode.getTokenAfter(node);
93
94
 
94
- if (sourceCode.getFirstTokenBetween(leftBracket, rightBracket, { includeComments: true, filter: astUtils.isCommentToken })) {
95
-
96
- // Don't perform any fixes if there are comments inside the brackets.
97
- return null;
95
+ // Don't perform any fixes if there are comments inside the brackets.
96
+ if (sourceCode.commentsExistBetween(leftBracket, rightBracket)) {
97
+ return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
98
98
  }
99
99
 
100
- const tokenAfterProperty = sourceCode.getTokenAfter(rightBracket);
101
- const needsSpaceAfterProperty = tokenAfterProperty &&
102
- rightBracket.range[1] === tokenAfterProperty.range[0] &&
103
- !astUtils.canTokensBeAdjacent(String(value), tokenAfterProperty);
104
-
105
- const textBeforeDot = astUtils.isDecimalInteger(node.object) ? " " : "";
106
- const textAfterProperty = needsSpaceAfterProperty ? " " : "";
107
-
108
- return fixer.replaceTextRange(
100
+ // Replace the brackets by an identifier.
101
+ if (!node.optional) {
102
+ yield fixer.insertTextBefore(
103
+ leftBracket,
104
+ astUtils.isDecimalInteger(node.object) ? " ." : "."
105
+ );
106
+ }
107
+ yield fixer.replaceTextRange(
109
108
  [leftBracket.range[0], rightBracket.range[1]],
110
- `${textBeforeDot}.${value}${textAfterProperty}`
109
+ value
111
110
  );
111
+
112
+ // Insert a space after the property if it will be connected to the next token.
113
+ if (
114
+ nextToken &&
115
+ rightBracket.range[1] === nextToken.range[0] &&
116
+ !astUtils.canTokensBeAdjacent(String(value), nextToken)
117
+ ) {
118
+ yield fixer.insertTextAfter(node, " ");
119
+ }
112
120
  }
113
121
  });
114
122
  }
@@ -141,29 +149,24 @@ module.exports = {
141
149
  data: {
142
150
  key: node.property.name
143
151
  },
144
- fix(fixer) {
145
- const dot = sourceCode.getTokenBefore(node.property);
146
- const textAfterDot = sourceCode.text.slice(dot.range[1], node.property.range[0]);
147
-
148
- if (textAfterDot.trim()) {
152
+ *fix(fixer) {
153
+ const dotToken = sourceCode.getTokenBefore(node.property);
149
154
 
150
- // Don't perform any fixes if there are comments between the dot and the property name.
151
- return null;
155
+ // A statement that starts with `let[` is parsed as a destructuring variable declaration, not a MemberExpression.
156
+ if (node.object.type === "Identifier" && node.object.name === "let" && !node.optional) {
157
+ return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
152
158
  }
153
159
 
154
- if (node.object.type === "Identifier" && node.object.name === "let") {
155
-
156
- /*
157
- * A statement that starts with `let[` is parsed as a destructuring variable declaration, not
158
- * a MemberExpression.
159
- */
160
- return null;
160
+ // Don't perform any fixes if there are comments between the dot and the property name.
161
+ if (sourceCode.commentsExistBetween(dotToken, node.property)) {
162
+ return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
161
163
  }
162
164
 
163
- return fixer.replaceTextRange(
164
- [dot.range[0], node.property.range[1]],
165
- `[${textAfterDot}"${node.property.name}"]`
166
- );
165
+ // Replace the identifier to brackets.
166
+ if (!node.optional) {
167
+ yield fixer.remove(dotToken);
168
+ }
169
+ yield fixer.replaceText(node.property, `["${node.property.name}"]`);
167
170
  }
168
171
  });
169
172
  }