diff options
author | Pig Fang <g-plane@hotmail.com> | 2024-08-15 04:58:48 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-08-14 22:58:48 +0200 |
commit | 3a3315cc7f3466ce229f6f150402d5ccf72b3d1d (patch) | |
tree | a0299679b2c1eb1cee496905caab380a95f22813 /cli | |
parent | 22a834ff5b4b312b8f91be8991f2b495d49fad2f (diff) |
feat(fmt): support HTML, Svelte, Vue, Astro and Angular (#25019)
This commit adds capability to format HTML, Svelte, Vue, Astro and Angular
files.
"--unstable-html" is required to format HTML files, and "--unstable-component"
flag is needed to format other formats. These can also be specified in the config
file.
Close #25015
Diffstat (limited to 'cli')
-rw-r--r-- | cli/Cargo.toml | 3 | ||||
-rw-r--r-- | cli/args/flags.rs | 45 | ||||
-rw-r--r-- | cli/args/mod.rs | 8 | ||||
-rw-r--r-- | cli/lsp/language_server.rs | 6 | ||||
-rw-r--r-- | cli/tools/fmt.rs | 206 |
5 files changed, 265 insertions, 3 deletions
diff --git a/cli/Cargo.toml b/cli/Cargo.toml index ebbe042d1..4ce3f1725 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -123,7 +123,8 @@ libc.workspace = true libz-sys.workspace = true log = { workspace = true, features = ["serde"] } lsp-types.workspace = true -malva = "=0.8.0" +malva = "=0.9.0" +markup_fmt = "=0.12.0" memmem.workspace = true monch.workspace = true notify.workspace = true diff --git a/cli/args/flags.rs b/cli/args/flags.rs index b9908f413..f8577ed1b 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -206,6 +206,8 @@ pub struct FmtFlags { pub no_semicolons: Option<bool>, pub watch: Option<WatchFlags>, pub unstable_css: bool, + pub unstable_html: bool, + pub unstable_component: bool, pub unstable_yaml: bool, } @@ -2104,7 +2106,8 @@ Ignore formatting a file by adding an ignore comment at the top of the file: .default_value("ts") .value_parser([ "ts", "tsx", "js", "jsx", "md", "json", "jsonc", "css", "scss", - "sass", "less", "yml", "yaml", "ipynb", + "sass", "less", "html", "svelte", "vue", "astro", "yml", "yaml", + "ipynb", ]), ) .arg( @@ -2189,6 +2192,20 @@ Ignore formatting a file by adding an ignore comment at the top of the file: .action(ArgAction::SetTrue), ) .arg( + Arg::new("unstable-html") + .long("unstable-html") + .help("Enable formatting HTML files.") + .value_parser(FalseyValueParser::new()) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new("unstable-component") + .long("unstable-component") + .help("Enable formatting Svelte, Vue, Astro and Angular files.") + .value_parser(FalseyValueParser::new()) + .action(ArgAction::SetTrue), + ) + .arg( Arg::new("unstable-yaml") .long("unstable-yaml") .help("Enable formatting YAML files.") @@ -4058,6 +4075,8 @@ fn fmt_parse(flags: &mut Flags, matches: &mut ArgMatches) { let prose_wrap = matches.remove_one::<String>("prose-wrap"); let no_semicolons = matches.remove_one::<bool>("no-semicolons"); let unstable_css = matches.get_flag("unstable-css"); + let unstable_html = matches.get_flag("unstable-html"); + let unstable_component = matches.get_flag("unstable-component"); let unstable_yaml = matches.get_flag("unstable-yaml"); flags.subcommand = DenoSubcommand::Fmt(FmtFlags { @@ -4071,6 +4090,8 @@ fn fmt_parse(flags: &mut Flags, matches: &mut ArgMatches) { no_semicolons, watch: watch_arg_parse(matches), unstable_css, + unstable_html, + unstable_component, unstable_yaml, }); } @@ -5891,6 +5912,8 @@ mod tests { prose_wrap: None, no_semicolons: None, unstable_css: false, + unstable_html: false, + unstable_component: false, unstable_yaml: false, watch: Default::default(), }), @@ -5916,6 +5939,8 @@ mod tests { prose_wrap: None, no_semicolons: None, unstable_css: false, + unstable_html: false, + unstable_component: false, unstable_yaml: false, watch: Default::default(), }), @@ -5941,6 +5966,8 @@ mod tests { prose_wrap: None, no_semicolons: None, unstable_css: false, + unstable_html: false, + unstable_component: false, unstable_yaml: false, watch: Default::default(), }), @@ -5966,6 +5993,8 @@ mod tests { prose_wrap: None, no_semicolons: None, unstable_css: false, + unstable_html: false, + unstable_component: false, unstable_yaml: false, watch: Some(Default::default()), }), @@ -5980,6 +6009,8 @@ mod tests { "--watch", "--no-clear-screen", "--unstable-css", + "--unstable-html", + "--unstable-component", "--unstable-yaml" ]); assert_eq!( @@ -5998,6 +6029,8 @@ mod tests { prose_wrap: None, no_semicolons: None, unstable_css: true, + unstable_html: true, + unstable_component: true, unstable_yaml: true, watch: Some(WatchFlags { hmr: false, @@ -6034,6 +6067,8 @@ mod tests { prose_wrap: None, no_semicolons: None, unstable_css: false, + unstable_html: false, + unstable_component: false, unstable_yaml: false, watch: Some(Default::default()), }), @@ -6059,6 +6094,8 @@ mod tests { prose_wrap: None, no_semicolons: None, unstable_css: false, + unstable_html: false, + unstable_component: false, unstable_yaml: false, watch: Default::default(), }), @@ -6092,6 +6129,8 @@ mod tests { prose_wrap: None, no_semicolons: None, unstable_css: false, + unstable_html: false, + unstable_component: false, unstable_yaml: false, watch: Some(Default::default()), }), @@ -6130,6 +6169,8 @@ mod tests { prose_wrap: Some("never".to_string()), no_semicolons: Some(true), unstable_css: false, + unstable_html: false, + unstable_component: false, unstable_yaml: false, watch: Default::default(), }), @@ -6162,6 +6203,8 @@ mod tests { prose_wrap: None, no_semicolons: Some(false), unstable_css: false, + unstable_html: false, + unstable_component: false, unstable_yaml: false, watch: Default::default(), }), diff --git a/cli/args/mod.rs b/cli/args/mod.rs index 65f9183d5..4a644cb09 100644 --- a/cli/args/mod.rs +++ b/cli/args/mod.rs @@ -282,6 +282,8 @@ impl BenchOptions { #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] pub struct UnstableFmtOptions { pub css: bool, + pub html: bool, + pub component: bool, pub yaml: bool, } @@ -316,6 +318,8 @@ impl FmtOptions { options: resolve_fmt_options(fmt_flags, fmt_config.options), unstable: UnstableFmtOptions { css: unstable.css || fmt_flags.unstable_css, + html: unstable.html || fmt_flags.unstable_html, + component: unstable.component || fmt_flags.unstable_component, yaml: unstable.yaml || fmt_flags.unstable_yaml, }, files: fmt_config.files, @@ -1339,6 +1343,8 @@ impl CliOptions { let workspace = self.workspace(); UnstableFmtOptions { css: workspace.has_unstable("fmt-css"), + html: workspace.has_unstable("fmt-html"), + component: workspace.has_unstable("fmt-component"), yaml: workspace.has_unstable("fmt-yaml"), } } @@ -1673,6 +1679,8 @@ impl CliOptions { "byonm", "bare-node-builtins", "fmt-css", + "fmt-html", + "fmt-component", "fmt-yaml", ]); // add more unstable flags to the same vector holding granular flags diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 4bbc7036f..6fa079bd8 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -1372,6 +1372,12 @@ impl Inner { css: maybe_workspace .map(|w| w.has_unstable("fmt-css")) .unwrap_or(false), + html: maybe_workspace + .map(|w| w.has_unstable("fmt-html")) + .unwrap_or(false), + component: maybe_workspace + .map(|w| w.has_unstable("fmt-component")) + .unwrap_or(false), yaml: maybe_workspace .map(|w| w.has_unstable("fmt-yaml")) .unwrap_or(false), diff --git a/cli/tools/fmt.rs b/cli/tools/fmt.rs index 348e2d862..2d06aafca 100644 --- a/cli/tools/fmt.rs +++ b/cli/tools/fmt.rs @@ -36,6 +36,7 @@ use deno_core::unsync::spawn_blocking; use log::debug; use log::info; use log::warn; +use std::borrow::Cow; use std::fs; use std::io::stdin; use std::io::stdout; @@ -248,6 +249,10 @@ fn format_markdown( | "scss" | "sass" | "less" + | "html" + | "svelte" + | "vue" + | "astro" | "yml" | "yaml" ) { @@ -274,6 +279,20 @@ fn format_markdown( Ok(None) } } + "html" => { + if unstable_options.html { + format_html(&fake_filename, text, fmt_options) + } else { + Ok(None) + } + } + "svelte" | "vue" | "astro" => { + if unstable_options.component { + format_html(&fake_filename, text, fmt_options) + } else { + Ok(None) + } + } "yml" | "yaml" => { if unstable_options.yaml { pretty_yaml::format_text( @@ -330,6 +349,107 @@ pub fn format_css( .map_err(AnyError::from) } +pub fn format_html( + file_path: &Path, + file_text: &str, + fmt_options: &FmtOptionsConfig, +) -> Result<Option<String>, AnyError> { + markup_fmt::format_text( + file_text, + markup_fmt::detect_language(file_path) + .unwrap_or(markup_fmt::Language::Html), + &get_resolved_markup_fmt_config(fmt_options), + |text, hints| { + let mut file_name = + file_path.file_name().expect("missing file name").to_owned(); + file_name.push("."); + file_name.push(hints.ext); + let path = file_path.with_file_name(file_name); + match hints.ext { + "css" | "scss" | "sass" | "less" => { + let mut malva_config = get_resolved_malva_config(fmt_options); + malva_config.layout.print_width = hints.print_width; + if hints.attr { + malva_config.language.quotes = + if let Some(true) = fmt_options.single_quote { + malva::config::Quotes::AlwaysDouble + } else { + malva::config::Quotes::AlwaysSingle + }; + malva_config.language.single_line_top_level_declarations = true; + } + malva::format_text( + text, + malva::detect_syntax(path).unwrap_or(malva::Syntax::Css), + &malva_config, + ) + .map(Cow::from) + .map_err(AnyError::from) + } + "json" | "jsonc" => { + let mut json_config = get_resolved_json_config(fmt_options); + json_config.line_width = hints.print_width as u32; + dprint_plugin_json::format_text(&path, text, &json_config).map( + |formatted| { + if let Some(formatted) = formatted { + Cow::from(formatted) + } else { + Cow::from(text) + } + }, + ) + } + _ => { + let mut typescript_config = + get_resolved_typescript_config(fmt_options); + typescript_config.line_width = hints.print_width as u32; + if hints.attr { + typescript_config.quote_style = if let Some(true) = + fmt_options.single_quote + { + dprint_plugin_typescript::configuration::QuoteStyle::AlwaysDouble + } else { + dprint_plugin_typescript::configuration::QuoteStyle::AlwaysSingle + }; + } + dprint_plugin_typescript::format_text( + &path, + text.to_string(), + &typescript_config, + ) + .map(|formatted| { + if let Some(formatted) = formatted { + Cow::from(formatted) + } else { + Cow::from(text) + } + }) + } + } + }, + ) + .map(Some) + .map_err(|error| match error { + markup_fmt::FormatError::Syntax(error) => AnyError::from(error), + markup_fmt::FormatError::External(errors) => { + let last = errors.len() - 1; + AnyError::msg( + errors + .into_iter() + .enumerate() + .map(|(i, error)| { + if i == last { + format!("{error}") + } else { + format!("{error}\n\n") + } + }) + .collect::<String>(), + ) + } + }) +} + /// Formats a single TS, TSX, JS, JSX, JSONC, JSON, MD, or IPYNB file. pub fn format_file( file_path: &Path, @@ -351,6 +471,20 @@ pub fn format_file( Ok(None) } } + "html" => { + if unstable_options.html { + format_html(file_path, file_text, fmt_options) + } else { + Ok(None) + } + } + "svelte" | "vue" | "astro" => { + if unstable_options.component { + format_html(file_path, file_text, fmt_options) + } else { + Ok(None) + } + } "yml" | "yaml" => { if unstable_options.yaml { pretty_yaml::format_text( @@ -810,7 +944,7 @@ fn get_resolved_malva_config( less_import_options_prefer_single_line: None, less_mixin_args_prefer_single_line: None, less_mixin_params_prefer_single_line: None, - top_level_declarations_prefer_single_line: None, + single_line_top_level_declarations: false, selector_override_comment_directive: "deno-fmt-selector-override".into(), ignore_comment_directive: "deno-fmt-ignore".into(), }; @@ -821,6 +955,64 @@ fn get_resolved_malva_config( } } +fn get_resolved_markup_fmt_config( + options: &FmtOptionsConfig, +) -> markup_fmt::config::FormatOptions { + use markup_fmt::config::*; + + let layout_options = LayoutOptions { + print_width: options.line_width.unwrap_or(80) as usize, + use_tabs: options.use_tabs.unwrap_or_default(), + indent_width: options.indent_width.unwrap_or(2) as usize, + line_break: LineBreak::Lf, + }; + + let language_options = LanguageOptions { + quotes: Quotes::Double, + format_comments: false, + script_indent: true, + html_script_indent: None, + vue_script_indent: Some(false), + svelte_script_indent: None, + astro_script_indent: None, + style_indent: true, + html_style_indent: None, + vue_style_indent: Some(false), + svelte_style_indent: None, + astro_style_indent: None, + closing_bracket_same_line: false, + closing_tag_line_break_for_empty: ClosingTagLineBreakForEmpty::Fit, + max_attrs_per_line: None, + prefer_attrs_single_line: false, + html_normal_self_closing: None, + html_void_self_closing: Some(true), + component_self_closing: None, + svg_self_closing: None, + mathml_self_closing: None, + whitespace_sensitivity: WhitespaceSensitivity::Css, + component_whitespace_sensitivity: None, + doctype_keyword_case: DoctypeKeywordCase::Upper, + v_bind_style: None, + v_on_style: None, + v_for_delimiter_style: None, + v_slot_style: None, + component_v_slot_style: None, + default_v_slot_style: None, + named_v_slot_style: None, + v_bind_same_name_short_hand: None, + strict_svelte_attr: false, + svelte_attr_shorthand: Some(true), + svelte_directive_shorthand: Some(true), + astro_attr_shorthand: Some(true), + ignore_comment_directive: "deno-fmt-ignore".into(), + }; + + FormatOptions { + layout: layout_options, + language: language_options, + } +} + fn get_resolved_yaml_config( options: &FmtOptionsConfig, ) -> pretty_yaml::config::FormatOptions { @@ -952,6 +1144,10 @@ fn is_supported_ext_fmt(path: &Path) -> bool { | "scss" | "sass" | "less" + | "html" + | "svelte" + | "vue" + | "astro" | "md" | "mkd" | "mkdn" @@ -1002,6 +1198,14 @@ mod test { assert!(is_supported_ext_fmt(Path::new("foo.Sass"))); assert!(is_supported_ext_fmt(Path::new("foo.less"))); assert!(is_supported_ext_fmt(Path::new("foo.LeSS"))); + assert!(is_supported_ext_fmt(Path::new("foo.html"))); + assert!(is_supported_ext_fmt(Path::new("foo.HTML"))); + assert!(is_supported_ext_fmt(Path::new("foo.svelte"))); + assert!(is_supported_ext_fmt(Path::new("foo.Svelte"))); + assert!(is_supported_ext_fmt(Path::new("foo.vue"))); + assert!(is_supported_ext_fmt(Path::new("foo.VUE"))); + assert!(is_supported_ext_fmt(Path::new("foo.astro"))); + assert!(is_supported_ext_fmt(Path::new("foo.AsTrO"))); assert!(is_supported_ext_fmt(Path::new("foo.yml"))); assert!(is_supported_ext_fmt(Path::new("foo.Yml"))); assert!(is_supported_ext_fmt(Path::new("foo.yaml"))); |