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