]> git.lizzy.rs Git - rust.git/blob - src/rustfmt_diff.rs
Merge pull request #2352 from topecongiro/issue-2337
[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 term;
16 use utils::use_colored_tty;
17
18 #[derive(Debug, PartialEq)]
19 pub enum DiffLine {
20     Context(String),
21     Expected(String),
22     Resulting(String),
23 }
24
25 #[derive(Debug, PartialEq)]
26 pub struct Mismatch {
27     pub line_number: u32,
28     pub lines: Vec<DiffLine>,
29 }
30
31 impl Mismatch {
32     fn new(line_number: u32) -> Mismatch {
33         Mismatch {
34             line_number: line_number,
35             lines: Vec::new(),
36         }
37     }
38 }
39
40 // This struct handles writing output to stdout and abstracts away the logic
41 // of printing in color, if it's possible in the executing environment.
42 pub struct OutputWriter {
43     terminal: Option<Box<term::Terminal<Output = io::Stdout>>>,
44 }
45
46 impl OutputWriter {
47     // Create a new OutputWriter instance based on the caller's preference
48     // for colorized output and the capabilities of the terminal.
49     pub fn new(color: Color) -> Self {
50         if let Some(t) = term::stdout() {
51             if use_colored_tty(color) && t.supports_color() {
52                 return OutputWriter { terminal: Some(t) };
53             }
54         }
55         OutputWriter { terminal: None }
56     }
57
58     // Write output in the optionally specified color. The output is written
59     // in the specified color if this OutputWriter instance contains a
60     // Terminal in its `terminal` field.
61     pub fn writeln(&mut self, msg: &str, color: Option<term::color::Color>) {
62         match &mut self.terminal {
63             Some(ref mut t) => {
64                 if let Some(color) = color {
65                     t.fg(color).unwrap();
66                 }
67                 writeln!(t, "{}", msg).unwrap();
68                 if color.is_some() {
69                     t.reset().unwrap();
70                 }
71             }
72             None => println!("{}", msg),
73         }
74     }
75 }
76
77 // Produces a diff between the expected output and actual output of rustfmt.
78 pub fn make_diff(expected: &str, actual: &str, context_size: usize) -> Vec<Mismatch> {
79     let mut line_number = 1;
80     let mut context_queue: VecDeque<&str> = VecDeque::with_capacity(context_size);
81     let mut lines_since_mismatch = context_size + 1;
82     let mut results = Vec::new();
83     let mut mismatch = Mismatch::new(0);
84
85     for result in diff::lines(expected, actual) {
86         match result {
87             diff::Result::Left(str) => {
88                 if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
89                     results.push(mismatch);
90                     mismatch = Mismatch::new(line_number - context_queue.len() as u32);
91                 }
92
93                 while let Some(line) = context_queue.pop_front() {
94                     mismatch.lines.push(DiffLine::Context(line.to_owned()));
95                 }
96
97                 mismatch.lines.push(DiffLine::Resulting(str.to_owned()));
98                 lines_since_mismatch = 0;
99             }
100             diff::Result::Right(str) => {
101                 if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
102                     results.push(mismatch);
103                     mismatch = Mismatch::new(line_number - context_queue.len() as u32);
104                 }
105
106                 while let Some(line) = context_queue.pop_front() {
107                     mismatch.lines.push(DiffLine::Context(line.to_owned()));
108                 }
109
110                 mismatch.lines.push(DiffLine::Expected(str.to_owned()));
111                 line_number += 1;
112                 lines_since_mismatch = 0;
113             }
114             diff::Result::Both(str, _) => {
115                 if context_queue.len() >= context_size {
116                     let _ = context_queue.pop_front();
117                 }
118
119                 if lines_since_mismatch < context_size {
120                     mismatch.lines.push(DiffLine::Context(str.to_owned()));
121                 } else if context_size > 0 {
122                     context_queue.push_back(str);
123                 }
124
125                 line_number += 1;
126                 lines_since_mismatch += 1;
127             }
128         }
129     }
130
131     results.push(mismatch);
132     results.remove(0);
133
134     results
135 }
136
137 pub fn print_diff<F>(diff: Vec<Mismatch>, get_section_title: F, color: Color)
138 where
139     F: Fn(u32) -> String,
140 {
141     let mut writer = OutputWriter::new(color);
142
143     for mismatch in diff {
144         let title = get_section_title(mismatch.line_number);
145         writer.writeln(&format!("{}", title), None);
146
147         for line in mismatch.lines {
148             match line {
149                 DiffLine::Context(ref str) => writer.writeln(&format!(" {}⏎", str), None),
150                 DiffLine::Expected(ref str) => {
151                     writer.writeln(&format!("+{}⏎", str), Some(term::color::GREEN))
152                 }
153                 DiffLine::Resulting(ref str) => {
154                     writer.writeln(&format!("-{}⏎", str), Some(term::color::RED))
155                 }
156             }
157         }
158     }
159 }
160
161 #[cfg(test)]
162 mod test {
163     use super::{make_diff, Mismatch};
164     use super::DiffLine::*;
165
166     #[test]
167     fn diff_simple() {
168         let src = "one\ntwo\nthree\nfour\nfive\n";
169         let dest = "one\ntwo\ntrois\nfour\nfive\n";
170         let diff = make_diff(src, dest, 1);
171         assert_eq!(
172             diff,
173             vec![
174                 Mismatch {
175                     line_number: 2,
176                     lines: vec![
177                         Context("two".to_owned()),
178                         Resulting("three".to_owned()),
179                         Expected("trois".to_owned()),
180                         Context("four".to_owned()),
181                     ],
182                 },
183             ]
184         );
185     }
186
187     #[test]
188     fn diff_simple2() {
189         let src = "one\ntwo\nthree\nfour\nfive\nsix\nseven\n";
190         let dest = "one\ntwo\ntrois\nfour\ncinq\nsix\nseven\n";
191         let diff = make_diff(src, dest, 1);
192         assert_eq!(
193             diff,
194             vec![
195                 Mismatch {
196                     line_number: 2,
197                     lines: vec![
198                         Context("two".to_owned()),
199                         Resulting("three".to_owned()),
200                         Expected("trois".to_owned()),
201                         Context("four".to_owned()),
202                     ],
203                 },
204                 Mismatch {
205                     line_number: 5,
206                     lines: vec![
207                         Resulting("five".to_owned()),
208                         Expected("cinq".to_owned()),
209                         Context("six".to_owned()),
210                     ],
211                 },
212             ]
213         );
214     }
215
216     #[test]
217     fn diff_zerocontext() {
218         let src = "one\ntwo\nthree\nfour\nfive\n";
219         let dest = "one\ntwo\ntrois\nfour\nfive\n";
220         let diff = make_diff(src, dest, 0);
221         assert_eq!(
222             diff,
223             vec![
224                 Mismatch {
225                     line_number: 3,
226                     lines: vec![Resulting("three".to_owned()), Expected("trois".to_owned())],
227                 },
228             ]
229         );
230     }
231
232     #[test]
233     fn diff_trailing_newline() {
234         let src = "one\ntwo\nthree\nfour\nfive";
235         let dest = "one\ntwo\nthree\nfour\nfive\n";
236         let diff = make_diff(src, dest, 1);
237         assert_eq!(
238             diff,
239             vec![
240                 Mismatch {
241                     line_number: 5,
242                     lines: vec![Context("five".to_owned()), Expected("".to_owned())],
243                 },
244             ]
245         );
246     }
247 }