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