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