Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cacheStrategy option #6357

Merged
merged 21 commits into from Sep 28, 2022
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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".
ybiquitous marked this conversation as resolved.
Show resolved Hide resolved

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
44 changes: 44 additions & 0 deletions lib/__tests__/standalone-cache.test.js
Expand Up @@ -194,3 +194,47 @@ describe('standalone cache uses cacheLocation', () => {
expect(cache.getKey(validFile)).toBeTruthy();
});
});

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

safeChdir(cwd);

const expectedCacheFilePath = path.join(cwd, '.stylelintcache');
kaorun343 marked this conversation as resolved.
Show resolved Hide resolved

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

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

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

expect(results.some((file) => file.source === 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) => file.source === 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) => file.source === 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) => file.source === newFileDest)).toBe(false);
});
ybiquitous marked this conversation as resolved.
Show resolved Hide resolved
});
kaorun343 marked this conversation as resolved.
Show resolved Hide resolved
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;
}

kaorun343 marked this conversation as resolved.
Show resolved Hide resolved
if (cli.flags.fix) {
optionsBase.fix = cli.flags.fix;
}
Expand Down
5 changes: 3 additions & 2 deletions lib/standalone.js
Expand Up @@ -39,6 +39,7 @@ async function standalone({
allowEmptyInput = false,
cache: useCache = false,
cacheLocation,
cacheStrategy,
code,
codeFilename,
config,
Expand Down Expand Up @@ -179,10 +180,10 @@ async function standalone({
const stylelintVersion = pkg.version;
const hashOfConfig = hash(`${stylelintVersion}_${JSON.stringify(config || {})}`);

fileCache = new FileCache(cacheLocation, cwd, hashOfConfig);
fileCache = new FileCache(cacheLocation, cacheStrategy, cwd, hashOfConfig);
} else {
// No need to calculate hash here, we just want to delete cache file.
fileCache = new FileCache(cacheLocation, cwd);
fileCache = new FileCache(cacheLocation, cacheStrategy, cwd);
// Remove cache file if cache option is disabled
fileCache.destroy();
}
Expand Down
14 changes: 13 additions & 1 deletion lib/utils/FileCache.js
Expand Up @@ -5,7 +5,11 @@ const fileEntryCache = require('file-entry-cache');
const getCacheFile = require('./getCacheFile');
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;
const DEFAULT_HASH = '';

/** @typedef {import('file-entry-cache').FileDescriptor["meta"] & { hashOfConfig?: string }} CacheMetadata */
Expand All @@ -18,13 +22,21 @@ const DEFAULT_HASH = '';
class FileCache {
constructor(
cacheLocation = DEFAULT_CACHE_LOCATION,
cacheStrategy = DEFAULT_CACHE_STRATEGY,
cwd = process.cwd(),
hashOfConfig = DEFAULT_HASH,
) {
kaorun343 marked this conversation as resolved.
Show resolved Hide resolved
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 = hashOfConfig;
}

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