From 16036a8a5170030a95a56b28b29b1b355d0d0f80 Mon Sep 17 00:00:00 2001 From: Satya Rohith Date: Tue, 19 Jan 2021 23:09:35 +0530 Subject: [PATCH] feat: add markdown support to deno fmt (#8887) This commit adds support for formatting markdown files with "deno fmt". Additionally "--ext={js|jsx|ts|tsx|md}" flag was added to "deno fmt" that allows to specify file type when providing contents over stdio. --- .dprintrc.json | 1 + Cargo.lock | 107 +++++++++++------ cli/Cargo.toml | 3 +- cli/flags.rs | 17 +++ cli/fs_util.rs | 43 ++++++- cli/main.rs | 6 +- cli/tests/badly_formatted.md | 28 +++++ cli/tests/badly_formatted_fixed.md | 23 ++++ .../expected_fmt_check_formatted_files.out | 2 +- cli/tests/fmt/expected_fmt_check_ignore.out | 2 +- .../fmt/expected_fmt_check_tests_dir.out | 2 +- cli/tests/fmt/formatted3.md | 17 +++ cli/tests/integration_tests.rs | 48 +++++--- cli/tools/fmt.rs | 109 +++++++++++++++--- 14 files changed, 328 insertions(+), 80 deletions(-) create mode 100644 cli/tests/badly_formatted.md create mode 100644 cli/tests/badly_formatted_fixed.md create mode 100644 cli/tests/fmt/formatted3.md diff --git a/.dprintrc.json b/.dprintrc.json index 5658f9bc78..8c20ae0bc3 100644 --- a/.dprintrc.json +++ b/.dprintrc.json @@ -22,6 +22,7 @@ "cli/dts/typescript.d.ts", "cli/tests/encoding", "cli/tests/inline_js_source_map*", + "cli/tests/badly_formatted.md", "cli/tsc/*typescript.js", "test_util/wpt", "gh-pages", diff --git a/Cargo.lock b/Cargo.lock index 0cb765cdec..b2525c9aea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -78,7 +78,7 @@ dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", "swc_macros_common", - "syn 1.0.56", + "syn 1.0.58", ] [[package]] @@ -113,7 +113,7 @@ checksum = "a3548b8efc9f8e8a5a0a2808c5bd8451a9031b9e5b879a79590304ae928b0a70" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.56", + "syn 1.0.58", ] [[package]] @@ -124,7 +124,7 @@ checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.56", + "syn 1.0.58", ] [[package]] @@ -147,7 +147,7 @@ dependencies = [ "proc-macro-error", "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.56", + "syn 1.0.58", ] [[package]] @@ -373,7 +373,7 @@ dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", "strsim 0.9.3", - "syn 1.0.56", + "syn 1.0.58", ] [[package]] @@ -384,7 +384,7 @@ checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" dependencies = [ "darling_core", "quote 1.0.7", - "syn 1.0.56", + "syn 1.0.58", ] [[package]] @@ -420,6 +420,7 @@ dependencies = [ "deno_web", "deno_websocket", "dissimilar", + "dprint-plugin-markdown", "dprint-plugin-typescript", "encoding_rs", "env_logger", @@ -613,7 +614,7 @@ checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.56", + "syn 1.0.58", ] [[package]] @@ -656,9 +657,9 @@ dependencies = [ [[package]] name = "dprint-core" -version = "0.34.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fc292281fc3ec69dfbd36c3c5c73886469a6630e1747ff452b46f5deed208d" +checksum = "87368d637be4fedbddd60e41958e227af4d7d32127b9a5a6cb05b0053b525f94" dependencies = [ "bumpalo", "fnv", @@ -666,10 +667,22 @@ dependencies = [ ] [[package]] -name = "dprint-plugin-typescript" -version = "0.38.1" +name = "dprint-plugin-markdown" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67bb41b131e30df80105eb6b4f183a4bf003ee7dfb20661ac15a2a6f329de5b1" +checksum = "45dd3b8784047301e25e27ab168c79d86f883f65b83d26df31785cf4db144137" +dependencies = [ + "dprint-core", + "pulldown-cmark", + "regex", + "serde", +] + +[[package]] +name = "dprint-plugin-typescript" +version = "0.38.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54feef4e4c831395dd5874500be4b37ce9ae175c1fe1a00e3ec97abd9287a6eb" dependencies = [ "dprint-core", "dprint-swc-ecma-ast-view", @@ -723,7 +736,7 @@ dependencies = [ "heck", "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.56", + "syn 1.0.58", ] [[package]] @@ -735,7 +748,7 @@ dependencies = [ "pmutil", "proc-macro2 1.0.24", "swc_macros_common", - "syn 1.0.56", + "syn 1.0.58", ] [[package]] @@ -848,7 +861,7 @@ dependencies = [ "pmutil", "proc-macro2 1.0.24", "swc_macros_common", - "syn 1.0.56", + "syn 1.0.58", ] [[package]] @@ -969,7 +982,7 @@ dependencies = [ "proc-macro-hack", "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.56", + "syn 1.0.58", ] [[package]] @@ -1305,7 +1318,7 @@ dependencies = [ "pmutil", "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.56", + "syn 1.0.58", ] [[package]] @@ -1436,7 +1449,7 @@ dependencies = [ "heck", "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.56", + "syn 1.0.58", ] [[package]] @@ -1778,7 +1791,7 @@ dependencies = [ "proc-macro-hack", "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.56", + "syn 1.0.58", ] [[package]] @@ -1816,7 +1829,7 @@ checksum = "2c0e815c3ee9a031fdf5af21c10aa17c573c9c6a566328d99e3936c34e36461f" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.56", + "syn 1.0.58", ] [[package]] @@ -1827,7 +1840,7 @@ checksum = "caa25a6393f22ce819b0f50e0be89287292fda8d425be38ee0ca14c4931d9e71" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.56", + "syn 1.0.58", ] [[package]] @@ -1850,7 +1863,7 @@ checksum = "3894e5d549cccbe44afecf72922f277f603cd4bb0219c8342631ef18fffbe004" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.56", + "syn 1.0.58", ] [[package]] @@ -1874,7 +1887,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.56", + "syn 1.0.58", "version_check", ] @@ -1929,6 +1942,17 @@ dependencies = [ "libc", ] +[[package]] +name = "pulldown-cmark" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8" +dependencies = [ + "bitflags", + "memchr", + "unicase", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -2236,7 +2260,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db9dfbf470021de34cfaf6983067f460ea19164934a7c2d4b92eec0968eb95f1" dependencies = [ "quote 1.0.7", - "syn 1.0.56", + "syn 1.0.58", ] [[package]] @@ -2317,7 +2341,7 @@ checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.56", + "syn 1.0.58", ] [[package]] @@ -2340,7 +2364,7 @@ checksum = "2dc6b7951b17b051f3210b063f12cc17320e2fe30ae05b0fe2a3abb068551c76" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.56", + "syn 1.0.58", ] [[package]] @@ -2477,7 +2501,7 @@ dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", "swc_macros_common", - "syn 1.0.56", + "syn 1.0.58", ] [[package]] @@ -2593,7 +2617,7 @@ dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", "swc_macros_common", - "syn 1.0.56", + "syn 1.0.58", ] [[package]] @@ -2796,7 +2820,7 @@ dependencies = [ "pmutil", "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.56", + "syn 1.0.58", ] [[package]] @@ -2808,7 +2832,7 @@ dependencies = [ "pmutil", "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.56", + "syn 1.0.58", ] [[package]] @@ -2832,7 +2856,7 @@ dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", "swc_macros_common", - "syn 1.0.56", + "syn 1.0.58", ] [[package]] @@ -2848,9 +2872,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.56" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9802ddde94170d186eeee5005b798d9c159fa970403f1be19976d0cfb939b72" +checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", @@ -2950,7 +2974,7 @@ checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.56", + "syn 1.0.58", ] [[package]] @@ -3007,7 +3031,7 @@ checksum = "42517d2975ca3114b22a16192634e8241dc5cc1f130be194645970cc1c371494" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.56", + "syn 1.0.58", ] [[package]] @@ -3263,6 +3287,15 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-bidi" version = "0.3.4" @@ -3413,7 +3446,7 @@ dependencies = [ "log", "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.56", + "syn 1.0.58", "wasm-bindgen-shared", ] @@ -3447,7 +3480,7 @@ checksum = "f249f06ef7ee334cc3b8ff031bfc11ec99d00f34d86da7498396dc1e3b1498fe" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.56", + "syn 1.0.58", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 04861e3027..e69d256213 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -46,7 +46,8 @@ base64 = "0.13.0" byteorder = "1.4.2" clap = "2.33.3" dissimilar = "1.0.2" -dprint-plugin-typescript = "0.38.1" +dprint-plugin-typescript = "0.38.3" +dprint-plugin-markdown = "0.5.1" encoding_rs = "0.8.26" env_logger = "0.8.2" filetime = "0.2.13" diff --git a/cli/flags.rs b/cli/flags.rs index f018d00b07..e87924491d 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -50,6 +50,7 @@ pub enum DenoSubcommand { check: bool, files: Vec, ignore: Vec, + ext: String, }, Info { json: bool, @@ -400,8 +401,11 @@ fn fmt_parse(flags: &mut Flags, matches: &clap::ArgMatches) { Some(f) => f.map(PathBuf::from).collect(), None => vec![], }; + let ext = matches.value_of("ext").unwrap().to_string(); + flags.subcommand = DenoSubcommand::Fmt { check: matches.is_present("check"), + ext, files, ignore, } @@ -809,6 +813,14 @@ Ignore formatting a file by adding an ignore comment at the top of the file: .help("Check if the source files are formatted") .takes_value(false), ) + .arg( + Arg::with_name("ext") + .long("ext") + .help("Set standard input (stdin) content type") + .takes_value(true) + .default_value("ts") + .possible_values(&["ts", "tsx", "js", "jsx", "md"]), + ) .arg( Arg::with_name("ignore") .long("ignore") @@ -1991,6 +2003,7 @@ mod tests { PathBuf::from("script_1.ts"), PathBuf::from("script_2.ts") ], + ext: "ts".to_string() }, ..Flags::default() } @@ -2004,6 +2017,7 @@ mod tests { ignore: vec![], check: true, files: vec![], + ext: "ts".to_string(), }, ..Flags::default() } @@ -2017,6 +2031,7 @@ mod tests { ignore: vec![], check: false, files: vec![], + ext: "ts".to_string(), }, ..Flags::default() } @@ -2030,6 +2045,7 @@ mod tests { ignore: vec![], check: false, files: vec![], + ext: "ts".to_string(), }, watch: true, unstable: true, @@ -2053,6 +2069,7 @@ mod tests { ignore: vec![PathBuf::from("bar.js")], check: true, files: vec![PathBuf::from("foo.ts")], + ext: "ts".to_string(), }, watch: true, unstable: true, diff --git a/cli/fs_util.rs b/cli/fs_util.rs index 130a209ced..a6cd06e78f 100644 --- a/cli/fs_util.rs +++ b/cli/fs_util.rs @@ -90,17 +90,30 @@ pub fn resolve_from_cwd(path: &Path) -> Result { /// Checks if the path has extension Deno supports. pub fn is_supported_ext(path: &Path) -> bool { - let lowercase_ext = path - .extension() - .and_then(|e| e.to_str()) - .map(|e| e.to_lowercase()); - if let Some(ext) = lowercase_ext { - ext == "ts" || ext == "tsx" || ext == "js" || ext == "jsx" || ext == "mjs" + if let Some(ext) = get_extension(path) { + matches!(ext.as_str(), "ts" | "tsx" | "js" | "jsx" | "mjs") } else { false } } +/// This function is similar to is_supported_ext but also allows .md extension. +pub fn is_supported_ext_md(path: &Path) -> bool { + if let Some(ext) = get_extension(path) { + matches!(ext.as_str(), "ts" | "tsx" | "js" | "jsx" | "mjs" | "md") + } else { + false + } +} + +/// Get the extension of a file in lowercase. +pub fn get_extension(file_path: &Path) -> Option { + return file_path + .extension() + .and_then(|e| e.to_str()) + .map(|e| e.to_lowercase()); +} + /// Collects file paths that satisfy the given predicate, by recursively walking `files`. /// If the walker visits a path that is listed in `ignore`, it skips descending into the directory. pub fn collect_files

( @@ -207,6 +220,24 @@ mod tests { assert!(!is_supported_ext(Path::new("foo.mjsx"))); } + #[test] + fn test_is_supported_ext_md() { + assert!(!is_supported_ext_md(Path::new("tests/subdir/redirects"))); + assert!(is_supported_ext_md(Path::new("README.md"))); + assert!(is_supported_ext_md(Path::new("readme.MD"))); + assert!(is_supported_ext_md(Path::new("lib/typescript.d.ts"))); + assert!(is_supported_ext_md(Path::new("cli/tests/001_hello.js"))); + assert!(is_supported_ext_md(Path::new("cli/tests/002_hello.ts"))); + assert!(is_supported_ext_md(Path::new("foo.jsx"))); + assert!(is_supported_ext_md(Path::new("foo.tsx"))); + assert!(is_supported_ext_md(Path::new("foo.TS"))); + assert!(is_supported_ext_md(Path::new("foo.TSX"))); + assert!(is_supported_ext_md(Path::new("foo.JS"))); + assert!(is_supported_ext_md(Path::new("foo.JSX"))); + assert!(is_supported_ext_md(Path::new("foo.mjs"))); + assert!(!is_supported_ext_md(Path::new("foo.mjsx"))); + } + #[test] fn test_collect_files() { fn create_files(dir_path: &PathBuf, files: &[&str]) { diff --git a/cli/main.rs b/cli/main.rs index de0e63e849..d98313f545 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -807,9 +807,10 @@ async fn format_command( args: Vec, ignore: Vec, check: bool, + ext: String, ) -> Result<(), AnyError> { if args.len() == 1 && args[0].to_string_lossy() == "-" { - return tools::fmt::format_stdin(check); + return tools::fmt::format_stdin(check, ext); } tools::fmt::format(args, ignore, check, flags.watch).await?; @@ -1179,7 +1180,8 @@ fn get_subcommand( check, files, ignore, - } => format_command(flags, files, ignore, check).boxed_local(), + ext, + } => format_command(flags, files, ignore, check, ext).boxed_local(), DenoSubcommand::Info { file, json } => { info_command(flags, file, json).boxed_local() } diff --git a/cli/tests/badly_formatted.md b/cli/tests/badly_formatted.md new file mode 100644 index 0000000000..adffe3e465 --- /dev/null +++ b/cli/tests/badly_formatted.md @@ -0,0 +1,28 @@ +# Hello Markdown + +```js +console.log("Hello World" + +) +``` + +```javascript +console.log("Hello World2" + +) +``` + +```ts + +function hello(name: string ) { + console.log(name); +}; + +hello( "alice"); +``` + +```typescript +function foo(): number { + return 2; +} +``` \ No newline at end of file diff --git a/cli/tests/badly_formatted_fixed.md b/cli/tests/badly_formatted_fixed.md new file mode 100644 index 0000000000..359a8aeade --- /dev/null +++ b/cli/tests/badly_formatted_fixed.md @@ -0,0 +1,23 @@ +# Hello Markdown + +```js +console.log("Hello World"); +``` + +```javascript +console.log("Hello World2"); +``` + +```ts +function hello(name: string) { + console.log(name); +} + +hello("alice"); +``` + +```typescript +function foo(): number { + return 2; +} +``` diff --git a/cli/tests/fmt/expected_fmt_check_formatted_files.out b/cli/tests/fmt/expected_fmt_check_formatted_files.out index 158c556c29..7c1e471b9a 100644 --- a/cli/tests/fmt/expected_fmt_check_formatted_files.out +++ b/cli/tests/fmt/expected_fmt_check_formatted_files.out @@ -1 +1 @@ -Checked 2 files +Checked 3 files diff --git a/cli/tests/fmt/expected_fmt_check_ignore.out b/cli/tests/fmt/expected_fmt_check_ignore.out index c05ac45a1e..158c556c29 100644 --- a/cli/tests/fmt/expected_fmt_check_ignore.out +++ b/cli/tests/fmt/expected_fmt_check_ignore.out @@ -1 +1 @@ -Checked 1 file +Checked 2 files diff --git a/cli/tests/fmt/expected_fmt_check_tests_dir.out b/cli/tests/fmt/expected_fmt_check_tests_dir.out index 00d7cb3fa4..e2dc2b4aef 100644 --- a/cli/tests/fmt/expected_fmt_check_tests_dir.out +++ b/cli/tests/fmt/expected_fmt_check_tests_dir.out @@ -1,2 +1,2 @@ [WILDCARD] -error: Found 5 not formatted files in [WILDCARD] files +error: Found 6 not formatted files in [WILDCARD] files diff --git a/cli/tests/fmt/formatted3.md b/cli/tests/fmt/formatted3.md new file mode 100644 index 0000000000..e6e6165844 --- /dev/null +++ b/cli/tests/fmt/formatted3.md @@ -0,0 +1,17 @@ +# Hello + +```js +function foo() { + return 42; +} + +foo(); +``` + +```ts +function bar(): number { + return 42; +} + +bar(); +``` diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index 0af4709fba..80a7222e53 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -511,20 +511,31 @@ fn cache_invalidation_test_no_check() { #[test] fn fmt_test() { let t = TempDir::new().expect("tempdir fail"); - let fixed = util::root_path().join("cli/tests/badly_formatted_fixed.js"); - let badly_formatted_original = + let fixed_js = util::root_path().join("cli/tests/badly_formatted_fixed.js"); + let fixed_md = util::root_path().join("cli/tests/badly_formatted_fixed.md"); + let badly_formatted_original_js = util::root_path().join("cli/tests/badly_formatted.mjs"); - let badly_formatted = t.path().join("badly_formatted.js"); - let badly_formatted_str = badly_formatted.to_str().unwrap(); - std::fs::copy(&badly_formatted_original, &badly_formatted) + let badly_formatted_original_md = + util::root_path().join("cli/tests/badly_formatted.md"); + let badly_formatted_js = t.path().join("badly_formatted.js"); + let badly_formatted_md = t.path().join("badly_formatted.md"); + let badly_formatted_js_str = badly_formatted_js.to_str().unwrap(); + let badly_formatted_md_str = badly_formatted_md.to_str().unwrap(); + std::fs::copy(&badly_formatted_original_js, &badly_formatted_js) + .expect("Failed to copy file"); + std::fs::copy(&badly_formatted_original_md, &badly_formatted_md) .expect("Failed to copy file"); // First, check formatting by ignoring the badly formatted file. let status = util::deno_cmd() .current_dir(util::root_path()) .arg("fmt") - .arg(format!("--ignore={}", badly_formatted_str)) + .arg(format!( + "--ignore={},{}", + badly_formatted_js_str, badly_formatted_md_str + )) .arg("--check") - .arg(badly_formatted_str) + .arg(badly_formatted_js_str) + .arg(badly_formatted_md_str) .spawn() .expect("Failed to spawn script") .wait() @@ -535,7 +546,8 @@ fn fmt_test() { .current_dir(util::root_path()) .arg("fmt") .arg("--check") - .arg(badly_formatted_str) + .arg(badly_formatted_js_str) + .arg(badly_formatted_md_str) .spawn() .expect("Failed to spawn script") .wait() @@ -545,15 +557,19 @@ fn fmt_test() { let status = util::deno_cmd() .current_dir(util::root_path()) .arg("fmt") - .arg(badly_formatted_str) + .arg(badly_formatted_js_str) + .arg(badly_formatted_md_str) .spawn() .expect("Failed to spawn script") .wait() .expect("Failed to wait for child process"); assert!(status.success()); - let expected = std::fs::read_to_string(fixed).unwrap(); - let actual = std::fs::read_to_string(badly_formatted).unwrap(); - assert_eq!(expected, actual); + let expected_js = std::fs::read_to_string(fixed_js).unwrap(); + let expected_md = std::fs::read_to_string(fixed_md).unwrap(); + let actual_js = std::fs::read_to_string(badly_formatted_js).unwrap(); + let actual_md = std::fs::read_to_string(badly_formatted_md).unwrap(); + assert_eq!(expected_js, actual_js); + assert_eq!(expected_md, actual_md); } // Helper function to skip watcher output that contains "Restarting" @@ -2751,7 +2767,7 @@ itest!(fmt_quiet_check_fmt_dir { }); itest!(fmt_check_formatted_files { - args: "fmt --check fmt/formatted1.js fmt/formatted2.ts", + args: "fmt --check fmt/formatted1.js fmt/formatted2.ts fmt/formatted3.md", output: "fmt/expected_fmt_check_formatted_files.out", exit_code: 0, }); @@ -2768,6 +2784,12 @@ itest!(fmt_stdin { output_str: Some("const a = 1;\n"), }); +itest!(fmt_stdin_markdown { + args: "fmt --ext=md -", + input: Some("# Hello Markdown\n```ts\nconsole.log( \"text\")\n```\n"), + output_str: Some("# Hello Markdown\n\n```ts\nconsole.log(\"text\");\n```\n"), +}); + itest!(fmt_stdin_check_formatted { args: "fmt --check -", input: Some("const a = 1;\n"), diff --git a/cli/tools/fmt.rs b/cli/tools/fmt.rs index d5500a8f21..61875d1729 100644 --- a/cli/tools/fmt.rs +++ b/cli/tools/fmt.rs @@ -10,13 +10,12 @@ use crate::colors; use crate::diff::diff; use crate::file_watcher; -use crate::fs_util::{collect_files, is_supported_ext}; +use crate::fs_util::{collect_files, get_extension, is_supported_ext_md}; use crate::text_encoding; use deno_core::error::generic_error; use deno_core::error::AnyError; use deno_core::futures; use deno_core::futures::FutureExt; -use dprint_plugin_typescript as dprint; use std::fs; use std::io::stdin; use std::io::stdout; @@ -38,11 +37,10 @@ pub async fn format( ) -> Result<(), AnyError> { let target_file_resolver = || { // collect the files that are to be formatted - collect_files(&args, &ignore, is_supported_ext) + collect_files(&args, &ignore, is_supported_ext_md) }; - let operation = |paths: Vec| { - let config = get_config(); + let config = get_typescript_config(); async move { if check { check_source_files(config, paths).await?; @@ -63,8 +61,48 @@ pub async fn format( Ok(()) } +/// Formats markdown (using https://github.com/dprint/dprint-plugin-markdown) and its code blocks +/// (ts/tsx, js/jsx). +fn format_markdown( + file_text: &str, + ts_config: dprint_plugin_typescript::configuration::Configuration, +) -> Result { + let md_config = get_markdown_config(); + dprint_plugin_markdown::format_text( + &file_text, + &md_config, + Box::new(move |tag, text, line_width| { + let tag = tag.to_lowercase(); + if matches!( + tag.as_str(), + "ts" | "tsx" | "js" | "jsx" | "javascript" | "typescript" + ) { + // It's important to tell dprint proper file extension, otherwise + // it might parse the file twice. + let extension = match tag.as_str() { + "javascript" => "js", + "typescript" => "ts", + rest => rest, + }; + let fake_filename = + PathBuf::from(format!("deno_fmt_stdin.{}", extension)); + + let mut codeblock_config = ts_config.clone(); + codeblock_config.line_width = line_width; + dprint_plugin_typescript::format_text( + &fake_filename, + &text, + &codeblock_config, + ) + } else { + Ok(text.to_string()) + } + }), + ) +} + async fn check_source_files( - config: dprint::configuration::Configuration, + config: dprint_plugin_typescript::configuration::Configuration, paths: Vec, ) -> Result<(), AnyError> { let not_formatted_files_count = Arc::new(AtomicUsize::new(0)); @@ -79,7 +117,12 @@ async fn check_source_files( move |file_path| { checked_files_count.fetch_add(1, Ordering::Relaxed); let file_text = read_file_contents(&file_path)?.text; - let r = dprint::format_text(&file_path, &file_text, &config); + let ext = get_extension(&file_path).unwrap_or_else(String::new); + let r = if ext == "md" { + format_markdown(&file_text, config.clone()) + } else { + dprint_plugin_typescript::format_text(&file_path, &file_text, &config) + }; match r { Ok(formatted_text) => { if formatted_text != file_text { @@ -120,7 +163,7 @@ async fn check_source_files( } async fn format_source_files( - config: dprint::configuration::Configuration, + config: dprint_plugin_typescript::configuration::Configuration, paths: Vec, ) -> Result<(), AnyError> { let formatted_files_count = Arc::new(AtomicUsize::new(0)); @@ -133,7 +176,16 @@ async fn format_source_files( move |file_path| { checked_files_count.fetch_add(1, Ordering::Relaxed); let file_contents = read_file_contents(&file_path)?; - let r = dprint::format_text(&file_path, &file_contents.text, &config); + let ext = get_extension(&file_path).unwrap_or_else(String::new); + let r = if ext == "md" { + format_markdown(&file_contents.text, config.clone()) + } else { + dprint_plugin_typescript::format_text( + &file_path, + &file_contents.text, + &config, + ) + }; match r { Ok(formatted_text) => { if formatted_text != file_contents.text { @@ -178,17 +230,25 @@ async fn format_source_files( } /// Format stdin and write result to stdout. -/// Treats input as TypeScript. +/// Treats input as TypeScript or as set by `--ext` flag. /// Compatible with `--check` flag. -pub fn format_stdin(check: bool) -> Result<(), AnyError> { +pub fn format_stdin(check: bool, ext: String) -> Result<(), AnyError> { let mut source = String::new(); if stdin().read_to_string(&mut source).is_err() { return Err(generic_error("Failed to read from stdin")); } - let config = get_config(); - - // dprint will fallback to jsx parsing if parsing this as a .ts file doesn't work - match dprint::format_text(&PathBuf::from("_stdin.ts"), &source, &config) { + let config = get_typescript_config(); + let r = if ext.as_str() == "md" { + format_markdown(&source, config) + } else { + // dprint will fallback to jsx parsing if parsing this as a .ts file doesn't work + dprint_plugin_typescript::format_text( + &PathBuf::from("_stdin.ts"), + &source, + &config, + ) + }; + match r { Ok(formatted_text) => { if check { if formatted_text != source { @@ -213,9 +273,22 @@ fn files_str(len: usize) -> &'static str { } } -fn get_config() -> dprint::configuration::Configuration { - use dprint::configuration::*; - ConfigurationBuilder::new().deno().build() +fn get_typescript_config( +) -> dprint_plugin_typescript::configuration::Configuration { + dprint_plugin_typescript::configuration::ConfigurationBuilder::new() + .deno() + .build() +} + +fn get_markdown_config() -> dprint_plugin_markdown::configuration::Configuration +{ + dprint_plugin_markdown::configuration::ConfigurationBuilder::new() + // Matches `.dprintrc.json` in the repository + .text_wrap(dprint_plugin_markdown::configuration::TextWrap::Always) + .ignore_directive("deno-fmt-ignore") + .ignore_start_directive("deno-fmt-ignore-start") + .ignore_end_directive("deno-fmt-ignore-end") + .build() } struct FileContents {