mirror of
https://github.com/rust-lang/rust.git
synced 2026-05-15 12:39:31 +03:00
ae59ee755a
The version-sort algorithm makes changes to the sort order of imports, and we're updating them here for the self tests.
400 lines
13 KiB
Rust
400 lines
13 KiB
Rust
use std::collections::VecDeque;
|
|
use std::fmt;
|
|
use std::io;
|
|
use std::io::Write;
|
|
|
|
use crate::config::{Color, Config, Verbosity};
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub(crate) enum DiffLine {
|
|
Context(String),
|
|
Expected(String),
|
|
Resulting(String),
|
|
}
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub(crate) struct Mismatch {
|
|
/// The line number in the formatted version.
|
|
pub(crate) line_number: u32,
|
|
/// The line number in the original version.
|
|
pub(crate) line_number_orig: u32,
|
|
/// The set of lines (context and old/new) in the mismatch.
|
|
pub(crate) lines: Vec<DiffLine>,
|
|
}
|
|
|
|
impl Mismatch {
|
|
fn new(line_number: u32, line_number_orig: u32) -> Mismatch {
|
|
Mismatch {
|
|
line_number,
|
|
line_number_orig,
|
|
lines: Vec::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A single span of changed lines, with 0 or more removed lines
|
|
/// and a vector of 0 or more inserted lines.
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub struct ModifiedChunk {
|
|
/// The first to be removed from the original text
|
|
pub line_number_orig: u32,
|
|
/// The number of lines which have been replaced
|
|
pub lines_removed: u32,
|
|
/// The new lines
|
|
pub lines: Vec<String>,
|
|
}
|
|
|
|
/// Set of changed sections of a file.
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub struct ModifiedLines {
|
|
/// The set of changed chunks.
|
|
pub chunks: Vec<ModifiedChunk>,
|
|
}
|
|
|
|
impl From<Vec<Mismatch>> for ModifiedLines {
|
|
fn from(mismatches: Vec<Mismatch>) -> ModifiedLines {
|
|
let chunks = mismatches.into_iter().map(|mismatch| {
|
|
let lines = mismatch.lines.iter();
|
|
let num_removed = lines
|
|
.filter(|line| matches!(line, DiffLine::Resulting(_)))
|
|
.count();
|
|
|
|
let new_lines = mismatch.lines.into_iter().filter_map(|line| match line {
|
|
DiffLine::Context(_) | DiffLine::Resulting(_) => None,
|
|
DiffLine::Expected(str) => Some(str),
|
|
});
|
|
|
|
ModifiedChunk {
|
|
line_number_orig: mismatch.line_number_orig,
|
|
lines_removed: num_removed as u32,
|
|
lines: new_lines.collect(),
|
|
}
|
|
});
|
|
|
|
ModifiedLines {
|
|
chunks: chunks.collect(),
|
|
}
|
|
}
|
|
}
|
|
|
|
// Converts a `Mismatch` into a serialized form, which just includes
|
|
// enough information to modify the original file.
|
|
// Each section starts with a line with three integers, space separated:
|
|
// lineno num_removed num_added
|
|
// followed by (`num_added`) lines of added text. The line numbers are
|
|
// relative to the original file.
|
|
impl fmt::Display for ModifiedLines {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
for chunk in &self.chunks {
|
|
writeln!(
|
|
f,
|
|
"{} {} {}",
|
|
chunk.line_number_orig,
|
|
chunk.lines_removed,
|
|
chunk.lines.len()
|
|
)?;
|
|
|
|
for line in &chunk.lines {
|
|
writeln!(f, "{line}")?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
// Allows to convert `Display`ed `ModifiedLines` back to the structural data.
|
|
impl std::str::FromStr for ModifiedLines {
|
|
type Err = ();
|
|
|
|
fn from_str(s: &str) -> Result<ModifiedLines, ()> {
|
|
let mut chunks = vec![];
|
|
|
|
let mut lines = s.lines();
|
|
while let Some(header) = lines.next() {
|
|
let mut header = header.split_whitespace();
|
|
let (orig, rem, new_lines) = match (header.next(), header.next(), header.next()) {
|
|
(Some(orig), Some(removed), Some(added)) => (orig, removed, added),
|
|
_ => return Err(()),
|
|
};
|
|
let (orig, rem, new_lines): (u32, u32, usize) =
|
|
match (orig.parse(), rem.parse(), new_lines.parse()) {
|
|
(Ok(a), Ok(b), Ok(c)) => (a, b, c),
|
|
_ => return Err(()),
|
|
};
|
|
let lines = lines.by_ref().take(new_lines);
|
|
let lines: Vec<_> = lines.map(ToOwned::to_owned).collect();
|
|
if lines.len() != new_lines {
|
|
return Err(());
|
|
}
|
|
|
|
chunks.push(ModifiedChunk {
|
|
line_number_orig: orig,
|
|
lines_removed: rem,
|
|
lines,
|
|
});
|
|
}
|
|
|
|
Ok(ModifiedLines { chunks })
|
|
}
|
|
}
|
|
|
|
// This struct handles writing output to stdout and abstracts away the logic
|
|
// of printing in color, if it's possible in the executing environment.
|
|
pub(crate) struct OutputWriter {
|
|
terminal: Option<Box<dyn term::Terminal<Output = io::Stdout>>>,
|
|
}
|
|
|
|
impl OutputWriter {
|
|
// Create a new OutputWriter instance based on the caller's preference
|
|
// for colorized output and the capabilities of the terminal.
|
|
pub(crate) fn new(color: Color) -> Self {
|
|
if let Some(t) = term::stdout() {
|
|
if color.use_colored_tty() && t.supports_color() {
|
|
return OutputWriter { terminal: Some(t) };
|
|
}
|
|
}
|
|
OutputWriter { terminal: None }
|
|
}
|
|
|
|
// Write output in the optionally specified color. The output is written
|
|
// in the specified color if this OutputWriter instance contains a
|
|
// Terminal in its `terminal` field.
|
|
pub(crate) fn writeln(&mut self, msg: &str, color: Option<term::color::Color>) {
|
|
match &mut self.terminal {
|
|
Some(ref mut t) => {
|
|
if let Some(color) = color {
|
|
t.fg(color).unwrap();
|
|
}
|
|
writeln!(t, "{msg}").unwrap();
|
|
if color.is_some() {
|
|
t.reset().unwrap();
|
|
}
|
|
}
|
|
None => println!("{msg}"),
|
|
}
|
|
}
|
|
}
|
|
|
|
// Produces a diff between the expected output and actual output of rustfmt.
|
|
pub(crate) fn make_diff(expected: &str, actual: &str, context_size: usize) -> Vec<Mismatch> {
|
|
let mut line_number = 1;
|
|
let mut line_number_orig = 1;
|
|
let mut context_queue: VecDeque<&str> = VecDeque::with_capacity(context_size);
|
|
let mut lines_since_mismatch = context_size + 1;
|
|
let mut results = Vec::new();
|
|
let mut mismatch = Mismatch::new(0, 0);
|
|
|
|
for result in diff::lines(expected, actual) {
|
|
match result {
|
|
diff::Result::Left(str) => {
|
|
if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
|
|
results.push(mismatch);
|
|
mismatch = Mismatch::new(
|
|
line_number - context_queue.len() as u32,
|
|
line_number_orig - context_queue.len() as u32,
|
|
);
|
|
}
|
|
|
|
while let Some(line) = context_queue.pop_front() {
|
|
mismatch.lines.push(DiffLine::Context(line.to_owned()));
|
|
}
|
|
|
|
mismatch.lines.push(DiffLine::Resulting(str.to_owned()));
|
|
line_number_orig += 1;
|
|
lines_since_mismatch = 0;
|
|
}
|
|
diff::Result::Right(str) => {
|
|
if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
|
|
results.push(mismatch);
|
|
mismatch = Mismatch::new(
|
|
line_number - context_queue.len() as u32,
|
|
line_number_orig - context_queue.len() as u32,
|
|
);
|
|
}
|
|
|
|
while let Some(line) = context_queue.pop_front() {
|
|
mismatch.lines.push(DiffLine::Context(line.to_owned()));
|
|
}
|
|
|
|
mismatch.lines.push(DiffLine::Expected(str.to_owned()));
|
|
line_number += 1;
|
|
lines_since_mismatch = 0;
|
|
}
|
|
diff::Result::Both(str, _) => {
|
|
if context_queue.len() >= context_size {
|
|
let _ = context_queue.pop_front();
|
|
}
|
|
|
|
if lines_since_mismatch < context_size {
|
|
mismatch.lines.push(DiffLine::Context(str.to_owned()));
|
|
} else if context_size > 0 {
|
|
context_queue.push_back(str);
|
|
}
|
|
|
|
line_number += 1;
|
|
line_number_orig += 1;
|
|
lines_since_mismatch += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
results.push(mismatch);
|
|
results.remove(0);
|
|
|
|
results
|
|
}
|
|
|
|
pub(crate) fn print_diff<F>(diff: Vec<Mismatch>, get_section_title: F, config: &Config)
|
|
where
|
|
F: Fn(u32) -> String,
|
|
{
|
|
let color = config.color();
|
|
let line_terminator = if config.verbose() == Verbosity::Verbose {
|
|
"⏎"
|
|
} else {
|
|
""
|
|
};
|
|
|
|
let mut writer = OutputWriter::new(color);
|
|
|
|
for mismatch in diff {
|
|
let title = get_section_title(mismatch.line_number_orig);
|
|
writer.writeln(&title, None);
|
|
|
|
for line in mismatch.lines {
|
|
match line {
|
|
DiffLine::Context(ref str) => {
|
|
writer.writeln(&format!(" {str}{line_terminator}"), None)
|
|
}
|
|
DiffLine::Expected(ref str) => writer.writeln(
|
|
&format!("+{str}{line_terminator}"),
|
|
Some(term::color::GREEN),
|
|
),
|
|
DiffLine::Resulting(ref str) => {
|
|
writer.writeln(&format!("-{str}{line_terminator}"), Some(term::color::RED))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::DiffLine::*;
|
|
use super::{Mismatch, make_diff};
|
|
use super::{ModifiedChunk, ModifiedLines};
|
|
|
|
#[test]
|
|
fn diff_simple() {
|
|
let src = "one\ntwo\nthree\nfour\nfive\n";
|
|
let dest = "one\ntwo\ntrois\nfour\nfive\n";
|
|
let diff = make_diff(src, dest, 1);
|
|
assert_eq!(
|
|
diff,
|
|
vec![Mismatch {
|
|
line_number: 2,
|
|
line_number_orig: 2,
|
|
lines: vec![
|
|
Context("two".to_owned()),
|
|
Resulting("three".to_owned()),
|
|
Expected("trois".to_owned()),
|
|
Context("four".to_owned()),
|
|
],
|
|
}]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn diff_simple2() {
|
|
let src = "one\ntwo\nthree\nfour\nfive\nsix\nseven\n";
|
|
let dest = "one\ntwo\ntrois\nfour\ncinq\nsix\nseven\n";
|
|
let diff = make_diff(src, dest, 1);
|
|
assert_eq!(
|
|
diff,
|
|
vec![
|
|
Mismatch {
|
|
line_number: 2,
|
|
line_number_orig: 2,
|
|
lines: vec![
|
|
Context("two".to_owned()),
|
|
Resulting("three".to_owned()),
|
|
Expected("trois".to_owned()),
|
|
Context("four".to_owned()),
|
|
],
|
|
},
|
|
Mismatch {
|
|
line_number: 5,
|
|
line_number_orig: 5,
|
|
lines: vec![
|
|
Resulting("five".to_owned()),
|
|
Expected("cinq".to_owned()),
|
|
Context("six".to_owned()),
|
|
],
|
|
},
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn diff_zerocontext() {
|
|
let src = "one\ntwo\nthree\nfour\nfive\n";
|
|
let dest = "one\ntwo\ntrois\nfour\nfive\n";
|
|
let diff = make_diff(src, dest, 0);
|
|
assert_eq!(
|
|
diff,
|
|
vec![Mismatch {
|
|
line_number: 3,
|
|
line_number_orig: 3,
|
|
lines: vec![Resulting("three".to_owned()), Expected("trois".to_owned())],
|
|
}]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn diff_trailing_newline() {
|
|
let src = "one\ntwo\nthree\nfour\nfive";
|
|
let dest = "one\ntwo\nthree\nfour\nfive\n";
|
|
let diff = make_diff(src, dest, 1);
|
|
assert_eq!(
|
|
diff,
|
|
vec![Mismatch {
|
|
line_number: 5,
|
|
line_number_orig: 5,
|
|
lines: vec![Context("five".to_owned()), Expected("".to_owned())],
|
|
}]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn modified_lines_from_str() {
|
|
use std::str::FromStr;
|
|
|
|
let src = "1 6 2\nfn some() {}\nfn main() {}\n25 3 1\n struct Test {}";
|
|
let lines = ModifiedLines::from_str(src).unwrap();
|
|
assert_eq!(
|
|
lines,
|
|
ModifiedLines {
|
|
chunks: vec![
|
|
ModifiedChunk {
|
|
line_number_orig: 1,
|
|
lines_removed: 6,
|
|
lines: vec!["fn some() {}".to_owned(), "fn main() {}".to_owned(),]
|
|
},
|
|
ModifiedChunk {
|
|
line_number_orig: 25,
|
|
lines_removed: 3,
|
|
lines: vec![" struct Test {}".to_owned()]
|
|
}
|
|
]
|
|
}
|
|
);
|
|
|
|
let src = "1 5 3";
|
|
assert_eq!(ModifiedLines::from_str(src), Err(()));
|
|
|
|
let src = "1 5 3\na\nb";
|
|
assert_eq!(ModifiedLines::from_str(src), Err(()));
|
|
}
|
|
}
|