Skip to content

Commit

Permalink
Optimization: Run cache init code only when globby() is used (styleli…
Browse files Browse the repository at this point in the history
…nt#2293)

- Refactored sync calls into promises.
- Copy edits.
  • Loading branch information
sergesemashko committed Mar 3, 2017
1 parent ebc37b5 commit d12e5a0
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 72 deletions.
12 changes: 7 additions & 5 deletions docs/user-guide/node-api.md
Expand Up @@ -79,17 +79,19 @@ You can use this option to see what your linting results would be like without t

## `cache`

Store the info about processed files in order to only operate on the changed ones. The cache is stored in `.stylelintcache` by default. Enabling this option can dramatically improve stylelint's running time by ensuring that only changed files are linted.
Store the info about processed files in order to only operate on the changed ones the next time you run stylelint. Enabling this option can dramatically improve stylelint's speed, because only changed files will be linted.

**Note:** If you run stylelint with `--cache` and then run stylelint without `--cache`, the `.stylelintcache` file will be deleted. This is necessary because the results of the lint might change and make `.stylelintcache` invalid.
By default, the cache is stored in `.stylelintcache` in `process.cwd()`. To change this, use the `cacheLocation` option.

**Note:** If you run stylelint with `cache` and then run stylelint without `cache`, the `.stylelintcache` file will be deleted. This is necessary because we have to assume that `.stylelintcache` was invalidated by that second command.

## `cacheLocation`

Path to the cache location. Can be a file or a directory. If no location is specified, `.stylelintcache` will be used. In that case, the file will be created in the directory where the `stylelint` command is executed.
A path to a file or directory to be used for `cache`. Only meaningful alongside `cache`. If no location is specified, `.stylelintcache` will be created in `process.cwd()`.

If 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`. This will allow to reuse a single location for the caches from different projects and still receive the benefits of the cache.
If 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 `process.cwd()` (e.g. `.cache_hashOfCWD`). This allows stylelint to reuse a single location for a variety of caches from different projects.

**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.
**Note:** If the directory of `cacheLocation` does not exist, make sure you add a trailing `/` on \*nix systems or `\` on Windows. Otherwise, the path will be assumed to be a file.

### `reportNeedlessDisables`

Expand Down
106 changes: 61 additions & 45 deletions lib/__tests__/standalone-cache.test.js
Expand Up @@ -4,21 +4,13 @@ const path = require("path")
const standalone = require("../standalone")
const hash = require("../utils/hash")
const fixturesPath = path.join(__dirname, "fixtures")
const fsExtra = require("fs-extra")
const fs = require("fs")
const fsExtra = require("fs-promise")
const fileExists = require("file-exists-promise")

const cwd = process.cwd()
const invalidFile = `${fixturesPath}/empty-block.css`
const validFile = `${fixturesPath}/cache/valid.css`
const changedFile = `${fixturesPath}/cache/validChanged.css`

function fileExists(filePath) {
try {
return fs.statSync(filePath).isFile()
} catch (err) {
return false
}
}
const invalidFile = path.normalize(`${fixturesPath}/empty-block.css`)
const validFile = path.normalize(`${fixturesPath}/cache/valid.css`)
const newFileDest = path.normalize(`${fixturesPath}/cache/newFile.css`)

// Config object is getting mutated internally.
// Return new object of the same structure to
Expand All @@ -43,72 +35,88 @@ describe("standalone cache", () => {

afterEach(() => {
// Clean up after each test case
fsExtra.removeSync(expectedCacheFilePath)
fsExtra.removeSync(changedFile)
return Promise.all([
fsExtra.remove(expectedCacheFilePath),
fsExtra.remove(newFileDest),
])
})

it("cache file is created at $CWD/.stylelintcache", () => {
// Ensure cache file exists
expect(fileExists(expectedCacheFilePath)).toBe(true)
const cacheFile = fsExtra.readJsonSync(expectedCacheFilePath)
// Ensure cache file contains only linted css file
expect(typeof cacheFile[validFile] === "object").toBe(true)
expect(typeof cacheFile[changedFile] === "undefined").toBe(true)
return fileExists(expectedCacheFilePath).then(isFileExist => {
expect(!!isFileExist).toBe(true)
return fsExtra.readJson(expectedCacheFilePath)
}).then(cacheFile => {
// Ensure cache file contains only linted css file
expect(typeof cacheFile[validFile] === "object").toBe(true)
expect(typeof cacheFile[newFileDest] === "undefined").toBe(true)
})
})

it("only changed files are linted", () => {
// Add "changed" file
fsExtra.copySync(validFile, changedFile)
// Next run should lint only changed files
return standalone(getConfig()).then(output => {
return fsExtra.copy(validFile, newFileDest).then(() => {
// Next run should lint only changed files
return standalone(getConfig())
}).then(output => {
// Ensure only changed files are linted
const isValidFileLinted = !!output.results.find(file => file.source === validFile)
const isChangedFileLinted = !!output.results.find(file => file.source === changedFile)
const isNewFileLinted = !!output.results.find(file => file.source === newFileDest)
expect(isValidFileLinted).toBe(false)
expect(isChangedFileLinted).toBe(true)
expect(isNewFileLinted).toBe(true)
// Ensure cache file contains linted css files
const cachedFiles = fsExtra.readJsonSync(expectedCacheFilePath)
return fsExtra.readJson(expectedCacheFilePath)
}).then(cachedFiles => {
expect(typeof cachedFiles[validFile] === "object").toBe(true)
expect(typeof cachedFiles[changedFile] === "object").toBe(true)
expect(typeof cachedFiles[newFileDest] === "object").toBe(true)
})
})

it("all files are linted on config change", () => {
fsExtra.copySync(validFile, changedFile)
const changedConfig = getConfig()
changedConfig.config.rules["block-no-empty"] = false
// All file should be re-linted as config has changed
return standalone(changedConfig).then(output => {
return fsExtra.copy(validFile, newFileDest).then(() => {
// All file should be re-linted as config has changed
return standalone(changedConfig)
}).then(output => {
// Ensure all files are re-linted
const isValidFileLinted = !!output.results.find(file => file.source === validFile)
const isChangedFileLinted = !!output.results.find(file => file.source === changedFile)
const isNewFileLinted = !!output.results.find(file => file.source === newFileDest)
expect(isValidFileLinted).toBe(true)
expect(isChangedFileLinted).toBe(true)
expect(isNewFileLinted).toBe(true)
})
})

it("invalid files are not cached", () => {
fsExtra.copySync(invalidFile, changedFile)
// Should lint only changed files
return standalone(getConfig()).then((output) => {
return fsExtra.copy(invalidFile, newFileDest).then(() => {
// Should lint only changed files
return standalone(getConfig())
}).then((output) => {
expect(output.errored).toBe(true)
// Ensure only changed files are linted
const isValidFileLinted = !!output.results.find(file => file.source === validFile)
const isInvalidFileLinted = !!output.results.find(file => file.source === changedFile)
const isInvalidFileLinted = !!output.results.find(file => file.source === newFileDest)
expect(isValidFileLinted).toBe(false)
expect(isInvalidFileLinted).toBe(true)
// Ensure cache file doesn't contain invalid file
const cachedFiles = fsExtra.readJsonSync(expectedCacheFilePath)
return fsExtra.readJsonSync(expectedCacheFilePath)
}).then(cachedFiles => {
expect(typeof cachedFiles[validFile] === "object").toBe(true)
expect(typeof cachedFiles[changedFile] === "undefined").toBe(true)
expect(typeof cachedFiles[newFileDest] === "undefined").toBe(true)
})
})
it("cache file is removed when cache is disabled", () => {
const noCacheConfig = getConfig()

noCacheConfig.cache = false
let cacheFileExists = true
return standalone(noCacheConfig).then(() => {
expect(fileExists(expectedCacheFilePath)).toBe(false)
return fileExists(expectedCacheFilePath).then(() => {
throw new Error(`Cache file is supposed to be removed, ${expectedCacheFilePath} is found instead`)
}).catch(() => {
cacheFileExists = false
expect(cacheFileExists).toBe(false)
})
})
})
})
Expand All @@ -118,28 +126,36 @@ describe("standalone cache uses cacheLocation", () => {
const expectedCacheFilePath = `${cacheLocationDir}/.stylelintcache_${hash(cwd)}`
afterEach(() => {
// clean up after each test
fsExtra.removeSync(cacheLocationFile)
fsExtra.removeSync(expectedCacheFilePath)
return Promise.all([
fsExtra.remove(expectedCacheFilePath),
fsExtra.remove(newFileDest),
])
})
it("cacheLocation is a file", () => {
const config = getConfig()
config.cacheLocation = cacheLocationFile
return standalone(config).then(() => {
// Ensure cache file is created
expect(fileExists(cacheLocationFile)).toBe(true)
return fileExists(cacheLocationFile)
}).then(fileStats => {
expect(!!fileStats).toBe(true)
// Ensure cache file contains cached entity
const cacheFile = fsExtra.readJsonSync(cacheLocationFile)
return fsExtra.readJson(cacheLocationFile)
}).then(cacheFile => {
expect(typeof cacheFile[validFile] === "object").toBe(true)
})
})
it("cacheLocation is a directory", () => {
const config = getConfig()
config.cacheLocation = cacheLocationDir
return standalone(config).then(() => {
return fileExists(expectedCacheFilePath)
}).then(cacheFileStats => {
// Ensure cache file is created
expect(fileExists(expectedCacheFilePath)).toBe(true)
expect(!!cacheFileStats).toBe(true)
// Ensure cache file contains cached entity
const cacheFile = fsExtra.readJsonSync(expectedCacheFilePath)
return fsExtra.readJson(expectedCacheFilePath)
}).then(cacheFile => {
expect(typeof cacheFile[validFile] === "object").toBe(true)
})
})
Expand Down
19 changes: 8 additions & 11 deletions lib/cli.js
Expand Up @@ -96,21 +96,18 @@ const meowOptions = {
--cache [default: false]
Store the info about processed files in order to only operate on the
changed ones. The cache is stored in ".stylelintcache" by default.
Enabling this option can dramatically improve Stylelints's running
time by ensuring that only changed files are linted.
changed ones the next time you run stylelint. By default, the cache
is stored in "./.stylelintcache". To adjust this, use --cache-location.
--cache-location [default: '.stylelintcache']
Path to the cache location. Can be a file or a directory. If no location
is specified, ".stylelintcache" will be used. In that case, the file
will be created in the directory where the "stylelint" command is executed.
If 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"
Path to a file or directory to be used for the cache location.
Default is "./.stylelintcache". If a directory is specified, a cache
file will be created inside the specified folder, with a name derived
from a hash of the current working directory.
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.
If the directory for the cache does not exist, make sure you add a trailing "/"
on \*nix systems or "\" on Windows. Otherwise the path will be assumed to be a file.
--formatter, -f [default: "string"]
Expand Down
21 changes: 13 additions & 8 deletions lib/standalone.js
Expand Up @@ -27,14 +27,8 @@ module.exports = function (options/*: Object */)/*: Promise<stylelint$standalone
const customSyntax = options.customSyntax
const allowEmptyInput = options.allowEmptyInput
const cacheLocation = options.cacheLocation
const useCache = options.cache
const stylelintVersion = pkg.version
const hashOfConfig = hash(`${stylelintVersion}_${JSON.stringify(config)}`)
const fileCache = new FileCache(cacheLocation, hashOfConfig)
if (!useCache) {
// remove cache file as cache option is disabled
fileCache.destroy()
}
const useCache = options.cache || false
let fileCache

const startTime = Date.now()

Expand Down Expand Up @@ -89,6 +83,17 @@ module.exports = function (options/*: Object */)/*: Promise<stylelint$standalone
alwaysIgnoredGlobs.map(file => "!" + file)
)

if (useCache) {
const stylelintVersion = pkg.version
const hashOfConfig = hash(`${stylelintVersion}_${JSON.stringify(config)}`)
fileCache = new FileCache(cacheLocation, hashOfConfig)
} else {
// No need to calculate hash here, we just want to delete cache file.
fileCache = new FileCache(cacheLocation)
// Remove cache file if cache option is disabled
fileCache.destroy()
}

return globby(fileList).then(filePaths => {
if (!filePaths.length) {
if (allowEmptyInput === undefined || !allowEmptyInput) {
Expand Down
5 changes: 3 additions & 2 deletions lib/utils/FileCache.js
Expand Up @@ -7,12 +7,13 @@ const debug = require("debug")("stylelint:file-cache")
const getCacheFile = require("./getCacheFile")

const DEFAULT_CACHE_LOCATION = "./.stylelintcache"
const DEFAULT_HASH = ""

function FileCache(cacheLocation/*: String */, hashOfConfig/*: String */) {
function FileCache(cacheLocation/*: String */, hashOfConfig/*: ?String */) {
const cacheFile = path.resolve(getCacheFile(cacheLocation || DEFAULT_CACHE_LOCATION, process.cwd()))
debug(`Cache file is created at ${cacheFile}`)
this._fileCache = fileEntryCache.create(cacheFile)
this._hashOfConfig = hashOfConfig
this._hashOfConfig = hashOfConfig || DEFAULT_HASH
}

FileCache.prototype.hasFileChanged = function (absoluteFilepath) {
Expand Down
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -80,8 +80,9 @@
"d3-queue": "^3.0.3",
"eslint": "~3.16.0",
"eslint-config-stylelint": "^6.0.0",
"file-exists-promise": "^1.0.2",
"flow-bin": "^0.39.0",
"fs-extra": "^2.0.0",
"fs-promise": "^2.0.0",
"jest": "^18.0.0",
"npm-run-all": "^4.0.0",
"npmpub": "^3.0.1",
Expand Down

0 comments on commit d12e5a0

Please sign in to comment.