Skip to content

Commit

Permalink
Add cacheStrategy option (#6357)
Browse files Browse the repository at this point in the history
Co-authored-by: Richard Hallows <jeddy3@users.noreply.github.com>
Co-authored-by: Masafumi Koba <473530+ybiquitous@users.noreply.github.com>
  • Loading branch information
3 people committed Sep 28, 2022
1 parent 5be33b7 commit d3ab981
Show file tree
Hide file tree
Showing 11 changed files with 126 additions and 7 deletions.
5 changes: 5 additions & 0 deletions .changeset/mighty-apricots-cheat.md
@@ -0,0 +1,5 @@
---
"stylelint": minor
---

Added: `cacheStrategy` option
4 changes: 4 additions & 0 deletions docs/user-guide/usage/cli.md
Expand Up @@ -20,6 +20,10 @@ The process exits without throwing an error when glob pattern matches no files.

Path to a file or directory for the cache location. [More info](options.md#cachelocation).

### `--cache-strategy`

Strategy for the cache to use for detecting changed files. Can be either "metadata" or "content". [More info](options.md#cachestrategy).

### `--cache`

Store the results of processed files so that Stylelint only operates on the changed ones. By default, the cache is stored in `./.stylelintcache` in `process.cwd()`. [More info](options.md#cache).
Expand Down
8 changes: 8 additions & 0 deletions docs/user-guide/usage/options.md
Expand Up @@ -110,6 +110,14 @@ If a directory is specified, Stylelint creates a cache file inside the specified

_If the directory of `cacheLocation` does not exist, make sure you add a trailing `/` on \*nix systems or `\` on Windows. Otherwise, Stylelint assumes the path to be a file._

## `cacheStrategy`

CLI flag: `--cache-strategy`

Strategy for the cache to use for detecting changed files. Can be either "metadata" or "content".

The "content" strategy can be useful in cases where the modification time of your files changes even if their contents have not. For example, this can happen during git operations like "git clone" because git does not track file modification time.

## `maxWarnings`

CLI flags: `--max-warnings, --mw`
Expand Down
10 changes: 10 additions & 0 deletions lib/__tests__/__snapshots__/cli.test.js.snap
Expand Up @@ -88,6 +88,16 @@ exports[`CLI --help 1`] = `
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.
--cache-strategy [default: \\"metadata\\"]
Strategy for the cache to use for detecting changed files. Can be either
\\"metadata\\" or \\"content\\".
The \\"content\\" strategy can be useful in cases where the modification time of
your files changes even if their contents have not. For example, this can happen
during git operations like \\"git clone\\" because git does not track file modification
time.
--formatter, -f [default: \\"string\\"]
The output formatter: \\"compact\\", \\"github\\", \\"json\\", \\"string\\", \\"tap\\", \\"unix\\" or \\"verbose\\".
Expand Down
5 changes: 5 additions & 0 deletions lib/__tests__/cli.test.js
Expand Up @@ -50,6 +50,11 @@ describe('buildCLI', () => {
expect(buildCLI(['--cache-location=foo']).flags.cacheLocation).toBe('foo');
});

it('flags.cacheStrategy', () => {
expect(buildCLI(['--cache-strategy=content']).flags.cacheStrategy).toBe('content');
expect(buildCLI(['--cache-strategy=metadata']).flags.cacheStrategy).toBe('metadata');
});

it('flags.color', () => {
expect(buildCLI(['--color']).flags.color).toBe(true);
expect(buildCLI(['--no-color']).flags.color).toBe(false);
Expand Down
50 changes: 50 additions & 0 deletions lib/__tests__/standalone-cache.test.js
Expand Up @@ -242,3 +242,53 @@ describe('standalone cache uses a config file', () => {
expect(resultsNew[0].warnings).toHaveLength(1);
});
});

describe('standalone cache uses cacheStrategy', () => {
const cwd = path.join(__dirname, 'tmp', 'standalone-cache-uses-cacheStrategy');

safeChdir(cwd);

const expectedCacheFilePath = path.join(cwd, '.stylelintcache');

afterEach(async () => {
// clean up after each test
await removeFile(expectedCacheFilePath);
await removeFile(newFileDest);
});

it('cacheStrategy is invalid', async () => {
await expect(standalone(getConfig({ cacheStrategy: 'foo' }))).rejects.toThrow(
'"foo" cache strategy is unsupported. Specify either "metadata" or "content"',
);
});

it('cacheStrategy is "metadata"', async () => {
const cacheStrategy = 'metadata';

await fs.copyFile(validFile, newFileDest);
const { results } = await standalone(getConfig({ cacheStrategy }));

expect(results.some((file) => isChanged(file, newFileDest))).toBe(true);

// No content change, but file metadata id changed
await fs.utimes(newFileDest, new Date(), new Date());
const { results: resultsCached } = await standalone(getConfig({ cacheStrategy }));

expect(resultsCached.some((file) => isChanged(file, newFileDest))).toBe(true);
});

it('cacheStrategy is "content"', async () => {
const cacheStrategy = 'content';

await fs.copyFile(validFile, newFileDest);
const { results } = await standalone(getConfig({ cacheStrategy }));

expect(results.some((file) => isChanged(file, newFileDest))).toBe(true);

// No content change, but file metadata id changed
await fs.utimes(newFileDest, new Date(), new Date());
const { results: resultsCached } = await standalone(getConfig({ cacheStrategy }));

expect(resultsCached.some((file) => isChanged(file, newFileDest))).toBe(false);
});
});
19 changes: 19 additions & 0 deletions lib/cli.js
Expand Up @@ -21,6 +21,7 @@ const EXIT_CODE_ERROR = 2;
* @typedef {object} CLIFlags
* @property {boolean} [cache]
* @property {string} [cacheLocation]
* @property {string} [cacheStrategy]
* @property {string | false} config
* @property {string} [configBasedir]
* @property {string} [customSyntax]
Expand Down Expand Up @@ -64,6 +65,7 @@ const EXIT_CODE_ERROR = 2;
* @property {boolean} [cache]
* @property {string} [configFile]
* @property {string} [cacheLocation]
* @property {string} [cacheStrategy]
* @property {string} [customSyntax]
* @property {string} [codeFilename]
* @property {string} [configBasedir]
Expand Down Expand Up @@ -173,6 +175,16 @@ const meowOptions = {
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.
--cache-strategy [default: "metadata"]
Strategy for the cache to use for detecting changed files. Can be either
"metadata" or "content".
The "content" strategy can be useful in cases where the modification time of
your files changes even if their contents have not. For example, this can happen
during git operations like "git clone" because git does not track file modification
time.
--formatter, -f [default: "string"]
The output formatter: ${getFormatterOptionsText({ useOr: true })}.
Expand Down Expand Up @@ -236,6 +248,9 @@ const meowOptions = {
cacheLocation: {
type: 'string',
},
cacheStrategy: {
type: 'string',
},
color: {
type: 'boolean',
},
Expand Down Expand Up @@ -410,6 +425,10 @@ module.exports = async (argv) => {
optionsBase.cacheLocation = cli.flags.cacheLocation;
}

if (cli.flags.cacheStrategy) {
optionsBase.cacheStrategy = cli.flags.cacheStrategy;
}

if (cli.flags.fix) {
optionsBase.fix = cli.flags.fix;
}
Expand Down
6 changes: 5 additions & 1 deletion lib/createStylelint.js
Expand Up @@ -36,7 +36,11 @@ function createStylelint(options = {}) {

stylelint._specifiedConfigCache = new Map();
stylelint._postcssResultCache = new Map();
stylelint._fileCache = new FileCache(stylelint._options.cacheLocation, stylelint._options.cwd);
stylelint._fileCache = new FileCache(
stylelint._options.cacheLocation,
stylelint._options.cacheStrategy,
stylelint._options.cwd,
);
stylelint._createStylelintResult = createStylelintResult.bind(null, stylelint);
stylelint._getPostcssResult = getPostcssResult.bind(null, stylelint);
stylelint._lintSource = lintSource.bind(null, stylelint);
Expand Down
2 changes: 2 additions & 0 deletions lib/standalone.js
Expand Up @@ -36,6 +36,7 @@ async function standalone({
allowEmptyInput = false,
cache: useCache = false,
cacheLocation,
cacheStrategy,
code,
codeFilename,
config,
Expand Down Expand Up @@ -91,6 +92,7 @@ async function standalone({

const stylelint = createStylelint({
cacheLocation,
cacheStrategy,
config,
configFile,
configBasedir,
Expand Down
23 changes: 17 additions & 6 deletions lib/utils/FileCache.js
Expand Up @@ -7,20 +7,31 @@ const hash = require('./hash');
const pkg = require('../../package.json');
const path = require('path');

const CACHE_STRATEGY_METADATA = 'metadata';
const CACHE_STRATEGY_CONTENT = 'content';

const DEFAULT_CACHE_LOCATION = './.stylelintcache';
const DEFAULT_CACHE_STRATEGY = CACHE_STRATEGY_METADATA;

/** @typedef {import('file-entry-cache').FileDescriptor["meta"] & { hashOfConfig?: string }} CacheMetadata */

class FileCache {
/**
* @param {string | undefined} cacheLocation
* @param {string} cwd
*/
constructor(cacheLocation = DEFAULT_CACHE_LOCATION, cwd) {
constructor(
cacheLocation = DEFAULT_CACHE_LOCATION,
cacheStrategy = DEFAULT_CACHE_STRATEGY,
cwd = process.cwd(),
) {
if (![CACHE_STRATEGY_METADATA, CACHE_STRATEGY_CONTENT].includes(cacheStrategy)) {
throw new Error(
`"${cacheStrategy}" cache strategy is unsupported. Specify either "${CACHE_STRATEGY_METADATA}" or "${CACHE_STRATEGY_CONTENT}"`,
);
}

const cacheFile = path.resolve(getCacheFile(cacheLocation, cwd));
const useCheckSum = cacheStrategy === CACHE_STRATEGY_CONTENT;

debug(`Cache file is created at ${cacheFile}`);
this._fileCache = fileEntryCache.create(cacheFile);
this._fileCache = fileEntryCache.create(cacheFile, undefined, useCheckSum);
this._hashOfConfig = '';
}

Expand Down
1 change: 1 addition & 0 deletions types/stylelint/index.d.ts
Expand Up @@ -222,6 +222,7 @@ declare module 'stylelint' {
globbyOptions?: GlobbyOptions;
cache?: boolean;
cacheLocation?: string;
cacheStrategy?: string;
code?: string;
codeFilename?: string;
config?: Config;
Expand Down

0 comments on commit d3ab981

Please sign in to comment.