Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/oxc_formatter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ unicode-width = { workspace = true }

[dev-dependencies]
insta = { workspace = true }
oxc_tasks_common = { path = "../../tasks/common" }
pico-args = { workspace = true }
serde_json = { workspace = true }
serde = { workspace = true, features = ["derive"] }
Expand Down
17 changes: 14 additions & 3 deletions crates/oxc_formatter/examples/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
//! ```bash
//! cargo run -p oxc_formatter --example formatter [filename]
//! cargo run -p oxc_formatter --example formatter -- --no-semi [filename]
//! cargo run -p oxc_formatter --example formatter -- --diff [filename]
//! ```

use std::{fs, path::Path};
Expand All @@ -19,13 +20,15 @@ use oxc_formatter::{
};
use oxc_parser::Parser;
use oxc_span::SourceType;
use oxc_tasks_common::print_diff_in_terminal;
use pico_args::Arguments;

/// Format a JavaScript or TypeScript file
fn main() -> Result<(), String> {
let mut args = Arguments::from_env();
let no_semi = args.contains("--no-semi");
let show_ir = args.contains("--ir");
let show_diff = args.contains("--diff");
let print_width = args.opt_value_from_str::<&'static str, u16>("--print-width").unwrap_or(None);
let name = args.free_from_str().unwrap_or_else(|_| "test.js".to_string());

Expand Down Expand Up @@ -68,9 +71,17 @@ fn main() -> Result<(), String> {
println!("--- End IR ---\n");
}

println!("--- Formatted Code ---");
let code = formatted.print().map_err(|e| e.to_string())?.into_code();
println!("{code}");
println!("--- End Formatted Code ---");

if show_diff {
println!("--- Diff ---");
print_diff_in_terminal(&source_text, &code);
println!("--- End Diff ---");
} else {
println!("--- Formatted Code ---");
println!("{code}");
println!("--- End Formatted Code ---");
}

Ok(())
}
Original file line number Diff line number Diff line change
Expand Up @@ -857,7 +857,7 @@ mod test {
use oxc_parser::Parser;
use oxc_semantic::SemanticBuilder;
use oxc_span::SourceType;
use oxc_tasks_common::print_diff_in_terminal;
use oxc_tasks_common::print_text_diff;
use oxc_transformer::{JsxRuntime, TransformOptions, Transformer};

use super::ModuleRunnerTransform;
Expand Down Expand Up @@ -921,7 +921,7 @@ mod test {
let result = transform(source_text, false).unwrap().code;
if result != expected {
let diff = TextDiff::from_lines(&expected, &result);
print_diff_in_terminal(&diff);
print_text_diff(&diff);
panic!("Expected code does not match the result");
}
}
Expand All @@ -932,7 +932,7 @@ mod test {
let result = transform(source_text, true).unwrap().code;
if result != expected {
let diff = TextDiff::from_lines(&expected, &result);
print_diff_in_terminal(&diff);
print_text_diff(&diff);
panic!("Expected code does not match the result");
}
}
Expand All @@ -944,7 +944,7 @@ mod test {
transform(source_text, false).unwrap();
if code != expected {
let diff = TextDiff::from_lines(&expected, &code);
print_diff_in_terminal(&diff);
print_text_diff(&diff);
panic!("Expected code does not match the result");
}
for dep in deps {
Expand Down
105 changes: 92 additions & 13 deletions tasks/common/src/diff.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,98 @@
use std::collections::VecDeque;

use console::Style;
use similar::{ChangeTag, DiffableStr, TextDiff};

pub fn print_diff_in_terminal<T>(diff: &TextDiff<T>)
where
T: DiffableStr + ?Sized,
{
for op in diff.ops() {
for change in diff.iter_changes(op) {
let (sign, style) = match change.tag() {
ChangeTag::Delete => ("-", Style::new().red()),
ChangeTag::Insert => ("+", Style::new().green()),
// ChangeTag::Equal => (" ", Style::new()),
ChangeTag::Equal => continue,
};
print!("{}{}", style.apply_to(sign).bold(), style.apply_to(change));
const CONTEXT_LINES: usize = 3;

/// Prints a colored diff to the terminal with context lines around changes.
///
/// Features:
/// - 3 lines of context above and below each change
/// - Line numbers in a gutter with `│` separator
/// - Blank line between adjacent hunks
/// - `...` separator when lines are skipped between hunks
/// - Colors: red (`-`) for deletions, green (`+`) for insertions, dim for context
///
/// Example output:
/// ```text
/// 1 │fn main() {
/// 2 │ let x = 1;
/// - 3 │ let y = 2;
/// + 3 │ let y = 3;
/// 4 │ println!("{}", x + y);
/// 5 │}
/// ...
/// 42 │fn other() {
/// - 43 │ old_code();
/// + 43 │ new_code();
/// 44 │}
/// ```
pub fn print_diff_in_terminal(expected: &str, result: &str) {
let diff = TextDiff::from_lines(expected, result);
print_text_diff(&diff);
}

/// Prints an existing `TextDiff` to the terminal.
/// Use this when you need access to the `TextDiff` for other operations (e.g., `diff.ratio()`).
pub fn print_text_diff<T: DiffableStr + ?Sized>(diff: &TextDiff<T>) {
let mut context_buffer: VecDeque<_> = VecDeque::with_capacity(CONTEXT_LINES);
let mut trailing_remaining = 0;
let mut has_printed = false;
let mut had_gap = false;
let mut needs_separator = false;

for change in diff.ops().iter().flat_map(|op| diff.iter_changes(op)) {
let tag = change.tag();

if tag == ChangeTag::Equal {
if trailing_remaining > 0 {
print_line(tag, change.new_index(), &change.to_string());
trailing_remaining -= 1;
needs_separator = true;
} else {
if context_buffer.len() == CONTEXT_LINES {
context_buffer.pop_front();
had_gap = true;
}
context_buffer.push_back((tag, change.new_index(), change.to_string()));
}
continue;
}

if has_printed && (needs_separator || !context_buffer.is_empty()) {
if had_gap {
println!("{}", Style::new().cyan().apply_to("..."));
} else {
println!();
}
}

while let Some((tag, line_num, content)) = context_buffer.pop_front() {
print_line(tag, line_num, &content);
}

let line_num =
if tag == ChangeTag::Delete { change.old_index() } else { change.new_index() };
print_line(tag, line_num, &change.to_string());

has_printed = true;
had_gap = false;
needs_separator = false;
trailing_remaining = CONTEXT_LINES;
}
}

fn print_line(tag: ChangeTag, line_num: Option<usize>, content: &str) {
let (sign, style) = match tag {
ChangeTag::Delete => ("-", Style::new().red()),
ChangeTag::Insert => ("+", Style::new().green()),
ChangeTag::Equal => (" ", Style::new().dim()),
};
print!(
"{}{} │{}",
style.apply_to(sign).bold(),
Style::new().dim().apply_to(format!("{:>4}", line_num.map_or(0, |n| n + 1))),
style.apply_to(content)
);
}
2 changes: 1 addition & 1 deletion tasks/common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ mod request;
mod snapshot;
mod test_file;

pub use diff::print_diff_in_terminal;
pub use diff::{print_diff_in_terminal, print_text_diff};

pub use crate::{request::agent, snapshot::Snapshot, test_file::*};

Expand Down
2 changes: 1 addition & 1 deletion tasks/prettier_conformance/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ impl TestRunner {
"{}",
path.strip_prefix(fixtures_root()).unwrap().to_string_lossy()
);
oxc_tasks_common::print_diff_in_terminal(&diff);
oxc_tasks_common::print_text_diff(&diff);
}
println!();
}
Expand Down
6 changes: 3 additions & 3 deletions tasks/transform_conformance/src/test_case.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use oxc::{
span::{SourceType, VALID_EXTENSIONS},
transformer::{BabelOptions, HelperLoaderMode, TransformOptions},
};
use oxc_tasks_common::{normalize_path, print_diff_in_terminal, project_root};
use oxc_tasks_common::{normalize_path, print_text_diff, project_root};

use crate::{
TestRunnerOptions,
Expand Down Expand Up @@ -363,7 +363,7 @@ impl TestCase {
if !passed {
let diff = TextDiff::from_lines(&output, actual_errors);
println!("Diff:\n");
print_diff_in_terminal(&diff);
print_text_diff(&diff);
}
}
} else {
Expand All @@ -378,7 +378,7 @@ impl TestCase {
if !passed {
let diff = TextDiff::from_lines(&output, &self.transformed_code);
println!("Diff:\n");
print_diff_in_terminal(&diff);
print_text_diff(&diff);
}
}

Expand Down
Loading