diff --git a/dprint_plugin/src/lib.rs b/dprint_plugin/src/lib.rs index 605e319..13edef7 100644 --- a/dprint_plugin/src/lib.rs +++ b/dprint_plugin/src/lib.rs @@ -8,7 +8,7 @@ use dprint_core::{ }; use markup_fmt::{ config::{FormatOptions, Quotes}, - detect_language, format_text, FormatError, + detect_language, format_text, FormatError, Hints, }; use std::path::Path; @@ -83,8 +83,8 @@ impl SyncPluginHandler for MarkupFmtPluginHandler { std::str::from_utf8(&file_text)?, language, config, - |path, code, print_width| { - let additional_config = build_additional_config(path, print_width, config); + |path, code, hints| { + let additional_config = build_additional_config(hints, config); format_with_host(path, code.into(), &additional_config).and_then(|result| { match result { Some(code) => String::from_utf8(code) @@ -116,39 +116,25 @@ impl SyncPluginHandler for MarkupFmtPluginHandler { generate_plugin_code!(MarkupFmtPluginHandler, MarkupFmtPluginHandler); #[doc(hidden)] -pub fn build_additional_config( - path: &Path, - print_width: usize, - config: &FormatOptions, -) -> ConfigKeyMap { +pub fn build_additional_config(hints: Hints, config: &FormatOptions) -> ConfigKeyMap { let mut additional_config = ConfigKeyMap::new(); - additional_config.insert("lineWidth".into(), (print_width as i32).into()); - additional_config.insert("printWidth".into(), (print_width as i32).into()); + additional_config.insert("lineWidth".into(), (hints.print_width as i32).into()); + additional_config.insert("printWidth".into(), (hints.print_width as i32).into()); - let file_name = path.file_name().and_then(|s| s.to_str()); - match &file_name { - Some("expr.ts" | "binding.ts" | "type_params.ts") => { - // dprint-plugin-typescript - additional_config.insert("semiColons".into(), "asi".into()); - // Biome - additional_config.insert("semicolons".into(), "asNeeded".into()); - } - Some("attr_expr.tsx") => { - // Only for dprint-plugin-typescript currently, - // because it conflicts with the `quoteStyle` option in Biome. - match config.language.quotes { - Quotes::Double => { - additional_config.insert("quoteStyle".into(), "alwaysSingle".into()); - } - Quotes::Single => { - additional_config.insert("quoteStyle".into(), "alwaysDouble".into()); - } + if hints.attr { + // Only for dprint-plugin-typescript currently, + // because it conflicts with the `quoteStyle` option in Biome. + match config.language.quotes { + Quotes::Double => { + additional_config.insert("quoteStyle".into(), "alwaysSingle".into()); + } + Quotes::Single => { + additional_config.insert("quoteStyle".into(), "alwaysDouble".into()); } } - Some("style_attr.css") => { - additional_config.insert("singleLineTopLevelDeclarations".into(), true.into()); - } - _ => {} + } + if hints.ext == "css" { + additional_config.insert("singleLineTopLevelDeclarations".into(), true.into()); } additional_config diff --git a/dprint_plugin/tests/integration.rs b/dprint_plugin/tests/integration.rs index b2d9c63..d63cec7 100644 --- a/dprint_plugin/tests/integration.rs +++ b/dprint_plugin/tests/integration.rs @@ -15,9 +15,9 @@ fn integration_with_dprint_ts_snapshot() { &input, detect_language(path).unwrap(), &options, - |path, code, print_width| -> anyhow::Result> { + |path, code, hints| -> anyhow::Result> { let additional_config = - dprint_plugin_markup::build_additional_config(path, print_width, &options); + dprint_plugin_markup::build_additional_config(hints, &options); if let Some(syntax) = malva::detect_syntax(path) { malva::format_text( code, @@ -100,9 +100,9 @@ fn integration_with_biome_snapshot() { &input, detect_language(path).unwrap(), &options, - |path, code, print_width| -> anyhow::Result> { + |path, code, hints| -> anyhow::Result> { let additional_config = - dprint_plugin_markup::build_additional_config(path, print_width, &options); + dprint_plugin_markup::build_additional_config(hints, &options); if let Some(syntax) = malva::detect_syntax(path) { malva::format_text( code, diff --git a/markup_fmt/src/ctx.rs b/markup_fmt/src/ctx.rs index 3a7c3e1..ef0ded3 100644 --- a/markup_fmt/src/ctx.rs +++ b/markup_fmt/src/ctx.rs @@ -15,7 +15,7 @@ const QUOTES: [&str; 3] = ["\"", "\"", "'"]; pub(crate) struct Ctx<'b, E, F> where - F: for<'a> FnMut(&Path, &'a str, usize) -> Result, E>, + F: for<'a> FnMut(&Path, &'a str, Hints<'b>) -> Result, E>, { pub(crate) source: &'b str, pub(crate) language: Language, @@ -29,7 +29,7 @@ where impl<'b, E, F> Ctx<'b, E, F> where - F: for<'a> FnMut(&Path, &'a str, usize) -> Result, E>, + F: for<'a> FnMut(&Path, &'a str, Hints<'b>) -> Result, E>, { pub(crate) fn script_indent(&self) -> bool { match self.language { @@ -97,12 +97,12 @@ where } pub(crate) fn format_general_expr(&mut self, code: &str, start: usize) -> String { - self.format_expr(code, Path::new("expr.tsx"), start) + self.format_expr(code, Path::new("expr.tsx"), false, start) } pub(crate) fn format_attr_expr(&mut self, code: &str, start: usize) -> String { let code = UNESCAPING_AC.replace_all(code, "ES); - let formatted = self.format_expr(&code, Path::new("attr_expr.tsx"), start); + let formatted = self.format_expr(&code, Path::new("attr_expr.tsx"), true, start); if memchr(b'\'', formatted.as_bytes()).is_some() && memchr(b'"', formatted.as_bytes()).is_some() { @@ -115,7 +115,7 @@ where } } - fn format_expr(&mut self, code: &str, path: &Path, start: usize) -> String { + fn format_expr(&mut self, code: &str, path: &Path, attr: bool, start: usize) -> String { if code.trim().is_empty() { String::new() } else { @@ -134,9 +134,14 @@ where path, wrapped, code, - self.print_width - .saturating_sub(self.indent_level) - .saturating_sub(2), // this is technically wrong, just workaround + Hints { + print_width: self + .print_width + .saturating_sub(self.indent_level) + .saturating_sub(2), // this is technically wrong, just workaround + attr, + ext: "tsx", + }, ); let formatted = formatted.trim_matches(|c: char| c.is_ascii_whitespace() || c == ';'); let formatted = formatted @@ -170,9 +175,14 @@ where Path::new("binding.ts"), wrapped, code, - self.print_width - .saturating_sub(self.indent_level) - .saturating_sub(2), // this is technically wrong, just workaround + Hints { + print_width: self + .print_width + .saturating_sub(self.indent_level) + .saturating_sub(2), // this is technically wrong, just workaround + attr: false, + ext: "ts", + }, ); let formatted = formatted.trim_matches(|c: char| c.is_ascii_whitespace() || c == ';'); formatted @@ -199,9 +209,14 @@ where Path::new("type_params.ts"), wrapped, code, - self.print_width - .saturating_sub(self.indent_level) - .saturating_sub(TYPE_PARAMS_INDENT), // this is technically wrong, just workaround + Hints { + print_width: self + .print_width + .saturating_sub(self.indent_level) + .saturating_sub(TYPE_PARAMS_INDENT), // this is technically wrong, just workaround + attr: true, + ext: "ts", + }, ); let formatted = formatted.trim_matches(|c: char| c.is_ascii_whitespace() || c == ';'); formatted @@ -221,9 +236,14 @@ where Path::new("stmt_header.js"), wrapped, code, - self.print_width - .saturating_sub(self.indent_level) - .saturating_sub(keyword.len() + 1), // this is technically wrong, just workaround + Hints { + print_width: self + .print_width + .saturating_sub(self.indent_level) + .saturating_sub(keyword.len() + 1), // this is technically wrong, just workaround + attr: false, + ext: "js", + }, ); formatted .strip_prefix(keyword) @@ -240,7 +260,7 @@ where pub(crate) fn format_script<'a>( &mut self, code: &'a str, - lang: &str, + lang: &'b str, start: usize, ) -> Cow<'a, str> { self.format_with_external_formatter( @@ -251,20 +271,25 @@ where .replace(|c: char| !c.is_ascii_whitespace(), " ") + code, code, - self.print_width - .saturating_sub(self.indent_level) - .saturating_sub(if self.script_indent() { - self.indent_width - } else { - 0 - }), + Hints { + print_width: self + .print_width + .saturating_sub(self.indent_level) + .saturating_sub(if self.script_indent() { + self.indent_width + } else { + 0 + }), + attr: false, + ext: lang, + }, ) } pub(crate) fn format_style<'a>( &mut self, code: &'a str, - lang: &str, + lang: &'b str, start: usize, ) -> Cow<'a, str> { self.format_with_external_formatter( @@ -275,13 +300,18 @@ where .replace(|c: char| !c.is_ascii_whitespace(), " ") + code, code, - self.print_width - .saturating_sub(self.indent_level) - .saturating_sub(if self.style_indent() { - self.indent_width - } else { - 0 - }), + Hints { + print_width: self + .print_width + .saturating_sub(self.indent_level) + .saturating_sub(if self.style_indent() { + self.indent_width + } else { + 0 + }), + attr: false, + ext: lang, + }, ) } @@ -294,13 +324,18 @@ where .replace(|c: char| !c.is_ascii_whitespace(), " ") + code, code, - self.print_width - .saturating_sub(self.indent_level) - .saturating_sub(if self.style_indent() { - self.indent_width - } else { - 0 - }), + Hints { + print_width: self + .print_width + .saturating_sub(self.indent_level) + .saturating_sub(if self.style_indent() { + self.indent_width + } else { + 0 + }), + attr: true, + ext: "css", + }, ) .trim() .to_owned() @@ -315,13 +350,18 @@ where .replace(|c: char| !c.is_ascii_whitespace(), " ") + code, code, - self.print_width - .saturating_sub(self.indent_level) - .saturating_sub(if self.script_indent() { - self.indent_width - } else { - 0 - }), + Hints { + print_width: self + .print_width + .saturating_sub(self.indent_level) + .saturating_sub(if self.script_indent() { + self.indent_width + } else { + 0 + }), + attr: false, + ext: "json", + }, ) } @@ -330,9 +370,9 @@ where path: &Path, code: String, _original_code: &'a str, - print_width: usize, + hints: Hints<'b>, ) -> Cow<'a, str> { - match (self.external_formatter)(path, &code, print_width) { + match (self.external_formatter)(path, &code, hints) { Ok(Cow::Owned(formatted)) => Cow::from(formatted), Ok(Cow::Borrowed(..)) => Cow::from(code), Err(e) => { @@ -343,16 +383,25 @@ where } } +/// Hints provide some useful additional information to the external formatter. +pub struct Hints<'s> { + pub print_width: usize, + /// Whether the code is inside attribute. + pub attr: bool, + /// Fake file extension. + pub ext: &'s str, +} + pub(crate) trait NestWithCtx { fn nest_with_ctx<'b, E, F>(self, ctx: &mut Ctx<'b, E, F>) -> Self where - F: for<'a> FnMut(&Path, &'a str, usize) -> Result, E>; + F: for<'a> FnMut(&Path, &'a str, Hints<'b>) -> Result, E>; } impl NestWithCtx for Doc<'_> { fn nest_with_ctx<'b, E, F>(self, ctx: &mut Ctx<'b, E, F>) -> Self where - F: for<'a> FnMut(&Path, &'a str, usize) -> Result, E>, + F: for<'a> FnMut(&Path, &'a str, Hints<'b>) -> Result, E>, { ctx.indent_level += ctx.indent_width; let doc = self.nest(ctx.indent_width); diff --git a/markup_fmt/src/lib.rs b/markup_fmt/src/lib.rs index d057aa7..3827a75 100644 --- a/markup_fmt/src/lib.rs +++ b/markup_fmt/src/lib.rs @@ -10,7 +10,7 @@ mod printer; mod state; use crate::{config::FormatOptions, ctx::Ctx, parser::Parser, printer::DocGen, state::State}; -pub use crate::{error::*, parser::Language}; +pub use crate::{ctx::Hints, error::*, parser::Language}; use std::{borrow::Cow, path::Path}; use tiny_pretty::{IndentKind, PrintOptions}; @@ -56,7 +56,7 @@ pub fn format_text( external_formatter: F, ) -> Result> where - F: for<'a> FnMut(&Path, &'a str, usize) -> Result, E>, + F: for<'a> FnMut(&Path, &'a str, Hints) -> Result, E>, { let mut parser = Parser::new(code, language.clone()); let ast = parser.parse_root().map_err(FormatError::Syntax)?; diff --git a/markup_fmt/src/printer.rs b/markup_fmt/src/printer.rs index 3ed42d2..43c51d3 100644 --- a/markup_fmt/src/printer.rs +++ b/markup_fmt/src/printer.rs @@ -1,7 +1,7 @@ use crate::{ ast::*, config::{Quotes, VSlotStyle, WhitespaceSensitivity}, - ctx::{Ctx, NestWithCtx}, + ctx::{Ctx, Hints, NestWithCtx}, helpers, state::State, Language, @@ -11,15 +11,15 @@ use std::{borrow::Cow, path::Path}; use tiny_pretty::Doc; pub(super) trait DocGen<'s> { - fn doc(&self, ctx: &mut Ctx<'_, E, F>, state: &State<'s>) -> Doc<'s> + fn doc(&self, ctx: &mut Ctx<'s, E, F>, state: &State<'s>) -> Doc<'s> where - F: for<'a> FnMut(&Path, &'a str, usize) -> Result, E>; + F: for<'a> FnMut(&Path, &'a str, Hints) -> Result, E>; } impl<'s> DocGen<'s> for AngularCase<'s> { - fn doc(&self, ctx: &mut Ctx<'_, E, F>, state: &State<'s>) -> Doc<'s> + fn doc(&self, ctx: &mut Ctx<'s, E, F>, state: &State<'s>) -> Doc<'s> where - F: for<'a> FnMut(&Path, &'a str, usize) -> Result, E>, + F: for<'a> FnMut(&Path, &'a str, Hints) -> Result, E>, { Doc::text("@case (") .append(Doc::text(ctx.format_general_expr(self.expr.0, self.expr.1))) @@ -34,9 +34,9 @@ impl<'s> DocGen<'s> for AngularCase<'s> { } impl<'s> DocGen<'s> for AngularElseIf<'s> { - fn doc(&self, ctx: &mut Ctx<'_, E, F>, state: &State<'s>) -> Doc<'s> + fn doc(&self, ctx: &mut Ctx<'s, E, F>, state: &State<'s>) -> Doc<'s> where - F: for<'a> FnMut(&Path, &'a str, usize) -> Result, E>, + F: for<'a> FnMut(&Path, &'a str, Hints) -> Result, E>, { let mut docs = Vec::with_capacity(5); docs.push(Doc::text("@else if (")); @@ -57,9 +57,9 @@ impl<'s> DocGen<'s> for AngularElseIf<'s> { } impl<'s> DocGen<'s> for AngularFor<'s> { - fn doc(&self, ctx: &mut Ctx<'_, E, F>, state: &State<'s>) -> Doc<'s> + fn doc(&self, ctx: &mut Ctx<'s, E, F>, state: &State<'s>) -> Doc<'s> where - F: for<'a> FnMut(&Path, &'a str, usize) -> Result, E>, + F: for<'a> FnMut(&Path, &'a str, Hints) -> Result, E>, { let mut docs = Vec::with_capacity(5); docs.push(Doc::text("@for (")); @@ -101,9 +101,9 @@ impl<'s> DocGen<'s> for AngularFor<'s> { } impl<'s> DocGen<'s> for AngularIf<'s> { - fn doc(&self, ctx: &mut Ctx<'_, E, F>, state: &State<'s>) -> Doc<'s> + fn doc(&self, ctx: &mut Ctx<'s, E, F>, state: &State<'s>) -> Doc<'s> where - F: for<'a> FnMut(&Path, &'a str, usize) -> Result, E>, + F: for<'a> FnMut(&Path, &'a str, Hints) -> Result, E>, { let mut docs = Vec::with_capacity(5); docs.push(Doc::text("@if (")); @@ -139,9 +139,9 @@ impl<'s> DocGen<'s> for AngularIf<'s> { } impl<'s> DocGen<'s> for AngularInterpolation<'s> { - fn doc(&self, ctx: &mut Ctx<'_, E, F>, _: &State<'s>) -> Doc<'s> + fn doc(&self, ctx: &mut Ctx<'s, E, F>, _: &State<'s>) -> Doc<'s> where - F: for<'a> FnMut(&Path, &'a str, usize) -> Result, E>, + F: for<'a> FnMut(&Path, &'a str, Hints) -> Result, E>, { Doc::text("{{") .append(Doc::line_or_space()) @@ -156,9 +156,9 @@ impl<'s> DocGen<'s> for AngularInterpolation<'s> { } impl<'s> DocGen<'s> for AngularLet<'s> { - fn doc(&self, ctx: &mut Ctx<'_, E, F>, _: &State<'s>) -> Doc<'s> + fn doc(&self, ctx: &mut Ctx<'s, E, F>, _: &State<'s>) -> Doc<'s> where - F: for<'a> FnMut(&Path, &'a str, usize) -> Result, E>, + F: for<'a> FnMut(&Path, &'a str, Hints) -> Result, E>, { Doc::text("@let ") .append(Doc::text(self.name)) @@ -169,9 +169,9 @@ impl<'s> DocGen<'s> for AngularLet<'s> { } impl<'s> DocGen<'s> for AngularSwitch<'s> { - fn doc(&self, ctx: &mut Ctx<'_, E, F>, state: &State<'s>) -> Doc<'s> + fn doc(&self, ctx: &mut Ctx<'s, E, F>, state: &State<'s>) -> Doc<'s> where - F: for<'a> FnMut(&Path, &'a str, usize) -> Result, E>, + F: for<'a> FnMut(&Path, &'a str, Hints) -> Result, E>, { let mut docs = Vec::with_capacity(5); docs.push(Doc::text("@switch (")); @@ -199,9 +199,9 @@ impl<'s> DocGen<'s> for AngularSwitch<'s> { } impl<'s> DocGen<'s> for AstroAttribute<'s> { - fn doc(&self, ctx: &mut Ctx, _: &State<'s>) -> Doc<'s> + fn doc(&self, ctx: &mut Ctx<'s, E, F>, _: &State<'s>) -> Doc<'s> where - F: for<'a> FnMut(&Path, &'a str, usize) -> Result, E>, + F: for<'a> FnMut(&Path, &'a str, Hints) -> Result, E>, { let expr_code = ctx.format_general_expr(self.expr.0, self.expr.1); let expr = Doc::text("{") @@ -224,9 +224,9 @@ impl<'s> DocGen<'s> for AstroAttribute<'s> { } impl<'s> DocGen<'s> for AstroExpr<'s> { - fn doc(&self, ctx: &mut Ctx<'_, E, F>, state: &State<'s>) -> Doc<'s> + fn doc(&self, ctx: &mut Ctx<'s, E, F>, state: &State<'s>) -> Doc<'s> where - F: for<'a> FnMut(&Path, &'a str, usize) -> Result, E>, + F: for<'a> FnMut(&Path, &'a str, Hints) -> Result, E>, { const PLACEHOLDER: &str = "$AstroTpl$"; let script = self @@ -289,9 +289,9 @@ impl<'s> DocGen<'s> for AstroExpr<'s> { } impl<'s> DocGen<'s> for Attribute<'s> { - fn doc(&self, ctx: &mut Ctx<'_, E, F>, state: &State<'s>) -> Doc<'s> + fn doc(&self, ctx: &mut Ctx<'s, E, F>, state: &State<'s>) -> Doc<'s> where - F: for<'a> FnMut(&Path, &'a str, usize) -> Result, E>, + F: for<'a> FnMut(&Path, &'a str, Hints) -> Result, E>, { match self { Attribute::Native(native_attribute) => native_attribute.doc(ctx, state), @@ -305,9 +305,9 @@ impl<'s> DocGen<'s> for Attribute<'s> { } impl<'s> DocGen<'s> for Comment<'s> { - fn doc(&self, ctx: &mut Ctx, _: &State<'s>) -> Doc<'s> + fn doc(&self, ctx: &mut Ctx<'s, E, F>, _: &State<'s>) -> Doc<'s> where - F: for<'a> FnMut(&Path, &'a str, usize) -> Result, E>, + F: for<'a> FnMut(&Path, &'a str, Hints) -> Result, E>, { if ctx.options.format_comments { Doc::text("