1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-23 07:44:48 -05:00

fmt: respect prettierrc and prettierignore (#3346)

This commit is contained in:
罗文 2019-11-17 00:48:45 +08:00 committed by Ry Dahl
parent 26bf928d28
commit cb00fd6e98
15 changed files with 415 additions and 11 deletions

View file

@ -340,6 +340,32 @@ Automatically downloads Prettier dependencies on first run.
deno fmt myfile1.ts myfile2.ts", 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(
Arg::with_name("stdout") Arg::with_name("stdout")
.long("stdout") .long("stdout")
@ -966,6 +992,8 @@ pub fn flags_from_vec(
} }
let prettier_flags = [ let prettier_flags = [
["1", "prettierrc"],
["1", "ignore-path"],
["1", "print-width"], ["1", "print-width"],
["1", "tab-width"], ["1", "tab-width"],
["0", "use-tabs"], ["0", "use-tabs"],
@ -989,7 +1017,11 @@ pub fn flags_from_vec(
if t == "0" { if t == "0" {
argv.push(format!("--{}", keyword)); argv.push(format!("--{}", keyword));
} else { } 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()); argv.push(fmt_match.value_of(keyword).unwrap().to_string());
} }
} }
@ -1384,7 +1416,11 @@ mod tests {
PRETTIER_URL, PRETTIER_URL,
"script_1.ts", "script_1.ts",
"script_2.ts", "script_2.ts",
"--write" "--write",
"--config",
"auto",
"--ignore-path",
"auto"
] ]
); );
} }
@ -1602,7 +1638,16 @@ mod tests {
assert_eq!(subcommand, DenoSubcommand::Run); assert_eq!(subcommand, DenoSubcommand::Run);
assert_eq!( assert_eq!(
argv, 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![ let (flags, subcommand, argv) = flags_from_vec(svec![
"deno", "deno",
"fmt", "fmt",
"--prettierrc=auto",
"--print-width=100", "--print-width=100",
"--tab-width=4", "--tab-width=4",
"--use-tabs", "--use-tabs",
@ -2054,6 +2100,7 @@ mod tests {
"--quote-props=preserve", "--quote-props=preserve",
"--jsx-single-quote", "--jsx-single-quote",
"--jsx-bracket-same-line", "--jsx-bracket-same-line",
"--ignore-path=.prettier-ignore",
"script.ts" "script.ts"
]); ]);
assert_eq!( assert_eq!(
@ -2072,6 +2119,10 @@ mod tests {
PRETTIER_URL, PRETTIER_URL,
"script.ts", "script.ts",
"--write", "--write",
"--config",
"auto",
"--ignore-path",
".prettier-ignore",
"--print-width", "--print-width",
"100", "100",
"--tab-width", "--tab-width",

View file

@ -24,6 +24,8 @@
// This script formats the given source files. If the files are omitted, it // This script formats the given source files. If the files are omitted, it
// formats the all files in the repository. // formats the all files in the repository.
import { parse } from "../flags/mod.ts"; 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 { ExpandGlobOptions, WalkInfo, expandGlob } from "../fs/mod.ts";
import { prettier, prettierPlugins } from "./prettier.ts"; import { prettier, prettierPlugins } from "./prettier.ts";
const { args, cwd, exit, readAll, readFile, stdin, stdout, writeFile } = Deno; const { args, cwd, exit, readAll, readFile, stdin, stdout, writeFile } = Deno;
@ -40,6 +42,10 @@ Options:
it will output to stdout, Defaults to it will output to stdout, Defaults to
false. false.
--ignore <path> Ignore the given path(s). --ignore <path> Ignore the given path(s).
--ignore-path <auto|disable|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. --stdin Specifies to read the code from stdin.
If run the command in a pipe, you do not If run the command in a pipe, you do not
need to specify this flag. need to specify this flag.
@ -49,6 +55,10 @@ Options:
parser for stdin. available parser: parser for stdin. available parser:
typescript/babel/markdown/json. Defaults typescript/babel/markdown/json. Defaults
to typescript. to typescript.
--config <auto|disable|path> Specify the configuration file of the
prettier.
Optional value: auto/disable/filepath.
Defaults to null.
JS/TS Styling Options: JS/TS Styling Options:
--print-width <int> The line length where Prettier will try --print-width <int> The line length where Prettier will try
@ -106,7 +116,7 @@ Example:
// Available parsers // Available parsers
type ParserLabel = "typescript" | "babel" | "markdown" | "json"; type ParserLabel = "typescript" | "babel" | "markdown" | "json";
interface PrettierOptions { interface PrettierBuildInOptions {
printWidth: number; printWidth: number;
tabWidth: number; tabWidth: number;
useTabs: boolean; useTabs: boolean;
@ -120,6 +130,9 @@ interface PrettierOptions {
arrowParens: string; arrowParens: string;
proseWrap: string; proseWrap: string;
endOfLine: string; endOfLine: string;
}
interface PrettierOptions extends PrettierBuildInOptions {
write: boolean; write: boolean;
} }
@ -343,10 +356,134 @@ async function* getTargetFiles(
} }
} }
async function main(opts): Promise<void> { /**
const { help, ignore, check, _: args } = opts; * auto detect prettier configuration file and return config if file exist.
*/
async function autoResolveConfig(): Promise<PrettierBuildInOptions> {
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<PrettierBuildInOptions> {
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<string[]> {
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<string[]> {
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<void> {
const { help, check, _: args } = opts;
let prettierOpts: PrettierOptions = {
printWidth: Number(opts["print-width"]), printWidth: Number(opts["print-width"]),
tabWidth: Number(opts["tab-width"]), tabWidth: Number(opts["tab-width"]),
useTabs: Boolean(opts["use-tabs"]), useTabs: Boolean(opts["use-tabs"]),
@ -368,10 +505,37 @@ async function main(opts): Promise<void> {
exit(0); exit(0);
} }
const files = getTargetFiles( const configFilepath = opts["config"];
args.length ? args : ["."],
Array.isArray(ignore) ? ignore : [ignore] 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(); const tty = Deno.isTTY();
@ -396,6 +560,7 @@ main(
parse(args.slice(1), { parse(args.slice(1), {
string: [ string: [
"ignore", "ignore",
"ignore-path",
"printWidth", "printWidth",
"tab-width", "tab-width",
"trailing-comma", "trailing-comma",

View file

@ -378,4 +378,160 @@ test(async function testPrettierReadFromStdin(): Promise<void> {
} }
}); });
test(async function testPrettierWithAutoConfig(): Promise<void> {
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<void> {
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<void> {
// 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<void> {
// 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); runIfMain(import.meta);

1
std/prettier/testdata/5.ts vendored Normal file
View file

@ -0,0 +1 @@
console.log("0" )

View file

@ -0,0 +1,5 @@
// prettier.config.js or .prettierrc.js
export default {
singleQuote: true,
trailingComma: "all"
};

View file

@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "all"
}

View file

@ -0,0 +1,2 @@
singleQuote = true
trailingComma= "all"

View file

@ -0,0 +1,5 @@
// prettier.config.js or .prettierrc.js
export default {
singleQuote: true,
trailingComma: "all"
};

View file

@ -0,0 +1,3 @@
# ignore javascript/markdown file
*.js
*.md

View file

@ -0,0 +1 @@
console.log("javascript" )

View file

@ -0,0 +1 @@
console.log("typescript" )

View file

@ -0,0 +1 @@
console.log("javascript1" )

View file

@ -0,0 +1 @@
console.log("typescript1" )

View file

@ -0,0 +1,5 @@
test format
```javascript
console.log('')
```

View file

@ -0,0 +1,3 @@
# ignore typescript/markdown file
*.ts
*.md