]> git.lizzy.rs Git - rust.git/blob - src/rustfmt_diff.rs
Merge pull request #3334 from rchaser53/issue-3227
[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 std::collections::VecDeque;
12 use std::io;
13 use std::io::Write;
14
15 use diff;
16
17 use crate::config::{Color, Config, Verbosity};
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<dyn 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 color.use_colored_tty() && 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_orig);
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) =
199             mismatch
200                 .lines
201                 .iter()
202                 .fold((0, 0), |(rem, add), line| match *line {
203                     DiffLine::Context(_) => panic!("No Context expected"),
204                     DiffLine::Expected(_) => (rem, add + 1),
205                     DiffLine::Resulting(_) => (rem + 1, add),
206                 });
207         // Write a header with enough information to separate the modified lines.
208         writeln!(
209             out,
210             "{} {} {}",
211             mismatch.line_number_orig, num_removed, num_added
212         )
213         .unwrap();
214
215         for line in mismatch.lines {
216             match line {
217                 DiffLine::Context(_) | DiffLine::Resulting(_) => (),
218                 DiffLine::Expected(ref str) => {
219                     writeln!(out, "{}", str).unwrap();
220                 }
221             }
222         }
223     }
224 }
225
226 #[cfg(test)]
227 mod test {
228     use super::DiffLine::*;
229     use super::{make_diff, Mismatch};
230
231     #[test]
232     fn diff_simple() {
233         let src = "one\ntwo\nthree\nfour\nfive\n";
234         let dest = "one\ntwo\ntrois\nfour\nfive\n";
235         let diff = make_diff(src, dest, 1);
236         assert_eq!(
237             diff,
238             vec![Mismatch {
239                 line_number: 2,
240                 line_number_orig: 2,
241                 lines: vec![
242                     Context("two".to_owned()),
243                     Resulting("three".to_owned()),
244                     Expected("trois".to_owned()),
245                     Context("four".to_owned()),
246                 ],
247             }]
248         );
249     }
250
251     #[test]
252     fn diff_simple2() {
253         let src = "one\ntwo\nthree\nfour\nfive\nsix\nseven\n";
254         let dest = "one\ntwo\ntrois\nfour\ncinq\nsix\nseven\n";
255         let diff = make_diff(src, dest, 1);
256         assert_eq!(
257             diff,
258             vec![
259                 Mismatch {
260                     line_number: 2,
261                     line_number_orig: 2,
262                     lines: vec![
263                         Context("two".to_owned()),
264                         Resulting("three".to_owned()),
265                         Expected("trois".to_owned()),
266                         Context("four".to_owned()),
267                     ],
268                 },
269                 Mismatch {
270                     line_number: 5,
271                     line_number_orig: 5,
272                     lines: vec![
273                         Resulting("five".to_owned()),
274                         Expected("cinq".to_owned()),
275                         Context("six".to_owned()),
276                     ],
277                 },
278             ]
279         );
280     }
281
282     #[test]
283     fn diff_zerocontext() {
284         let src = "one\ntwo\nthree\nfour\nfive\n";
285         let dest = "one\ntwo\ntrois\nfour\nfive\n";
286         let diff = make_diff(src, dest, 0);
287         assert_eq!(
288             diff,
289             vec![Mismatch {
290                 line_number: 3,
291                 line_number_orig: 3,
292                 lines: vec![Resulting("three".to_owned()), Expected("trois".to_owned())],
293             }]
294         );
295     }
296
297     #[test]
298     fn diff_trailing_newline() {
299         let src = "one\ntwo\nthree\nfour\nfive";
300         let dest = "one\ntwo\nthree\nfour\nfive\n";
301         let diff = make_diff(src, dest, 1);
302         assert_eq!(
303             diff,
304             vec![Mismatch {
305                 line_number: 5,
306                 line_number_orig: 5,
307                 lines: vec![Context("five".to_owned()), Expected("".to_owned())],
308             }]
309         );
310     }
311 }