Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,11 @@ useEffect(

This version updates the TypeScript parser to correctly handle JSX text with double slashes (`//`). In previous versions, this would cause Prettier to crash.

#### CLI: Add `--only-changed` flag ([#5910] by [@g-harel])

Flag used with `--write` to avoid re-checking files that were not changed since they were last written (with the same formatting configuration).

[#5910]: https://github.com/prettier/prettier/pull/5910
[#6186]: https://github.com/prettier/prettier/pull/6186
[#6206]: https://github.com/prettier/prettier/pull/6206
[#6209]: https://github.com/prettier/prettier/pull/6209
Expand All @@ -272,3 +277,4 @@ This version updates the TypeScript parser to correctly handle JSX text with dou
[@duailibe]: https://github.com/duailibe
[@gavinjoyce]: https://github.com/gavinjoyce
[@sosukesuzuki]: https://github.com/sosukesuzuki
[@g-harel]: https://github.com/g-harel
2 changes: 2 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ Prettier CLI will ignore files located in `node_modules` directory. To opt-out f

This rewrites all processed files in place. This is comparable to the `eslint --fix` workflow.

To avoid re-checking unchanged files, use the `--only-changed` flag.

## `--loglevel`

Change the level of logging for the CLI. Valid options are:
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"editorconfig-to-prettier": "0.1.1",
"escape-string-regexp": "1.0.5",
"esutils": "2.0.2",
"find-cache-dir": "1.0.0",
"find-parent-dir": "0.3.0",
"find-project-root": "1.1.1",
"flow-parser": "0.84.0",
Expand Down Expand Up @@ -71,6 +72,7 @@
"unicode-regex": "2.0.0",
"unified": "6.1.6",
"vnopts": "1.0.2",
"write-file-atomic": "2.3.0",
"yaml": "1.0.2",
"yaml-unist-parser": "1.0.0"
},
Expand Down
79 changes: 79 additions & 0 deletions src/cli/changed-cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"use strict";

const crypto = require("crypto");

// Generates a hash of the input string.
function hash(data) {
return crypto
.createHash("sha1")
.update(data)
.digest("base64");
Copy link
Copy Markdown
Member

@alexander-akait alexander-akait Apr 9, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe md4 (better use xxHash, but xxHash doesn't built-in in node 😞 ), sha1 is very slow, also we don't need strong here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We had a bit of a discussion about this on the issue and ended up finding that hashing is not a bottleneck in this case. I'm open to changing it, but I'm not sure it's worth adding a dependency.

#5853 (comment)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okey, let's wait other feedback, for me md4 is best solution here, when you have really big codebase, it is increase perf around 5%

}

// Generates the cache key using the file path, options and the support info hash.
function calcKey(path, options, supportInfoHash) {
return hash(path + JSON.stringify(options) + supportInfoHash);
}

class ChangedCache {
// Initializes the in-memory cache data from the configured location.
// Also calculates the static support info hash used to compute file keys.
// A missing cache file is not treated as an error because it is expected on first run.
constructor(options) {
this.location = options.location;
this.readFile = options.readFile;
this.writeFile = options.writeFile;
this.context = options.context;
this.supportInfoHash = hash(JSON.stringify(options.supportInfo));

this.cache = {};

let contents;
try {
contents = this.readFile(this.location, "utf8");
} catch (err) {
if (err.code !== "ENOENT") {
this.context.logger.error(`Could not read cache file: ${err}`);
}
return;
}

try {
this.cache = JSON.parse(contents);
} catch (err) {
this.context.logger.error(`Could not parse cache contents: ${err}`);
}
}

// Writes the in-memory cache data to the configured file.
// Previous file contents are overwritten.
close() {
let contents;
try {
contents = JSON.stringify(this.cache);
} catch (err) {
this.context.logger.error(`Could not serialize cache: ${err}`);
return;
}

try {
this.writeFile(this.location, contents, "utf8");
} catch (err) {
this.context.logger.error(`Could not write cache to file: ${err}`);
}
}

// Checks if the expected contents of the file path match the in-memory data.
notChanged(path, options, content) {
return (
this.cache[calcKey(path, options, this.supportInfoHash)] === hash(content)
);
}

// Updates the expected contents of the file path in the in-memory data.
update(path, options, content) {
this.cache[calcKey(path, options, this.supportInfoHash)] = hash(content);
}
}

module.exports = ChangedCache;
4 changes: 4 additions & 0 deletions src/cli/constant.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ const options = {
default: "log",
choices: ["silent", "error", "warn", "log", "debug"]
},
"only-changed": {
type: "boolean",
description: "Only format files changed since last '--write'."
},
stdin: {
type: "boolean",
description: "Force reading input from stdin."
Expand Down
5 changes: 5 additions & 0 deletions src/cli/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ function run(args) {
process.exit(1);
}

if (context.argv["only-changed"] && !context.argv["write"]) {
context.logger.error("Cannot use --only-changed without --write.");
process.exit(1);
}
Copy link
Copy Markdown
Member

@alexander-akait alexander-akait Apr 9, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should enable cache by default and better add option --no-cache (better DX, this do many tools. example jest)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought it might be best to avoid changing existing behavior silently, especially since this flag writes a file that needs to gitignored. I'll defer to the maintainers, y'all know best.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/cc @prettier/core

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can do this for v2

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, probably better to do it for v2

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But what is problem implement --no-cache by default?


if (context.argv["find-config-path"] && context.filePatterns.length) {
context.logger.error("Cannot use --find-config-path with multiple files");
process.exit(1);
Expand Down
55 changes: 48 additions & 7 deletions src/cli/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const globby = require("globby");
const chalk = require("chalk");
const readline = require("readline");
const stringify = require("json-stable-stringify");
const findCacheDir = require("find-cache-dir");

const minimist = require("./minimist");
const prettier = require("../../index");
Expand All @@ -20,6 +21,7 @@ const optionsNormalizer = require("../main/options-normalizer");
const thirdParty = require("../common/third-party");
const arrayify = require("../utils/arrayify");
const isTTY = require("../utils/is-tty");
const ChangedCache = require("./changed-cache");

const OPTION_USAGE_THRESHOLD = 25;
const CHOICE_USAGE_MARGIN = 3;
Expand Down Expand Up @@ -441,6 +443,18 @@ function formatFiles(context) {
context.logger.log("Checking formatting...");
}

let changedCache = null;
if (context.argv["only-changed"]) {
const cacheDir = findCacheDir({ name: "prettier", create: true });
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/cc @g-harel need use || os.tmp() https://github.com/avajs/find-cache-dir#findcachediroptions

It returns a string containing the absolute path to the cache directory, or undefined if package.json was never found.

In theory you can install prettier globally and doesn't have package.json

Copy link
Copy Markdown
Contributor Author

@g-harel g-harel Jul 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch! I've openned a PR with a fix (#6327).

changedCache = new ChangedCache({
location: path.join(cacheDir, "changed"),
readFile: fs.readFileSync,
writeFile: thirdParty.writeFileAtomic,
context: context,
supportInfo: prettier.getSupportInfo()
});
}

eachFilename(context, context.filePatterns, filename => {
const fileIgnored = ignorer.filter([filename]).length === 0;
if (
Expand All @@ -457,9 +471,15 @@ function formatFiles(context) {
filepath: filename
});

let removeFilename = () => {};
if (isTTY()) {
// Don't use `console.log` here since we need to replace this line.
context.logger.log(filename, { newline: false });
removeFilename = () => {
readline.clearLine(process.stdout, 0);
readline.cursorTo(process.stdout, 0, null);
removeFilename = () => {};
};
}

let input;
Expand All @@ -477,6 +497,19 @@ function formatFiles(context) {
return;
}

if (changedCache) {
if (changedCache.notChanged(filename, options, input)) {
// Remove previously printed filename to log it with "unchanged".
removeFilename();

if (!context.argv["check"] && !context.argv["list-different"]) {
context.logger.log(chalk.grey(`${filename} unchanged`));
}

return;
}
}

if (fileIgnored) {
writeOutput(context, { formatted: input }, options);
return;
Expand All @@ -501,11 +534,8 @@ function formatFiles(context) {

const isDifferent = output !== input;

if (isTTY()) {
// Remove previously printed filename to log it with duration.
readline.clearLine(process.stdout, 0);
readline.cursorTo(process.stdout, 0, null);
}
// Remove previously printed filename to log it with duration.
removeFilename();

if (context.argv["write"]) {
// Don't write the file if it won't change in order not to invalidate
Expand All @@ -524,8 +554,15 @@ function formatFiles(context) {
// Don't exit the process if one file failed
process.exitCode = 2;
}
} else if (!context.argv["check"] && !context.argv["list-different"]) {
context.logger.log(`${chalk.grey(filename)} ${Date.now() - start}ms`);
} else {
if (!context.argv["check"] && !context.argv["list-different"]) {
context.logger.log(`${chalk.grey(filename)} ${Date.now() - start}ms`);
}
}

// Cache is updated to record pretty content.
if (changedCache) {
changedCache.update(filename, options, output);
}
} else if (context.argv["debug-check"]) {
if (result.filepath) {
Expand All @@ -546,6 +583,10 @@ function formatFiles(context) {
}
});

if (changedCache) {
changedCache.close();
}

// Print check summary based on expected exit code
if (context.argv["check"]) {
context.logger.log(
Expand Down
3 changes: 2 additions & 1 deletion src/common/third-party.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ module.exports = {
cosmiconfig: require("cosmiconfig"),
findParentDir: require("find-parent-dir").sync,
getStream: require("get-stream"),
isCI: () => require("is-ci")
isCI: () => require("is-ci"),
writeFileAtomic: require("write-file-atomic").sync
};
11 changes: 11 additions & 0 deletions tests_integration/__tests__/__snapshots__/early-exit.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ Other options:
--loglevel <silent|error|warn|log|debug>
What level of logs to report.
Defaults to log.
--only-changed Only format files changed since last '--write'.
--require-pragma Require either '@prettier' or '@format' to be present in the file's first docblock comment
in order for it to be formatted.
Defaults to false.
Expand Down Expand Up @@ -294,6 +295,7 @@ Other options:
--loglevel <silent|error|warn|log|debug>
What level of logs to report.
Defaults to log.
--only-changed Only format files changed since last '--write'.
--require-pragma Require either '@prettier' or '@format' to be present in the file's first docblock comment
in order for it to be formatted.
Defaults to false.
Expand Down Expand Up @@ -344,6 +346,15 @@ exports[`throw error with --help not-found (stdout) 1`] = `""`;

exports[`throw error with --help not-found (write) 1`] = `Array []`;

exports[`throw error with --only-changed without --write (stderr) 1`] = `
"[error] Cannot use --only-changed without --write.
"
`;

exports[`throw error with --only-changed without --write (stdout) 1`] = `""`;

exports[`throw error with --only-changed without --write (write) 1`] = `Array []`;

exports[`throw error with --write + --debug-check (stderr) 1`] = `
"[error] Cannot use --write and --debug-check together.
"
Expand Down
11 changes: 11 additions & 0 deletions tests_integration/__tests__/__snapshots__/help-options.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,17 @@ exports[`show detailed usage with --help no-semi (stdout) 1`] = `

exports[`show detailed usage with --help no-semi (write) 1`] = `Array []`;

exports[`show detailed usage with --help only-changed (stderr) 1`] = `""`;

exports[`show detailed usage with --help only-changed (stdout) 1`] = `
"--only-changed

Only format files changed since last '--write'.
"
`;

exports[`show detailed usage with --help only-changed (write) 1`] = `Array []`;

exports[`show detailed usage with --help parser (stderr) 1`] = `""`;

exports[`show detailed usage with --help parser (stdout) 1`] = `
Expand Down
43 changes: 43 additions & 0 deletions tests_integration/__tests__/__snapshots__/only-changed.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`create cache with --write --only-changed + formatted file (stderr) 1`] = `""`;

exports[`create cache with --write --only-changed + formatted file (stdout) 1`] = `
"formatted.js 0ms
"
`;

exports[`create cache with --write --only-changed + unformatted file (stderr) 1`] = `""`;

exports[`create cache with --write --only-changed + unformatted file (stdout) 1`] = `
"unformatted.js 0ms
"
`;

exports[`detect config change with --write --only-changed + unformatted file (stderr) 1`] = `""`;

exports[`detect config change with --write --only-changed + unformatted file (stderr) 2`] = `""`;

exports[`detect config change with --write --only-changed + unformatted file (stdout) 1`] = `
"unformatted.js 0ms
"
`;

exports[`detect config change with --write --only-changed + unformatted file (stdout) 2`] = `
"unformatted.js 0ms
"
`;

exports[`detect unchanged with --write --only-changed + formatted file (stderr) 1`] = `""`;

exports[`detect unchanged with --write --only-changed + formatted file (stderr) 2`] = `""`;

exports[`detect unchanged with --write --only-changed + formatted file (stdout) 1`] = `
"formatted.js 0ms
"
`;

exports[`detect unchanged with --write --only-changed + formatted file (stdout) 2`] = `
"formatted.js unchanged
"
`;
6 changes: 6 additions & 0 deletions tests_integration/__tests__/early-exit.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ describe("throw error with --write + --debug-check", () => {
});
});

describe("throw error with --only-changed without --write", () => {
runPrettier("cli", ["--only-changed"]).test({
status: 1
});
});

describe("throw error with --find-config-path + multiple files", () => {
runPrettier("cli", ["--find-config-path", "abc.js", "def.js"]).test({
status: 1
Expand Down
Loading