Skip to content

Add a new "modified lines" write mode. #2086

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Feb 15, 2018
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
2 changes: 1 addition & 1 deletion rustfmt-config/src/config_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ macro_rules! create_config {

// Macro hygiene won't allow us to make `set_$i()` methods on Config
// for each item, so this struct is used to give the API to set values:
// `config.get().option(false)`. It's pretty ugly. Consider replacing
// `config.set().option(false)`. It's pretty ugly. Consider replacing
// with `config.set_option(false)` if we ever get a stable/usable
// `concat_idents!()`.
pub struct ConfigSetter<'a>(&'a mut Config);
Expand Down
2 changes: 2 additions & 0 deletions rustfmt-config/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ configuration_option_enum! { WriteMode:
Plain,
// Outputs a checkstyle XML file.
Checkstyle,
// Output the changed lines (for internal value only)
Modified,
}

configuration_option_enum! { Color:
Expand Down
11 changes: 10 additions & 1 deletion rustfmt-core/src/filemap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use std::path::Path;

use checkstyle::{output_checkstyle_file, output_footer, output_header};
use config::{Config, NewlineStyle, WriteMode};
use rustfmt_diff::{make_diff, print_diff, Mismatch};
use rustfmt_diff::{make_diff, output_modified, print_diff, Mismatch};
use syntax::codemap::FileName;

use FileRecord;
Expand Down Expand Up @@ -164,6 +164,15 @@ where
return Ok(has_diff);
}
}
WriteMode::Modified => {
let filename = filename_to_path();
if let Ok((ori, fmt)) = source_and_formatted_text(text, filename, config) {
let mismatch = make_diff(&ori, &fmt, 0);
let has_diff = !mismatch.is_empty();
output_modified(out, mismatch);
return Ok(has_diff);
}
}
WriteMode::Checkstyle => {
let filename = filename_to_path();
let diff = create_diff(filename, text, config)?;
Expand Down
75 changes: 74 additions & 1 deletion rustfmt-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ extern crate unicode_segmentation;

use std::collections::HashMap;
use std::fmt;
use std::io::{self, stdout, Write};
use std::io::{self, stdout, BufRead, Write};
use std::iter::repeat;
use std::path::PathBuf;
use std::rc::Rc;
Expand Down Expand Up @@ -704,6 +704,79 @@ pub fn format_input<T: Write>(
}
}

/// 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>,
}

/// The successful result of formatting via get_modified_lines().
pub struct ModifiedLinesResult {
/// The high level summary details
pub summary: Summary,
/// The result Filemap
pub filemap: FileMap,
/// Map of formatting errors
pub report: FormatReport,
/// The sets of updated lines.
pub modified_lines: ModifiedLines,
}

/// Format a file and return a `ModifiedLines` data structure describing
/// the changed ranges of lines.
pub fn get_modified_lines(
input: Input,
config: &Config,
) -> Result<ModifiedLinesResult, (io::Error, Summary)> {
let mut data = Vec::new();

let mut config = config.clone();
config.set().write_mode(config::WriteMode::Modified);
let (summary, filemap, formatreport) = format_input(input, &config, Some(&mut data))?;

let mut lines = data.lines();
let mut chunks = Vec::new();
while let Some(Ok(header)) = lines.next() {
// Parse the header line
let values: Vec<_> = header
.split(' ')
.map(|s| s.parse::<u32>().unwrap())
.collect();
assert_eq!(values.len(), 3);
let line_number_orig = values[0];
let lines_removed = values[1];
let num_added = values[2];
let mut added_lines = Vec::new();
for _ in 0..num_added {
added_lines.push(lines.next().unwrap().unwrap());
}
chunks.push(ModifiedChunk {
line_number_orig,
lines_removed,
lines: added_lines,
});
}
Ok(ModifiedLinesResult {
summary: summary,
filemap: filemap,
report: formatreport,
modified_lines: ModifiedLines { chunks },
})
}

#[derive(Debug)]
pub enum Input {
File(PathBuf),
Expand Down
64 changes: 60 additions & 4 deletions rustfmt-core/src/rustfmt_diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use diff;
use std::collections::VecDeque;
use std::io;
use term;
use std::io::Write;
use utils::use_colored_tty;

#[derive(Debug, PartialEq)]
Expand All @@ -24,14 +25,19 @@ pub enum DiffLine {

#[derive(Debug, PartialEq)]
pub struct Mismatch {
/// The line number in the formatted version.
pub line_number: u32,
/// The line number in the original version.
pub line_number_orig: u32,
/// The set of lines (context and old/new) in the mismatch.
pub lines: Vec<DiffLine>,
}

impl Mismatch {
fn new(line_number: u32) -> Mismatch {
fn new(line_number: u32, line_number_orig: u32) -> Mismatch {
Mismatch {
line_number,
line_number_orig,
lines: Vec::new(),
}
}
Expand Down Expand Up @@ -77,30 +83,38 @@ impl OutputWriter {
// Produces a diff between the expected output and actual output of rustfmt.
pub 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);
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);
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);
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() {
Expand All @@ -123,6 +137,7 @@ pub fn make_diff(expected: &str, actual: &str, context_size: usize) -> Vec<Misma
}

line_number += 1;
line_number_orig += 1;
lines_since_mismatch += 1;
}
}
Expand Down Expand Up @@ -158,6 +173,42 @@ where
}
}

/// Convert a Mismatch into a serialised 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.
pub fn output_modified<W>(mut out: W, diff: Vec<Mismatch>)
where
W: Write,
{
for mismatch in diff {
let (num_removed, num_added) = mismatch.lines.iter().fold((0, 0), |(rem, add), line| {
match *line {
DiffLine::Context(_) => panic!("No Context expected"),
DiffLine::Expected(_) => (rem, add + 1),
DiffLine::Resulting(_) => (rem + 1, add),
}
});
// Write a header with enough information to separate the modified lines.
writeln!(
out,
"{} {} {}",
mismatch.line_number_orig, num_removed, num_added
).unwrap();

for line in mismatch.lines {
match line {
DiffLine::Context(_) | DiffLine::Resulting(_) => (),
DiffLine::Expected(ref str) => {
writeln!(out, "{}", str).unwrap();
}
}
}
}
}

#[cfg(test)]
mod test {
use super::{make_diff, Mismatch};
Expand All @@ -173,6 +224,7 @@ mod test {
vec![
Mismatch {
line_number: 2,
line_number_orig: 2,
lines: vec![
Context("two".to_owned()),
Resulting("three".to_owned()),
Expand All @@ -194,6 +246,7 @@ mod test {
vec![
Mismatch {
line_number: 2,
line_number_orig: 2,
lines: vec![
Context("two".to_owned()),
Resulting("three".to_owned()),
Expand All @@ -203,6 +256,7 @@ mod test {
},
Mismatch {
line_number: 5,
line_number_orig: 5,
lines: vec![
Resulting("five".to_owned()),
Expected("cinq".to_owned()),
Expand All @@ -223,6 +277,7 @@ mod test {
vec![
Mismatch {
line_number: 3,
line_number_orig: 3,
lines: vec![Resulting("three".to_owned()), Expected("trois".to_owned())],
},
]
Expand All @@ -239,6 +294,7 @@ mod test {
vec![
Mismatch {
line_number: 5,
line_number_orig: 5,
lines: vec![Context("five".to_owned()), Expected("".to_owned())],
},
]
Expand Down
25 changes: 25 additions & 0 deletions rustfmt-core/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,30 @@ fn checkstyle_test() {
assert_output(Path::new(filename), Path::new(expected_filename));
}

#[test]
fn modified_test() {
// Test "modified" output
let filename = "tests/writemode/source/modified.rs";
let result = get_modified_lines(Input::File(filename.into()), &Config::default()).unwrap();
assert_eq!(
result.modified_lines,
ModifiedLines {
chunks: vec![
ModifiedChunk {
line_number_orig: 4,
lines_removed: 4,
lines: vec!["fn blah() {}".into()],
},
ModifiedChunk {
line_number_orig: 9,
lines_removed: 6,
lines: vec!["#[cfg(a, b)]".into(), "fn main() {}".into()],
},
],
}
);
}

// Helper function for comparing the results of rustfmt
// to a known output file generated by one of the write modes.
fn assert_output(source: &Path, expected_filename: &Path) {
Expand Down Expand Up @@ -529,6 +553,7 @@ fn rustfmt_diff_make_diff_tests() {
vec![
Mismatch {
line_number: 1,
line_number_orig: 1,
lines: vec![
DiffLine::Context("a".into()),
DiffLine::Resulting("b".into()),
Expand Down
14 changes: 14 additions & 0 deletions rustfmt-core/tests/writemode/source/modified.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// rustfmt-write_mode: modified
// Test "modified" output

fn
blah
()
{ }


#[cfg
( a , b
)]
fn
main() {}
5 changes: 5 additions & 0 deletions rustfmt-core/tests/writemode/target/modified.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
4 4 1
fn blah() {}
10 5 2
#[cfg(a, b)]
fn main() {}