Skip to content

Commit 9d4f4e4

Browse files
royriojasroyriojas
authored andcommitted
New: Implement cache in order to only operate on changed files since previous run. (fixes #2998)
1 parent 11fe628 commit 9d4f4e4

File tree

13 files changed

+347
-55
lines changed

13 files changed

+347
-55
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ tmp/
99
jsdoc/
1010
versions.json
1111
*.iml
12+
.eslintcache
13+
.cache

Makefile.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -390,10 +390,20 @@ target.all = function() {
390390

391391
target.lint = function() {
392392
var errors = 0,
393+
makeFileCache = " ",
394+
jsCache = " ",
395+
testCache = " ",
393396
lastReturn;
394397

398+
// using the cache locally to speed up linting process
399+
if (!process.env.TRAVIS) {
400+
makeFileCache = " --cache --cache-file .cache/makefile_cache ";
401+
jsCache = " --cache --cache-file .cache/js_cache ";
402+
testCache = " --cache --cache-file .cache/test_cache ";
403+
}
404+
395405
echo("Validating Makefile.js");
396-
lastReturn = exec(ESLINT + MAKEFILE);
406+
lastReturn = exec(ESLINT + makeFileCache + MAKEFILE);
397407
if (lastReturn.code !== 0) {
398408
errors++;
399409
}
@@ -411,13 +421,13 @@ target.lint = function() {
411421
}
412422

413423
echo("Validating JavaScript files");
414-
lastReturn = exec(ESLINT + JS_FILES);
424+
lastReturn = exec(ESLINT + jsCache + JS_FILES);
415425
if (lastReturn.code !== 0) {
416426
errors++;
417427
}
418428

419429
echo("Validating JavaScript test files");
420-
lastReturn = exec(ESLINT + TEST_FILES);
430+
lastReturn = exec(ESLINT + testCache + TEST_FILES);
421431
if (lastReturn.code !== 0) {
422432
errors++;
423433
}

docs/developer-guide/nodejs-api.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ The `CLIEngine` is a constructor, and you can create a new instance by passing i
122122
* `rules` - An object of rules to use (default: null). Corresponds to `--rule`.
123123
* `useEslintrc` - Set to false to disable use of `.eslintrc` files (default: true). Corresponds to `--no-eslintrc`.
124124
* `parser` - Specify the parser to be used (default: `espree`). Corresponds to `--parser`.
125+
* `cache` - Operate only on changed files (default: `false`). Corresponds to `--cache`.
126+
* `cacheFile` - Name of the file where the cache will be stored (default: `.eslintcache`). Corresponds to `--cache-file`.
125127

126128
For example:
127129

docs/user-guide/command-line-interface.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ Basic configuration:
3030
--ext [String] Specify JavaScript file extensions - default: .js
3131
--global [String] Define global variables
3232
--parser String Specify the parser to be used - default: espree
33+
--cache Only check changed files - default: false
34+
--cache-file String Path to the cache file - default: .eslintcache
3335
3436
Specifying rules and plugins:
3537
--rulesdir [path::String] Use additional rules from this directory
@@ -58,6 +60,7 @@ Miscellaneous:
5860
--init Run config initialization wizard - default: false
5961
-h, --help Show help
6062
-v, --version Outputs the version number
63+
6164
```
6265

6366
### Basic configuration
@@ -275,6 +278,14 @@ Example:
275278

276279
eslint -v
277280

281+
### `--cache`
282+
283+
Store the info about processed files in order to only operate on the changed ones.
284+
285+
### `--cache-file`
286+
287+
Path to the cache file. If none specified `.eslintcache` will be used. The file will be created in the directory where the `eslint` command is executed.
288+
278289
## Ignoring files from linting
279290

280291
ESLint supports `.eslintignore` files to exclude files from the linting process when ESLint operates on a directory. Files given as individual CLI arguments will be exempt from exclusion. The `.eslintignore` file is a plain text file containing one pattern per line. It can be located in any of the target directory's ancestors; it will affect files in its containing directory as well as all sub-directories. Here's a simple example of a `.eslintignore` file:

lib/cli-engine.js

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ var fs = require("fs"),
2929
IgnoredPaths = require("./ignored-paths"),
3030
Config = require("./config"),
3131
util = require("./util"),
32-
validator = require("./config-validator");
32+
validator = require("./config-validator"),
33+
fileEntryCache = require("file-entry-cache");
3334

3435
var DEFAULT_PARSER = require("../conf/eslint.json").parser;
3536

@@ -81,7 +82,9 @@ var defaultOptions = {
8182
extensions: [".js"],
8283
ignore: true,
8384
ignorePath: null,
84-
parser: DEFAULT_PARSER
85+
parser: DEFAULT_PARSER,
86+
cache: false,
87+
cacheFile: ".eslintcache"
8588
},
8689
loadedPlugins = Object.create(null);
8790

@@ -302,6 +305,17 @@ function CLIEngine(options) {
302305
*/
303306
this.options = assign(Object.create(defaultOptions), options || {});
304307

308+
/**
309+
* cache used to not operate on files that haven't changed since last successful
310+
* execution (e.g. file passed with no errors and no warnings
311+
* @type {Object}
312+
*/
313+
this._fileCache = fileEntryCache.create(path.resolve(this.options.cacheFile)); // eslint-disable-line no-underscore-dangle
314+
315+
if (!this.options.useCache) {
316+
this._fileCache.destroy(); // eslint-disable-line no-underscore-dangle
317+
}
318+
305319
// load in additional rules
306320
if (this.options.rulePaths) {
307321
this.options.rulePaths.forEach(function(rulesdir) {
@@ -399,17 +413,20 @@ CLIEngine.prototype = {
399413
* @returns {Object} The results for all files that were linted.
400414
*/
401415
executeOnFiles: function(files) {
402-
403416
var results = [],
404417
processed = {},
405418
options = this.options,
419+
fileCache = this._fileCache, // eslint-disable-line no-underscore-dangle
406420
configHelper = new Config(options),
407421
ignoredPaths = options.ignore ? IgnoredPaths.load(options).patterns : [],
408422
extensions = options.extensions.map(function(ext) {
409423
return ext.charAt(0) === "." ? ext : "." + ext;
410424
}),
411425
globOptions,
412-
stats;
426+
stats,
427+
startTime;
428+
429+
startTime = Date.now();
413430

414431
files = files.map(processPath);
415432
ignoredPaths = ignoredPaths.map(processPath);
@@ -431,10 +448,52 @@ CLIEngine.prototype = {
431448
return;
432449
}
433450

451+
if (options.useCache) {
452+
// get the descriptor for this file
453+
// with the metadata and the flag that determines if
454+
// the file has changed
455+
var descriptor = fileCache.getFileDescriptor(filename);
456+
if (!descriptor.changed) {
457+
debug("Skipping file since hasn't changed: " + filename);
458+
459+
// Adding the filename to the processed hashmap
460+
// so the reporting is not affected (showing a warning about .eslintignore being used
461+
// when it is not really used)
462+
463+
processed[filename] = true;
464+
465+
// Add the the cached results (always will be 0 error and 0 warnings)
466+
// cause we don't save to cache files that failed
467+
// to guarantee that next execution will process those files as well
468+
results.push(descriptor.meta.results);
469+
470+
// move to the next file
471+
return;
472+
}
473+
}
474+
434475
debug("Processing " + filename);
435476

436477
processed[filename] = true;
437-
results.push(processFile(filename, configHelper));
478+
var res = processFile(filename, configHelper);
479+
480+
if (options.useCache) {
481+
// if a file contains errors or warnings we don't want to
482+
// store the file in the cache so we can guarantee that
483+
// next execution will also operate on this file
484+
if ( res.errorCount > 0 || res.warningCount > 0 ) {
485+
debug("File has problems, skipping it: " + filename);
486+
// remove the entry from the cache
487+
fileCache.removeEntry( filename );
488+
} else {
489+
// since the file passed we store the result here
490+
// TODO: check this as we might not need to store the
491+
// successful runs as it will always should be 0 error 0 warnings
492+
descriptor.meta.results = res;
493+
}
494+
}
495+
496+
results.push(res);
438497
}
439498

440499
files.forEach(function(pattern) {
@@ -453,6 +512,13 @@ CLIEngine.prototype = {
453512

454513
stats = calculateStatsPerRun(results);
455514

515+
if (options.useCache) {
516+
// persist the cache to disk
517+
fileCache.reconcile();
518+
}
519+
520+
debug("Linting complete in: " + (Date.now() - startTime) + "ms");
521+
456522
return {
457523
results: results,
458524
errorCount: stats.errorCount,

lib/cli.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ function translateOptions(cliOptions) {
4949
configFile: cliOptions.config,
5050
rulePaths: cliOptions.rulesdir,
5151
useEslintrc: cliOptions.eslintrc,
52-
parser: cliOptions.parser
52+
parser: cliOptions.parser,
53+
useCache: cliOptions.cache,
54+
cacheFile: cliOptions.cacheFile
5355
};
5456
}
5557

lib/options.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,18 @@ module.exports = optionator({
5757
default: "espree",
5858
description: "Specify the parser to be used"
5959
},
60+
{
61+
option: "cache",
62+
type: "Boolean",
63+
default: "false",
64+
description: "Only check changed files"
65+
},
66+
{
67+
option: "cache-file",
68+
type: "String",
69+
default: ".eslintcache",
70+
description: "Path to the cache file"
71+
},
6072
{
6173
heading: "Specifying rules and plugins"
6274
},

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"globals": "^8.6.0",
4949
"handlebars": "^3.0.3",
5050
"inquirer": "^0.9.0",
51+
"file-entry-cache": "^1.1.1",
5152
"is-my-json-valid": "^2.10.0",
5253
"is-resolvable": "^1.0.0",
5354
"js-yaml": "^3.2.5",

tests/fixtures/cache/src/fail-file.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
var abc = 'some value'
2+
console.log(foo);

tests/fixtures/cache/src/test-file.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
var abc = 2;
2+
3+
console.log(abc);

0 commit comments

Comments
 (0)