]> git.lizzy.rs Git - rust.git/blob - src/rustfmt_diff.rs
Suppress warning about unused attribute
[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};
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_diff() { "⏎" } else { "" };
158
159     let mut writer = OutputWriter::new(color);
160
161     for mismatch in diff {
162         let title = get_section_title(mismatch.line_number);
163         writer.writeln(&title, None);
164
165         for line in mismatch.lines {
166             match line {
167                 DiffLine::Context(ref str) => {
168                     writer.writeln(&format!(" {}{}", str, line_terminator), None)
169                 }
170                 DiffLine::Expected(ref str) => writer.writeln(
171                     &format!("+{}{}", str, line_terminator),
172                     Some(term::color::GREEN),
173                 ),
174                 DiffLine::Resulting(ref str) => writer.writeln(
175                     &format!("-{}{}", str, line_terminator),
176                     Some(term::color::RED),
177                 ),
178             }
179         }
180     }
181 }
182
183 /// Convert a Mismatch into a serialised form which just includes
184 /// enough information to modify the original file.
185 /// Each section starts with a line with three integers, space separated:
186 ///     lineno num_removed num_added
187 /// followed by (num_added) lines of added text.  The line numbers are
188 /// relative to the original file.
189 pub fn output_modified<W>(mut out: W, diff: Vec<Mismatch>)
190 where
191     W: Write,
192 {
193     for mismatch in diff {
194         let (num_removed, num_added) = mismatch.lines.iter().fold((0, 0), |(rem, add), line| {
195             match *line {
196                 DiffLine::Context(_) => panic!("No Context expected"),
197                 DiffLine::Expected(_) => (rem, add + 1),
198                 DiffLine::Resulting(_) => (rem + 1, add),
199             }
200         });
201         // Write a header with enough information to separate the modified lines.
202         writeln!(
203             out,
204             "{} {} {}",
205             mismatch.line_number_orig, num_removed, num_added
206         ).unwrap();
207
208         for line in mismatch.lines {
209             match line {
210                 DiffLine::Context(_) | DiffLine::Resulting(_) => (),
211                 DiffLine::Expected(ref str) => {
212                     writeln!(out, "{}", str).unwrap();
213                 }
214             }
215         }
216     }
217 }
218
219 #[cfg(test)]
220 mod test {
221     use super::DiffLine::*;
222     use super::{make_diff, Mismatch};
223
224     #[test]
225     fn diff_simple() {
226         let src = "one\ntwo\nthree\nfour\nfive\n";
227         let dest = "one\ntwo\ntrois\nfour\nfive\n";
228         let diff = make_diff(src, dest, 1);
229         assert_eq!(
230             diff,
231             vec![Mismatch {
232                 line_number: 2,
233                 line_number_orig: 2,
234                 lines: vec![
235                     Context("two".to_owned()),
236                     Resulting("three".to_owned()),
237                     Expected("trois".to_owned()),
238                     Context("four".to_owned()),
239                 ],
240             }]
241         );
242     }
243
244     #[test]
245     fn diff_simple2() {
246         let src = "one\ntwo\nthree\nfour\nfive\nsix\nseven\n";
247         let dest = "one\ntwo\ntrois\nfour\ncinq\nsix\nseven\n";
248         let diff = make_diff(src, dest, 1);
249         assert_eq!(
250             diff,
251             vec![
252                 Mismatch {
253                     line_number: 2,
254                     line_number_orig: 2,
255                     lines: vec![
256                         Context("two".to_owned()),
257                         Resulting("three".to_owned()),
258                         Expected("trois".to_owned()),
259                         Context("four".to_owned()),
260                     ],
261                 },
262                 Mismatch {
263                     line_number: 5,
264                     line_number_orig: 5,
265                     lines: vec![
266                         Resulting("five".to_owned()),
267                         Expected("cinq".to_owned()),
268                         Context("six".to_owned()),
269                     ],
270                 },
271             ]
272         );
273     }
274
275     #[test]
276     fn diff_zerocontext() {
277         let src = "one\ntwo\nthree\nfour\nfive\n";
278         let dest = "one\ntwo\ntrois\nfour\nfive\n";
279         let diff = make_diff(src, dest, 0);
280         assert_eq!(
281             diff,
282             vec![Mismatch {
283                 line_number: 3,
284                 line_number_orig: 3,
285                 lines: vec![Resulting("three".to_owned()), Expected("trois".to_owned())],
286             }]
287         );
288     }
289
290     #[test]
291     fn diff_trailing_newline() {
292         let src = "one\ntwo\nthree\nfour\nfive";
293         let dest = "one\ntwo\nthree\nfour\nfive\n";
294         let diff = make_diff(src, dest, 1);
295         assert_eq!(
296             diff,
297             vec![Mismatch {
298                 line_number: 5,
299                 line_number_orig: 5,
300                 lines: vec![Context("five".to_owned()), Expected("".to_owned())],
301             }]
302         );
303     }
304 }