Skip to content

Commit

Permalink
Update: cache-file accepts a directory. (fixes #4241)
Browse files Browse the repository at this point in the history
  • Loading branch information
royriojas committed Oct 28, 2015
1 parent 34e8700 commit b5e66da
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 19 deletions.
3 changes: 2 additions & 1 deletion docs/developer-guide/nodejs-api.md
Expand Up @@ -129,7 +129,8 @@ The `CLIEngine` is a constructor, and you can create a new instance by passing i
* `useEslintrc` - Set to false to disable use of `.eslintrc` files (default: true). Corresponds to `--no-eslintrc`.
* `parser` - Specify the parser to be used (default: `espree`). Corresponds to `--parser`.
* `cache` - Operate only on changed files (default: `false`). Corresponds to `--cache`.
* `cacheFile` - Name of the file where the cache will be stored (default: `.eslintcache`). Corresponds to `--cache-file`.
* `cacheFile` - Name of the file where the cache will be stored (default: `.eslintcache`). Corresponds to `--cache-file`. Deprecated: use `cacheLocation` instead.
* `cacheLocation` - Name of the file or directory where the cache will be stored (default: `.estlintcache`). Correspond to `--cache-location`

For example:

Expand Down
18 changes: 16 additions & 2 deletions docs/user-guide/command-line-interface.md
Expand Up @@ -34,7 +34,9 @@ Basic configuration:
Caching:
--cache Only check changed files - default: false
--cache-file String Path to the cache file - default: .eslintcache
--cache-file path::String Path to the cache file - default: .eslintcache.
Deprecated: use --cache-location
--cache-location path::String Path to the cache file or directory.
Specifying rules and plugins:
--rulesdir [path::String] Use additional rules from this directory
Expand Down Expand Up @@ -142,7 +144,19 @@ Store the info about processed files in order to only operate on the changed one

#### `--cache-file`

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.
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. **Deprecated**: Use `--cache-location` instead.

#### `--cache-location`

Path to the cache location. Can be a file or a directory. If none specified `.eslintcache` will be used. The file will be created in the directory where the `eslint` command is executed.

In case a directory is specified a cache file will be created inside the specified folder. The name of the file will be based on the hash of the current working directory (CWD). e.g.: `.cache_hashOfCWD`

**Important note:** If the directory for the cache does not exist make sure you add a trailing `/` on *nix systems or `\` in windows. Otherwise the path will be assumed to be a file.

Example:

eslint 'src/**/*.js' --cache --cache-location '/Users/user/.eslintcache/'

### Specifying rules and plugins

Expand Down
89 changes: 76 additions & 13 deletions lib/cli-engine.js
Expand Up @@ -90,6 +90,10 @@ var defaultOptions = {
ignorePath: null,
parser: DEFAULT_PARSER,
cache: false,
// in order to honor the cacheFile option if specified
// this option should not have a default value otherwise
// it will always be used
cacheLocation: "",
cacheFile: ".eslintcache",
fix: false
},
Expand Down Expand Up @@ -338,6 +342,74 @@ function processPath(extensions) {
};
}

/**
* create a md5Hash of a given string
* @param {string} str the string to calculate the hash for
* @returns {string} the calculated hash
*/
function md5Hash(str) {
return crypto
.createHash("md5")
.update(str, "utf8")
.digest("hex");
}

/**
* return the cacheFile to be used by eslint, based on whether the provided parameter is
* a directory or looks like a directory (ends in `path.sep`), in which case the file
* name will be the `cacheFile/.cache_hashOfCWD`
*
* if cacheFile points to a file or looks like a file then in will just use that file
*
* @param {string} cacheFile The name of file to be used to store the cache
* @returns {string} the resolved path to the cache file
*/
function getCacheFile(cacheFile) {
var resolvedCacheFile = path.resolve(cacheFile);
var looksLikeADirectory = cacheFile[cacheFile.length - 1 ] === path.sep;

/**
* return the name for the cache file in case the provided parameter is a directory
* @returns {string} the resolved path to the cacheFile
*/
function getCacheFileForDirectory() {
return path.join(resolvedCacheFile, ".cache_" + md5Hash(process.cwd()));
}

var fileStats;

try {
fileStats = fs.lstatSync(resolvedCacheFile);
} catch (ex) {
fileStats = null;
}


// in case the file exists we need to verify if the provided path
// is a directory or a file. If it is a directory we want to create a file
// inside that directory
if (fileStats) {
// is a directory or is a file, but the original file the user provided
// looks like a directory but `path.resolve` removed the `last path.sep`
// so we need to still treat this like a directory
if (fileStats.isDirectory() || looksLikeADirectory) {
return getCacheFileForDirectory();
}
// is file so just use that file
return resolvedCacheFile;
}

// here we known the file or directory doesn't exist,
// so we will try to infer if its a directory if it looks like a directory
// for the current operating system.

// if the last character passed is a path separator we assume is a directory
if (looksLikeADirectory) {
return getCacheFileForDirectory();
}

return resolvedCacheFile;
}

//------------------------------------------------------------------------------
// Public Interface
Expand All @@ -356,12 +428,15 @@ function CLIEngine(options) {
*/
this.options = assign(Object.create(defaultOptions), options || {});


var cacheFile = getCacheFile(this.options.cacheLocation || this.options.cacheFile);

/**
* cache used to not operate on files that haven't changed since last successful
* execution (e.g. file passed with no errors and no warnings
* @type {Object}
*/
this._fileCache = fileEntryCache.create(path.resolve(this.options.cacheFile)); // eslint-disable-line no-underscore-dangle
this._fileCache = fileEntryCache.create(cacheFile); // eslint-disable-line no-underscore-dangle

if (!this.options.cache) {
this._fileCache.destroy(); // eslint-disable-line no-underscore-dangle
Expand Down Expand Up @@ -511,18 +586,6 @@ CLIEngine.prototype = {
ignore: ignoredPathsList
};

/**
* create a md5Hash of a given string
* @param {string} str the string to calculate the hash for
* @returns {string} the calculated hash
*/
function md5Hash(str) {
return crypto
.createHash("md5")
.update(str)
.digest("hex");
}

/**
* Calculates the hash of the config file used to validate a given file
* @param {string} filename The path of the file to retrieve a config object for to calculate the hash
Expand Down
1 change: 1 addition & 0 deletions lib/cli.js
Expand Up @@ -53,6 +53,7 @@ function translateOptions(cliOptions) {
parser: cliOptions.parser,
cache: cliOptions.cache,
cacheFile: cliOptions.cacheFile,
cacheLocation: cliOptions.cacheLocation,
fix: cliOptions.fix
};
}
Expand Down
9 changes: 7 additions & 2 deletions lib/options.js
Expand Up @@ -69,9 +69,14 @@ module.exports = optionator({
},
{
option: "cache-file",
type: "String",
type: "path::String",
default: ".eslintcache",
description: "Path to the cache file"
description: "Path to the cache file. Deprecated: use --cache-location"
},
{
option: "cache-location",
type: "path::String",
description: "Path to the cache file or directory"
},
{
heading: "Specifying rules and plugins"
Expand Down
97 changes: 96 additions & 1 deletion tests/lib/cli-engine.js
Expand Up @@ -19,7 +19,8 @@ var assert = require("chai").assert,
rules = require("../../lib/rules"),
Config = require("../../lib/config"),
fs = require("fs"),
os = require("os");
os = require("os"),
crypto = require("crypto");

require("shelljs/global");
proxyquire = proxyquire.noCallThru().noPreserveCache();
Expand Down Expand Up @@ -1174,6 +1175,100 @@ describe("CLIEngine", function() {
delCache();
});

describe("when the cacheFile is a directory or looks like a directory", function() {
/**
* helper method to delete the cache files created during testing
* @returns {void}
*/
function delCacheDir() {
try {
fs.unlinkSync("./tmp/.cacheFileDir/.cache_hashOfCurrentWorkingDirectory");
} catch (ex) {
// we don't care if the file didn't exist
// since we wanted it to be deleted anyway
}
}
beforeEach(function() {
delCacheDir();
});

afterEach(function() {
delCacheDir();
});

it("should create the cache file inside the provided directory", function() {
assert.isFalse(fs.existsSync(path.resolve("./tmp/.cacheFileDir/.cache_hashOfCurrentWorkingDirectory")), "the cache for eslint does not exist");

sandbox.stub(crypto, "createHash", function() {
return {
update: function() {
return this;
},
digest: function() {
return "hashOfCurrentWorkingDirectory";
}
};
});

engine = new CLIEngine({
useEslintrc: false,
// specifying cache true the cache will be created
cache: true,
cacheFile: "./tmp/.cacheFileDir/",
rules: {
"no-console": 0,
"no-unused-vars": 2
},
extensions: ["js"],
ignore: false
});

var file = getFixturePath("cache/src", "test-file.js");

engine.executeOnFiles([file]);

assert.isTrue(fs.existsSync(path.resolve("./tmp/.cacheFileDir/.cache_hashOfCurrentWorkingDirectory")), "the cache for eslint was created");

sandbox.restore();
});
});

it("should create the cache file inside the provided directory using the cacheLocation option", function() {
assert.isFalse(fs.existsSync(path.resolve("./tmp/.cacheFileDir/.cache_hashOfCurrentWorkingDirectory")), "the cache for eslint does not exist");

sandbox.stub(crypto, "createHash", function() {
return {
update: function() {
return this;
},
digest: function() {
return "hashOfCurrentWorkingDirectory";
}
};
});

engine = new CLIEngine({
useEslintrc: false,
// specifying cache true the cache will be created
cache: true,
cacheLocation: "./tmp/.cacheFileDir/",
rules: {
"no-console": 0,
"no-unused-vars": 2
},
extensions: ["js"],
ignore: false
});

var file = getFixturePath("cache/src", "test-file.js");

engine.executeOnFiles([file]);

assert.isTrue(fs.existsSync(path.resolve("./tmp/.cacheFileDir/.cache_hashOfCurrentWorkingDirectory")), "the cache for eslint was created");

sandbox.restore();
});

it("should invalidate the cache if the configuration changed between executions", function() {
assert.isFalse(fs.existsSync(path.resolve(".eslintcache")), "the cache for eslint does not exist");

Expand Down

0 comments on commit b5e66da

Please sign in to comment.