diff --git a/Cargo.lock b/Cargo.lock index 29ff6ddb2a..f26d6e7e2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -445,6 +445,7 @@ dependencies = [ "deno_core", "deno_typescript", "dirs", + "dissimilar", "dlopen", "dprint-plugin-typescript", "futures 0.3.5", @@ -553,6 +554,12 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "dissimilar" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39de161cd2ebbd6e5783db53a82a47b6a47dcfef754130839603561745528b94" + [[package]] name = "dlopen" version = "0.1.8" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index d6176bcf60..81eb9d7fbf 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -28,6 +28,7 @@ bytes = "0.5.4" byteorder = "1.3.4" clap = "2.33.1" dirs = "2.0.2" +dissimilar = "1.0" dlopen = "0.1.8" dprint-plugin-typescript = "0.19.2" futures = { version = "0.3.5", features = ["compat", "io-compat"] } diff --git a/cli/colors.rs b/cli/colors.rs index 764704a5b8..b9a5a7353e 100644 --- a/cli/colors.rs +++ b/cli/colors.rs @@ -52,6 +52,12 @@ pub fn red_bold(s: String) -> impl fmt::Display { style(&s, style_spec) } +pub fn green_bold(s: String) -> impl fmt::Display { + let mut style_spec = ColorSpec::new(); + style_spec.set_fg(Some(Ansi256(10))).set_bold(true); + style(&s, style_spec) +} + pub fn italic_bold(s: String) -> impl fmt::Display { let mut style_spec = ColorSpec::new(); style_spec.set_bold(true).set_italic(true); @@ -64,6 +70,18 @@ pub fn black_on_white(s: String) -> impl fmt::Display { style(&s, style_spec) } +pub fn white_on_red(s: String) -> impl fmt::Display { + let mut style_spec = ColorSpec::new(); + style_spec.set_bg(Some(Red)).set_fg(Some(White)); + style(&s, style_spec) +} + +pub fn white_on_green(s: String) -> impl fmt::Display { + let mut style_spec = ColorSpec::new(); + style_spec.set_bg(Some(Ansi256(10))).set_fg(Some(White)); + style(&s, style_spec) +} + pub fn yellow(s: String) -> impl fmt::Display { let mut style_spec = ColorSpec::new(); style_spec.set_fg(Some(Ansi256(11))); diff --git a/cli/diff.rs b/cli/diff.rs new file mode 100644 index 0000000000..e6d7fa4bb0 --- /dev/null +++ b/cli/diff.rs @@ -0,0 +1,171 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +use crate::colors; +use dissimilar::{diff as difference, Chunk}; +use std::fmt; +use std::fmt::Write; + +fn fmt_add() -> String { + format!("{}", colors::green_bold("+".to_string())) +} + +fn fmt_add_text(x: String) -> String { + format!("{}", colors::green(x)) +} + +fn fmt_add_text_highlight(x: String) -> String { + format!("{}", colors::white_on_green(x)) +} + +fn fmt_rem() -> String { + format!("{}", colors::red_bold("-".to_string())) +} + +fn fmt_rem_text(x: String) -> String { + format!("{}", colors::red(x)) +} + +fn fmt_rem_text_highlight(x: String) -> String { + format!("{}", colors::white_on_red(x)) +} + +fn write_line_diff( + diff: &mut String, + orig_line: &mut usize, + edit_line: &mut usize, + line_number_width: usize, + orig: &mut String, + edit: &mut String, +) -> fmt::Result { + let split = orig.split('\n').enumerate(); + for (i, s) in split { + write!( + diff, + "{:0width$}{} ", + *orig_line + i, + colors::gray("|".to_string()), + width = line_number_width + )?; + write!(diff, "{}", fmt_rem())?; + write!(diff, "{}", s)?; + writeln!(diff)?; + } + + let split = edit.split('\n').enumerate(); + for (i, s) in split { + write!( + diff, + "{:0width$}{} ", + *edit_line + i, + colors::gray("|".to_string()), + width = line_number_width + )?; + write!(diff, "{}", fmt_add())?; + write!(diff, "{}", s)?; + writeln!(diff)?; + } + + *orig_line += orig.split('\n').count(); + *edit_line += edit.split('\n').count(); + + orig.clear(); + edit.clear(); + + Ok(()) +} + +/// Print diff of the same file_path, before and after formatting. +/// +/// Diff format is loosely based on Github diff formatting. +pub fn diff(orig_text: &str, edit_text: &str) -> Result { + let lines = edit_text.split('\n').count(); + let line_number_width = lines.to_string().chars().count(); + + let mut diff = String::new(); + + let mut text1 = orig_text.to_string(); + let mut text2 = edit_text.to_string(); + + if !text1.ends_with('\n') { + writeln!(text1)?; + } + if !text2.ends_with('\n') { + writeln!(text2)?; + } + + let mut orig_line: usize = 1; + let mut edit_line: usize = 1; + let mut orig: String = String::new(); + let mut edit: String = String::new(); + let mut changes = false; + + let chunks = difference(&text1, &text2); + for chunk in chunks { + match chunk { + Chunk::Delete(s) => { + let split = s.split('\n').enumerate(); + for (i, s) in split { + if i > 0 { + orig.push_str("\n"); + } + orig.push_str(&fmt_rem_text_highlight(s.to_string())); + } + changes = true + } + Chunk::Insert(s) => { + let split = s.split('\n').enumerate(); + for (i, s) in split { + if i > 0 { + edit.push_str("\n"); + } + edit.push_str(&fmt_add_text_highlight(s.to_string())); + } + changes = true + } + Chunk::Equal(s) => { + let split = s.split('\n').enumerate(); + for (i, s) in split { + if i > 0 { + if changes { + write_line_diff( + &mut diff, + &mut orig_line, + &mut edit_line, + line_number_width, + &mut orig, + &mut edit, + )?; + changes = false + } else { + orig.clear(); + edit.clear(); + orig_line += 1; + edit_line += 1; + } + } + orig.push_str(&fmt_rem_text(s.to_string())); + edit.push_str(&fmt_add_text(s.to_string())); + } + } + } + } + Ok(diff) +} + +#[test] +fn test_diff() { + let simple_console_log_unfmt = "console.log('Hello World')"; + let simple_console_log_fmt = "console.log(\"Hello World\");"; + assert_eq!( + colors::strip_ansi_codes( + &diff(simple_console_log_unfmt, simple_console_log_fmt).unwrap() + ), + "1| -console.log('Hello World')\n1| +console.log(\"Hello World\");\n" + ); + + let line_number_unfmt = "\n\n\n\nconsole.log(\n'Hello World'\n)"; + let line_number_fmt = "console.log(\n\"Hello World\"\n);"; + assert_eq!( + colors::strip_ansi_codes(&diff(line_number_unfmt, line_number_fmt).unwrap()), + "1| -\n2| -\n3| -\n4| -\n5| -console.log(\n1| +console.log(\n6| -'Hello World'\n2| +\"Hello World\"\n7| -)\n3| +);\n" + ) +} diff --git a/cli/fmt.rs b/cli/fmt.rs index 5d1becffa5..0d7a909bc0 100644 --- a/cli/fmt.rs +++ b/cli/fmt.rs @@ -7,6 +7,8 @@ //! the future it can be easily extended to provide //! the same functions as ops available in JS runtime. +use crate::colors; +use crate::diff::diff; use crate::fs::files_in_subtree; use crate::op_error::OpError; use deno_core::ErrBox; @@ -63,7 +65,9 @@ async fn check_source_files( ) -> Result<(), ErrBox> { let not_formatted_files_count = Arc::new(AtomicUsize::new(0)); let formatter = Arc::new(dprint::Formatter::new(config)); - let output_lock = Arc::new(Mutex::new(0)); // prevent threads outputting at the same time + + // prevent threads outputting at the same time + let output_lock = Arc::new(Mutex::new(0)); run_parallelized(paths, { let not_formatted_files_count = not_formatted_files_count.clone(); @@ -74,6 +78,25 @@ async fn check_source_files( Ok(formatted_text) => { if formatted_text != file_text { not_formatted_files_count.fetch_add(1, Ordering::SeqCst); + let _g = output_lock.lock().unwrap(); + match diff(&file_text, &formatted_text) { + Ok(diff) => { + println!(); + println!( + "{} {}:", + colors::bold("from".to_string()), + file_path.display().to_string() + ); + println!("{}", diff); + } + Err(e) => { + eprintln!( + "Error generating diff: {}", + file_path.to_string_lossy() + ); + eprintln!(" {}", e); + } + } } } Err(e) => { diff --git a/cli/main.rs b/cli/main.rs index 7da0e2df36..cf819ce5f8 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -1,6 +1,7 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. #![deny(warnings)] +extern crate dissimilar; #[macro_use] extern crate lazy_static; #[macro_use] @@ -25,6 +26,7 @@ mod checksum; pub mod colors; pub mod deno_dir; pub mod diagnostics; +mod diff; mod disk_cache; mod doc; mod file_fetcher;