Add --cache CLI option (#12800)

* Install `file-entry-cache` and types

* Implement `FormatResultsCache`

* Add `--cache` and `--cache-location` option

* Implement `--cache` and `--cache-location`

* Add tests for `--cache`

* Add tests for `--cache-location`

* Avoid spellcheck error

* Update snapshots for new options

* Add `groupBy` for website

* Use `fs.promises` instead of `fs/promises`

* Use `rimraf` instead of `fs.rm`

* Fix properties order of `options`

* Install `find-cache-dir` and types

* Change default cache file location to `node_modules/.cache/prettier`

* Fix tests following to change default cache file location

* Update snapshots for changing default cache file location

* Set `os.tmpdir()` as a fallback for default cache dir

* Add docs for `--cache` and `--cache-location`

* Use `sdbm` instead of `node:crypto`

* Add changelog

* Fix `getHashOfOptions`

* Remove `cache-location`

* `prettiercache` -> `prettier-cache`

* Remove cache file when run Prettier without `--cache` option

* Implement `--cache-strategy`

* Add tests for invalid cache strategy

* Fix lint problems

* Tweaks tests

* Add tests for timestamp

* Add tests for `--cache-strategy`

* Add docs for cache-strategy

* Address review

* Fix `findCacheFile`

* Use Set.prototype.has

* Throw error with --stdin-filepath

* Update snapshots

* Fix error for cache and stdin

* Use string constructor instead of toString

* Update changelog

* Use flag validation

* Fix `cache-strategy` definition

* Update docs

* Mark highlihgt

* Throw error for `--cache-strategy` without `--cache`

* Update docs

* Fix typo

* Updates snapshots

* Update docs/cli.md

Co-authored-by: Simon Lydell <simon.lydell@gmail.com>

* Fix error message

* Remove `:::` syntax

* Defaults `content`

* Update docs

* Fix by Prettier

Co-authored-by: Simon Lydell <simon.lydell@gmail.com>
This commit is contained in:
Sosuke SuzukiandSimon Lydell authored and GitHub committed 2022-06-11 23:32:32 +09:00
1 parent 97bab8f984
commit fdccc0f48e
24 files changed
+811 -24

No files matched your search

+25
View File
@@ -0,0 +1,25 @@
#### [HIGHLIGHT]Add `--cache` and `--cache-strategy` CLI option (#12800 by @sosukesuzuki)
Two new CLI options have been added for a caching system similar to [ESLint's one](https://eslint.org/docs/user-guide/command-line-interface#caching).
##### `--cache`
If this option is enabled, the following values are used as cache keys and the file is formatted only if one of them is changed.
- Prettier version
- Options
- Node.js version
- (if `--cache-strategy` is `content`) content of the file
- (if `--cache-strategy` is `metadata`) file metadata, such as timestamps
```bash
prettier --write --cache src
```
##### `--cache-strategy`
Strategy for the cache to use for detecting changed files. Can be either `metadata` or `content`. If no strategy is specified, `content` will be used.
```bash
prettier --write --cache --cache-strategy metadata src
```
+1
View File
@@ -289,6 +289,7 @@
"sandhose",
"Sapegin",
"sbdchd",
"sdbm",
"scandir",
"Serializers",
"setlocal",
+32
View File
@@ -204,3 +204,35 @@ Prevent errors when pattern is unmatched.
## `--no-plugin-search`
Disable plugin autoloading.
## `--cache`
If this option is enabled, the following values are used as cache keys and the file is formatted only if one of them is changed.
- Prettier version
- Options
- Node.js version
- (if `--cache-strategy` is `metadata`) file metadata, such as timestamps
- (if `--cache-strategy` is `content`) content of the file
```bash
prettier --write --cache src
```
Running Prettier without `--cache` will delete the cache.
Also, since the cache file is stored in `./node_modules/.cache/prettier/.prettier-cache`, so you can use `rm ./node_modules/.cache/prettier/.prettier-cache` to remove it manually.
> Plugins version and implementation are not used as cache keys. We recommend that you delete the cache when updating plugins.
## `--cache-strategy`
Strategy for the cache to use for detecting changed files. Can be either `metadata` or `content`.
In general, `metadata` is faster. However, `content` is useful for updating the timestamp without changing the file content. This can happen, for example, during git operations such as `git clone`, because it does not track file modification times.
If no strategy is specified, `content` will be used.
```bash
prettier --write --cache --cache-strategy metadata src
```
+5
View File
@@ -47,6 +47,8 @@
"esutils": "2.0.3",
"fast-glob": "3.2.11",
"fast-json-stable-stringify": "2.1.0",
"file-entry-cache": "6.0.1",
"find-cache-dir": "3.3.2",
"find-parent-dir": "0.3.1",
"flow-parser": "0.180.0",
"get-stdin": "8.0.0",
@@ -79,6 +81,7 @@
"remark-math": "3.0.1",
"remark-parse": "8.0.3",
"resolve": "1.22.0",
"sdbm": "2.0.0",
"semver": "7.3.7",
"string-width": "5.0.1",
"strip-ansi": "7.0.1",
@@ -96,6 +99,8 @@
"@esbuild-plugins/node-modules-polyfill": "0.1.4",
"@glimmer/reference": "0.84.2",
"@types/estree": "0.0.51",
"@types/file-entry-cache": "5.0.2",
"@types/find-cache-dir": "3.2.1",
"@types/jest": "27.4.1",
"@typescript-eslint/eslint-plugin": "5.20.0",
"babel-jest": "27.5.1",
+18
View File
@@ -9,6 +9,7 @@
"html-void-elements": "html-void-elements.json",
"leven": "leven.js",
"mem": "mem.js",
"sdbm": "sdbm.js",
"string-width": "string-width.js",
"strip-ansi": "strip-ansi.js"
},
@@ -503,6 +504,23 @@
},
"contributors": []
},
{
"name": "sdbm",
"maintainers": [],
"version": "2.0.0",
"description": "SDBM non-cryptographic hash function",
"repository": "sindresorhus/sdbm",
"homepage": null,
"private": false,
"license": "MIT",
"licenseText": "MIT License\n\nCopyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "https://sindresorhus.com"
},
"contributors": []
},
{
"name": "ansi-regex",
"maintainers": [],
+1
View File
@@ -8,6 +8,7 @@ const vendors = [
"html-void-elements",
"leven",
"mem",
"sdbm",
"string-width",
"strip-ansi",
"tempy",
+19
View File
@@ -71,6 +71,25 @@ const categoryOrder = [
*/
/* eslint sort-keys: "error" */
const options = {
cache: {
default: false,
description: "Only format changed files. Cannot use with --stdin-filepath.",
type: "boolean",
},
"cache-strategy": {
choices: [
{
description: "Use the file metadata such as timestamps as cache keys",
value: "metadata",
},
{
description: "Use the file content as cache keys",
value: "content",
},
],
description: "Strategy for the cache to use for detecting changed files.",
type: "choice",
},
check: {
alias: "c",
category: coreOptions.CATEGORY_OUTPUT,
+2 -17
View File
@@ -1,9 +1,10 @@
"use strict";
const path = require("path");
const { promises: fs } = require("fs");
const fastGlob = require("fast-glob");
const { statSafe } = require("./utils.js");
/** @typedef {import('./context').Context} Context */
/**
@@ -173,22 +174,6 @@ function sortPaths(paths) {
return paths.sort((a, b) => a.localeCompare(b));
}
/**
* Get stats of a given path.
* @param {string} filePath The path to target file.
* @returns {Promise<import('fs').Stats | undefined>} The stats.
*/
async function statSafe(filePath) {
try {
return await fs.stat(filePath);
} catch (error) {
/* istanbul ignore next */
if (error.code !== "ENOENT") {
throw error;
}
}
}
/**
* This function should be replaced with `fastGlob.escapePath` when these issues are fixed:
* - https://github.com/mrmlnc/fast-glob/issues/261
+19
View File
@@ -0,0 +1,19 @@
"use strict";
const os = require("os");
const path = require("path");
const findCacheDir = require("find-cache-dir");
/**
* Find default cache file (`./node_modules/.cache/prettier/.prettier-cache`) using https://github.com/avajs/find-cache-dir
*
* @returns {string}
*/
function findCacheFile() {
const cacheDir =
findCacheDir({ name: "prettier", create: true }) || os.tmpdir();
const cacheFilePath = path.join(cacheDir, ".prettier-cache");
return cacheFilePath;
}
module.exports = findCacheFile;
+96
View File
@@ -0,0 +1,96 @@
"use strict";
// Inspired by LintResultsCache from ESLint
// https://github.com/eslint/eslint/blob/c2d0a830754b6099a3325e6d3348c3ba983a677a/lib/cli-engine/lint-result-cache.js
const fileEntryCache = require("file-entry-cache");
const stringify = require("fast-json-stable-stringify");
// eslint-disable-next-line no-restricted-modules
const { version: prettierVersion } = require("../index.js");
const { createHash } = require("./utils.js");
const optionsHashCache = new WeakMap();
const nodeVersion = process && process.version;
/**
* @param {*} options
* @returns {string}
*/
function getHashOfOptions(options) {
if (optionsHashCache.has(options)) {
return optionsHashCache.get(options);
}
const hash = createHash(
`${prettierVersion}_${nodeVersion}_${stringify(options)}`
);
optionsHashCache.set(options, hash);
return hash;
}
/**
* @typedef {{ hashOfOptions?: string }} OurMeta
* @typedef {import("file-entry-cache").FileDescriptor} FileDescriptor
*
* @param {import("file-entry-cache").FileDescriptor} fileDescriptor
* @returns {FileDescriptor["meta"] & OurMeta}
*/
function getMetadataFromFileDescriptor(fileDescriptor) {
return fileDescriptor.meta;
}
class FormatResultsCache {
/**
* @param {string} cacheFileLocation The path of cache file location. (default: `node_modules/.cache/prettier/prettier-cache`)
* @param {string} cacheStrategy
*/
constructor(cacheFileLocation, cacheStrategy) {
const useChecksum = cacheStrategy === "content";
this.cacheFileLocation = cacheFileLocation;
this.fileEntryCache = fileEntryCache.create(
/* cacheId */ cacheFileLocation,
/* directory */ undefined,
useChecksum
);
}
/**
* @param {string} filePath
* @param {any} options
*/
existsAvailableFormatResultsCache(filePath, options) {
const fileDescriptor = this.fileEntryCache.getFileDescriptor(filePath);
const hashOfOptions = getHashOfOptions(options);
const meta = getMetadataFromFileDescriptor(fileDescriptor);
const changed =
fileDescriptor.changed || meta.hashOfOptions !== hashOfOptions;
if (fileDescriptor.notFound) {
return false;
}
if (changed) {
return false;
}
return true;
}
/**
* @param {string} filePath
* @param {any} options
*/
setFormatResultsCache(filePath, options) {
const fileDescriptor = this.fileEntryCache.getFileDescriptor(filePath);
const meta = getMetadataFromFileDescriptor(fileDescriptor);
if (fileDescriptor && !fileDescriptor.notFound) {
meta.hashOfOptions = getHashOfOptions(options);
}
}
reconcile() {
this.fileEntryCache.reconcile();
}
}
module.exports = FormatResultsCache;
+43 -2
View File
@@ -15,6 +15,9 @@ const { createIgnorer, errors } = require("./prettier-internal.js");
const { expandPatterns, fixWindowsSlashes } = require("./expand-patterns.js");
const getOptionsForFile = require("./options/get-options-for-file.js");
const isTTY = require("./is-tty.js");
const findCacheFile = require("./find-cache-file.js");
const FormatResultsCache = require("./format-results-cache.js");
const { statSafe } = require("./utils.js");
function diff(a, b) {
return require("diff").createTwoFilesPatch("", "", a, b, "", "", {
@@ -295,6 +298,26 @@ async function formatFiles(context) {
context.logger.log("Checking formatting...");
}
let formatResultsCache;
const cacheFilePath = findCacheFile();
if (context.argv.cache) {
formatResultsCache = new FormatResultsCache(
cacheFilePath,
context.argv.cacheStrategy || "content"
);
} else {
if (context.argv.cacheStrategy) {
context.logger.error(
"`--cache-strategy` cannot be used without `--cache`."
);
process.exit(2);
}
const stat = await statSafe(cacheFilePath);
if (stat) {
await fs.unlink(cacheFilePath);
}
}
for await (const pathOrError of expandPatterns(context)) {
if (typeof pathOrError === "object") {
context.logger.error(pathOrError.error);
@@ -362,17 +385,28 @@ async function formatFiles(context) {
const start = Date.now();
const isCacheExists = formatResultsCache?.existsAvailableFormatResultsCache(
filename,
options
);
let result;
let output;
try {
result = format(context, input, options);
if (isCacheExists) {
result = { formatted: input };
} else {
result = format(context, input, options);
}
output = result.formatted;
} catch (error) {
handleError(context, filename, error, printedFilename);
continue;
}
formatResultsCache?.setFormatResultsCache(filename, options);
const isDifferent = output !== input;
if (printedFilename) {
@@ -408,7 +442,12 @@ async function formatFiles(context) {
process.exitCode = 2;
}
} else if (!context.argv.check && !context.argv.listDifferent) {
context.logger.log(`${chalk.grey(filename)} ${Date.now() - start}ms`);
const message = `${chalk.grey(filename)} ${Date.now() - start}ms`;
if (isCacheExists) {
context.logger.log(`${message} (cached)`);
} else {
context.logger.log(message);
}
}
} else if (context.argv.debugCheck) {
/* istanbul ignore else */
@@ -431,6 +470,8 @@ async function formatFiles(context) {
}
}
formatResultsCache?.reconcile();
// Print check summary based on expected exit code
if (context.argv.check) {
if (numberOfUnformattedFilesFound === 0) {
+4
View File
@@ -103,6 +103,10 @@ async function main(context) {
} else if (context.argv.fileInfo) {
await logFileInfoOrDie(context);
} else if (useStdin) {
if (context.argv.cache) {
context.logger.error("`--cache` cannot be used with stdin.");
process.exit(2);
}
await formatStdin(context);
} else if (hasFilePatterns) {
await formatFiles(context);
+30 -1
View File
@@ -1,5 +1,10 @@
"use strict";
const { promises: fs } = require("fs");
// eslint-disable-next-line no-restricted-modules
const { default: sdbm } = require("../../vendors/sdbm.js");
// eslint-disable-next-line no-console
const printToScreen = console.log.bind(console);
@@ -38,4 +43,28 @@ function pick(object, keys) {
return Object.fromEntries(entries);
}
module.exports = { printToScreen, groupBy, pick };
/**
* @param {string} source
* @returns {string}
*/
function createHash(source) {
return String(sdbm(source));
}
/**
* Get stats of a given path.
* @param {string} filePath The path to target file.
* @returns {Promise<import('fs').Stats | undefined>} The stats.
*/
async function statSafe(filePath) {
try {
return await fs.stat(filePath);
} catch (error) {
/* istanbul ignore next */
if (error.code !== "ENOENT") {
throw error;
}
}
}
module.exports = { printToScreen, groupBy, pick, createHash, statSafe };
@@ -139,6 +139,10 @@ Editor options:
Other options:
--cache Only format changed files. Cannot use with --stdin-filepath.
Defaults to false.
--cache-strategy <metadata|content>
Strategy for the cache to use for detecting changed files.
--no-color Do not colorize error messages.
--no-error-on-unmatched-pattern
Prevent errors when pattern is unmatched.
@@ -309,6 +313,10 @@ Editor options:
Other options:
--cache Only format changed files. Cannot use with --stdin-filepath.
Defaults to false.
--cache-strategy <metadata|content>
Strategy for the cache to use for detecting changed files.
--no-color Do not colorize error messages.
--no-error-on-unmatched-pattern
Prevent errors when pattern is unmatched.
@@ -44,6 +44,35 @@ Default: true
exports[`show detailed usage with --help bracket-spacing (write) 1`] = `[]`;
exports[`show detailed usage with --help cache (stderr) 1`] = `""`;
exports[`show detailed usage with --help cache (stdout) 1`] = `
"--cache
Only format changed files. Cannot use with --stdin-filepath.
Default: false
"
`;
exports[`show detailed usage with --help cache (write) 1`] = `[]`;
exports[`show detailed usage with --help cache-strategy (stderr) 1`] = `""`;
exports[`show detailed usage with --help cache-strategy (stdout) 1`] = `
"--cache-strategy <metadata|content>
Strategy for the cache to use for detecting changed files.
Valid options:
metadata Use the file metadata such as timestamps as cache keys
content Use the file content as cache keys
"
`;
exports[`show detailed usage with --help cache-strategy (write) 1`] = `[]`;
exports[`show detailed usage with --help check (stderr) 1`] = `""`;
exports[`show detailed usage with --help check (stdout) 1`] = `
+373
View File
@@ -0,0 +1,373 @@
"use strict";
const path = require("path");
const { promises: fs } = require("fs");
const rimraf = require("rimraf");
const { default: stripAnsi } = require("../../../vendors/strip-ansi.js");
const runPrettier = require("../run-prettier.js");
function resolveDir(dir) {
return path.join(__dirname, "..", dir);
}
describe("--cache option", () => {
const dir = resolveDir("cli/cache");
const defaultCacheFile = path.join(
dir,
"node_modules/.cache/prettier/.prettier-cache"
);
let contentA;
let contentB;
beforeAll(async () => {
contentA = await fs.readFile(path.join(dir, "a.js"), "utf8");
contentB = await fs.readFile(path.join(dir, "b.js"), "utf8");
});
afterEach(async () => {
rimraf.sync(path.join(dir, "node_modules"));
await fs.writeFile(path.join(dir, "a.js"), contentA);
await fs.writeFile(path.join(dir, "b.js"), contentB);
});
it("throw error when cache-strategy is invalid", async () => {
const { stderr } = await runPrettier(dir, [
"--cache",
"--cache-strategy",
"invalid",
".",
]);
expect(stripAnsi(stderr.trim())).toBe(
'[error] Invalid --cache-strategy value. Expected "content" or "metadata", but received "invalid".'
);
});
it("throws error when use with --stdin-filepath", async () => {
const { stderr } = await runPrettier(
dir,
["--cache", "--stdin-filepath", "foo.js"],
{ input: "const a = a;" }
);
expect(stripAnsi(stderr.trim())).toBe(
"[error] `--cache` cannot be used with stdin."
);
});
it("throws error when use `--cache-strategy` without `--cache`.", async () => {
const { stderr } = await runPrettier(
dir,
["foo.js", "--cache-strategy", "content"],
{
input: "const a = a;",
}
);
expect(stripAnsi(stderr.trim())).toBe(
"[error] `--cache-strategy` cannot be used without `--cache`."
);
});
describe("--cache-strategy metadata", () => {
it("creates default cache file named `node_modules/.cache/prettier/.prettier-cache`", async () => {
await expect(fs.stat(defaultCacheFile)).rejects.toHaveProperty(
"code",
"ENOENT"
);
await runPrettier(dir, ["--cache", "--cache-strategy", "metadata", "."]);
await expect(fs.stat(defaultCacheFile)).resolves.not.toThrowError();
});
it("does'nt format when cache is available", async () => {
const { stdout: firstStdout } = await runPrettier(dir, [
"--cache",
"--write",
"--cache-strategy",
"metadata",
".",
]);
expect(stripAnsi(firstStdout).split("\n").filter(Boolean)).toEqual(
expect.arrayContaining([
expect.stringMatching(/^a\.js .+ms$/),
expect.stringMatching(/^b\.js .+ms$/),
])
);
const { stdout: secondStdout } = await runPrettier(dir, [
"--cache",
"--write",
"--cache-strategy",
"metadata",
".",
]);
expect(stripAnsi(secondStdout).split("\n").filter(Boolean)).toEqual(
expect.arrayContaining([
expect.stringMatching(/^a\.js .+ms \(cached\)$/),
expect.stringMatching(/^b\.js .+ms \(cached\)$/),
])
);
});
it("re-formats when a file has been updated.", async () => {
const { stdout: firstStdout } = await runPrettier(dir, [
"--cache",
"--write",
"--cache-strategy",
"metadata",
".",
]);
expect(stripAnsi(firstStdout).split("\n").filter(Boolean)).toEqual(
expect.arrayContaining([
expect.stringMatching(/^a\.js .+ms$/),
expect.stringMatching(/^b\.js .+ms$/),
])
);
// Update `a.js`
await fs.writeFile(path.join(dir, "a.js"), "const a = `a`;");
const { stdout: secondStdout } = await runPrettier(dir, [
"--cache",
"--write",
"--cache-strategy",
"metadata",
".",
]);
expect(stripAnsi(secondStdout).split("\n").filter(Boolean)).toEqual(
// the cache of `b.js` is only available.
expect.arrayContaining([
expect.stringMatching(/^a\.js .+ms$/),
expect.stringMatching(/^b\.js .+ms \(cached\)$/),
])
);
});
it("re-formats when timestamp has been updated", async () => {
const { stdout: firstStdout } = await runPrettier(dir, [
"--cache",
"--write",
"--cache-strategy",
"metadata",
".",
]);
expect(stripAnsi(firstStdout).split("\n").filter(Boolean)).toEqual(
expect.arrayContaining([
expect.stringMatching(/^a\.js .+ms$/),
expect.stringMatching(/^b\.js .+ms$/),
])
);
// update timestamp
const time = new Date();
await fs.utimes(path.join(dir, "a.js"), time, time);
const { stdout: secondStdout } = await runPrettier(dir, [
"--cache",
"--write",
"--cache-strategy",
"metadata",
".",
]);
expect(stripAnsi(secondStdout).split("\n").filter(Boolean)).toEqual(
// the cache of `b.js` is only available.
expect.arrayContaining([
expect.stringMatching(/^a\.js .+ms$/),
expect.stringMatching(/^b\.js .+ms \(cached\)$/),
])
);
});
it("re-formats when options has been updated.", async () => {
const { stdout: firstStdout } = await runPrettier(dir, [
"--cache",
"--write",
"--cache-strategy",
"metadata",
".",
]);
expect(stripAnsi(firstStdout).split("\n").filter(Boolean)).toEqual(
expect.arrayContaining([
expect.stringMatching(/^a\.js .+ms$/),
expect.stringMatching(/^b\.js .+ms$/),
])
);
const { stdout: secondStdout } = await runPrettier(dir, [
"--cache",
"--cache-strategy",
"metadata",
"--write",
"--trailing-comma",
"all",
".",
]);
expect(stripAnsi(secondStdout).split("\n").filter(Boolean)).toEqual(
expect.arrayContaining([
expect.stringMatching(/^a\.js .+ms$/),
expect.stringMatching(/^b\.js .+ms$/),
])
);
});
it("removes cache file when run Prettier without `--cache` option", async () => {
await runPrettier(dir, [
"--cache",
"--write",
"--cache-strategy",
"metadata",
".",
]);
await expect(fs.stat(defaultCacheFile)).resolves.not.toThrowError();
await runPrettier(dir, ["--write", "."]);
await expect(fs.stat(defaultCacheFile)).rejects.toThrowError();
});
});
describe("--cache-strategy content", () => {
it("creates default cache file named `node_modules/.cache/prettier/.prettier-cache`", async () => {
await expect(fs.stat(defaultCacheFile)).rejects.toHaveProperty(
"code",
"ENOENT"
);
await runPrettier(dir, ["--cache", "--cache-strategy", "content", "."]);
await expect(fs.stat(defaultCacheFile)).resolves.not.toThrowError();
});
it("does'nt format when cache is available", async () => {
const { stdout: firstStdout } = await runPrettier(dir, [
"--cache",
"--cache-strategy",
"content",
"--write",
".",
]);
expect(stripAnsi(firstStdout).split("\n").filter(Boolean)).toEqual(
expect.arrayContaining([
expect.stringMatching(/^a\.js .+ms$/),
expect.stringMatching(/^b\.js .+ms$/),
])
);
const { stdout: secondStdout } = await runPrettier(dir, [
"--cache",
"--cache-strategy",
"content",
"--write",
".",
]);
expect(stripAnsi(secondStdout).split("\n").filter(Boolean)).toEqual(
expect.arrayContaining([
expect.stringMatching(/^a\.js .+ms \(cached\)$/),
expect.stringMatching(/^b\.js .+ms \(cached\)$/),
])
);
});
it("re-formats when a file has been updated.", async () => {
const { stdout: firstStdout } = await runPrettier(dir, [
"--cache",
"--cache-strategy",
"content",
"--write",
".",
]);
expect(stripAnsi(firstStdout).split("\n").filter(Boolean)).toEqual(
expect.arrayContaining([
expect.stringMatching(/^a\.js .+ms$/),
expect.stringMatching(/^b\.js .+ms$/),
])
);
// Update `a.js`
await fs.writeFile(path.join(dir, "a.js"), "const a = `a`;");
const { stdout: secondStdout } = await runPrettier(dir, [
"--cache",
"--cache-strategy",
"content",
"--write",
".",
]);
expect(stripAnsi(secondStdout).split("\n").filter(Boolean)).toEqual(
// the cache of `b.js` is only available.
expect.arrayContaining([
expect.stringMatching(/^a\.js .+ms$/),
expect.stringMatching(/^b\.js .+ms \(cached\)$/),
])
);
});
it("does'nt re-format when timestamp has been updated", async () => {
const { stdout: firstStdout } = await runPrettier(dir, [
"--cache",
"--cache-strategy",
"content",
"--write",
".",
]);
expect(stripAnsi(firstStdout).split("\n").filter(Boolean)).toEqual(
expect.arrayContaining([
expect.stringMatching(/^a\.js .+ms$/),
expect.stringMatching(/^b\.js .+ms$/),
])
);
// update timestamp
const time = new Date();
await fs.utimes(path.join(dir, "a.js"), time, time);
const { stdout: secondStdout } = await runPrettier(dir, [
"--cache",
"--cache-strategy",
"content",
"--write",
".",
]);
expect(stripAnsi(secondStdout).split("\n").filter(Boolean)).toEqual(
expect.arrayContaining([
expect.stringMatching(/^a\.js .+ms \(cached\)$/),
expect.stringMatching(/^b\.js .+ms \(cached\)$/),
])
);
});
it("re-formats when options has been updated.", async () => {
const { stdout: firstStdout } = await runPrettier(dir, [
"--cache",
"--cache-strategy",
"content",
"--write",
".",
]);
expect(stripAnsi(firstStdout).split("\n").filter(Boolean)).toEqual(
expect.arrayContaining([
expect.stringMatching(/^a\.js .+ms$/),
expect.stringMatching(/^b\.js .+ms$/),
])
);
const { stdout: secondStdout } = await runPrettier(dir, [
"--cache",
"--write",
"--cache-strategy",
"content",
"--trailing-comma",
"all",
".",
]);
expect(stripAnsi(secondStdout).split("\n").filter(Boolean)).toEqual(
expect.arrayContaining([
expect.stringMatching(/^a\.js .+ms$/),
expect.stringMatching(/^b\.js .+ms$/),
])
);
});
it("removes cache file when run Prettier without `--cache` option", async () => {
await runPrettier(dir, ["--cache", "--write", "."]);
await expect(fs.stat(defaultCacheFile)).resolves.not.toThrowError();
await runPrettier(dir, ["--write", "."]);
await expect(fs.stat(defaultCacheFile)).rejects.toThrowError();
});
});
});
+3
View File
@@ -0,0 +1,3 @@
function a() {
console.log("this is a.js")
}
+3
View File
@@ -0,0 +1,3 @@
function b() {
console.log("this is b.js");
}
+3
View File
@@ -0,0 +1,3 @@
{
"description": "this file is needed for testing to create default cache file"
}
+3
View File
@@ -0,0 +1,3 @@
// This file is generated automatically.
export {default} from "sdbm";
export * from "sdbm";
+35
View File
@@ -0,0 +1,35 @@
// @ts-nocheck
// This file is generated automatically
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// node_modules/sdbm/index.js
var sdbm_exports = {};
__export(sdbm_exports, {
default: () => sdbm
});
module.exports = __toCommonJS(sdbm_exports);
function sdbm(string) {
let hash = 0;
for (let i = 0; i < string.length; i++) {
hash = string.charCodeAt(i) + (hash << 6) + (hash << 16) - hash;
}
return hash >>> 0;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {});
+1 -1
View File
@@ -1,5 +1,5 @@
import * as React from "react";
import { groupBy } from "../../../src/cli/utils.js";
import { groupBy } from "../util.js";
import { SidebarCategory } from "./components.js";
import Option from "./options.js";
+24
View File
@@ -94,3 +94,27 @@ export function getAstAutoFold(parser) {
return astAutoFold.glimmer;
}
}
/**
* Copied from https://github.com/prettier/prettier/blob/6fe21780115cf5f74f83876d64b03a727fbab220/src/cli/utils.js#L6-L27
* @template Obj
* @template Key
* @param {Array<Obj>} array
* @param {(value: Obj) => Key} iteratee
* @returns {{[p in Key]: T}}
*/
export function groupBy(array, iteratee) {
const result = Object.create(null);
for (const value of array) {
const key = iteratee(value);
if (Array.isArray(result[key])) {
result[key].push(value);
} else {
result[key] = [value];
}
}
return result;
}
+34 -3
View File
@@ -1638,6 +1638,18 @@
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40"
integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==
"@types/file-entry-cache@5.0.2":
version "5.0.2"
resolved "https://registry.yarnpkg.com/@types/file-entry-cache/-/file-entry-cache-5.0.2.tgz#3d31097d34fb5ff6bd9951f80d4082bda52feece"
integrity sha512-6uLb9gNrW+e4JivzglLQ2eJSyd7xvu5gSkwKIlOZOmuFgz8U7O9ddTwWjmWgUaIeukdQhoWefI5fQ5/MRTw8XA==
dependencies:
"@types/node" "*"
"@types/find-cache-dir@3.2.1":
version "3.2.1"
resolved "https://registry.yarnpkg.com/@types/find-cache-dir/-/find-cache-dir-3.2.1.tgz#7b959a4b9643a1e6a1a5fe49032693cc36773501"
integrity sha512-frsJrz2t/CeGifcu/6uRo4b+SzAwT4NYCVPu1GN8IB9XTzrpPkGuV0tmh9mN+/L0PklAlsC3u5Fxt0ju00LXIw==
"@types/graceful-fs@^4.1.2":
version "4.1.5"
resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15"
@@ -2394,6 +2406,11 @@ commenting@1.1.0:
resolved "https://registry.yarnpkg.com/commenting/-/commenting-1.1.0.tgz#fae14345c6437b8554f30bc6aa6c1e1633033590"
integrity sha512-YeNK4tavZwtH7jEgK1ZINXzLKm6DZdEMfsaaieOsCAN0S8vsY7UeuO3Q7d/M018EFgE+IeUAuBOKkFccBZsUZA==
commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@@ -3367,7 +3384,7 @@ fetch-blob@^3.1.2, fetch-blob@^3.1.4:
node-domexception "^1.0.0"
web-streams-polyfill "^3.0.3"
file-entry-cache@^6.0.1:
file-entry-cache@6.0.1, file-entry-cache@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==
@@ -3381,6 +3398,15 @@ fill-range@^7.0.1:
dependencies:
to-regex-range "^5.0.1"
find-cache-dir@3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b"
integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==
dependencies:
commondir "^1.0.1"
make-dir "^3.0.2"
pkg-dir "^4.1.0"
find-parent-dir@0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/find-parent-dir/-/find-parent-dir-0.3.1.tgz#c5c385b96858c3351f95d446cab866cbf9f11125"
@@ -4849,7 +4875,7 @@ magic-string@^0.25.3:
dependencies:
sourcemap-codec "^1.4.4"
make-dir@^3.0.0:
make-dir@^3.0.0, make-dir@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
@@ -5399,7 +5425,7 @@ pirates@^4.0.4:
resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b"
integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==
pkg-dir@^4.2.0:
pkg-dir@^4.1.0, pkg-dir@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==
@@ -5846,6 +5872,11 @@ scslre@^0.1.6:
regexp-ast-analysis "^0.2.3"
regexpp "^3.2.0"
sdbm@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/sdbm/-/sdbm-2.0.0.tgz#23828c1195e341d0f5810c59dfa60d86278f8718"
integrity sha512-dspMGxvHiwSTgyrmm90jHQV2sDqK46ssbDK+bQAlJ5aRuPo3C7So108V6rCuCDbm1CrNWuPeMpmTNQKPl7vO+A==
semver-compare@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"