From cb00fd6e988184420f842b1e77ca4cf627d32773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BD=97=E6=96=87?= Date: Sun, 17 Nov 2019 00:48:45 +0800 Subject: [PATCH] fmt: respect prettierrc and prettierignore (#3346) --- cli/flags.rs | 57 +++++- std/prettier/main.ts | 181 +++++++++++++++++- std/prettier/main_test.ts | 156 +++++++++++++++ std/prettier/testdata/5.ts | 1 + .../testdata/config_file_js/.prettierrc.js | 5 + .../config_file_json/.prettierrc.json | 4 + .../config_file_toml/.prettierrc.toml | 2 + .../testdata/config_file_ts/.prettierrc.ts | 5 + .../testdata/ignore_file/.prettierignore | 3 + std/prettier/testdata/ignore_file/0.js | 1 + std/prettier/testdata/ignore_file/0.ts | 1 + std/prettier/testdata/ignore_file/1.js | 1 + std/prettier/testdata/ignore_file/1.ts | 1 + std/prettier/testdata/ignore_file/README.md | 5 + .../ignore_file/typescript.prettierignore | 3 + 15 files changed, 415 insertions(+), 11 deletions(-) create mode 100644 std/prettier/testdata/5.ts create mode 100644 std/prettier/testdata/config_file_js/.prettierrc.js create mode 100644 std/prettier/testdata/config_file_json/.prettierrc.json create mode 100644 std/prettier/testdata/config_file_toml/.prettierrc.toml create mode 100644 std/prettier/testdata/config_file_ts/.prettierrc.ts create mode 100644 std/prettier/testdata/ignore_file/.prettierignore create mode 100644 std/prettier/testdata/ignore_file/0.js create mode 100644 std/prettier/testdata/ignore_file/0.ts create mode 100644 std/prettier/testdata/ignore_file/1.js create mode 100644 std/prettier/testdata/ignore_file/1.ts create mode 100644 std/prettier/testdata/ignore_file/README.md create mode 100644 std/prettier/testdata/ignore_file/typescript.prettierignore diff --git a/cli/flags.rs b/cli/flags.rs index 693d290c45..393ca48b60 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -340,6 +340,32 @@ Automatically downloads Prettier dependencies on first run. deno fmt myfile1.ts myfile2.ts", ) + .arg( + Arg::with_name("prettierrc") + .long("prettierrc") + .value_name("auto|disable|FILE") + .help("Specify the configuration file of the prettier. + auto: Auto detect prettier configuration file in current working dir. + disable: Disable load configuration file. + FILE: Load specified prettier configuration file. support .json/.toml/.js/.ts file + ") + .takes_value(true) + .require_equals(true) + .default_value("auto") + ) + .arg( + Arg::with_name("ignore-path") + .long("ignore-path") + .value_name("auto|disable|FILE") + .help("Path to a file containing patterns that describe files to ignore. + auto: Auto detect .pretierignore file in current working dir. + disable: Disable load .prettierignore file. + FILE: Load specified prettier ignore file. + ") + .takes_value(true) + .require_equals(true) + .default_value("auto") + ) .arg( Arg::with_name("stdout") .long("stdout") @@ -966,6 +992,8 @@ pub fn flags_from_vec( } let prettier_flags = [ + ["1", "prettierrc"], + ["1", "ignore-path"], ["1", "print-width"], ["1", "tab-width"], ["0", "use-tabs"], @@ -989,7 +1017,11 @@ pub fn flags_from_vec( if t == "0" { argv.push(format!("--{}", keyword)); } else { - argv.push(format!("--{}", keyword)); + if keyword == "prettierrc" { + argv.push("--config".to_string()); + } else { + argv.push(format!("--{}", keyword)); + } argv.push(fmt_match.value_of(keyword).unwrap().to_string()); } } @@ -1384,7 +1416,11 @@ mod tests { PRETTIER_URL, "script_1.ts", "script_2.ts", - "--write" + "--write", + "--config", + "auto", + "--ignore-path", + "auto" ] ); } @@ -1602,7 +1638,16 @@ mod tests { assert_eq!(subcommand, DenoSubcommand::Run); assert_eq!( argv, - svec!["deno", PRETTIER_URL, "script_1.ts", "script_2.ts"] + svec![ + "deno", + PRETTIER_URL, + "script_1.ts", + "script_2.ts", + "--config", + "auto", + "--ignore-path", + "auto" + ] ); } @@ -2043,6 +2088,7 @@ mod tests { let (flags, subcommand, argv) = flags_from_vec(svec![ "deno", "fmt", + "--prettierrc=auto", "--print-width=100", "--tab-width=4", "--use-tabs", @@ -2054,6 +2100,7 @@ mod tests { "--quote-props=preserve", "--jsx-single-quote", "--jsx-bracket-same-line", + "--ignore-path=.prettier-ignore", "script.ts" ]); assert_eq!( @@ -2072,6 +2119,10 @@ mod tests { PRETTIER_URL, "script.ts", "--write", + "--config", + "auto", + "--ignore-path", + ".prettier-ignore", "--print-width", "100", "--tab-width", diff --git a/std/prettier/main.ts b/std/prettier/main.ts index 2b5aa3e5d0..cd8800d231 100755 --- a/std/prettier/main.ts +++ b/std/prettier/main.ts @@ -24,6 +24,8 @@ // This script formats the given source files. If the files are omitted, it // formats the all files in the repository. import { parse } from "../flags/mod.ts"; +import * as path from "../path/mod.ts"; +import * as toml from "../encoding/toml.ts"; import { ExpandGlobOptions, WalkInfo, expandGlob } from "../fs/mod.ts"; import { prettier, prettierPlugins } from "./prettier.ts"; const { args, cwd, exit, readAll, readFile, stdin, stdout, writeFile } = Deno; @@ -40,6 +42,10 @@ Options: it will output to stdout, Defaults to false. --ignore Ignore the given path(s). + --ignore-path Path to a file containing patterns that + describe files to ignore. Optional + value: auto/disable/filepath. Defaults + to null. --stdin Specifies to read the code from stdin. If run the command in a pipe, you do not need to specify this flag. @@ -49,6 +55,10 @@ Options: parser for stdin. available parser: typescript/babel/markdown/json. Defaults to typescript. + --config Specify the configuration file of the + prettier. + Optional value: auto/disable/filepath. + Defaults to null. JS/TS Styling Options: --print-width The line length where Prettier will try @@ -106,7 +116,7 @@ Example: // Available parsers type ParserLabel = "typescript" | "babel" | "markdown" | "json"; -interface PrettierOptions { +interface PrettierBuildInOptions { printWidth: number; tabWidth: number; useTabs: boolean; @@ -120,6 +130,9 @@ interface PrettierOptions { arrowParens: string; proseWrap: string; endOfLine: string; +} + +interface PrettierOptions extends PrettierBuildInOptions { write: boolean; } @@ -343,10 +356,134 @@ async function* getTargetFiles( } } -async function main(opts): Promise { - const { help, ignore, check, _: args } = opts; +/** + * auto detect prettier configuration file and return config if file exist. + */ +async function autoResolveConfig(): Promise { + const configFileNamesMap = { + ".prettierrc.json": 1, + ".prettierrc.yaml": 1, + ".prettierrc.yml": 1, + ".prettierrc.js": 1, + ".prettierrc.ts": 1, + "prettier.config.js": 1, + "prettier.config.ts": 1, + ".prettierrc.toml": 1 + }; - const prettierOpts: PrettierOptions = { + const files = await Deno.readDir("."); + + for (const f of files) { + if (f.isFile() && configFileNamesMap[f.name]) { + const c = await resolveConfig(f.name); + if (c) { + return c; + } + } + } + + return; +} + +/** + * parse prettier configuration file. + * @param filepath the configuration file path. + * support extension name with .json/.toml/.js + */ +async function resolveConfig( + filepath: string +): Promise { + let config: PrettierOptions = undefined; + + function generateError(msg: string): Error { + return new Error(`Invalid prettier configuration file: ${msg}.`); + } + + const raw = new TextDecoder().decode(await Deno.readFile(filepath)); + + switch (path.extname(filepath)) { + case ".json": + try { + config = JSON.parse(raw) as PrettierOptions; + } catch (err) { + throw generateError(err.message); + } + break; + case ".yml": + case ".yaml": + // TODO: Unimplemented loading yaml / yml configuration file yet. + break; + case ".toml": + try { + config = toml.parse(raw) as PrettierOptions; + } catch (err) { + throw generateError(err.message); + } + break; + case ".js": + case ".ts": + const absPath = path.isAbsolute(filepath) + ? filepath + : path.join(cwd(), filepath); + + try { + const output = await import( + // TODO: Remove platform condition + // after https://github.com/denoland/deno/issues/3355 fixed + Deno.build.os === "win" ? "file://" + absPath : absPath + ); + + if (output && output.default) { + config = output.default; + } else { + throw new Error( + "Prettier of JS version should have default exports." + ); + } + } catch (err) { + throw generateError(err.message); + } + + break; + default: + break; + } + + return config; +} + +/** + * auto detect .prettierignore and return pattern if file exist. + */ +async function autoResolveIgnoreFile(): Promise { + const files = await Deno.readDir("."); + + for (const f of files) { + if (f.isFile() && f.name === ".prettierignore") { + return await resolveIgnoreFile(f.name); + } + } + + return []; +} + +/** + * parse prettier ignore file. + * @param filepath the ignore file path. + */ +async function resolveIgnoreFile(filepath: string): Promise { + const raw = new TextDecoder().decode(await Deno.readFile(filepath)); + + return raw + .split("\n") + .filter((v: string) => !!v.trim() && v.trim().indexOf("#") !== 0) + .map(v => v); +} + +async function main(opts): Promise { + const { help, check, _: args } = opts; + + let prettierOpts: PrettierOptions = { printWidth: Number(opts["print-width"]), tabWidth: Number(opts["tab-width"]), useTabs: Boolean(opts["use-tabs"]), @@ -368,10 +505,37 @@ async function main(opts): Promise { exit(0); } - const files = getTargetFiles( - args.length ? args : ["."], - Array.isArray(ignore) ? ignore : [ignore] - ); + const configFilepath = opts["config"]; + + if (configFilepath && configFilepath !== "disable") { + const config = + configFilepath === "auto" + ? await autoResolveConfig() + : await resolveConfig(configFilepath); + + if (config) { + prettierOpts = { ...prettierOpts, ...config }; + } + } + + let ignore = opts.ignore as string[]; + + if (!Array.isArray(ignore)) { + ignore = [ignore]; + } + + const ignoreFilepath = opts["ignore-path"]; + + if (ignoreFilepath && ignoreFilepath !== "disable") { + const ignorePatterns: string[] = + ignoreFilepath === "auto" + ? await autoResolveIgnoreFile() + : await resolveIgnoreFile(ignoreFilepath); + + ignore = ignore.concat(ignorePatterns); + } + + const files = getTargetFiles(args.length ? args : ["."], ignore); const tty = Deno.isTTY(); @@ -396,6 +560,7 @@ main( parse(args.slice(1), { string: [ "ignore", + "ignore-path", "printWidth", "tab-width", "trailing-comma", diff --git a/std/prettier/main_test.ts b/std/prettier/main_test.ts index 16c19ed036..a638907412 100644 --- a/std/prettier/main_test.ts +++ b/std/prettier/main_test.ts @@ -378,4 +378,160 @@ test(async function testPrettierReadFromStdin(): Promise { } }); +test(async function testPrettierWithAutoConfig(): Promise { + const configs = [ + "config_file_json", + "config_file_toml", + "config_file_js", + "config_file_ts" + ]; + + for (const configName of configs) { + const cwd = join(testdata, configName); + const prettierFile = join(Deno.cwd(), "prettier", "main.ts"); + const { stdout, stderr } = Deno.run({ + args: [ + execPath(), + "run", + "--allow-read", + "--allow-env", + prettierFile, + "../5.ts", + "--config", + "auto" + ], + stdout: "piped", + stderr: "piped", + cwd + }); + + const output = decoder.decode(await Deno.readAll(stdout)); + const errMsg = decoder.decode(await Deno.readAll(stderr)); + + assertEquals( + errMsg + .split(EOL) + .filter((line: string) => line.indexOf("Compile") !== 0) + .join(EOL), + "" + ); + + assertEquals(output, `console.log('0');\n`); + } +}); + +test(async function testPrettierWithSpecifiedConfig(): Promise { + interface Config { + dir: string; + name: string; + } + const configs: Config[] = [ + { + dir: "config_file_json", + name: ".prettierrc.json" + }, + { + dir: "config_file_toml", + name: ".prettierrc.toml" + }, + { + dir: "config_file_js", + name: ".prettierrc.js" + }, + { + dir: "config_file_ts", + name: ".prettierrc.ts" + } + ]; + + for (const config of configs) { + const cwd = join(testdata, config.dir); + const prettierFile = join(Deno.cwd(), "prettier", "main.ts"); + const { stdout, stderr } = Deno.run({ + args: [ + execPath(), + "run", + "--allow-read", + "--allow-env", + prettierFile, + "../5.ts", + "--config", + config.name + ], + stdout: "piped", + stderr: "piped", + cwd + }); + + const output = decoder.decode(await Deno.readAll(stdout)); + const errMsg = decoder.decode(await Deno.readAll(stderr)); + + assertEquals( + errMsg + .split(EOL) + .filter((line: string) => line.indexOf("Compile") !== 0) + .join(EOL), + "" + ); + + assertEquals(output, `console.log('0');\n`); + } +}); + +test(async function testPrettierWithAutoIgnore(): Promise { + // only format typescript file + const cwd = join(testdata, "ignore_file"); + const prettierFile = join(Deno.cwd(), "prettier", "main.ts"); + const { stdout, stderr } = Deno.run({ + args: [ + execPath(), + "run", + "--allow-read", + "--allow-env", + prettierFile, + "**/*", + "--ignore-path", + "auto" + ], + stdout: "piped", + stderr: "piped", + cwd + }); + + assertEquals(decoder.decode(await Deno.readAll(stderr)), ""); + + assertEquals( + decoder.decode(await Deno.readAll(stdout)), + `console.log("typescript");\nconsole.log("typescript1");\n` + ); +}); + +test(async function testPrettierWithSpecifiedIgnore(): Promise { + // only format javascript file + const cwd = join(testdata, "ignore_file"); + const prettierFile = join(Deno.cwd(), "prettier", "main.ts"); + const { stdout, stderr } = Deno.run({ + args: [ + execPath(), + "run", + "--allow-read", + "--allow-env", + prettierFile, + "**/*", + "--ignore-path", + "typescript.prettierignore" + ], + stdout: "piped", + stderr: "piped", + cwd + }); + + assertEquals(decoder.decode(await Deno.readAll(stderr)), ""); + + assertEquals( + decoder.decode(await Deno.readAll(stdout)), + `console.log("javascript");\nconsole.log("javascript1");\n` + ); +}); + runIfMain(import.meta); diff --git a/std/prettier/testdata/5.ts b/std/prettier/testdata/5.ts new file mode 100644 index 0000000000..f1b5b9cf1d --- /dev/null +++ b/std/prettier/testdata/5.ts @@ -0,0 +1 @@ +console.log("0" ) diff --git a/std/prettier/testdata/config_file_js/.prettierrc.js b/std/prettier/testdata/config_file_js/.prettierrc.js new file mode 100644 index 0000000000..ba52f4d095 --- /dev/null +++ b/std/prettier/testdata/config_file_js/.prettierrc.js @@ -0,0 +1,5 @@ +// prettier.config.js or .prettierrc.js +export default { + singleQuote: true, + trailingComma: "all" +}; diff --git a/std/prettier/testdata/config_file_json/.prettierrc.json b/std/prettier/testdata/config_file_json/.prettierrc.json new file mode 100644 index 0000000000..a20502b7f0 --- /dev/null +++ b/std/prettier/testdata/config_file_json/.prettierrc.json @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "trailingComma": "all" +} diff --git a/std/prettier/testdata/config_file_toml/.prettierrc.toml b/std/prettier/testdata/config_file_toml/.prettierrc.toml new file mode 100644 index 0000000000..e5aa41d6eb --- /dev/null +++ b/std/prettier/testdata/config_file_toml/.prettierrc.toml @@ -0,0 +1,2 @@ +singleQuote = true +trailingComma= "all" diff --git a/std/prettier/testdata/config_file_ts/.prettierrc.ts b/std/prettier/testdata/config_file_ts/.prettierrc.ts new file mode 100644 index 0000000000..ba52f4d095 --- /dev/null +++ b/std/prettier/testdata/config_file_ts/.prettierrc.ts @@ -0,0 +1,5 @@ +// prettier.config.js or .prettierrc.js +export default { + singleQuote: true, + trailingComma: "all" +}; diff --git a/std/prettier/testdata/ignore_file/.prettierignore b/std/prettier/testdata/ignore_file/.prettierignore new file mode 100644 index 0000000000..717509839d --- /dev/null +++ b/std/prettier/testdata/ignore_file/.prettierignore @@ -0,0 +1,3 @@ +# ignore javascript/markdown file +*.js +*.md diff --git a/std/prettier/testdata/ignore_file/0.js b/std/prettier/testdata/ignore_file/0.js new file mode 100644 index 0000000000..74c2894098 --- /dev/null +++ b/std/prettier/testdata/ignore_file/0.js @@ -0,0 +1 @@ +console.log("javascript" ) diff --git a/std/prettier/testdata/ignore_file/0.ts b/std/prettier/testdata/ignore_file/0.ts new file mode 100644 index 0000000000..e21cddf3d3 --- /dev/null +++ b/std/prettier/testdata/ignore_file/0.ts @@ -0,0 +1 @@ +console.log("typescript" ) diff --git a/std/prettier/testdata/ignore_file/1.js b/std/prettier/testdata/ignore_file/1.js new file mode 100644 index 0000000000..5bdbd94d91 --- /dev/null +++ b/std/prettier/testdata/ignore_file/1.js @@ -0,0 +1 @@ +console.log("javascript1" ) diff --git a/std/prettier/testdata/ignore_file/1.ts b/std/prettier/testdata/ignore_file/1.ts new file mode 100644 index 0000000000..21b56c25c9 --- /dev/null +++ b/std/prettier/testdata/ignore_file/1.ts @@ -0,0 +1 @@ +console.log("typescript1" ) diff --git a/std/prettier/testdata/ignore_file/README.md b/std/prettier/testdata/ignore_file/README.md new file mode 100644 index 0000000000..0b7d72a6f6 --- /dev/null +++ b/std/prettier/testdata/ignore_file/README.md @@ -0,0 +1,5 @@ +test format + +```javascript +console.log('') +``` \ No newline at end of file diff --git a/std/prettier/testdata/ignore_file/typescript.prettierignore b/std/prettier/testdata/ignore_file/typescript.prettierignore new file mode 100644 index 0000000000..212f62eef3 --- /dev/null +++ b/std/prettier/testdata/ignore_file/typescript.prettierignore @@ -0,0 +1,3 @@ +# ignore typescript/markdown file +*.ts +*.md