Repository: eslint/eslint (js)

Issue:
Some filesystem operations are unnecessarily performed multiple times
<!--
This template is for bug reports. If you are reporting a bug, please continue on. If you are here for another reason, please see below:

1. To propose a new rule: http://eslint.org/docs/developer-guide/contributing/new-rules
2. To request a change: http://eslint.org/docs/developer-guide/contributing/changes
3. If you have any questions, please stop by our chatroom: https://gitter.im/eslint/eslint

Note that leaving sections blank will make it difficult for us to troubleshoot and we may have to close the issue.
-->

**Tell us about your environment**

* **ESLint Version:** master
* **Node Version:** 7.8.0
* **npm Version:** 4.2.0

**What did you do? Please include the actual source code causing the issue.**

I added `console.log(filepath)` statement before [this line](https://github.com/eslint/eslint/blob/8569a9062fcfd26bd09226b3753ff23c424682ab/lib/ignored-paths.js#L103).

Then I ran eslint on some files in the repo:

```
$ eslint tests/ lib/ bin/ packages/ conf/
```

**What did you expect to happen?**

I expected the `.eslintignore` file to only be read once.

**What actually happened? Please include the actual, raw output from ESLint.**

The same `.eslintignore` file was synchronously read from the filesystem 10 times. This is certainly hurting performance.

The same thing happens [here](https://github.com/eslint/eslint/blob/8569a9062fcfd26bd09226b3753ff23c424682ab/lib/config/config-file.js#L70) -- the same config file gets read from the filesystem multiple times when multiple globs are passed to the CLI.

It seems like we can probably get some easy performance boosts here by caching filesystem results.

Pull request:
Chore: Cache fs reads in ignored-paths (fixes #8363)
**What is the purpose of this pull request? (put an "X" next to item)**

[ ] Documentation update
[ ] Bug fix ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/bug-report.md))
[ ] New rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-proposal.md))
[ ] Changes an existing rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-change-proposal.md))
[ ] Add autofixing to a rule
[ ] Add a CLI option
[ ] Add something to the core
[X] Other, please explain:
Per #8363 , this pr introduces caching fs reads to the ignored-paths.js file. This is to reduce the synchronous reads which would affect performance.


**What changes did you make? (Give an overview)**
Per #8363 , this pr introduces caching fs reads to the ignored-paths.js file. This is to reduce the synchronous reads which would affect performance.

**Is there anything you'd like reviewers to focus on?**
config-file.js - I am unclear on how to verify the additional reads since #8363 also mentions similar repeats in this file.

Also should there be any tests related to caching?

Any suggestions on on refactoring or any other approaches is appreciated.

Let me know what you think and how I can make better.



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e54322fbe..7626f8970 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,14 @@
+v4.0.0 - June 11, 2017
+
+* 4aefb49 Chore: avoid using deprecated rules on ESLint codebase (#8708) (Teddy Katz)
+* 389feba Chore: upgrade deps. (#8684) (薛定谔的猫)
+* 3da7b5e Fix: Semi-Style only check for comments when tokens exist (fixes #8696) (#8697) (Reyad Attiyat)
+* 3cfe9ee Fix: Add space between async and param on fix (fixes #8682) (#8693) (Reyad Attiyat)
+* c702858 Chore: enable no-multiple-empty-lines on ESLint codebase (#8694) (Teddy Katz)
+* 34c4020 Update: Add support for parens on left side for-loops (fixes: #8393) (#8679) (Victor Hom)
+* 735cd09 Docs: Correct the comment in an example for `no-mixed-requires` (#8686) (Fangzhou Li)
+* 026f048 Chore: remove dead code from prefer-const (#8683) (Teddy Katz)
+
 v4.0.0-rc.0 - June 2, 2017
 
 * 0058b0f8 Update: add --fix to no-debugger (#8660) (薛定谔的猫)
diff --git a/lib/ignored-paths.js b/lib/ignored-paths.js
index 0d9152495..d2d89bd98 100644
--- a/lib/ignored-paths.js
+++ b/lib/ignored-paths.js
@@ -37,7 +37,6 @@ const DEFAULT_OPTIONS = {
     cwd: process.cwd()
 };
 
-
 //------------------------------------------------------------------------------
 // Helpers
 //------------------------------------------------------------------------------
@@ -80,6 +79,7 @@ class IgnoredPaths {
      */
     constructor(options) {
         options = mergeDefaultOptions(options);
+        this.cache = {};
 
         /**
          * add pattern to node-ignore instance
@@ -91,17 +91,6 @@ class IgnoredPaths {
             return ig.addPattern(pattern);
         }
 
-        /**
-         * add ignore file to node-ignore instance
-         * @param {Object} ig, instance of node-ignore
-         * @param {string} filepath, file to add to ig
-         * @returns {array} raw ignore rules
-         */
-        function addIgnoreFile(ig, filepath) {
-            ig.ignoreFiles.push(filepath);
-            return ig.add(fs.readFileSync(filepath, "utf8"));
-        }
-
         this.defaultPatterns = [].concat(DEFAULT_IGNORE_DIRS, options.patterns || []);
         this.baseDir = options.cwd;
 
@@ -155,8 +144,8 @@ class IgnoredPaths {
             if (ignorePath) {
                 debug(`Adding ${ignorePath}`);
                 this.baseDir = path.dirname(path.resolve(options.cwd, ignorePath));
-                addIgnoreFile(this.ig.custom, ignorePath);
-                addIgnoreFile(this.ig.default, ignorePath);
+                this.addIgnoreFile(this.ig.custom, ignorePath);
+                this.addIgnoreFile(this.ig.default, ignorePath);
             }
 
             if (options.ignorePattern) {
@@ -168,6 +157,29 @@ class IgnoredPaths {
         this.options = options;
     }
 
+    /**
+     * read ignore filepath
+     * @param {string} filePath, file to add to ig
+     * @returns {array} raw ignore rules
+     */
+    readIgnoreFile(filePath) {
+        if (typeof this.cache[filePath] === "undefined") {
+            this.cache[filePath] = fs.readFileSync(filePath, "utf8");
+        }
+        return this.cache[filePath];
+    }
+
+    /**
+     * add ignore file to node-ignore instance
+     * @param {Object} ig, instance of node-ignore
+     * @param {string} filePath, file to add to ig
+     * @returns {array} raw ignore rules
+     */
+    addIgnoreFile(ig, filePath) {
+        ig.ignoreFiles.push(filePath);
+        return ig.add(this.readIgnoreFile(filePath));
+    }
+
     /**
      * Determine whether a file path is included in the default or custom ignore patterns
      * @param {string} filepath Path to check
diff --git a/package.json b/package.json
index 8134f00e6..26dbdd8c8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "eslint",
-  "version": "4.0.0-rc.0",
+  "version": "4.0.0",
   "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
   "description": "An AST-based pattern checker for JavaScript.",
   "bin": {
diff --git a/packages/eslint-config-eslint/default.yml b/packages/eslint-config-eslint/default.yml
index 4417add55..9180f7525 100644
--- a/packages/eslint-config-eslint/default.yml
+++ b/packages/eslint-config-eslint/default.yml
@@ -36,7 +36,6 @@ rules:
     }]
     max-statements-per-line: "error"
     new-cap: "error"
-    newline-after-var: "error"
     new-parens: "error"
     no-alert: "error"
     no-array-constructor: "error"
@@ -62,7 +61,6 @@ rules:
     no-multi-spaces: "error"
     no-multi-str: "error"
     no-multiple-empty-lines: ["error", {max: 2, maxBOF: 0, maxEOF: 0}]
-    no-native-reassign: "off"
     no-nested-ternary: "error"
     no-new: "error"
     no-new-func: "error"
@@ -101,6 +99,19 @@ rules:
     object-shorthand: "error"
     one-var-declaration-per-line: "error"
     operator-linebreak: "error"
+    padding-line-between-statements: [
+        "error",
+        {
+            blankLine: "always",
+            prev: ["const", "let", "var"],
+            next: "*"
+        },
+        {
+            blankLine: "any",
+            prev: ["const", "let", "var"],
+            next: ["const", "let", "var"]
+        }
+    ]
     prefer-arrow-callback: "error"
     prefer-const: "error"
     prefer-promise-reject-errors: "error"


Test patch:
diff --git a/tests/lib/ignored-paths.js b/tests/lib/ignored-paths.js
index 07d96672d..1631d37f4 100644
--- a/tests/lib/ignored-paths.js
+++ b/tests/lib/ignored-paths.js
@@ -164,6 +164,30 @@ describe("IgnoredPaths", () => {
                 )
             );
         });
+
+
+    });
+
+    describe("caching file reads", () => {
+
+        let readFileSyncCount;
+
+        before(() => {
+            readFileSyncCount = sinon.spy(fs, "readFileSync");
+        });
+
+        after(() => {
+            readFileSyncCount.restore();
+        });
+
+        it("should cache readFileSync on same file paths", () => {
+            const ignoreFilePath = getFixturePath(".eslintignore");
+            const ignoredPaths = new IgnoredPaths({ ignore: true, cwd: getFixturePath() });
+
+            ignoredPaths.readIgnoreFile(ignoreFilePath);
+            assert.isTrue(ignoredPaths.contains(ignoreFilePath));
+            sinon.assert.calledOnce(readFileSyncCount);
+        });
     });
 
     describe("initialization with ignorePattern", () => {