From 506601841218fe34a744c8fce0f1011c716bdc73 Mon Sep 17 00:00:00 2001 From: "Kevin (Kun) \"Kassimo\" Qian" Date: Sun, 9 Feb 2020 02:19:05 -0800 Subject: [PATCH] fmt: `deno fmt -` formats stdin and print to stdout (#3920) --- cli/flags.rs | 17 ++++++++- cli/fmt.rs | 63 +++++++++++++++++++++++++++++++++- cli/lib.rs | 4 ++- cli/tests/integration_tests.rs | 38 +++++++++++++++++--- 4 files changed, 115 insertions(+), 7 deletions(-) diff --git a/cli/flags.rs b/cli/flags.rs index 748a7f95e26ed..a8c51093e5e52 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -550,7 +550,10 @@ fn fmt_subcommand<'a, 'b>() -> App<'a, 'b> { deno fmt myfile1.ts myfile2.ts - deno fmt --check", + deno fmt --check + + # Format stdin and write to stdout + cat file.ts | deno fmt -", ) .arg( Arg::with_name("check") @@ -1390,6 +1393,18 @@ mod tests { ..DenoFlags::default() } ); + + let r = flags_from_vec_safe(svec!["deno", "fmt"]); + assert_eq!( + r.unwrap(), + DenoFlags { + subcommand: DenoSubcommand::Format { + check: false, + files: None + }, + ..DenoFlags::default() + } + ); } #[test] diff --git a/cli/fmt.rs b/cli/fmt.rs index f007d8da8da63..390203f14ae32 100644 --- a/cli/fmt.rs +++ b/cli/fmt.rs @@ -7,10 +7,17 @@ //! the future it can be easily extended to provide //! the same functions as ops available in JS runtime. +use crate::deno_error::DenoError; +use crate::deno_error::ErrorKind; +use deno_core::ErrBox; use dprint_plugin_typescript as dprint; use glob; use regex::Regex; use std::fs; +use std::io::stdin; +use std::io::stdout; +use std::io::Read; +use std::io::Write; use std::path::Path; use std::path::PathBuf; use std::time::Instant; @@ -157,9 +164,29 @@ fn get_matching_files(glob_paths: Vec) -> Vec { /// /// First argument supports globs, and if it is `None` /// then the current directory is recursively walked. -pub fn format_files(maybe_files: Option>, check: bool) { +pub fn format_files( + maybe_files: Option>, + check: bool, +) -> Result<(), ErrBox> { // TODO: improve glob to look for tsx?/jsx? files only let glob_paths = maybe_files.unwrap_or_else(|| vec!["**/*".to_string()]); + + for glob_path in glob_paths.iter() { + if glob_path == "-" { + // If the only given path is '-', format stdin. + if glob_paths.len() == 1 { + format_stdin(check); + } else { + // Otherwise it is an error + return Err(ErrBox::from(DenoError::new( + ErrorKind::Other, + "Ambiguous filename input. To format stdin, provide a single '-' instead.".to_owned() + ))); + } + return Ok(()); + } + } + let matching_files = get_matching_files(glob_paths); let matching_files = get_supported_files(matching_files); let config = get_config(); @@ -169,4 +196,38 @@ pub fn format_files(maybe_files: Option>, check: bool) { } else { format_source_files(config, matching_files); } + + Ok(()) +} + +/// Format stdin and write result to stdout. +/// Treats input as TypeScript. +/// Compatible with `--check` flag. +fn format_stdin(check: bool) { + let mut source = String::new(); + if stdin().read_to_string(&mut source).is_err() { + eprintln!("Failed to read from stdin"); + } + let config = get_config(); + + match dprint::format_text("_stdin.ts", &source, &config) { + Ok(None) => { + // Should not happen. + unreachable!(); + } + Ok(Some(formatted_text)) => { + if check { + if formatted_text != source { + println!("Not formatted stdin"); + } + } else { + let _r = stdout().write_all(formatted_text.as_bytes()); + // TODO(ry) Only ignore SIGPIPE. Currently ignoring all errors. + } + } + Err(e) => { + eprintln!("Error formatting from stdin"); + eprintln!(" {}", e); + } + } } diff --git a/cli/lib.rs b/cli/lib.rs index 9455544345a53..4e100127b074b 100644 --- a/cli/lib.rs +++ b/cli/lib.rs @@ -405,7 +405,9 @@ async fn run_script(flags: DenoFlags, script: String) { } async fn fmt_command(files: Option>, check: bool) { - fmt::format_files(files, check); + if let Err(err) = fmt::format_files(files, check) { + print_err_and_exit(err); + } } pub fn main() { diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index ac00a04c4da3b..dd242a32a850a 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -603,6 +603,32 @@ itest!(bundle { output: "bundle.test.out", }); +itest!(fmt_stdin { + args: "fmt -", + input: Some("const a = 1\n"), + output_str: Some("const a = 1;\n"), +}); + +itest!(fmt_stdin_ambiguous { + args: "fmt - file.ts", + input: Some("const a = 1\n"), + check_stderr: true, + exit_code: 1, + output_str: Some("Ambiguous filename input. To format stdin, provide a single '-' instead.\n"), +}); + +itest!(fmt_stdin_check_formatted { + args: "fmt --check -", + input: Some("const a = 1;\n"), + output_str: Some(""), +}); + +itest!(fmt_stdin_check_not_formatted { + args: "fmt --check -", + input: Some("const a = 1\n"), + output_str: Some("Not formatted stdin\n"), +}); + itest!(circular1 { args: "run --reload circular1.js", output: "circular1.js.out", @@ -917,6 +943,7 @@ mod util { pub args: &'static str, pub output: &'static str, pub input: Option<&'static str>, + pub output_str: Option<&'static str>, pub exit_code: i32, pub check_stderr: bool, pub http_server: bool, @@ -982,10 +1009,13 @@ mod util { ); } - let output_path = tests_dir.join(self.output); - println!("output path {}", output_path.display()); - let expected = - std::fs::read_to_string(output_path).expect("cannot read output"); + let expected = if let Some(s) = self.output_str { + s.to_owned() + } else { + let output_path = tests_dir.join(self.output); + println!("output path {}", output_path.display()); + std::fs::read_to_string(output_path).expect("cannot read output") + }; if !wildcard_match(&expected, &actual) { println!("OUTPUT\n{}\nOUTPUT", actual);