diff --git a/malva/README.md b/malva/README.md index c287f71..b38dedf 100644 --- a/malva/README.md +++ b/malva/README.md @@ -52,5 +52,5 @@ let line_bounds = LineBounds::new(input); assert_eq!("a { color: red; } -", &print_stylesheet(&stylesheet, &comments, line_bounds, Syntax::Css, &options)); +", &print_stylesheet(&stylesheet, &comments, Some(input), line_bounds, Syntax::Css, &options)); ``` diff --git a/malva/src/ctx.rs b/malva/src/ctx.rs index 43385d2..22392c6 100644 --- a/malva/src/ctx.rs +++ b/malva/src/ctx.rs @@ -7,6 +7,7 @@ use std::{array, cell::Cell, iter::Peekable, mem}; use tiny_pretty::Doc; pub(crate) struct Ctx<'a, 's: 'a> { + pub source: Option<&'s str>, pub syntax: Syntax, pub options: &'a LanguageOptions, pub comments: &'a [Comment<'s>], @@ -20,7 +21,7 @@ impl<'a, 's> Ctx<'a, 's> { &self, start: usize, end: usize, - ) -> impl Iterator> { + ) -> impl Iterator> + Clone { self.comments .iter() .filter(move |comment| comment.span.start >= start && comment.span.end <= end) diff --git a/malva/src/doc_gen/stmt.rs b/malva/src/doc_gen/stmt.rs index d073c18..00ed1bc 100644 --- a/malva/src/doc_gen/stmt.rs +++ b/malva/src/doc_gen/stmt.rs @@ -245,7 +245,7 @@ impl<'s> DocGen<'s> for SimpleBlock<'s> { impl<'s> DocGen<'s> for Statement<'s> { fn doc(&self, ctx: &Ctx<'_, 's>) -> Doc<'s> { - let stmt = match self { + match self { Statement::QualifiedRule(qualified_rule) => qualified_rule.doc(ctx), Statement::AtRule(at_rule) => at_rule.doc(ctx), Statement::Declaration(declaration) => declaration.doc(ctx), @@ -266,35 +266,6 @@ impl<'s> DocGen<'s> for Statement<'s> { sass_variable_declaration.doc(ctx) } Statement::UnknownSassAtRule(unknown_sass_at_rule) => unknown_sass_at_rule.doc(ctx), - }; - if ctx.syntax == Syntax::Sass { - stmt - } else { - match self { - Statement::AtRule(at_rule) if at_rule.block.is_none() => { - stmt.append(Doc::text(";")) - } - Statement::Declaration(decl) - if !matches!( - decl.value.last(), - Some(ComponentValue::SassNestingDeclaration(..)) - ) => - { - stmt.append(Doc::text(";")) - } - Statement::LessExtendRule(..) - | Statement::LessFunctionCall(..) - | Statement::LessMixinCall(..) - | Statement::LessVariableCall(..) - | Statement::LessVariableDeclaration(..) - | Statement::SassVariableDeclaration(..) => stmt.append(Doc::text(";")), - Statement::UnknownSassAtRule(unknown_sass_at_rule) - if unknown_sass_at_rule.block.is_none() => - { - stmt.append(Doc::text(";")) - } - _ => stmt, - } } } } @@ -443,24 +414,25 @@ fn format_single_stmt<'s>( let span = stmt.span(); - let has_comments = ctx.get_comments_between(*pos, span.start).fold( - !ignore_leading_whitespace, - |has_comments, comment| { - if has_comments && *pos > outer_span.start { - match ctx.line_bounds.line_distance(*pos, comment.span.start) { - 0 => docs.push(Doc::space()), - 1 => docs.push(line_break_doc.clone()), - _ => { - docs.push(Doc::empty_line()); - docs.push(Doc::hard_line()); + let comments = ctx.get_comments_between(*pos, span.start); + let has_comments = + comments + .clone() + .fold(!ignore_leading_whitespace, |has_comments, comment| { + if has_comments && *pos > outer_span.start { + match ctx.line_bounds.line_distance(*pos, comment.span.start) { + 0 => docs.push(Doc::space()), + 1 => docs.push(line_break_doc.clone()), + _ => { + docs.push(Doc::empty_line()); + docs.push(Doc::hard_line()); + } } } - } - docs.push(comment.doc(ctx)); - *pos = comment.span.end; - true - }, - ); + docs.push(comment.doc(ctx)); + *pos = comment.span.end; + true + }); if has_comments && *pos > outer_span.start { if ctx.line_bounds.line_distance(*pos, span.start) <= 1 { @@ -470,9 +442,50 @@ fn format_single_stmt<'s>( docs.push(Doc::hard_line()); } } - docs.push(stmt.doc(ctx)); + if comments + .last() + .and_then(|comment| comment.content.trim_start().strip_prefix("malva-ignore")) + .is_some_and(|rest| rest.is_empty() || rest.starts_with(|c: char| c.is_ascii_whitespace())) + { + if let Some(source) = ctx.source { + docs.extend(itertools::intersperse( + source[span.start..span.end].lines().map(Doc::text), + Doc::empty_line(), + )); + } else { + docs.push(stmt.doc(ctx)); + } + } else { + docs.push(stmt.doc(ctx)); + } *pos = span.end; + if ctx.syntax != Syntax::Sass { + match stmt { + Statement::AtRule(at_rule) if at_rule.block.is_none() => docs.push(Doc::text(";")), + Statement::Declaration(decl) + if !matches!( + decl.value.last(), + Some(ComponentValue::SassNestingDeclaration(..)) + ) => + { + docs.push(Doc::text(";")); + } + Statement::LessExtendRule(..) + | Statement::LessFunctionCall(..) + | Statement::LessMixinCall(..) + | Statement::LessVariableCall(..) + | Statement::LessVariableDeclaration(..) + | Statement::SassVariableDeclaration(..) => docs.push(Doc::text(";")), + Statement::UnknownSassAtRule(unknown_sass_at_rule) + if unknown_sass_at_rule.block.is_none() => + { + docs.push(Doc::text(";")); + } + _ => {} + } + } + ctx.get_comments_between( *pos, next_stmt diff --git a/malva/src/lib.rs b/malva/src/lib.rs index 9edb260..b90a16a 100644 --- a/malva/src/lib.rs +++ b/malva/src/lib.rs @@ -34,6 +34,7 @@ pub fn format_text(input: &str, syntax: Syntax, options: &FormatOptions) -> Resu Ok(print_stylesheet( &stylesheet, &comments, + Some(input), line_bounds, syntax, options, @@ -45,6 +46,7 @@ pub fn format_text(input: &str, syntax: Syntax, options: &FormatOptions) -> Resu pub fn print_stylesheet<'a, 's>( stylesheet: &'a Stylesheet<'s>, comments: &'a [Comment<'s>], + source: Option<&'s str>, line_bounds: LineBounds, syntax: Syntax, options: &'a FormatOptions, @@ -52,6 +54,7 @@ pub fn print_stylesheet<'a, 's>( use tiny_pretty::{IndentKind, PrintOptions}; let ctx = Ctx { + source, syntax, options: &options.language, comments, diff --git a/malva/tests/fmt/css/ignore/default.css b/malva/tests/fmt/css/ignore/default.css new file mode 100644 index 0000000..1a5abeb --- /dev/null +++ b/malva/tests/fmt/css/ignore/default.css @@ -0,0 +1,19 @@ +a,b{} + +/* malva-ignore*/ +a,b{} + +/* malva-ignore for some reason */ +a,b{} + +a{ + /* malva-ignore */ + width : 0; + height : 0; +} + +/* do not malva-ignore */ +a,b{} + +/* malva-ignore-will-not-apply */ +a,b{} diff --git a/malva/tests/fmt/css/ignore/default.snap b/malva/tests/fmt/css/ignore/default.snap new file mode 100644 index 0000000..78ee90b --- /dev/null +++ b/malva/tests/fmt/css/ignore/default.snap @@ -0,0 +1,22 @@ +--- +source: malva/tests/fmt.rs +--- +a, b {} + +/* malva-ignore*/ +a,b{} + +/* malva-ignore for some reason */ +a,b{} + +a { + /* malva-ignore */ + width : 0; + height: 0; +} + +/* do not malva-ignore */ +a, b {} + +/* malva-ignore-will-not-apply */ +a, b {} diff --git a/malva/tests/fmt/scss/ignore/default.scss b/malva/tests/fmt/scss/ignore/default.scss new file mode 100644 index 0000000..e85f83f --- /dev/null +++ b/malva/tests/fmt/scss/ignore/default.scss @@ -0,0 +1,37 @@ +a,b{} + +/* malva-ignore*/ +a,b{} + +/* malva-ignore for some reason */ +a,b{} + +a{ + /* malva-ignore */ + width : 0; + height : 0; +} + +/* do not malva-ignore */ +a,b{} + +/* malva-ignore-will-not-apply */ +a,b{} + +// malva-ignore +a,b{} + +// malva-ignore for some reason +a,b{} + +a{ + // malva-ignore + width : 0; + height : 0; +} + +// do not malva-ignore +a,b{} + +// malva-ignore-will-not-apply +a,b{} diff --git a/malva/tests/fmt/scss/ignore/default.snap b/malva/tests/fmt/scss/ignore/default.snap new file mode 100644 index 0000000..6b02962 --- /dev/null +++ b/malva/tests/fmt/scss/ignore/default.snap @@ -0,0 +1,40 @@ +--- +source: malva/tests/fmt.rs +--- +a, b {} + +/* malva-ignore*/ +a,b{} + +/* malva-ignore for some reason */ +a,b{} + +a { + /* malva-ignore */ + width : 0; + height: 0; +} + +/* do not malva-ignore */ +a, b {} + +/* malva-ignore-will-not-apply */ +a, b {} + +// malva-ignore +a,b{} + +// malva-ignore for some reason +a,b{} + +a { + // malva-ignore + width : 0; + height: 0; +} + +// do not malva-ignore +a, b {} + +// malva-ignore-will-not-apply +a, b {}