]> git.lizzy.rs Git - rust.git/blob - src/rustfmt_diff.rs
rewrite_string: take care of blank lines appearing within the last line
[rust.git] / src / rustfmt_diff.rs
1 // Copyright 2017 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 use config::{Color, Config, Verbosity};
12 use diff;
13 use std::collections::VecDeque;
14 use std::io;
15 use std::io::Write;
16
17 #[derive(Debug, PartialEq)]
18 pub enum DiffLine {
19     Context(String),
20     Expected(String),
21     Resulting(String),
22 }
23
24 #[derive(Debug, PartialEq)]
25 pub struct Mismatch {
26     /// The line number in the formatted version.
27     pub line_number: u32,
28     /// The line number in the original version.
29     pub line_number_orig: u32,
30     /// The set of lines (context and old/new) in the mismatch.
31     pub lines: Vec<DiffLine>,
32 }
33
34 impl Mismatch {
35     fn new(line_number: u32, line_number_orig: u32) -> Mismatch {
36         Mismatch {
37             line_number,
38             line_number_orig,
39             lines: Vec::new(),
40         }
41     }
42 }
43
44 // This struct handles writing output to stdout and abstracts away the logic
45 // of printing in color, if it's possible in the executing environment.
46 pub struct OutputWriter {
47     terminal: Option<Box<term::Terminal<Output = io::Stdout>>>,
48 }
49
50 impl OutputWriter {
51     // Create a new OutputWriter instance based on the caller's preference
52     // for colorized output and the capabilities of the terminal.
53     pub fn new(color: Color) -> Self {
54         if let Some(t) = term::stdout() {
55             if color.use_colored_tty() && t.supports_color() {
56                 return OutputWriter { terminal: Some(t) };
57             }
58         }
59         OutputWriter { terminal: None }
60     }
61
62     // Write output in the optionally specified color. The output is written
63     // in the specified color if this OutputWriter instance contains a
64     // Terminal in its `terminal` field.
65     pub fn writeln(&mut self, msg: &str, color: Option<term::color::Color>) {
66         match &mut self.terminal {
67             Some(ref mut t) => {
68                 if let Some(color) = color {
69                     t.fg(color).unwrap();
70                 }
71                 writeln!(t, "{}", msg).unwrap();
72                 if color.is_some() {
73                     t.reset().unwrap();
74                 }
75             }
76             None => println!("{}", msg),
77         }
78     }
79 }
80
81 // Produces a diff between the expected output and actual output of rustfmt.
82 pub fn make_diff(expected: &str, actual: &str, context_size: usize) -> Vec<Mismatch> {
83     let mut line_number = 1;
84     let mut line_number_orig = 1;
85     let mut context_queue: VecDeque<&str> = VecDeque::with_capacity(context_size);
86     let mut lines_since_mismatch = context_size + 1;
87     let mut results = Vec::new();
88     let mut mismatch = Mismatch::new(0, 0);
89
90     for result in diff::lines(expected, actual) {
91         match result {
92             diff::Result::Left(str) => {
93                 if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
94                     results.push(mismatch);
95                     mismatch = Mismatch::new(
96                         line_number - context_queue.len() as u32,
97                         line_number_orig - context_queue.len() as u32,
98                     );
99                 }
100
101                 while let Some(line) = context_queue.pop_front() {
102                     mismatch.lines.push(DiffLine::Context(line.to_owned()));
103                 }
104
105                 mismatch.lines.push(DiffLine::Resulting(str.to_owned()));
106                 line_number_orig += 1;
107                 lines_since_mismatch = 0;
108             }
109             diff::Result::Right(str) => {
110                 if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
111                     results.push(mismatch);
112                     mismatch = Mismatch::new(
113                         line_number - context_queue.len() as u32,
114                         line_number_orig - context_queue.len() as u32,
115                     );
116                 }
117
118                 while let Some(line) = context_queue.pop_front() {
119                     mismatch.lines.push(DiffLine::Context(line.to_owned()));
120                 }
121
122                 mismatch.lines.push(DiffLine::Expected(str.to_owned()));
123                 line_number += 1;
124                 lines_since_mismatch = 0;
125             }
126             diff::Result::Both(str, _) => {
127                 if context_queue.len() >= context_size {
128                     let _ = context_queue.pop_front();
129                 }
130
131                 if lines_since_mismatch < context_size {
132                     mismatch.lines.push(DiffLine::Context(str.to_owned()));
133                 } else if context_size > 0 {
134                     context_queue.push_back(str);
135                 }
136
137                 line_number += 1;
138                 line_number_orig += 1;
139                 lines_since_mismatch += 1;
140             }
141         }
142     }
143
144     results.push(mismatch);
145     results.remove(0);
146
147     results
148 }
149
150 pub fn print_diff<F>(diff: Vec<Mismatch>, get_section_title: F, config: &Config)
151 where
152     F: Fn(u32) -> String,
153 {
154     let color = config.color();
155     let line_terminator = if config.verbose() == Verbosity::Verbose {
156         "⏎"
157     } else {
158         ""
159     };
160
161     let mut writer = OutputWriter::new(color);
162
163     for mismatch in diff {
164         let title = get_section_title(mismatch.line_number_orig);
165         writer.writeln(&title, None);
166
167         for line in mismatch.lines {
168             match line {
169                 DiffLine::Context(ref str) => {
170                     writer.writeln(&format!(" {}{}", str, line_terminator), None)
171                 }
172                 DiffLine::Expected(ref str) => writer.writeln(
173                     &format!("+{}{}", str, line_terminator),
174                     Some(term::color::GREEN),
175                 ),
176                 DiffLine::Resulting(ref str) => writer.writeln(
177                     &format!("-{}{}", str, line_terminator),
178                     Some(term::color::RED),
179                 ),
180             }
181         }
182     }
183 }
184
185 /// Convert a Mismatch into a serialised form which just includes
186 /// enough information to modify the original file.
187 /// Each section starts with a line with three integers, space separated:
188 ///     lineno num_removed num_added
189 /// followed by (num_added) lines of added text.  The line numbers are
190 /// relative to the original file.
191 pub fn output_modified<W>(mut out: W, diff: Vec<Mismatch>)
192 where
193     W: Write,
194 {
195     for mismatch in diff {
196         let (num_removed, num_added) =
197             mismatch
198                 .lines
199                 .iter()
200                 .fold((0, 0), |(rem, add), line| match *line {
201                     DiffLine::Context(_) => panic!("No Context expected"),
202                     DiffLine::Expected(_) => (rem, add + 1),
203                     DiffLine::Resulting(_) => (rem + 1, add),
204                 });
205         // Write a header with enough information to separate the modified lines.
206         writeln!(
207             out,
208             "{} {} {}",
209             mismatch.line_number_orig, num_removed, num_added
210         )
211         .unwrap();
212
213         for line in mismatch.lines {
214             match line {
215                 DiffLine::Context(_) | DiffLine::Resulting(_) => (),
216                 DiffLine::Expected(ref str) => {
217                     writeln!(out, "{}", str).unwrap();
218                 }
219             }
220         }
221     }
222 }
223
224 #[cfg(test)]
225 mod test {
226     use super::DiffLine::*;
227     use super::{make_diff, Mismatch};
228
229     #[test]
230     fn diff_simple() {
231         let src = "one\ntwo\nthree\nfour\nfive\n";
232         let dest = "one\ntwo\ntrois\nfour\nfive\n";
233         let diff = make_diff(src, dest, 1);
234         assert_eq!(
235             diff,
236             vec![Mismatch {
237                 line_number: 2,
238                 line_number_orig: 2,
239                 lines: vec![
240                     Context("two".to_owned()),
241                     Resulting("three".to_owned()),
242                     Expected("trois".to_owned()),
243                     Context("four".to_owned()),
244                 ],
245             }]
246         );
247     }
248
249     #[test]
250     fn diff_simple2() {
251         let src = "one\ntwo\nthree\nfour\nfive\nsix\nseven\n";
252         let dest = "one\ntwo\ntrois\nfour\ncinq\nsix\nseven\n";
253         let diff = make_diff(src, dest, 1);
254         assert_eq!(
255             diff,
256             vec![
257                 Mismatch {
258                     line_number: 2,
259                     line_number_orig: 2,
260                     lines: vec![
261                         Context("two".to_owned()),
262                         Resulting("three".to_owned()),
263                         Expected("trois".to_owned()),
264                         Context("four".to_owned()),
265                     ],
266                 },
267                 Mismatch {
268                     line_number: 5,
269                     line_number_orig: 5,
270                     lines: vec![
271                         Resulting("five".to_owned()),
272                         Expected("cinq".to_owned()),
273                         Context("six".to_owned()),
274                     ],
275                 },
276             ]
277         );
278     }
279
280     #[test]
281     fn diff_zerocontext() {
282         let src = "one\ntwo\nthree\nfour\nfive\n";
283         let dest = "one\ntwo\ntrois\nfour\nfive\n";
284         let diff = make_diff(src, dest, 0);
285         assert_eq!(
286             diff,
287             vec![Mismatch {
288                 line_number: 3,
289                 line_number_orig: 3,
290                 lines: vec![Resulting("three".to_owned()), Expected("trois".to_owned())],
291             }]
292         );
293     }
294
295     #[test]
296     fn diff_trailing_newline() {
297         let src = "one\ntwo\nthree\nfour\nfive";
298         let dest = "one\ntwo\nthree\nfour\nfive\n";
299         let diff = make_diff(src, dest, 1);
300         assert_eq!(
301             diff,
302             vec![Mismatch {
303                 line_number: 5,
304                 line_number_orig: 5,
305                 lines: vec![Context("five".to_owned()), Expected("".to_owned())],
306             }]
307         );
308     }
309 }