eslint 6.0.1 → 6.1.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,32 @@
1
+ v6.1.0 - July 20, 2019
2
+
3
+ * [`8f86cca`](https://github.com/eslint/eslint/commit/8f86ccaa89daf10123370868c5dcb48c1fcbef7d) Upgrade: eslint-scope@^5.0.0 (#12011) (Kevin Partington)
4
+ * [`d08683e`](https://github.com/eslint/eslint/commit/d08683e3c807f92daf266894093c70f8d5ac6afa) Fix: glob processing (fixes #11940) (#11986) (Toru Nagashima)
5
+ * [`bfcf8b2`](https://github.com/eslint/eslint/commit/bfcf8b21011466b570b536ca31ec10fd228b3dca) Fix: dot-location errors with parenthesized objects (fixes #11868) (#11933) (Milos Djermanovic)
6
+ * [`79e8d09`](https://github.com/eslint/eslint/commit/79e8d099bbbebfa4d804484eeeeea9c074ede870) Fix: add parens for sequence expr in arrow-body-style (fixes #11917) (#11918) (Pig Fang)
7
+ * [`105c098`](https://github.com/eslint/eslint/commit/105c098f3cece8b83ab8d1566b8ea41dd94a60b9) Docs: update docs for object-curly-spacing (fixes #11634) (#12009) (Chiawen Chen)
8
+ * [`c90a12c`](https://github.com/eslint/eslint/commit/c90a12c283698befcafd2c86f8bd8942428fe80b) Chore: update release script for new website repo (#12006) (Kai Cataldo)
9
+ * [`e2c08a9`](https://github.com/eslint/eslint/commit/e2c08a9c8d86238955ecc8fd5a626584ee91eba5) Sponsors: Sync README with website (ESLint Jenkins)
10
+ * [`b974fcb`](https://github.com/eslint/eslint/commit/b974fcbd3321ab382a914520018d4c051b2e5c62) Update: Check computed property keys in no-extra-parens (#11952) (Milos Djermanovic)
11
+ * [`222d27c`](https://github.com/eslint/eslint/commit/222d27c32a6d6d8828233b3b99e93ecefa94c603) Update: Add for-in and for-of checks for props in no-param-reassign (#11941) (Milos Djermanovic)
12
+ * [`e4c450f`](https://github.com/eslint/eslint/commit/e4c450febc9bd77b33f6473667afa9f955be6b71) Fix: no-extra-parens autofix with `in` in a for-loop init (fixes #11706) (#11848) (Milos Djermanovic)
13
+ * [`2dafe2d`](https://github.com/eslint/eslint/commit/2dafe2d288d1e0d353bb938d12a5da888091cfdb) Fix: prefer-const produces invalid autofix (fixes #11699) (#11827) (Milos Djermanovic)
14
+ * [`cb475fd`](https://github.com/eslint/eslint/commit/cb475fd8c0bbfcb00340459966b6780f39ea87a7) Fix: Cache file error handling on read-only file system. (fixes #11945) (#11946) (Cuki)
15
+ * [`89412c3`](https://github.com/eslint/eslint/commit/89412c3cbc52e556dba590fa94e10bf40faf1fdf) Docs: Fixed a typo (fixes #11999) (#12000) (Eddie Olson)
16
+ * [`6669f78`](https://github.com/eslint/eslint/commit/6669f78a3dd305aef6191e7eea24fae2ae4fd2e8) Fix: --init with Vue.js failed (fixes #11970) (#11985) (Toru Nagashima)
17
+ * [`93633c2`](https://github.com/eslint/eslint/commit/93633c2b3716b17816bcb3dc221c49b75db41317) Upgrade: Upgrade lodash dependency (fixes #11992) (#11994) (Cyd La Luz)
18
+ * [`776dae7`](https://github.com/eslint/eslint/commit/776dae71f2f5c7b5f0650ea3c277eca26e324e41) Docs: fix wrong Node.js version in getting started (#11993) (Toru Nagashima)
19
+ * [`4448261`](https://github.com/eslint/eslint/commit/4448261f5d217d8a06eb0ef898401928b54a34e3) Docs: some typos and optimization points (#11960) (Jason Lee)
20
+ * [`2a10856`](https://github.com/eslint/eslint/commit/2a10856d1ed5880a09a5ba452bd80d49c1be4e6c) Chore: Add temporary test files to .gitignore (#11978) (Milos Djermanovic)
21
+ * [`d83b233`](https://github.com/eslint/eslint/commit/d83b23382de3b80056a7e6330ed5846316c94147) Chore: update path for release bundles (#11977) (Kai Cataldo)
22
+ * [`1fb3620`](https://github.com/eslint/eslint/commit/1fb362093a65b99456a11029967d9ee0c31fd697) Fix: creating of enabledGlobals object without prototype (fixes #11929) (#11935) (finico)
23
+ * [`c2f2db9`](https://github.com/eslint/eslint/commit/c2f2db97c6d6a415b78ee7b3e8924853d465e757) Docs: Replace global true and false with writable and readonly in rules (#11956) (Milos Djermanovic)
24
+ * [`19335b8`](https://github.com/eslint/eslint/commit/19335b8f47029b2f742d5507ba39484eaf68d07b) Fix: actual messageId and expected messageId are switched in rule tester (#11928) (Milos Djermanovic)
25
+ * [`8b216e0`](https://github.com/eslint/eslint/commit/8b216e04fb0dd0a1a4d3730ebe4b24780020b09c) Docs: Fix incorrect example comments for unicode-bom rule (fixes #11937) (#11938) (Brandon Yeager)
26
+ * [`cc3885b`](https://github.com/eslint/eslint/commit/cc3885b028e29ebc575c900f43af81cb0dabffb6) Chore: add v8-compile-cache to speed up instantiation time (#11921) (薛定谔的猫)
27
+ * [`d8f2688`](https://github.com/eslint/eslint/commit/d8f26886f19a17f2e1cdcb91e2db84fc7ba3fdfb) Upgrade: deps (#11904) (薛定谔的猫)
28
+ * [`e5f1ccc`](https://github.com/eslint/eslint/commit/e5f1ccc9e2d07ad0acf149027ffc382021d54da1) Docs: add 'stricter rule config validating' in migrating docs (#11905) (薛定谔的猫)
29
+
1
30
  v6.0.1 - June 24, 2019
2
31
 
3
32
  * [`b5bde06`](https://github.com/eslint/eslint/commit/b5bde0669bd6a7a6b8e38cdf204d8d4b932cea63) Fix: --rulesdir option didn't work (fixes #11888) (#11890) (Toru Nagashima)
package/README.md CHANGED
@@ -258,9 +258,9 @@ The following companies, organizations, and individuals support ESLint's ongoing
258
258
  <!-- NOTE: This section is autogenerated. Do not manually edit.-->
259
259
  <!--sponsorsstart-->
260
260
  <h3>Gold Sponsors</h3>
261
- <p><a href="https://www.shopify.com"><img src="https://images.opencollective.com/shopify/logo.png" alt="Shopify" height="96"></a> <a href="http://salesforce.com"><img src="https://images.opencollective.com/salesforce/logo.png" alt="Salesforce" height="96"></a> <a href="https://badoo.com/team?utm_source=eslint"><img src="https://images.opencollective.com/badoo/logo.png" alt="Badoo" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/logo.png" alt="Airbnb" height="96"></a> <a href="https://code.facebook.com/projects/"><img src="https://images.opencollective.com/fbopensource/logo.png" alt="Facebook Open Source" height="96"></a></p><h3>Silver Sponsors</h3>
262
- <p><a href="https://www.ampproject.org/"><img src="https://images.opencollective.com/amp/logo.png" alt="AMP Project" height="64"></a></p><h3>Bronze Sponsors</h3>
263
- <p><a href="https://clay.global"><img src="https://images.opencollective.com/clayglobal/logo.png" alt="clay" height="32"></a> <a href="https://discordapp.com"><img src="https://images.opencollective.com/discordapp/logo.png" alt="Discord" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://tekhattan.com"><img src="https://images.opencollective.com/tekhattan/logo.png" alt="TekHattan" height="32"></a> <a href="https://www.marfeel.com/"><img src="https://images.opencollective.com/marfeel/logo.png" alt="Marfeel" height="32"></a> <a href="http://www.firesticktricks.com"><img src="https://images.opencollective.com/fire-stick-tricks/logo.png" alt="Fire Stick Tricks" height="32"></a> <a href="https://jsheroes.io/"><img src="https://images.opencollective.com/jsheroes1/logo.png" alt="JSHeroes " height="32"></a> <a href="https://faithlife.com/ref/about"><img src="https://images.opencollective.com/faithlife/logo.png" alt="Faithlife" height="32"></a></p>
261
+ <p><a href="https://www.shopify.com"><img src="https://images.opencollective.com/shopify/eeb91aa/logo.png" alt="Shopify" height="96"></a> <a href="http://salesforce.com"><img src="https://images.opencollective.com/salesforce/853ecef/logo.png" alt="Salesforce" height="96"></a> <a href="https://badoo.com/team?utm_source=eslint"><img src="https://images.opencollective.com/badoo/2826a3b/logo.png" alt="Badoo" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/001a341/logo.png" alt="Airbnb" height="96"></a> <a href="https://code.facebook.com/projects/"><img src="https://images.opencollective.com/fbopensource/fbb8a5b/logo.png" alt="Facebook Open Source" height="96"></a></p><h3>Silver Sponsors</h3>
262
+ <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>
263
+ <p><a href="https://clay.global"><img src="https://images.opencollective.com/clayglobal/2468f34/logo.png" alt="clay" 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/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> <a href="https://jsheroes.io/"><img src="https://images.opencollective.com/jsheroes1/9fedf0b/logo.png" alt="JSHeroes " height="32"></a> <a href="https://faithlife.com/ref/about"><img src="https://images.opencollective.com/faithlife/534b832/logo.png" alt="Faithlife" height="32"></a></p>
264
264
  <!--sponsorsend-->
265
265
 
266
266
  ## <a name="technology-sponsors"></a>Technology Sponsors
package/bin/eslint.js CHANGED
@@ -9,6 +9,9 @@
9
9
 
10
10
  "use strict";
11
11
 
12
+ // to use V8's code cache to speed up instantiation time
13
+ require("v8-compile-cache");
14
+
12
15
  //------------------------------------------------------------------------------
13
16
  // Helpers
14
17
  //------------------------------------------------------------------------------
@@ -20,6 +20,7 @@ const path = require("path");
20
20
  const defaultOptions = require("../../conf/default-cli-options");
21
21
  const pkg = require("../../package.json");
22
22
  const ConfigOps = require("../shared/config-ops");
23
+ const naming = require("../shared/naming");
23
24
  const ModuleResolver = require("../shared/relative-module-resolver");
24
25
  const { Linter } = require("../linter");
25
26
  const builtInRules = require("../rules");
@@ -29,7 +30,6 @@ const { FileEnumerator } = require("./file-enumerator");
29
30
  const hash = require("./hash");
30
31
  const { IgnoredPaths } = require("./ignored-paths");
31
32
  const LintResultCache = require("./lint-result-cache");
32
- const naming = require("./naming");
33
33
 
34
34
  const debug = require("debug")("eslint:cli-engine");
35
35
  const validFixTypes = new Set(["problem", "suggestion", "layout"]);
@@ -734,7 +734,10 @@ class CLIEngine {
734
734
  try {
735
735
  fs.unlinkSync(cacheFilePath);
736
736
  } catch (error) {
737
- if (!error || error.code !== "ENOENT") {
737
+ const errorCode = error && error.code;
738
+
739
+ // Ignore errors when no such file exists or file system is read only (and cache file does not exist)
740
+ if (errorCode !== "ENOENT" && !(errorCode === "EROFS" && !fs.existsSync(cacheFilePath))) {
738
741
  throw error;
739
742
  }
740
743
  }
@@ -38,9 +38,9 @@ const path = require("path");
38
38
  const importFresh = require("import-fresh");
39
39
  const stripComments = require("strip-json-comments");
40
40
  const { validateConfigSchema } = require("../shared/config-validator");
41
+ const naming = require("../shared/naming");
41
42
  const ModuleResolver = require("../shared/relative-module-resolver");
42
43
  const { ConfigArray, ConfigDependency, OverrideTester } = require("./config-array");
43
- const naming = require("./naming");
44
44
  const debug = require("debug")("eslint:config-array-factory");
45
45
 
46
46
  //------------------------------------------------------------------------------
@@ -292,26 +292,18 @@ class FileEnumerator {
292
292
  _iterateFiles(pattern) {
293
293
  const { cwd, globInputPaths } = internalSlotsMap.get(this);
294
294
  const absolutePath = path.resolve(cwd, pattern);
295
-
296
- if (globInputPaths && isGlobPattern(pattern)) {
297
- return this._iterateFilesWithGlob(
298
- absolutePath,
299
- dotfilesPattern.test(pattern)
300
- );
301
- }
302
-
295
+ const isDot = dotfilesPattern.test(pattern);
303
296
  const stat = statSafeSync(absolutePath);
304
297
 
305
298
  if (stat && stat.isDirectory()) {
306
- return this._iterateFilesWithDirectory(
307
- absolutePath,
308
- dotfilesPattern.test(pattern)
309
- );
299
+ return this._iterateFilesWithDirectory(absolutePath, isDot);
310
300
  }
311
-
312
301
  if (stat && stat.isFile()) {
313
302
  return this._iterateFilesWithFile(absolutePath);
314
303
  }
304
+ if (globInputPaths && isGlobPattern(pattern)) {
305
+ return this._iterateFilesWithGlob(absolutePath, isDot);
306
+ }
315
307
 
316
308
  return [];
317
309
  }
@@ -18,6 +18,7 @@ const util = require("util"),
18
18
  recConfig = require("../../conf/eslint-recommended"),
19
19
  ConfigOps = require("../shared/config-ops"),
20
20
  log = require("../shared/logging"),
21
+ naming = require("../shared/naming"),
21
22
  ModuleResolver = require("../shared/relative-module-resolver"),
22
23
  autoconfig = require("./autoconfig.js"),
23
24
  ConfigFile = require("./config-file"),
@@ -97,17 +98,26 @@ function getModulesList(config, installESLint) {
97
98
  // Create a list of modules which should be installed based on config
98
99
  if (config.plugins) {
99
100
  for (const plugin of config.plugins) {
100
- modules[`eslint-plugin-${plugin}`] = "latest";
101
+ const moduleName = naming.normalizePackageName(plugin, "eslint-plugin");
102
+
103
+ modules[moduleName] = "latest";
101
104
  }
102
105
  }
103
- if (config.extends && config.extends.indexOf("eslint:") === -1) {
104
- const moduleName = `eslint-config-${config.extends}`;
105
-
106
- modules[moduleName] = "latest";
107
- Object.assign(
108
- modules,
109
- getPeerDependencies(`${moduleName}@latest`)
110
- );
106
+ if (config.extends) {
107
+ const extendList = Array.isArray(config.extends) ? config.extends : [config.extends];
108
+
109
+ for (const extend of extendList) {
110
+ if (extend.startsWith("eslint:") || extend.startsWith("plugin:")) {
111
+ continue;
112
+ }
113
+ const moduleName = naming.normalizePackageName(extend, "eslint-config");
114
+
115
+ modules[moduleName] = "latest";
116
+ Object.assign(
117
+ modules,
118
+ getPeerDependencies(`${moduleName}@latest`)
119
+ );
120
+ }
111
121
  }
112
122
 
113
123
  if (installESLint === false) {
@@ -262,7 +262,7 @@ function createDisableDirectives(options) {
262
262
  */
263
263
  function getDirectiveComments(filename, ast, ruleMapper) {
264
264
  const configuredRules = {};
265
- const enabledGlobals = {};
265
+ const enabledGlobals = Object.create(null);
266
266
  const exportedVariables = {};
267
267
  const problems = [];
268
268
  const disableDirectives = [];
@@ -549,8 +549,8 @@ class RuleTester {
549
549
  assert(false, `Invalid messageId '${error.messageId}'. Expected one of ${friendlyIDList}.`);
550
550
  }
551
551
  assert.strictEqual(
552
- error.messageId,
553
552
  message.messageId,
553
+ error.messageId,
554
554
  `messageId '${message.messageId}' does not match expected messageId '${error.messageId}'.`
555
555
  );
556
556
  if (hasOwnProperty(error, "data")) {
@@ -175,10 +175,10 @@ module.exports = {
175
175
  }
176
176
 
177
177
  /*
178
- * If the first token of the reutrn value is `{`,
178
+ * If the first token of the reutrn value is `{` or the return value is a sequence expression,
179
179
  * enclose the return value by parentheses to avoid syntax error.
180
180
  */
181
- if (astUtils.isOpeningBraceToken(firstValueToken)) {
181
+ if (astUtils.isOpeningBraceToken(firstValueToken) || blockBody[0].argument.type === "SequenceExpression") {
182
182
  fixes.push(
183
183
  fixer.insertTextBefore(firstValueToken, "("),
184
184
  fixer.insertTextAfter(lastValueToken, ")")
@@ -54,29 +54,31 @@ module.exports = {
54
54
  */
55
55
  function checkDotLocation(obj, prop, node) {
56
56
  const dot = sourceCode.getTokenBefore(prop);
57
- const textBeforeDot = sourceCode.getText().slice(obj.range[1], dot.range[0]);
57
+
58
+ // `obj` expression can be parenthesized, but those paren tokens are not a part of the `obj` node.
59
+ const tokenBeforeDot = sourceCode.getTokenBefore(dot);
60
+
61
+ const textBeforeDot = sourceCode.getText().slice(tokenBeforeDot.range[1], dot.range[0]);
58
62
  const textAfterDot = sourceCode.getText().slice(dot.range[1], prop.range[0]);
59
63
 
60
- if (dot.type === "Punctuator" && dot.value === ".") {
61
- if (onObject) {
62
- if (!astUtils.isTokenOnSameLine(obj, dot)) {
63
- const neededTextAfterObj = astUtils.isDecimalInteger(obj) ? " " : "";
64
-
65
- context.report({
66
- node,
67
- loc: dot.loc.start,
68
- messageId: "expectedDotAfterObject",
69
- fix: fixer => fixer.replaceTextRange([obj.range[1], prop.range[0]], `${neededTextAfterObj}.${textBeforeDot}${textAfterDot}`)
70
- });
71
- }
72
- } else if (!astUtils.isTokenOnSameLine(dot, prop)) {
64
+ if (onObject) {
65
+ if (!astUtils.isTokenOnSameLine(tokenBeforeDot, dot)) {
66
+ const neededTextAfterToken = astUtils.isDecimalIntegerNumericToken(tokenBeforeDot) ? " " : "";
67
+
73
68
  context.report({
74
69
  node,
75
70
  loc: dot.loc.start,
76
- messageId: "expectedDotBeforeProperty",
77
- fix: fixer => fixer.replaceTextRange([obj.range[1], prop.range[0]], `${textBeforeDot}${textAfterDot}.`)
71
+ messageId: "expectedDotAfterObject",
72
+ fix: fixer => fixer.replaceTextRange([tokenBeforeDot.range[1], prop.range[0]], `${neededTextAfterToken}.${textBeforeDot}${textAfterDot}`)
78
73
  });
79
74
  }
75
+ } else if (!astUtils.isTokenOnSameLine(dot, prop)) {
76
+ context.report({
77
+ node,
78
+ loc: dot.loc.start,
79
+ messageId: "expectedDotBeforeProperty",
80
+ fix: fixer => fixer.replaceTextRange([tokenBeforeDot.range[1], prop.range[0]], `${textBeforeDot}${textAfterDot}.`)
81
+ });
80
82
  }
81
83
  }
82
84
 
@@ -86,7 +88,9 @@ module.exports = {
86
88
  * @returns {void}
87
89
  */
88
90
  function checkNode(node) {
89
- checkDotLocation(node.object, node.property, node);
91
+ if (!node.computed) {
92
+ checkDotLocation(node.object, node.property, node);
93
+ }
90
94
  }
91
95
 
92
96
  return {
@@ -81,6 +81,8 @@ module.exports = {
81
81
  const PRECEDENCE_OF_ASSIGNMENT_EXPR = precedence({ type: "AssignmentExpression" });
82
82
  const PRECEDENCE_OF_UPDATE_EXPR = precedence({ type: "UpdateExpression" });
83
83
 
84
+ let reportsBuffer;
85
+
84
86
  /**
85
87
  * Determines if this rule should be enforced for a node given the current configuration.
86
88
  * @param {ASTNode} node - The node to be checked.
@@ -316,19 +318,33 @@ module.exports = {
316
318
  }
317
319
  }
318
320
 
319
- context.report({
320
- node,
321
- loc: leftParenToken.loc.start,
322
- messageId: "unexpected",
323
- fix(fixer) {
324
- const parenthesizedSource = sourceCode.text.slice(leftParenToken.range[1], rightParenToken.range[0]);
321
+ /**
322
+ * Finishes reporting
323
+ * @returns {void}
324
+ * @private
325
+ */
326
+ function finishReport() {
327
+ context.report({
328
+ node,
329
+ loc: leftParenToken.loc.start,
330
+ messageId: "unexpected",
331
+ fix(fixer) {
332
+ const parenthesizedSource = sourceCode.text.slice(leftParenToken.range[1], rightParenToken.range[0]);
333
+
334
+ return fixer.replaceTextRange([
335
+ leftParenToken.range[0],
336
+ rightParenToken.range[1]
337
+ ], (requiresLeadingSpace(node) ? " " : "") + parenthesizedSource + (requiresTrailingSpace(node) ? " " : ""));
338
+ }
339
+ });
340
+ }
325
341
 
326
- return fixer.replaceTextRange([
327
- leftParenToken.range[0],
328
- rightParenToken.range[1]
329
- ], (requiresLeadingSpace(node) ? " " : "") + parenthesizedSource + (requiresTrailingSpace(node) ? " " : ""));
330
- }
331
- });
342
+ if (reportsBuffer) {
343
+ reportsBuffer.reports.push({ node, finishReport });
344
+ return;
345
+ }
346
+
347
+ finishReport();
332
348
  }
333
349
 
334
350
  /**
@@ -498,6 +514,126 @@ module.exports = {
498
514
  }
499
515
  }
500
516
 
517
+ /**
518
+ * Finds the path from the given node to the specified ancestor.
519
+ * @param {ASTNode} node First node in the path.
520
+ * @param {ASTNode} ancestor Last node in the path.
521
+ * @returns {ASTNode[]} Path, including both nodes.
522
+ * @throws {Error} If the given node does not have the specified ancestor.
523
+ */
524
+ function pathToAncestor(node, ancestor) {
525
+ const path = [node];
526
+ let currentNode = node;
527
+
528
+ while (currentNode !== ancestor) {
529
+
530
+ currentNode = currentNode.parent;
531
+
532
+ /* istanbul ignore if */
533
+ if (currentNode === null) {
534
+ throw new Error("Nodes are not in the ancestor-descendant relationship.");
535
+ }
536
+
537
+ path.push(currentNode);
538
+ }
539
+
540
+ return path;
541
+ }
542
+
543
+ /**
544
+ * Finds the path from the given node to the specified descendant.
545
+ * @param {ASTNode} node First node in the path.
546
+ * @param {ASTNode} descendant Last node in the path.
547
+ * @returns {ASTNode[]} Path, including both nodes.
548
+ * @throws {Error} If the given node does not have the specified descendant.
549
+ */
550
+ function pathToDescendant(node, descendant) {
551
+ return pathToAncestor(descendant, node).reverse();
552
+ }
553
+
554
+ /**
555
+ * Checks whether the syntax of the given ancestor of an 'in' expression inside a for-loop initializer
556
+ * is preventing the 'in' keyword from being interpreted as a part of an ill-formed for-in loop.
557
+ *
558
+ * @param {ASTNode} node Ancestor of an 'in' expression.
559
+ * @param {ASTNode} child Child of the node, ancestor of the same 'in' expression or the 'in' expression itself.
560
+ * @returns {boolean} True if the keyword 'in' would be interpreted as the 'in' operator, without any parenthesis.
561
+ */
562
+ function isSafelyEnclosingInExpression(node, child) {
563
+ switch (node.type) {
564
+ case "ArrayExpression":
565
+ case "ArrayPattern":
566
+ case "BlockStatement":
567
+ case "ObjectExpression":
568
+ case "ObjectPattern":
569
+ case "TemplateLiteral":
570
+ return true;
571
+ case "ArrowFunctionExpression":
572
+ case "FunctionExpression":
573
+ return node.params.includes(child);
574
+ case "CallExpression":
575
+ case "NewExpression":
576
+ return node.arguments.includes(child);
577
+ case "MemberExpression":
578
+ return node.computed && node.property === child;
579
+ case "ConditionalExpression":
580
+ return node.consequent === child;
581
+ default:
582
+ return false;
583
+ }
584
+ }
585
+
586
+ /**
587
+ * Starts a new reports buffering. Warnings will be stored in a buffer instead of being reported immediately.
588
+ * An additional logic that requires multiple nodes (e.g. a whole subtree) may dismiss some of the stored warnings.
589
+ *
590
+ * @returns {void}
591
+ */
592
+ function startNewReportsBuffering() {
593
+ reportsBuffer = {
594
+ upper: reportsBuffer,
595
+ inExpressionNodes: [],
596
+ reports: []
597
+ };
598
+ }
599
+
600
+ /**
601
+ * Ends the current reports buffering.
602
+ * @returns {void}
603
+ */
604
+ function endCurrentReportsBuffering() {
605
+ const { upper, inExpressionNodes, reports } = reportsBuffer;
606
+
607
+ if (upper) {
608
+ upper.inExpressionNodes.push(...inExpressionNodes);
609
+ upper.reports.push(...reports);
610
+ } else {
611
+
612
+ // flush remaining reports
613
+ reports.forEach(({ finishReport }) => finishReport());
614
+ }
615
+
616
+ reportsBuffer = upper;
617
+ }
618
+
619
+ /**
620
+ * Checks whether the given node is in the current reports buffer.
621
+ * @param {ASTNode} node Node to check.
622
+ * @returns {boolean} True if the node is in the current buffer, false otherwise.
623
+ */
624
+ function isInCurrentReportsBuffer(node) {
625
+ return reportsBuffer.reports.some(r => r.node === node);
626
+ }
627
+
628
+ /**
629
+ * Removes the given node from the current reports buffer.
630
+ * @param {ASTNode} node Node to remove.
631
+ * @returns {void}
632
+ */
633
+ function removeFromCurrentReportsBuffer(node) {
634
+ reportsBuffer.reports = reportsBuffer.reports.filter(r => r.node !== node);
635
+ }
636
+
501
637
  return {
502
638
  ArrayExpression(node) {
503
639
  node.elements
@@ -540,7 +676,14 @@ module.exports = {
540
676
  }
541
677
  },
542
678
 
543
- BinaryExpression: checkBinaryLogical,
679
+ BinaryExpression(node) {
680
+ if (reportsBuffer && node.operator === "in") {
681
+ reportsBuffer.inExpressionNodes.push(node);
682
+ }
683
+
684
+ checkBinaryLogical(node);
685
+ },
686
+
544
687
  CallExpression: checkCallNew,
545
688
 
546
689
  ConditionalExpression(node) {
@@ -602,10 +745,6 @@ module.exports = {
602
745
  },
603
746
 
604
747
  ForStatement(node) {
605
- if (node.init && hasExcessParens(node.init)) {
606
- report(node.init);
607
- }
608
-
609
748
  if (node.test && hasExcessParens(node.test) && !isCondAssignException(node)) {
610
749
  report(node.test);
611
750
  }
@@ -613,6 +752,81 @@ module.exports = {
613
752
  if (node.update && hasExcessParens(node.update)) {
614
753
  report(node.update);
615
754
  }
755
+
756
+ if (node.init) {
757
+ startNewReportsBuffering();
758
+
759
+ if (hasExcessParens(node.init)) {
760
+ report(node.init);
761
+ }
762
+ }
763
+ },
764
+
765
+ "ForStatement > *.init:exit"(node) {
766
+
767
+ /*
768
+ * Removing parentheses around `in` expressions might change semantics and cause errors.
769
+ *
770
+ * For example, this valid for loop:
771
+ * for (let a = (b in c); ;);
772
+ * after removing parentheses would be treated as an invalid for-in loop:
773
+ * for (let a = b in c; ;);
774
+ */
775
+
776
+ if (reportsBuffer.reports.length) {
777
+ reportsBuffer.inExpressionNodes.forEach(inExpressionNode => {
778
+ const path = pathToDescendant(node, inExpressionNode);
779
+ let nodeToExclude;
780
+
781
+ for (let i = 0; i < path.length; i++) {
782
+ const pathNode = path[i];
783
+
784
+ if (i < path.length - 1) {
785
+ const nextPathNode = path[i + 1];
786
+
787
+ if (isSafelyEnclosingInExpression(pathNode, nextPathNode)) {
788
+
789
+ // The 'in' expression in safely enclosed by the syntax of its ancestor nodes (e.g. by '{}' or '[]').
790
+ return;
791
+ }
792
+ }
793
+
794
+ if (isParenthesised(pathNode)) {
795
+ if (isInCurrentReportsBuffer(pathNode)) {
796
+
797
+ // This node was supposed to be reported, but parentheses might be necessary.
798
+
799
+ if (isParenthesisedTwice(pathNode)) {
800
+
801
+ /*
802
+ * This node is parenthesised twice, it certainly has at least one pair of `extra` parentheses.
803
+ * If the --fix option is on, the current fixing iteration will remove only one pair of parentheses.
804
+ * The remaining pair is safely enclosing the 'in' expression.
805
+ */
806
+ return;
807
+ }
808
+
809
+ // Exclude the outermost node only.
810
+ if (!nodeToExclude) {
811
+ nodeToExclude = pathNode;
812
+ }
813
+
814
+ // Don't break the loop here, there might be some safe nodes or parentheses that will stay inside.
815
+
816
+ } else {
817
+
818
+ // This node will stay parenthesised, the 'in' expression in safely enclosed by '()'.
819
+ return;
820
+ }
821
+ }
822
+ }
823
+
824
+ // Exclude the node from the list (i.e. treat parentheses as necessary)
825
+ removeFromCurrentReportsBuffer(nodeToExclude);
826
+ });
827
+ }
828
+
829
+ endCurrentReportsBuffering();
616
830
  },
617
831
 
618
832
  IfStatement(node) {
@@ -664,6 +878,16 @@ module.exports = {
664
878
  }).forEach(property => report(property.value));
665
879
  },
666
880
 
881
+ Property(node) {
882
+ if (node.computed) {
883
+ const { key } = node;
884
+
885
+ if (key && hasExcessParens(key) && precedence(key) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
886
+ report(key);
887
+ }
888
+ }
889
+ },
890
+
667
891
  ReturnStatement(node) {
668
892
  const returnToken = sourceCode.getFirstToken(node);
669
893
 
@@ -67,7 +67,8 @@ module.exports = {
67
67
  let node = reference.identifier;
68
68
  let parent = node.parent;
69
69
 
70
- while (parent && !stopNodePattern.test(parent.type)) {
70
+ while (parent && (!stopNodePattern.test(parent.type) ||
71
+ parent.type === "ForInStatement" || parent.type === "ForOfStatement")) {
71
72
  switch (parent.type) {
72
73
 
73
74
  // e.g. foo.a = 0;
@@ -85,6 +86,16 @@ module.exports = {
85
86
  }
86
87
  break;
87
88
 
89
+ // e.g. for (foo.a in b) {}
90
+ case "ForInStatement":
91
+ case "ForOfStatement":
92
+ if (parent.left === node) {
93
+ return true;
94
+ }
95
+
96
+ // this is a stop node for parent.right and parent.body
97
+ return false;
98
+
88
99
  // EXCLUDES: e.g. cache.get(foo.a).b = 0;
89
100
  case "CallExpression":
90
101
  if (parent.callee !== node) {
@@ -420,8 +420,9 @@ module.exports = {
420
420
 
421
421
  let shouldFix = varDeclParent &&
422
422
 
423
- // Don't do a fix unless the variable is initialized (or it's in a for-in or for-of loop)
424
- (varDeclParent.parent.type === "ForInStatement" || varDeclParent.parent.type === "ForOfStatement" || varDeclParent.declarations[0].init) &&
423
+ // Don't do a fix unless all variables in the declarations are initialized (or it's in a for-in or for-of loop)
424
+ (varDeclParent.parent.type === "ForInStatement" || varDeclParent.parent.type === "ForOfStatement" ||
425
+ varDeclParent.declarations.every(declaration => declaration.init)) &&
425
426
 
426
427
  /*
427
428
  * If options.destructuring is "all", then this warning will not occur unless
@@ -450,7 +451,12 @@ module.exports = {
450
451
  node,
451
452
  messageId: "useConst",
452
453
  data: node,
453
- fix: shouldFix ? fixer => fixer.replaceText(sourceCode.getFirstToken(varDeclParent), "const") : null
454
+ fix: shouldFix
455
+ ? fixer => fixer.replaceText(
456
+ sourceCode.getFirstToken(varDeclParent, t => t.value === varDeclParent.kind),
457
+ "const"
458
+ )
459
+ : null
454
460
  });
455
461
  });
456
462
  }
@@ -37,6 +37,8 @@ const LINEBREAKS = new Set(["\r\n", "\r", "\n", "\u2028", "\u2029"]);
37
37
  // A set of node types that can contain a list of statements
38
38
  const STATEMENT_LIST_PARENTS = new Set(["Program", "BlockStatement", "SwitchCase"]);
39
39
 
40
+ const DECIMAL_INTEGER_PATTERN = /^(0|[1-9]\d*)$/u;
41
+
40
42
  /**
41
43
  * Checks reference if is non initializer and writable.
42
44
  * @param {Reference} reference - A reference to check.
@@ -283,6 +285,16 @@ function isCommaToken(token) {
283
285
  return token.value === "," && token.type === "Punctuator";
284
286
  }
285
287
 
288
+ /**
289
+ * Checks if the given token is a dot token or not.
290
+ *
291
+ * @param {Token} token - The token to check.
292
+ * @returns {boolean} `true` if the token is a dot token.
293
+ */
294
+ function isDotToken(token) {
295
+ return token.value === "." && token.type === "Punctuator";
296
+ }
297
+
286
298
  /**
287
299
  * Checks if the given token is a semicolon token or not.
288
300
  *
@@ -462,12 +474,14 @@ module.exports = {
462
474
  isColonToken,
463
475
  isCommaToken,
464
476
  isCommentToken,
477
+ isDotToken,
465
478
  isKeywordToken,
466
479
  isNotClosingBraceToken: negate(isClosingBraceToken),
467
480
  isNotClosingBracketToken: negate(isClosingBracketToken),
468
481
  isNotClosingParenToken: negate(isClosingParenToken),
469
482
  isNotColonToken: negate(isColonToken),
470
483
  isNotCommaToken: negate(isCommaToken),
484
+ isNotDotToken: negate(isDotToken),
471
485
  isNotOpeningBraceToken: negate(isOpeningBraceToken),
472
486
  isNotOpeningBracketToken: negate(isOpeningBracketToken),
473
487
  isNotOpeningParenToken: negate(isOpeningParenToken),
@@ -988,7 +1002,18 @@ module.exports = {
988
1002
  * '5' // false
989
1003
  */
990
1004
  isDecimalInteger(node) {
991
- return node.type === "Literal" && typeof node.value === "number" && /^(0|[1-9]\d*)$/u.test(node.raw);
1005
+ return node.type === "Literal" && typeof node.value === "number" &&
1006
+ DECIMAL_INTEGER_PATTERN.test(node.raw);
1007
+ },
1008
+
1009
+ /**
1010
+ * Determines whether this token is a decimal integer numeric token.
1011
+ * This is similar to isDecimalInteger(), but for tokens.
1012
+ * @param {Token} token - The token to check.
1013
+ * @returns {boolean} `true` if this token is a decimal integer.
1014
+ */
1015
+ isDecimalIntegerNumericToken(token) {
1016
+ return token.type === "Numeric" && DECIMAL_INTEGER_PATTERN.test(token.value);
992
1017
  },
993
1018
 
994
1019
  /**
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint",
3
- "version": "6.0.1",
3
+ "version": "6.1.0",
4
4
  "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
5
5
  "description": "An AST-based pattern checker for JavaScript.",
6
6
  "bin": {
@@ -49,7 +49,7 @@
49
49
  "cross-spawn": "^6.0.5",
50
50
  "debug": "^4.0.1",
51
51
  "doctrine": "^3.0.0",
52
- "eslint-scope": "^4.0.3",
52
+ "eslint-scope": "^5.0.0",
53
53
  "eslint-utils": "^1.3.1",
54
54
  "eslint-visitor-keys": "^1.0.0",
55
55
  "espree": "^6.0.0",
@@ -57,28 +57,29 @@
57
57
  "esutils": "^2.0.2",
58
58
  "file-entry-cache": "^5.0.1",
59
59
  "functional-red-black-tree": "^1.0.1",
60
- "glob-parent": "^3.1.0",
60
+ "glob-parent": "^5.0.0",
61
61
  "globals": "^11.7.0",
62
62
  "ignore": "^4.0.6",
63
63
  "import-fresh": "^3.0.0",
64
64
  "imurmurhash": "^0.1.4",
65
- "inquirer": "^6.2.2",
65
+ "inquirer": "^6.4.1",
66
66
  "is-glob": "^4.0.0",
67
67
  "js-yaml": "^3.13.1",
68
68
  "json-stable-stringify-without-jsonify": "^1.0.1",
69
69
  "levn": "^0.3.0",
70
- "lodash": "^4.17.11",
70
+ "lodash": "^4.17.14",
71
71
  "minimatch": "^3.0.4",
72
72
  "mkdirp": "^0.5.1",
73
73
  "natural-compare": "^1.4.0",
74
74
  "optionator": "^0.8.2",
75
75
  "progress": "^2.0.0",
76
76
  "regexpp": "^2.0.1",
77
- "semver": "^5.5.1",
78
- "strip-ansi": "^4.0.0",
79
- "strip-json-comments": "^2.0.1",
77
+ "semver": "^6.1.2",
78
+ "strip-ansi": "^5.2.0",
79
+ "strip-json-comments": "^3.0.1",
80
80
  "table": "^5.2.3",
81
- "text-table": "^0.2.0"
81
+ "text-table": "^0.2.0",
82
+ "v8-compile-cache": "^2.0.3"
82
83
  },
83
84
  "devDependencies": {
84
85
  "@babel/core": "^7.4.3",
@@ -109,22 +110,22 @@
109
110
  "leche": "^2.2.3",
110
111
  "lint-staged": "^8.1.5",
111
112
  "load-perf": "^0.2.0",
112
- "markdownlint": "^0.13.0",
113
- "markdownlint-cli": "^0.15.0",
114
- "metro-memory-fs": "^0.53.1",
113
+ "markdownlint": "^0.15.0",
114
+ "markdownlint-cli": "^0.17.0",
115
+ "metro-memory-fs": "^0.54.1",
115
116
  "mocha": "^6.1.2",
116
117
  "mocha-junit-reporter": "^1.23.0",
117
118
  "npm-license": "^0.3.3",
118
- "nyc": "^13.3.0",
119
+ "nyc": "^14.1.1",
119
120
  "proxyquire": "^2.0.1",
120
- "puppeteer": "^1.14.0",
121
- "recast": "^0.17.6",
121
+ "puppeteer": "^1.18.0",
122
+ "recast": "^0.18.1",
122
123
  "regenerator-runtime": "^0.13.2",
123
124
  "shelljs": "^0.8.2",
124
125
  "sinon": "^7.3.2",
125
126
  "temp": "^0.9.0",
126
- "webpack": "^4.29.6",
127
- "webpack-cli": "^3.3.0",
127
+ "webpack": "^4.35.0",
128
+ "webpack-cli": "^3.3.5",
128
129
  "yorkie": "^2.0.0"
129
130
  },
130
131
  "keywords": [