diff --git a/Cargo.lock b/Cargo.lock index 94281572b..5d18704be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -323,11 +323,10 @@ dependencies = [ [[package]] name = "biome_console" version = "0.5.7" -source = "git+https://github.com/biomejs/biome#92d3fbe59f48530b833af7fce5c4ee0940b7f5f6" +source = "git+https://github.com/biomejs/biome?rev=71c825e65e58fc1937b55b4f26edafdd183a50f3#71c825e65e58fc1937b55b4f26edafdd183a50f3" dependencies = [ "biome_markup", "biome_text_size", - "schemars", "serde", "termcolor", "unicode-segmentation", @@ -337,23 +336,20 @@ dependencies = [ [[package]] name = "biome_deserialize" version = "0.6.0" -source = "git+https://github.com/biomejs/biome#92d3fbe59f48530b833af7fce5c4ee0940b7f5f6" +source = "git+https://github.com/biomejs/biome?rev=71c825e65e58fc1937b55b4f26edafdd183a50f3#71c825e65e58fc1937b55b4f26edafdd183a50f3" dependencies = [ "biome_console", - "biome_deserialize_macros", "biome_diagnostics", "biome_json_parser", "biome_json_syntax", "biome_rowan", "enumflags2", - "indexmap 2.7.1", - "serde", ] [[package]] name = "biome_deserialize_macros" version = "0.6.0" -source = "git+https://github.com/biomejs/biome#92d3fbe59f48530b833af7fce5c4ee0940b7f5f6" +source = "git+https://github.com/biomejs/biome?rev=71c825e65e58fc1937b55b4f26edafdd183a50f3#71c825e65e58fc1937b55b4f26edafdd183a50f3" dependencies = [ "biome_string_case", "proc-macro-error", @@ -365,7 +361,7 @@ dependencies = [ [[package]] name = "biome_diagnostics" version = "0.5.7" -source = "git+https://github.com/biomejs/biome#92d3fbe59f48530b833af7fce5c4ee0940b7f5f6" +source = "git+https://github.com/biomejs/biome?rev=71c825e65e58fc1937b55b4f26edafdd183a50f3#71c825e65e58fc1937b55b4f26edafdd183a50f3" dependencies = [ "backtrace", "biome_console", @@ -374,20 +370,18 @@ dependencies = [ "biome_rowan", "biome_text_edit", "biome_text_size", - "bpaf", "enumflags2", - "oxc_resolver", "serde", - "serde_ini", "serde_json", "termcolor", + "terminal_size", "unicode-width", ] [[package]] name = "biome_diagnostics_categories" version = "0.5.7" -source = "git+https://github.com/biomejs/biome#92d3fbe59f48530b833af7fce5c4ee0940b7f5f6" +source = "git+https://github.com/biomejs/biome?rev=71c825e65e58fc1937b55b4f26edafdd183a50f3#71c825e65e58fc1937b55b4f26edafdd183a50f3" dependencies = [ "quote", "serde", @@ -396,7 +390,7 @@ dependencies = [ [[package]] name = "biome_diagnostics_macros" version = "0.5.7" -source = "git+https://github.com/biomejs/biome#92d3fbe59f48530b833af7fce5c4ee0940b7f5f6" +source = "git+https://github.com/biomejs/biome?rev=71c825e65e58fc1937b55b4f26edafdd183a50f3#71c825e65e58fc1937b55b4f26edafdd183a50f3" dependencies = [ "proc-macro-error", "proc-macro2", @@ -407,7 +401,7 @@ dependencies = [ [[package]] name = "biome_formatter" version = "0.5.7" -source = "git+https://github.com/biomejs/biome#92d3fbe59f48530b833af7fce5c4ee0940b7f5f6" +source = "git+https://github.com/biomejs/biome?rev=71c825e65e58fc1937b55b4f26edafdd183a50f3#71c825e65e58fc1937b55b4f26edafdd183a50f3" dependencies = [ "biome_console", "biome_deserialize", @@ -415,6 +409,7 @@ dependencies = [ "biome_diagnostics", "biome_rowan", "biome_string_case", + "camino", "cfg-if", "countme", "drop_bomb", @@ -427,7 +422,7 @@ dependencies = [ [[package]] name = "biome_grit_factory" version = "0.5.7" -source = "git+https://github.com/biomejs/biome#92d3fbe59f48530b833af7fce5c4ee0940b7f5f6" +source = "git+https://github.com/biomejs/biome?rev=71c825e65e58fc1937b55b4f26edafdd183a50f3#71c825e65e58fc1937b55b4f26edafdd183a50f3" dependencies = [ "biome_grit_syntax", "biome_rowan", @@ -436,7 +431,7 @@ dependencies = [ [[package]] name = "biome_grit_formatter" version = "0.0.0" -source = "git+https://github.com/biomejs/biome#92d3fbe59f48530b833af7fce5c4ee0940b7f5f6" +source = "git+https://github.com/biomejs/biome?rev=71c825e65e58fc1937b55b4f26edafdd183a50f3#71c825e65e58fc1937b55b4f26edafdd183a50f3" dependencies = [ "biome_formatter", "biome_grit_syntax", @@ -446,7 +441,7 @@ dependencies = [ [[package]] name = "biome_grit_parser" version = "0.1.0" -source = "git+https://github.com/biomejs/biome#92d3fbe59f48530b833af7fce5c4ee0940b7f5f6" +source = "git+https://github.com/biomejs/biome?rev=71c825e65e58fc1937b55b4f26edafdd183a50f3#71c825e65e58fc1937b55b4f26edafdd183a50f3" dependencies = [ "biome_console", "biome_diagnostics", @@ -464,17 +459,18 @@ dependencies = [ [[package]] name = "biome_grit_syntax" version = "0.5.7" -source = "git+https://github.com/biomejs/biome#92d3fbe59f48530b833af7fce5c4ee0940b7f5f6" +source = "git+https://github.com/biomejs/biome?rev=71c825e65e58fc1937b55b4f26edafdd183a50f3#71c825e65e58fc1937b55b4f26edafdd183a50f3" dependencies = [ "biome_rowan", "biome_string_case", + "camino", "serde", ] [[package]] name = "biome_json_factory" version = "0.5.7" -source = "git+https://github.com/biomejs/biome#92d3fbe59f48530b833af7fce5c4ee0940b7f5f6" +source = "git+https://github.com/biomejs/biome?rev=71c825e65e58fc1937b55b4f26edafdd183a50f3#71c825e65e58fc1937b55b4f26edafdd183a50f3" dependencies = [ "biome_json_syntax", "biome_rowan", @@ -483,7 +479,7 @@ dependencies = [ [[package]] name = "biome_json_parser" version = "0.5.7" -source = "git+https://github.com/biomejs/biome#92d3fbe59f48530b833af7fce5c4ee0940b7f5f6" +source = "git+https://github.com/biomejs/biome?rev=71c825e65e58fc1937b55b4f26edafdd183a50f3#71c825e65e58fc1937b55b4f26edafdd183a50f3" dependencies = [ "biome_console", "biome_diagnostics", @@ -499,17 +495,18 @@ dependencies = [ [[package]] name = "biome_json_syntax" version = "0.5.7" -source = "git+https://github.com/biomejs/biome#92d3fbe59f48530b833af7fce5c4ee0940b7f5f6" +source = "git+https://github.com/biomejs/biome?rev=71c825e65e58fc1937b55b4f26edafdd183a50f3#71c825e65e58fc1937b55b4f26edafdd183a50f3" dependencies = [ "biome_rowan", "biome_string_case", + "camino", "serde", ] [[package]] name = "biome_markup" version = "0.5.7" -source = "git+https://github.com/biomejs/biome#92d3fbe59f48530b833af7fce5c4ee0940b7f5f6" +source = "git+https://github.com/biomejs/biome?rev=71c825e65e58fc1937b55b4f26edafdd183a50f3#71c825e65e58fc1937b55b4f26edafdd183a50f3" dependencies = [ "proc-macro-error", "proc-macro2", @@ -519,7 +516,7 @@ dependencies = [ [[package]] name = "biome_parser" version = "0.5.7" -source = "git+https://github.com/biomejs/biome#92d3fbe59f48530b833af7fce5c4ee0940b7f5f6" +source = "git+https://github.com/biomejs/biome?rev=71c825e65e58fc1937b55b4f26edafdd183a50f3#71c825e65e58fc1937b55b4f26edafdd183a50f3" dependencies = [ "biome_console", "biome_diagnostics", @@ -533,7 +530,7 @@ dependencies = [ [[package]] name = "biome_rowan" version = "0.5.7" -source = "git+https://github.com/biomejs/biome#92d3fbe59f48530b833af7fce5c4ee0940b7f5f6" +source = "git+https://github.com/biomejs/biome?rev=71c825e65e58fc1937b55b4f26edafdd183a50f3#71c825e65e58fc1937b55b4f26edafdd183a50f3" dependencies = [ "biome_text_edit", "biome_text_size", @@ -541,18 +538,17 @@ dependencies = [ "hashbrown 0.14.5", "rustc-hash 2.1.0", "serde", - "tracing", ] [[package]] name = "biome_string_case" version = "0.5.7" -source = "git+https://github.com/biomejs/biome#92d3fbe59f48530b833af7fce5c4ee0940b7f5f6" +source = "git+https://github.com/biomejs/biome?rev=71c825e65e58fc1937b55b4f26edafdd183a50f3#71c825e65e58fc1937b55b4f26edafdd183a50f3" [[package]] name = "biome_text_edit" version = "0.5.7" -source = "git+https://github.com/biomejs/biome#92d3fbe59f48530b833af7fce5c4ee0940b7f5f6" +source = "git+https://github.com/biomejs/biome?rev=71c825e65e58fc1937b55b4f26edafdd183a50f3#71c825e65e58fc1937b55b4f26edafdd183a50f3" dependencies = [ "biome_text_size", "serde", @@ -562,16 +558,15 @@ dependencies = [ [[package]] name = "biome_text_size" version = "0.5.7" -source = "git+https://github.com/biomejs/biome#92d3fbe59f48530b833af7fce5c4ee0940b7f5f6" +source = "git+https://github.com/biomejs/biome?rev=71c825e65e58fc1937b55b4f26edafdd183a50f3#71c825e65e58fc1937b55b4f26edafdd183a50f3" dependencies = [ - "schemars", "serde", ] [[package]] name = "biome_unicode_table" version = "0.5.7" -source = "git+https://github.com/biomejs/biome#92d3fbe59f48530b833af7fce5c4ee0940b7f5f6" +source = "git+https://github.com/biomejs/biome?rev=71c825e65e58fc1937b55b4f26edafdd183a50f3#71c825e65e58fc1937b55b4f26edafdd183a50f3" [[package]] name = "bitflags" @@ -594,26 +589,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "bpaf" -version = "0.9.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "913d667d4716acd286a0dc58824a4c0ec8ce58eeca95cfb58172d17a9ec01035" -dependencies = [ - "bpaf_derive", -] - -[[package]] -name = "bpaf_derive" -version = "0.5.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34d8a24f809c4cda0832689019daa067d0ae927d801429196b238a3e8cb0cd3e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", -] - [[package]] name = "bstr" version = "1.9.1" @@ -657,9 +632,9 @@ checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "camino" -version = "1.1.6" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" dependencies = [ "serde", ] @@ -1152,20 +1127,6 @@ dependencies = [ "parking_lot_core", ] -[[package]] -name = "dashmap" -version = "6.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" -dependencies = [ - "cfg-if", - "crossbeam-utils", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", -] - [[package]] name = "debugid" version = "0.8.0" @@ -1287,12 +1248,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bda8e21c04aca2ae33ffc2fd8c23134f3cac46db123ba97bd9d3f3b8a4a85e1" -[[package]] -name = "dyn-clone" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" - [[package]] name = "either" version = "1.10.0" @@ -1347,6 +1302,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba2f4b465f5318854c6f8dd686ede6c0a9dc67d4b1ac241cf0eb51521a309147" dependencies = [ "enumflags2_derive", + "serde", ] [[package]] @@ -2359,15 +2315,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "json-strip-comments" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b271732a960335e715b6b2ae66a086f115c74eb97360e996d2bd809bfc063bba" -dependencies = [ - "memchr", -] - [[package]] name = "kqueue" version = "1.0.8" @@ -2427,7 +2374,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -2570,7 +2517,7 @@ dependencies = [ "cli_server", "colored", "console", - "dashmap 5.5.3", + "dashmap", "dialoguer", "env_logger", "flate2", @@ -2735,7 +2682,7 @@ version = "0.1.0" dependencies = [ "ai_builtins", "anyhow", - "dashmap 5.5.3", + "dashmap", "grit-util", "grit_cache", "marzano-core", @@ -3295,25 +3242,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" -[[package]] -name = "oxc_resolver" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bed381b6ab4bbfebfc7a011ad43b110ace8d201d02a39c0e09855f16b8f3f741" -dependencies = [ - "cfg-if", - "dashmap 6.1.0", - "indexmap 2.7.1", - "json-strip-comments", - "once_cell", - "rustc-hash 2.1.0", - "serde", - "serde_json", - "simdutf8", - "thiserror", - "tracing", -] - [[package]] name = "parking_lot" version = "0.12.1" @@ -3867,12 +3795,6 @@ dependencies = [ "winreg 0.52.0", ] -[[package]] -name = "result" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194d8e591e405d1eecf28819740abed6d719d1a2db87fc0bcdedee9a26d55560" - [[package]] name = "ring" version = "0.17.8" @@ -4037,32 +3959,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "schemars" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" -dependencies = [ - "dyn-clone", - "indexmap 2.7.1", - "schemars_derive", - "serde", - "serde_json", - "smallvec", -] - -[[package]] -name = "schemars_derive" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 2.0.96", -] - [[package]] name = "scoped-tls" version = "1.0.1" @@ -4135,28 +4031,6 @@ dependencies = [ "syn 2.0.96", ] -[[package]] -name = "serde_derive_internals" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "serde_ini" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb236687e2bb073a7521c021949be944641e671b8505a94069ca37b656c81139" -dependencies = [ - "result", - "serde", - "void", -] - [[package]] name = "serde_json" version = "1.0.137" @@ -4259,12 +4133,6 @@ dependencies = [ "libc", ] -[[package]] -name = "simdutf8" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" - [[package]] name = "similar" version = "2.7.0" @@ -4443,6 +4311,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "terminal_size" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" +dependencies = [ + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "termtree" version = "0.4.1" @@ -4697,7 +4575,7 @@ dependencies = [ "async-trait", "auto_impl", "bytes", - "dashmap 5.5.3", + "dashmap", "futures", "httparse", "lsp-types", @@ -5161,12 +5039,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "void" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" - [[package]] name = "wait-timeout" version = "0.2.0" @@ -5966,6 +5838,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-targets" version = "0.48.5" diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index e5131567a..8030f9516 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -87,8 +87,10 @@ tracing-subscriber = { version = "0.3", default-features = false, optional = tru tracing-log = { version = "0.2.0", optional = true } fs-err = { version = "2.11.0" } -biome_grit_parser = { git = "https://github.com/biomejs/biome" } -biome_grit_formatter = { git = "https://github.com/biomejs/biome" } +biome_grit_parser = { git = "https://github.com/biomejs/biome", rev = "71c825e65e58fc1937b55b4f26edafdd183a50f3" } +biome_grit_formatter = { git = "https://github.com/biomejs/biome", rev = "71c825e65e58fc1937b55b4f26edafdd183a50f3" } +# biome_grit_parser = { path = "../../../../../../biome/crates/biome_grit_parser" } +# biome_grit_formatter = { path = "../../../../../../biome/crates/biome_grit_formatter" } [target.'cfg(not(windows))'.dependencies] openssl = { version = "0.10", features = ["vendored"] } diff --git a/crates/cli/src/commands/format.rs b/crates/cli/src/commands/format.rs index de6fa1160..7a93a0ffd 100644 --- a/crates/cli/src/commands/format.rs +++ b/crates/cli/src/commands/format.rs @@ -1,73 +1,135 @@ use crate::{ - resolver::{resolve_from_cwd, GritModuleResolver, Source}, - ux::{format_diff, DiffString}, + commands::patterns_test::filter_patterns_by_regex, + flags::GlobalFormatFlags, + messenger_variant::create_emitter, + resolver::{resolve_from_cwd, Source}, }; -use anyhow::{anyhow, bail, ensure, Context, Result}; +use anyhow::{anyhow, Context, Result}; use biome_grit_formatter::context::GritFormatOptions; use clap::Args; use colored::Colorize; -use marzano_core::api::MatchResult; +use marzano_core::api::{DoneFile, MatchResult, Rewrite}; use marzano_gritmodule::{config::ResolvedGritDefinition, parser::PatternFileExt}; -use marzano_util::{rich_path::RichFile, runtime::ExecutionContext}; -use rayon::iter::{IntoParallelIterator, ParallelIterator}; +use marzano_language::{markdown_block::MarkdownBlock, target_language::TargetLanguage}; +use marzano_messenger::{ + emit::{ApplyDetails, Messager}, + output_mode::OutputMode, +}; use serde::Serialize; use std::collections::BTreeMap; #[derive(Args, Debug, Serialize, Clone)] -pub struct FormatArgs { +pub struct FormatGritArgs { /// Write formats to file instead of just showing them #[clap(long)] pub write: bool, + // Level of detail to show for results + #[clap( + long = "output", + default_value_t = OutputMode::Compact, + )] + output: OutputMode, + /// Regex of a specific pattern to test + #[clap(long = "filter")] + pub filter: Option, } -pub async fn run_format(arg: &FormatArgs) -> Result<()> { - let (resolved, _) = resolve_from_cwd(&Source::Local).await?; +pub async fn run_format(arg: &FormatGritArgs, flags: &GlobalFormatFlags) -> Result<()> { + let (mut resolved, _) = resolve_from_cwd(&Source::Local).await?; + + if let Some(filter) = &arg.filter { + resolved = filter_patterns_by_regex(resolved, filter)?; + } let file_path_to_resolved = group_resolved_patterns_by_group(resolved); - let mut results = file_path_to_resolved - .into_par_iter() - .map(|(file_path, resolved_patterns)| { - let result = format_file_resolved_patterns(&file_path, resolved_patterns, arg.clone()); - (file_path, result) - }) - .collect::>(); - - // sort outputs to ensure consistent stdout output - // also avoid using sort_by_key to prevent additional cloning of file_path - results.sort_by(|(file_path, _), (other_file_path, _)| file_path.cmp(other_file_path)); - - for (file_path, result) in results { - match result { - Err(error) => eprintln!("couldn't format '{}': {error:?}", file_path), - Ok(Some(diff)) => println!("{}:\n{}", file_path.bold(), diff), - Ok(None) => (), // `args.write` is true or file is already formated + + if file_path_to_resolved.is_empty() { + return Err(anyhow!("No patterns found to format")); + } + + println!( + "Formatting {} {}", + format!("{}", file_path_to_resolved.len()).bold().yellow(), + if file_path_to_resolved.len() == 1 { + "pattern" + } else { + "patterns" + } + ); + + // Create an emitter for formatting results + let mut emitter = create_emitter( + &crate::flags::OutputFormat::from(flags), + arg.output.clone(), + None, + false, + None, + None, + marzano_messenger::emit::VisibilityLevels::default(), + ) + .await?; + + let mut details = ApplyDetails { + matched: 0, + rewritten: 0, + named_pattern: None, + }; + let dry_run = !arg.write; + + for (file_path, resolved_patterns) in file_path_to_resolved { + let results = format_file_resolved_patterns(&file_path, resolved_patterns); + if let Ok(results) = results { + emitter.handle_results( + results, + &mut details, + dry_run, + false, + &mut false, + None, + None, + None, + &TargetLanguage::MarkdownBlock(MarkdownBlock::new(None)), + ); } } + + println!( + "Modified {} {}", + format!("{}", details.rewritten).bold().yellow(), + if details.rewritten == 1 { + "file" + } else { + "files" + } + ); + Ok(()) } fn group_resolved_patterns_by_group( resolved: Vec, ) -> Vec<(String, Vec)> { - resolved.into_iter().fold(Vec::new(), |mut acc, resolved| { - let file_path = &resolved.config.path; - if let Some((_, resolved_patterns)) = acc - .iter_mut() - .find(|(resolv_file_path, _)| resolv_file_path == file_path) - { - resolved_patterns.push(resolved); - } else { - acc.push((file_path.clone(), vec![resolved])); - } - acc - }) + let mut map = BTreeMap::new(); + + // Group into map + for resolved in resolved { + map.entry(resolved.config.path.clone()) + .or_insert_with(Vec::new) + .push(resolved); + } + + // Convert to Vec + map.into_iter().collect() } fn format_file_resolved_patterns( file_path: &str, patterns: Vec, - arg: FormatArgs, -) -> Result> { +) -> Result> { + // Apply patterns in reverse order to avoid conflicts + let mut patterns = patterns; + patterns.sort_by_key(|p| std::cmp::Reverse(p.config.range.map_or(0, |r| r.start_byte))); + let first_pattern = patterns .first() .ok_or_else(|| anyhow!("patterns is empty"))?; @@ -76,36 +138,89 @@ fn format_file_resolved_patterns( .raw .as_ref() .ok_or_else(|| anyhow!("pattern doesn't have raw data"))?; + + let format = first_pattern_raw_data.format; let old_file_content = &first_pattern_raw_data.content; - let new_file_content = match first_pattern_raw_data.format { - PatternFileExt::Yaml => format_yaml_file(&patterns, old_file_content)?, - PatternFileExt::Grit => format_grit_code(old_file_content)?, + let mut results = vec![]; + + let new_file_content = match format { + PatternFileExt::Yaml => { + let (this_results, new_file_content) = + yaml::apply_yaml_rewrites(&patterns, old_file_content)?; + results.extend(this_results); + new_file_content + } + PatternFileExt::Grit => { + let (this_results, new_file_content) = format_grit_code(old_file_content)?; + results.extend(this_results); + new_file_content + } PatternFileExt::Md => { - let hunks = patterns - .iter() - .map(format_pattern_as_hunk_changes) - .collect::>>()?; - apply_hunk_changes(old_file_content, hunks) + let mut new_file_content = old_file_content.clone(); + for pattern in &patterns { + if let Some(range) = pattern.config.range { + let (this_results, formatted_pattern) = format_grit_code(&pattern.body) + .with_context(|| format!("could not format '{}'", pattern.name()))?; + results.extend(this_results); + new_file_content.replace_range( + range.start_byte as usize..range.end_byte as usize, + formatted_pattern.as_str(), + ); + } else { + println!("pattern {} has no range", pattern.name()); + } + } + new_file_content } }; + results.push(MatchResult::DoneFile(DoneFile::new(file_path.to_owned()))); + if &new_file_content == old_file_content { - return Ok(None); + return Ok(results); } - if arg.write { - std::fs::write(file_path, new_file_content).with_context(|| "could not write to file")?; - Ok(None) - } else { - Ok(Some(format_diff(old_file_content, &new_file_content))) - } + results.push(MatchResult::Rewrite(Rewrite::for_file( + file_path, + old_file_content, + &new_file_content, + ))); + + Ok(results) +} + +/// format grit code using `biome` +fn format_grit_code(source: &str) -> Result<(Vec, String)> { + let result = std::panic::catch_unwind(|| biome_grit_parser::parse_grit(source)); + + let Ok(parsed) = result else { + return Err(anyhow!("Syntax error in grit code, parsing failed")); + }; + + let options = GritFormatOptions::default(); + let doc = biome_grit_formatter::format_node(options, &parsed.syntax()) + .with_context(|| "biome couldn't format")?; + let formatted = doc.print()?.into_code(); + Ok((vec![], formatted)) } -/// bubble clause that finds a grit pattern with name "\" in yaml and -/// replaces it's body to "\", `format_yaml_file` uses this pattern to replace -/// pattern bodies with formatted ones -const YAML_REPLACE_BODY_PATERN: &str = r#" +mod yaml { + use std::collections::BTreeMap; + + use anyhow::{anyhow, bail, Context, Result}; + use marzano_core::api::MatchResult; + use marzano_gritmodule::config::ResolvedGritDefinition; + use marzano_util::{rich_path::RichFile, runtime::ExecutionContext}; + + use crate::resolver::GritModuleResolver; + + use super::format_grit_code; + + /// bubble clause that finds a grit pattern with name "\" in yaml and + /// replaces it's body to "\", `format_yaml_file` uses this pattern to replace + /// pattern bodies with formatted ones + const YAML_REPLACE_BODY_PATERN: &str = r#" bubble file($body) where { $body <: contains block_mapping(items=$items) where { $items <: within `patterns: $_`, @@ -118,121 +233,100 @@ const YAML_REPLACE_BODY_PATERN: &str = r#" } "#; -/// format each pattern and use gritql pattern to match and rewrite -fn format_yaml_file(patterns: &[ResolvedGritDefinition], file_content: &str) -> Result { - let bubbles = patterns - .iter() - .map(|pattern| { - let formatted_body = format_grit_code(&pattern.body) - .with_context(|| format!("could not format '{}'", pattern.name()))?; - let bubble = YAML_REPLACE_BODY_PATERN - .replace("", pattern.name()) - .replace("", &format_yaml_body_code(&formatted_body)); - Ok(bubble) - }) - .collect::>>()? - .join(",\n"); - let pattern_body = format!("language yaml\nsequential{{ {bubbles} }}"); - apply_grit_rewrite(file_content, &pattern_body) -} + /// format each pattern and use gritql pattern to match and rewrite + pub(crate) fn apply_yaml_rewrites( + patterns: &[ResolvedGritDefinition], + file_content: &str, + ) -> Result<(Vec, String)> { + let mut results = vec![]; + let bubbles = patterns + .iter() + .map(|pattern| { + let (this_results, formatted_body) = format_grit_code(&pattern.body) + .with_context(|| format!("could not format '{}'", pattern.name()))?; + results.extend(this_results); + let bubble = YAML_REPLACE_BODY_PATERN + .replace("", pattern.name()) + .replace("", &format_yaml_body_code(&formatted_body)); + Ok(bubble) + }) + .collect::>>()? + .join(",\n"); + let pattern_body = format!("language yaml\nsequential{{ {bubbles} }}"); + let rewritten = apply_grit_rewrite(file_content, &pattern_body)?; + Ok((results, rewritten)) + } -fn format_yaml_body_code(input: &str) -> String { - // yaml body still needs two indentation to look good - let body_with_prefix = prefix_lines(input, &" ".repeat(2)); - let escaped_body = body_with_prefix.replace("\"", "\\\""); - // body: | - // escaped_body - format!("|\n{escaped_body}") -} + fn format_yaml_body_code(input: &str) -> String { + // yaml body still needs two indentation to look good + let body_with_prefix = prefix_lines(input, &" ".repeat(2)); + let escaped_body = body_with_prefix.replace("\"", "\\\""); + // body: | + // escaped_body + format!("|\n{escaped_body}") + } -fn prefix_lines(input: &str, prefix: &str) -> String { - input - .lines() - .map(|line| { - if line.is_empty() { - line.to_owned() - } else { - format!("{prefix}{line}") - } - }) - .collect::>() - .join("\n") -} + fn prefix_lines(input: &str, prefix: &str) -> String { + input + .lines() + .map(|line| { + if line.is_empty() { + line.to_owned() + } else { + format!("{prefix}{line}") + } + }) + .collect::>() + .join("\n") + } + + fn apply_grit_rewrite(input: &str, pattern: &str) -> Result { + let resolver = GritModuleResolver::new(); + let rich_pattern = resolver.make_pattern(pattern, None)?; + + let compiled = rich_pattern + .compile(&BTreeMap::new(), None, None, None) + .map(|cr| cr.problem) + .with_context(|| "could not compile pattern")?; -fn apply_grit_rewrite(input: &str, pattern: &str) -> Result { - let resolver = GritModuleResolver::new(); - let rich_pattern = resolver.make_pattern(pattern, None)?; - - let compiled = rich_pattern - .compile(&BTreeMap::new(), None, None, None) - .map(|cr| cr.problem) - .with_context(|| "could not compile pattern")?; - - let rich_file = RichFile::new(String::new(), input.to_owned()); - let runtime = ExecutionContext::default(); - for result in compiled.execute_file(&rich_file, &runtime) { - if let MatchResult::Rewrite(rewrite) = result { - let content = rewrite - .rewritten - .content - .ok_or_else(|| anyhow!("rewritten content is empty"))?; - return Ok(content); + let rich_file = RichFile::new(String::new(), input.to_owned()); + let runtime = ExecutionContext::default(); + for result in compiled.execute_file(&rich_file, &runtime) { + if let MatchResult::Rewrite(rewrite) = result { + let content = rewrite + .rewritten + .content + .ok_or_else(|| anyhow!("rewritten content is empty"))?; + return Ok(content); + } } + bail!("no rewrite result after applying grit pattern") } - bail!("no rewrite result after applying grit pattern") } -fn format_pattern_as_hunk_changes(pattern: &ResolvedGritDefinition) -> Result { - let formatted_grit_code = format_grit_code(&pattern.body)?; - let body_range = pattern - .config - .range - .ok_or_else(|| anyhow!("pattern doesn't have config range"))?; - Ok(HunkChange { - starting_byte: body_range.start_byte as usize, - ending_byte: body_range.end_byte as usize, - new_content: formatted_grit_code, - }) -} +#[cfg(test)] +mod tests { + use super::*; + use std::path::PathBuf; -/// format grit code using `biome` -fn format_grit_code(source: &str) -> Result { - let parsed = biome_grit_parser::parse_grit(source); - ensure!( - parsed.diagnostics().is_empty(), - "biome couldn't parse: {}", - parsed - .diagnostics() - .iter() - .map(|diag| diag.message.to_string()) - .collect::>() - .join("\n") - ); + #[tokio::test] + async fn test_format_go_imports() -> Result<()> { + // This somehow has a massive memory leak but only in --release mode + // See https://github.com/biomejs/biome/issues/5032 - let options = GritFormatOptions::default(); - let doc = biome_grit_formatter::format_node(options, &parsed.syntax()) - .with_context(|| "biome couldn't format")?; - Ok(doc.print()?.into_code()) -} + let fixtures_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .join("cli_bin") + .join("fixtures") + .join("go") + .join("imports.grit"); -/// Represent a hunk of text that needs to be changed -#[derive(Debug)] -struct HunkChange { - starting_byte: usize, - ending_byte: usize, - new_content: String, -} + let input = std::fs::read_to_string(&fixtures_path)?; + let result = format_grit_code(&input); -/// returns a new string that applies hunk changes -fn apply_hunk_changes(input: &str, mut hunks: Vec) -> String { - if hunks.is_empty() { - return input.to_string(); - } - hunks.sort_by_key(|hunk| -(hunk.starting_byte as isize)); - let mut buffer = input.to_owned(); - for hunk in hunks { - let hunk_range = hunk.starting_byte..hunk.ending_byte; - buffer.replace_range(hunk_range, &hunk.new_content); + println!("done: {:?}", result); + + Ok(()) } - buffer } diff --git a/crates/cli/src/commands/mod.rs b/crates/cli/src/commands/mod.rs index 845bcddd5..228d5d4b7 100644 --- a/crates/cli/src/commands/mod.rs +++ b/crates/cli/src/commands/mod.rs @@ -83,7 +83,7 @@ use check::CheckArg; use clap::Parser; use clap::Subcommand; use doctor::DoctorArgs; -use format::{run_format, FormatArgs}; +use format::{run_format, FormatGritArgs}; use indicatif::MultiProgress; use indicatif_log_bridge::LogWrapper; use init::InitArgs; @@ -174,7 +174,7 @@ pub enum Commands { /// Display version information about the CLI and agents Version(VersionArgs), /// Format grit files under current directory - Format(FormatArgs), + Format(FormatGritArgs), /// Generate documentation for the Grit CLI (internal use only) #[cfg(feature = "docgen")] #[clap(hide = true)] @@ -463,7 +463,7 @@ async fn run_command(_use_tracing: bool) -> Result<()> { run_plumbing(arg, multi, &mut apply_details, app.format_flags).await } Commands::Version(arg) => run_version(arg).await, - Commands::Format(arg) => run_format(&arg).await, + Commands::Format(arg) => run_format(&arg, &app.format_flags).await, #[cfg(feature = "docgen")] Commands::Docgen(arg) => run_docgen(arg).await, #[cfg(feature = "server")] diff --git a/crates/cli/src/commands/patterns_test.rs b/crates/cli/src/commands/patterns_test.rs index 40bf0a8c6..395f94ad4 100644 --- a/crates/cli/src/commands/patterns_test.rs +++ b/crates/cli/src/commands/patterns_test.rs @@ -4,7 +4,7 @@ use log::{debug, info}; use marzano_core::analysis::get_dependents_of_target_patterns_by_traversal_from_src; use marzano_core::api::MatchResult; -use marzano_gritmodule::config::{GritPatternSample, GritPatternTestInfo}; +use marzano_gritmodule::config::{GritPatternSample, GritPatternTestInfo, ResolvedGritDefinition}; use marzano_gritmodule::formatting::format_rich_files; use marzano_gritmodule::markdown::replace_sample_in_md_file; use marzano_gritmodule::patterns_directory::PatternsDirectory; @@ -255,6 +255,17 @@ pub async fn get_marzano_pattern_test_results( Ok(AggregatedTestResult::AllPassed) } +pub(crate) fn filter_patterns_by_regex( + patterns: Vec, + filter: &str, +) -> Result> { + let regex = regex::Regex::new(filter)?; + Ok(patterns + .into_iter() + .filter(|p| regex.is_match(&p.local_name)) + .collect()) +} + pub(crate) async fn run_patterns_test( arg: PatternsTestArgs, flags: GlobalFormatFlags, @@ -262,13 +273,8 @@ pub(crate) async fn run_patterns_test( let (mut patterns, _) = resolve_from_cwd(&Source::Local).await?; let libs = get_grit_files_from_flags_or_cwd(&flags).await?; - if arg.filter.is_some() { - let filter = arg.filter.as_ref().unwrap(); - let regex = regex::Regex::new(filter)?; - patterns = patterns - .into_iter() - .filter(|p| regex.is_match(&p.local_name)) - .collect::>() + if let Some(filter) = &arg.filter { + patterns = filter_patterns_by_regex(patterns, filter)?; } if !arg.exclude.is_empty() { diff --git a/crates/cli/src/result_formatting.rs b/crates/cli/src/result_formatting.rs index b4ec672ff..72d0f8062 100644 --- a/crates/cli/src/result_formatting.rs +++ b/crates/cli/src/result_formatting.rs @@ -57,15 +57,20 @@ impl FormattedResult { fn print_file_ranges(item: &mut T, f: &mut fmt::Formatter<'_>) -> fmt::Result { let name = item.file_name().bold(); - for range in item.ranges() { - writeln!( - f, - "{}:{}:{} - {}", - name, - range.start.line, - range.start.column, - T::action() - )?; + if item.ranges().is_empty() { + // Note we only print the file name if there are no ranges, the newline is handled by the caller + write!(f, "{}", name)?; + } else { + for range in item.ranges() { + writeln!( + f, + "{}:{}:{} - {}", + name, + range.start.line, + range.start.column, + T::action() + )?; + } } Ok(()) } diff --git a/crates/cli_bin/fixtures/go/imports.grit b/crates/cli_bin/fixtures/go/imports.grit new file mode 100644 index 000000000..922b19509 --- /dev/null +++ b/crates/cli_bin/fixtures/go/imports.grit @@ -0,0 +1,18 @@ +language go + +// All core stdlib functions can be done here +private pattern before_each_file_stdlib() { + before_each_file_prep_imports() +} + +private pattern after_each_file_stdlib() { + after_each_file_handle_imports() +} + +pattern before_each_file() { + before_each_file_stdlib() +} + +pattern after_each_file() { + after_each_file_stdlib() +} diff --git a/crates/cli_bin/tests/format.rs b/crates/cli_bin/tests/format.rs index 67081ad4a..155d6a278 100644 --- a/crates/cli_bin/tests/format.rs +++ b/crates/cli_bin/tests/format.rs @@ -18,12 +18,11 @@ fn format_patterns_with_rewrite() -> Result<()> { println!("stderr: {}", String::from_utf8(output.stderr.clone())?); println!("stdout: {}", String::from_utf8(output.stdout.clone())?); - assert!(output.stdout.is_empty()); assert!( output.status.success(), "Command didn't finish successfully" ); - assert_eq!(output.stderr, b"couldn't format '.grit/patterns/not_parsable.grit': biome couldn't parse: Expected a predicate here.\n"); + assert!(output.stderr.is_empty()); let yaml_file_content = std::fs::read_to_string(grit_dir.join(".grit/grit.yaml"))?; let test_move_import_file_content = diff --git a/crates/core/src/api.rs b/crates/core/src/api.rs index 153d2024c..1558e1fa7 100644 --- a/crates/core/src/api.rs +++ b/crates/core/src/api.rs @@ -510,6 +510,15 @@ impl Rewrite { let rewritten = EntireFile::from_file(rewritten_file)?; Ok(Rewrite::new(original, rewritten, None)) } + + pub fn for_file(path: &str, old_content: &str, new_content: &str) -> Self { + Self { + original: EntireFile::file_to_entire_file(path, old_content, None), + rewritten: EntireFile::file_to_entire_file(path, new_content, None), + reason: None, + id: Uuid::new_v4(), + } + } } impl FileMatchResult for Rewrite {