prettier/bin/prettier.js

334 lines
9.6 KiB
JavaScript
Raw Normal View History

2016-11-29 20:14:10 +03:00
#!/usr/bin/env node
2017-01-10 20:18:22 +03:00
"use strict";
2017-01-28 18:50:22 +03:00
2016-11-29 20:14:10 +03:00
const fs = require("fs");
2017-01-11 18:57:16 +03:00
const getStdin = require("get-stdin");
const glob = require("glob");
const chalk = require("chalk");
const minimist = require("minimist");
const readline = require("readline");
2017-01-22 03:42:13 +03:00
const prettier = require("../index");
Run AST comparison tests on Travis (#1553) * Run AST comparison tests on Travis It looks like some of these currently fail, so we should probably also sort that out. Inspired by https://github.com/prettier/prettier/issues/1552 * tests: Use specified parser when AST_COMPARE=1 This fixes some of the tests with AST_COMPARE=1 * Move cleanAST() into prettier.__debug This makes it available for tests to use. * AST_COMPARE=1 uses cleanAst() instead of removeEmptyStatements() Ths fixes some of the tests with AST_COMPARE=1 * Export parse() from src/parser.js This makes it available for tests to use. * tests: Use specified parser more when AST_COMPARE=1 This is a continuation of commit 86437a66d326919897fe89891a25824870f5bb79 This fixes some of the tests with AST_COMPARE=1 * massageAST: remove leadingComments/trailingComments This fixes some of the tests with AST_COMPARE=1 * massageAST: remove `extra` This fixes some of the tests with AST_COMPARE=1 * tests_config/run_spec.js: Rename variables for clarity * AST_COMPARE=1 tests compare unstringified objects This makes the test error output shorter. * fixup! Export parse() from src/parser.js * Revert "Run AST comparison tests on Travis" See https://github.com/prettier/prettier/pull/1553#issuecomment-300027747 This reverts commit 49873a956c532f23fd216551a35ae35c1a18407e. * fixup! fixup! Export parse() from src/parser.js * parser: Require babel-code-frame only when needed This addresses: * https://github.com/prettier/prettier/pull/1553#discussion_r115386253 * https://github.com/prettier/prettier/pull/1553#discussion_r115386250 * parser: Don't export now-unused parseWith* functions Addresses https://github.com/prettier/prettier/pull/1553#discussion_r115386964 * Move cleanAST/massageAST into own file, don't export This addresses: * https://github.com/prettier/prettier/pull/1553#discussion_r115386993 * https://github.com/prettier/prettier/pull/1553#discussion_r115386611 * Don't destructure require() result (Node v4 compat.) * Fix copy/paste error
2017-05-09 04:16:35 +03:00
const cleanAST = require('../src/clean-ast.js').cleanAST;
2016-11-29 20:14:10 +03:00
const argv = minimist(process.argv.slice(2), {
2017-01-13 23:03:53 +03:00
boolean: [
"write",
"stdin",
"use-tabs",
2017-04-13 04:51:08 +03:00
"semi",
2017-01-13 23:03:53 +03:00
"single-quote",
"bracket-spacing",
"jsx-bracket-same-line",
// The supports-color package (a sub sub dependency) looks directly at
// `process.argv` for `--no-color` and such-like options. The reason it is
// listed here is to avoid "Ignored unknown option: --no-color" warnings.
// See https://github.com/chalk/supports-color/#info for more information.
"color",
"list-different",
"help",
"version",
"debug-print-doc",
"debug-check",
// Deprecated in 0.0.10
"flow-parser"
],
string: ["print-width", "tab-width", "parser", "trailing-comma"],
2017-04-13 20:18:35 +03:00
default: { semi: true, color: true, "bracket-spacing": true, parser: "babylon" },
alias: { help: "h", version: "v", "list-different": "l" },
unknown: param => {
if (param.startsWith("-")) {
console.warn("Ignored unknown option: " + param + "\n");
return false;
}
}
});
if (argv["version"]) {
2017-01-22 03:42:13 +03:00
console.log(prettier.version);
process.exit(0);
}
const filepatterns = argv["_"];
2017-01-10 23:45:04 +03:00
const write = argv["write"];
2017-02-23 20:57:51 +03:00
const stdin = argv["stdin"] || (!filepatterns.length && !process.stdin.isTTY);
2016-11-29 20:14:10 +03:00
if (write && argv["debug-check"]) {
console.error("Cannot use --write and --debug-check together.");
process.exit(1);
}
function getParserOption() {
const optionName = "parser";
const value = argv[optionName];
if (value === undefined) {
return value;
}
// For backward compatibility. Deprecated in 0.0.10
if (argv["flow-parser"]) {
console.warn("`--flow-parser` is deprecated. Use `--parser flow` instead.");
return "flow";
}
if (value === "flow" || value === "babylon" || value === "typescript") {
return value;
}
console.warn(
"Ignoring unknown --" +
optionName +
' value, falling back to "babylon":\n' +
' Expected "flow" or "babylon", but received: ' +
JSON.stringify(value)
);
return "babylon";
}
function getIntOption(optionName) {
const value = argv[optionName];
if (value === undefined) {
return value;
}
if (/^\d+$/.test(value)) {
return Number(value);
}
console.error(
"Invalid --" +
optionName +
" value. Expected an integer, but received: " +
JSON.stringify(value)
);
process.exit(1);
}
function getTrailingComma() {
switch (argv["trailing-comma"]) {
case undefined:
case "none":
return "none";
case "":
2017-02-23 20:57:51 +03:00
console.warn(
"Warning: `--trailing-comma` was used without an argument. This is deprecated. " +
'Specify "none", "es5", or "all".'
);
case "es5":
return "es5";
case "all":
return "all";
default:
throw new Error("Invalid option for --trailing-comma");
}
}
const options = {
useTabs: argv["use-tabs"],
2017-04-13 04:51:08 +03:00
semi: argv["semi"],
printWidth: getIntOption("print-width"),
tabWidth: getIntOption("tab-width"),
bracketSpacing: argv["bracket-spacing"],
singleQuote: argv["single-quote"],
jsxBracketSameLine: argv["jsx-bracket-same-line"],
trailingComma: getTrailingComma(),
parser: getParserOption()
};
2017-01-11 18:57:16 +03:00
function format(input) {
if (argv["debug-print-doc"]) {
2017-01-22 03:42:13 +03:00
const doc = prettier.__debug.printToDoc(input, options);
return prettier.__debug.formatDoc(doc);
}
if (argv["debug-check"]) {
function diff(a, b) {
return require("diff")
.createTwoFilesPatch("", "", a, b, "", "", { context: 2 });
}
const pp = prettier.format(input, options);
const pppp = prettier.format(pp, options);
if (pp !== pppp) {
throw "prettier(input) !== prettier(prettier(input))\n" + diff(pp, pppp);
} else {
const ast = cleanAST(prettier.__debug.parse(input, options));
const past = cleanAST(prettier.__debug.parse(pp, options));
if (ast !== past) {
const MAX_AST_SIZE = 2097152; // 2MB
const astDiff = (ast.length > MAX_AST_SIZE || past.length > MAX_AST_SIZE)
? "AST diff too large to render"
: diff(ast, past);
throw (
"ast(input) !== ast(prettier(input))\n" +
astDiff + "\n" +
diff(input, pp)
);
}
}
return;
}
2017-01-22 03:42:13 +03:00
return prettier.format(input, options);
2017-01-11 18:57:16 +03:00
}
2017-01-10 07:03:35 +03:00
function handleError(filename, e) {
const isParseError = Boolean(e && e.loc);
const isValidationError = /Validation Error/.test(e && e.message);
// For parse errors and validation errors, we only want to show the error
// message formatted in a nice way. `String(e)` takes care of that. Other
// (unexpected) errors are passed as-is as a separate argument to
// `console.error`. That includes the stack trace (if any), and shows a nice
// `util.inspect` of throws things that aren't `Error` objects. (The Flow
// parser has mistakenly thrown arrays sometimes.)
if (isParseError) {
console.error(filename + ": " + String(e));
} else if (isValidationError) {
console.error(String(e));
// If validation fails for one file, it will fail for all of them.
process.exit(1);
} else {
console.error(filename + ":", e);
}
// Don't exit the process if one file failed
process.exitCode = 2;
}
2017-02-23 20:57:51 +03:00
if (argv["help"] || (!filepatterns.length && !stdin)) {
console.log(
"Usage: prettier [opts] [filename ...]\n\n" +
"Available options:\n" +
" --write Edit the file in-place. (Beware!)\n" +
" --list-different or -l Print filenames of files that are different from Prettier formatting.\n" +
" --stdin Read input from stdin.\n" +
" --print-width <int> Specify the length of line that the printer will wrap on. Defaults to 80.\n" +
" --tab-width <int> Specify the number of spaces per indentation-level. Defaults to 2.\n" +
" --use-tabs Indent lines with tabs instead of spaces.\n" +
" --no-semi Do not print semicolons, except at the beginning of lines which may need them.\n" +
" --single-quote Use single quotes instead of double quotes.\n" +
" --no-bracket-spacing Do not print spaces between brackets.\n" +
" --jsx-bracket-same-line Put > on the last line instead of at a new line.\n" +
" --trailing-comma <none|es5|all>\n" +
" Print trailing commas wherever possible. Defaults to none.\n" +
" --parser <flow|babylon> Specify which parse to use. Defaults to babylon.\n" +
" --no-color Do not colorize error messages.\n" +
" --version or -v Print Prettier version.\n" +
"\n"
);
process.exit(argv["help"] ? 0 : 1);
}
2017-01-11 18:57:16 +03:00
if (stdin) {
getStdin().then(input => {
try {
// Don't use `console.log` here since it adds an extra newline at the end.
process.stdout.write(format(input));
} catch (e) {
handleError("stdin", e);
return;
}
2017-01-11 18:57:16 +03:00
});
} else {
eachFilename(filepatterns, filename => {
if (write || argv["debug-check"]) {
// Don't use `console.log` here since we need to replace this line.
process.stdout.write(filename);
}
let input;
try {
input = fs.readFileSync(filename, "utf8");
} catch (e) {
// Add newline to split errors from filename line.
process.stdout.write("\n");
console.error("Unable to read file: " + filename + "\n" + e);
// Don't exit the process if one file failed
process.exitCode = 2;
return;
}
if (argv["list-different"]) {
if (!prettier.check(input, options)) {
console.log(filename);
process.exitCode = 1;
}
return;
}
const start = Date.now();
2017-01-11 18:57:16 +03:00
let output;
try {
output = format(input);
} catch (e) {
// Add newline to split errors from filename line.
process.stdout.write("\n");
handleError(filename, e);
return;
}
if (write) {
// Remove previously printed filename to log it with duration.
readline.clearLine(process.stdout, 0);
readline.cursorTo(process.stdout, 0, null);
2017-01-11 18:57:16 +03:00
// Don't write the file if it won't change in order not to invalidate
// mtime based caches.
if (output === input) {
console.log(chalk.grey("%s %dms"), filename, Date.now() - start);
2017-01-11 18:57:16 +03:00
} else {
console.log("%s %dms", filename, Date.now() - start);
try {
fs.writeFileSync(filename, output, "utf8");
} catch (err) {
console.error("Unable to write file: " + filename + "\n" + err);
// Don't exit the process if one file failed
process.exitCode = 2;
}
2017-01-11 18:57:16 +03:00
}
} else if (argv["debug-check"]) {
process.stdout.write("\n");
if (output) {
console.log(output);
} else {
process.exitCode = 2;
}
} else {
// Don't use `console.log` here since it adds an extra newline at the end.
process.stdout.write(output);
}
2017-01-10 23:45:04 +03:00
});
2017-01-11 18:57:16 +03:00
}
function eachFilename(patterns, callback) {
patterns.forEach(pattern => {
if (!glob.hasMagic(pattern)) {
callback(pattern);
return;
}
glob(pattern, (err, filenames) => {
if (err) {
console.error("Unable to expand glob pattern: " + pattern + "\n" + err);
// Don't exit the process if one pattern failed
process.exitCode = 2;
return;
}
filenames.forEach(filename => {
callback(filename);
});
});
});
}