From cba1e7b5a3c239235c0e855dc430c8aa89272401 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Mon, 13 Sep 2021 22:06:45 +0200 Subject: [PATCH] feat: add option flags to 'deno fmt' (#12060) --- cli/flags.rs | 162 ++++++++++++++++++++++++++++++++++++++++++++++- cli/main.rs | 12 +--- cli/tools/fmt.rs | 63 +++++++++++++++--- 3 files changed, 215 insertions(+), 22 deletions(-) diff --git a/cli/flags.rs b/cli/flags.rs index eb7d0901fc..81981b2786 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -13,6 +13,8 @@ use deno_runtime::permissions::PermissionsOptions; use log::debug; use log::Level; use std::net::SocketAddr; +use std::num::NonZeroU32; +use std::num::NonZeroU8; use std::num::NonZeroUsize; use std::path::PathBuf; use std::str::FromStr; @@ -86,6 +88,11 @@ pub struct FmtFlags { pub files: Vec, pub ignore: Vec, pub ext: String, + pub use_tabs: Option, + pub line_width: Option, + pub indent_width: Option, + pub single_quote: Option, + pub prose_wrap: Option, } #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] @@ -845,6 +852,47 @@ Ignore formatting a file by adding an ignore comment at the top of the file: .required(false), ) .arg(watch_arg()) + .arg( + Arg::with_name("options-use-tabs") + .long("options-use-tabs") + .help("Use tabs instead of spaces for indentation. Defaults to false."), + ) + .arg( + Arg::with_name("options-line-width") + .long("options-line-width") + .help("Define maximum line width. Defaults to 80.") + .takes_value(true) + .validator(|val: String| match val.parse::() { + Ok(_) => Ok(()), + Err(_) => { + Err("options-line-width should be a non zero integer".to_string()) + } + }), + ) + .arg( + Arg::with_name("options-indent-width") + .long("options-indent-width") + .help("Define indentation width. Defaults to 2.") + .takes_value(true) + .validator(|val: String| match val.parse::() { + Ok(_) => Ok(()), + Err(_) => { + Err("options-indent-width should be a non zero integer".to_string()) + } + }), + ) + .arg( + Arg::with_name("options-single-quote") + .long("options-single-quote") + .help("Use single quotes. Defaults to false."), + ) + .arg( + Arg::with_name("options-prose-wrap") + .long("options-prose-wrap") + .takes_value(true) + .possible_values(&["always", "never", "preserve"]) + .help("Define how prose should be wrapped. Defaults to always."), + ) } fn info_subcommand<'a, 'b>() -> App<'a, 'b> { @@ -1745,11 +1793,54 @@ fn fmt_parse(flags: &mut Flags, matches: &clap::ArgMatches) { }; let ext = matches.value_of("ext").unwrap().to_string(); + let use_tabs = if matches.is_present("options-use-tabs") { + Some(true) + } else { + None + }; + let line_width = if matches.is_present("options-line-width") { + Some( + matches + .value_of("options-line-width") + .unwrap() + .parse() + .unwrap(), + ) + } else { + None + }; + let indent_width = if matches.is_present("options-indent-width") { + Some( + matches + .value_of("options-indent-width") + .unwrap() + .parse() + .unwrap(), + ) + } else { + None + }; + let single_quote = if matches.is_present("options-single-quote") { + Some(true) + } else { + None + }; + let prose_wrap = if matches.is_present("options-prose-wrap") { + Some(matches.value_of("options-prose-wrap").unwrap().to_string()) + } else { + None + }; + flags.subcommand = DenoSubcommand::Fmt(FmtFlags { check: matches.is_present("check"), ext, files, ignore, + use_tabs, + line_width, + indent_width, + single_quote, + prose_wrap, }); } @@ -2466,7 +2557,12 @@ mod tests { PathBuf::from("script_1.ts"), PathBuf::from("script_2.ts") ], - ext: "ts".to_string() + ext: "ts".to_string(), + use_tabs: None, + line_width: None, + indent_width: None, + single_quote: None, + prose_wrap: None, }), ..Flags::default() } @@ -2481,6 +2577,11 @@ mod tests { check: true, files: vec![], ext: "ts".to_string(), + use_tabs: None, + line_width: None, + indent_width: None, + single_quote: None, + prose_wrap: None, }), ..Flags::default() } @@ -2495,6 +2596,11 @@ mod tests { check: false, files: vec![], ext: "ts".to_string(), + use_tabs: None, + line_width: None, + indent_width: None, + single_quote: None, + prose_wrap: None, }), ..Flags::default() } @@ -2509,6 +2615,11 @@ mod tests { check: false, files: vec![], ext: "ts".to_string(), + use_tabs: None, + line_width: None, + indent_width: None, + single_quote: None, + prose_wrap: None, }), watch: true, ..Flags::default() @@ -2531,6 +2642,11 @@ mod tests { check: true, files: vec![PathBuf::from("foo.ts")], ext: "ts".to_string(), + use_tabs: None, + line_width: None, + indent_width: None, + single_quote: None, + prose_wrap: None, }), watch: true, ..Flags::default() @@ -2545,7 +2661,12 @@ mod tests { ignore: vec![], check: false, files: vec![], - ext: "ts".to_string() + ext: "ts".to_string(), + use_tabs: None, + line_width: None, + indent_width: None, + single_quote: None, + prose_wrap: None, }), config_path: Some("deno.jsonc".to_string()), ..Flags::default() @@ -2567,13 +2688,48 @@ mod tests { ignore: vec![], check: false, files: vec![PathBuf::from("foo.ts")], - ext: "ts".to_string() + ext: "ts".to_string(), + use_tabs: None, + line_width: None, + indent_width: None, + single_quote: None, + prose_wrap: None, }), config_path: Some("deno.jsonc".to_string()), watch: true, ..Flags::default() } ); + + let r = flags_from_vec(svec![ + "deno", + "fmt", + "--options-use-tabs", + "--options-line-width", + "60", + "--options-indent-width", + "4", + "--options-single-quote", + "--options-prose-wrap", + "never" + ]); + assert_eq!( + r.unwrap(), + Flags { + subcommand: DenoSubcommand::Fmt(FmtFlags { + ignore: vec![], + check: false, + files: vec![], + ext: "ts".to_string(), + use_tabs: Some(true), + line_width: Some(NonZeroU32::new(60).unwrap()), + indent_width: Some(NonZeroU8::new(4).unwrap()), + single_quote: Some(true), + prose_wrap: Some("never".to_string()), + }), + ..Flags::default() + } + ); } #[test] diff --git a/cli/main.rs b/cli/main.rs index 55de5a61e2..672668c2a8 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -815,20 +815,12 @@ async fn format_command( if fmt_flags.files.len() == 1 && fmt_flags.files[0].to_string_lossy() == "-" { return tools::fmt::format_stdin( - fmt_flags.check, - fmt_flags.ext, + fmt_flags, maybe_fmt_config.map(|c| c.options).unwrap_or_default(), ); } - tools::fmt::format( - fmt_flags.files, - fmt_flags.ignore, - fmt_flags.check, - flags.watch, - maybe_fmt_config, - ) - .await?; + tools::fmt::format(fmt_flags, flags.watch, maybe_fmt_config).await?; Ok(()) } diff --git a/cli/tools/fmt.rs b/cli/tools/fmt.rs index 5f1507d19d..6758d7b8b5 100644 --- a/cli/tools/fmt.rs +++ b/cli/tools/fmt.rs @@ -14,6 +14,7 @@ use crate::config_file::ProseWrap; use crate::diff::diff; use crate::file_watcher; use crate::file_watcher::ResolutionResult; +use crate::flags::FmtFlags; use crate::fs_util::{collect_files, get_extension, is_supported_ext_fmt}; use crate::text_encoding; use deno_ast::ParsedSource; @@ -34,17 +35,22 @@ use std::sync::{Arc, Mutex}; /// Format JavaScript/TypeScript files. pub async fn format( - args: Vec, - ignore: Vec, - check: bool, + fmt_flags: FmtFlags, watch: bool, maybe_fmt_config: Option, ) -> Result<(), AnyError> { + let FmtFlags { + files, + ignore, + check, + .. + } = fmt_flags.clone(); + // First, prepare final configuration. // Collect included and ignored files. CLI flags take precendence // over config file, ie. if there's `files.ignore` in config file // and `--ignore` CLI flag, only the flag value is taken into account. - let mut include_files = args.clone(); + let mut include_files = files.clone(); let mut exclude_files = ignore; if let Some(fmt_config) = maybe_fmt_config.as_ref() { @@ -67,7 +73,11 @@ pub async fn format( } } - let fmt_options = maybe_fmt_config.map(|c| c.options).unwrap_or_default(); + // Now do the same for options + let fmt_options = resolve_fmt_options( + &fmt_flags, + maybe_fmt_config.map(|c| c.options).unwrap_or_default(), + ); let resolver = |changed: Option>| { let files_changed = changed.is_some(); @@ -345,19 +355,19 @@ async fn format_source_files( /// Treats input as TypeScript or as set by `--ext` flag. /// Compatible with `--check` flag. pub fn format_stdin( - check: bool, - ext: String, + fmt_flags: FmtFlags, fmt_options: FmtOptionsConfig, ) -> 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 file_path = PathBuf::from(format!("_stdin.{}", ext)); + let file_path = PathBuf::from(format!("_stdin.{}", fmt_flags.ext)); + let fmt_options = resolve_fmt_options(&fmt_flags, fmt_options); match format_file(&file_path, &source, fmt_options) { Ok(formatted_text) => { - if check { + if fmt_flags.check { if formatted_text != source { println!("Not formatted stdin"); } @@ -380,6 +390,41 @@ fn files_str(len: usize) -> &'static str { } } +fn resolve_fmt_options( + fmt_flags: &FmtFlags, + options: FmtOptionsConfig, +) -> FmtOptionsConfig { + let mut options = options; + + if let Some(use_tabs) = fmt_flags.use_tabs { + options.use_tabs = Some(use_tabs); + } + + if let Some(line_width) = fmt_flags.line_width { + options.line_width = Some(line_width.get()); + } + + if let Some(indent_width) = fmt_flags.indent_width { + options.indent_width = Some(indent_width.get()); + } + + if let Some(single_quote) = fmt_flags.single_quote { + options.single_quote = Some(single_quote); + } + + if let Some(prose_wrap) = &fmt_flags.prose_wrap { + options.prose_wrap = Some(match prose_wrap.as_str() { + "always" => ProseWrap::Always, + "never" => ProseWrap::Never, + "preserve" => ProseWrap::Preserve, + // validators in `flags.rs` makes other values unreachable + _ => unreachable!(), + }); + } + + options +} + fn get_resolved_typescript_config( options: &FmtOptionsConfig, ) -> dprint_plugin_typescript::configuration::Configuration {