]> git.proxmox.com Git - rustc.git/blob - src/tools/rustfmt/src/rustfmt_diff.rs
New upstream version 1.56.0~beta.4+dfsg1
[rustc.git] / src / tools / rustfmt / src / rustfmt_diff.rs
1 use std::collections::VecDeque;
2 use std::fmt;
3 use std::io;
4 use std::io::Write;
5
6 use crate::config::{Color, Config, Verbosity};
7
8 #[derive(Debug, PartialEq)]
9 pub enum DiffLine {
10 Context(String),
11 Expected(String),
12 Resulting(String),
13 }
14
15 #[derive(Debug, PartialEq)]
16 pub struct Mismatch {
17 /// The line number in the formatted version.
18 pub line_number: u32,
19 /// The line number in the original version.
20 pub line_number_orig: u32,
21 /// The set of lines (context and old/new) in the mismatch.
22 pub lines: Vec<DiffLine>,
23 }
24
25 impl Mismatch {
26 fn new(line_number: u32, line_number_orig: u32) -> Mismatch {
27 Mismatch {
28 line_number,
29 line_number_orig,
30 lines: Vec::new(),
31 }
32 }
33 }
34
35 /// A single span of changed lines, with 0 or more removed lines
36 /// and a vector of 0 or more inserted lines.
37 #[derive(Debug, PartialEq, Eq)]
38 pub struct ModifiedChunk {
39 /// The first to be removed from the original text
40 pub line_number_orig: u32,
41 /// The number of lines which have been replaced
42 pub lines_removed: u32,
43 /// The new lines
44 pub lines: Vec<String>,
45 }
46
47 /// Set of changed sections of a file.
48 #[derive(Debug, PartialEq, Eq)]
49 pub struct ModifiedLines {
50 /// The set of changed chunks.
51 pub chunks: Vec<ModifiedChunk>,
52 }
53
54 impl From<Vec<Mismatch>> for ModifiedLines {
55 fn from(mismatches: Vec<Mismatch>) -> ModifiedLines {
56 let chunks = mismatches.into_iter().map(|mismatch| {
57 let lines = mismatch.lines.iter();
58 let num_removed = lines
59 .filter(|line| matches!(line, DiffLine::Resulting(_)))
60 .count();
61
62 let new_lines = mismatch.lines.into_iter().filter_map(|line| match line {
63 DiffLine::Context(_) | DiffLine::Resulting(_) => None,
64 DiffLine::Expected(str) => Some(str),
65 });
66
67 ModifiedChunk {
68 line_number_orig: mismatch.line_number_orig,
69 lines_removed: num_removed as u32,
70 lines: new_lines.collect(),
71 }
72 });
73
74 ModifiedLines {
75 chunks: chunks.collect(),
76 }
77 }
78 }
79
80 // Converts a `Mismatch` into a serialized form, which just includes
81 // enough information to modify the original file.
82 // Each section starts with a line with three integers, space separated:
83 // lineno num_removed num_added
84 // followed by (`num_added`) lines of added text. The line numbers are
85 // relative to the original file.
86 impl fmt::Display for ModifiedLines {
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 for chunk in &self.chunks {
89 writeln!(
90 f,
91 "{} {} {}",
92 chunk.line_number_orig,
93 chunk.lines_removed,
94 chunk.lines.len()
95 )?;
96
97 for line in &chunk.lines {
98 writeln!(f, "{}", line)?;
99 }
100 }
101
102 Ok(())
103 }
104 }
105
106 // Allows to convert `Display`ed `ModifiedLines` back to the structural data.
107 impl std::str::FromStr for ModifiedLines {
108 type Err = ();
109
110 fn from_str(s: &str) -> Result<ModifiedLines, ()> {
111 let mut chunks = vec![];
112
113 let mut lines = s.lines();
114 while let Some(header) = lines.next() {
115 let mut header = header.split_whitespace();
116 let (orig, rem, new_lines) = match (header.next(), header.next(), header.next()) {
117 (Some(orig), Some(removed), Some(added)) => (orig, removed, added),
118 _ => return Err(()),
119 };
120 let (orig, rem, new_lines): (u32, u32, usize) =
121 match (orig.parse(), rem.parse(), new_lines.parse()) {
122 (Ok(a), Ok(b), Ok(c)) => (a, b, c),
123 _ => return Err(()),
124 };
125 let lines = lines.by_ref().take(new_lines);
126 let lines: Vec<_> = lines.map(ToOwned::to_owned).collect();
127 if lines.len() != new_lines {
128 return Err(());
129 }
130
131 chunks.push(ModifiedChunk {
132 line_number_orig: orig,
133 lines_removed: rem,
134 lines,
135 });
136 }
137
138 Ok(ModifiedLines { chunks })
139 }
140 }
141
142 // This struct handles writing output to stdout and abstracts away the logic
143 // of printing in color, if it's possible in the executing environment.
144 pub(crate) struct OutputWriter {
145 terminal: Option<Box<dyn term::Terminal<Output = io::Stdout>>>,
146 }
147
148 impl OutputWriter {
149 // Create a new OutputWriter instance based on the caller's preference
150 // for colorized output and the capabilities of the terminal.
151 pub(crate) fn new(color: Color) -> Self {
152 if let Some(t) = term::stdout() {
153 if color.use_colored_tty() && t.supports_color() {
154 return OutputWriter { terminal: Some(t) };
155 }
156 }
157 OutputWriter { terminal: None }
158 }
159
160 // Write output in the optionally specified color. The output is written
161 // in the specified color if this OutputWriter instance contains a
162 // Terminal in its `terminal` field.
163 pub(crate) fn writeln(&mut self, msg: &str, color: Option<term::color::Color>) {
164 match &mut self.terminal {
165 Some(ref mut t) => {
166 if let Some(color) = color {
167 t.fg(color).unwrap();
168 }
169 writeln!(t, "{}", msg).unwrap();
170 if color.is_some() {
171 t.reset().unwrap();
172 }
173 }
174 None => println!("{}", msg),
175 }
176 }
177 }
178
179 // Produces a diff between the expected output and actual output of rustfmt.
180 pub(crate) fn make_diff(expected: &str, actual: &str, context_size: usize) -> Vec<Mismatch> {
181 let mut line_number = 1;
182 let mut line_number_orig = 1;
183 let mut context_queue: VecDeque<&str> = VecDeque::with_capacity(context_size);
184 let mut lines_since_mismatch = context_size + 1;
185 let mut results = Vec::new();
186 let mut mismatch = Mismatch::new(0, 0);
187
188 for result in diff::lines(expected, actual) {
189 match result {
190 diff::Result::Left(str) => {
191 if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
192 results.push(mismatch);
193 mismatch = Mismatch::new(
194 line_number - context_queue.len() as u32,
195 line_number_orig - context_queue.len() as u32,
196 );
197 }
198
199 while let Some(line) = context_queue.pop_front() {
200 mismatch.lines.push(DiffLine::Context(line.to_owned()));
201 }
202
203 mismatch.lines.push(DiffLine::Resulting(str.to_owned()));
204 line_number_orig += 1;
205 lines_since_mismatch = 0;
206 }
207 diff::Result::Right(str) => {
208 if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
209 results.push(mismatch);
210 mismatch = Mismatch::new(
211 line_number - context_queue.len() as u32,
212 line_number_orig - context_queue.len() as u32,
213 );
214 }
215
216 while let Some(line) = context_queue.pop_front() {
217 mismatch.lines.push(DiffLine::Context(line.to_owned()));
218 }
219
220 mismatch.lines.push(DiffLine::Expected(str.to_owned()));
221 line_number += 1;
222 lines_since_mismatch = 0;
223 }
224 diff::Result::Both(str, _) => {
225 if context_queue.len() >= context_size {
226 let _ = context_queue.pop_front();
227 }
228
229 if lines_since_mismatch < context_size {
230 mismatch.lines.push(DiffLine::Context(str.to_owned()));
231 } else if context_size > 0 {
232 context_queue.push_back(str);
233 }
234
235 line_number += 1;
236 line_number_orig += 1;
237 lines_since_mismatch += 1;
238 }
239 }
240 }
241
242 results.push(mismatch);
243 results.remove(0);
244
245 results
246 }
247
248 pub(crate) fn print_diff<F>(diff: Vec<Mismatch>, get_section_title: F, config: &Config)
249 where
250 F: Fn(u32) -> String,
251 {
252 let color = config.color();
253 let line_terminator = if config.verbose() == Verbosity::Verbose {
254 "⏎"
255 } else {
256 ""
257 };
258
259 let mut writer = OutputWriter::new(color);
260
261 for mismatch in diff {
262 let title = get_section_title(mismatch.line_number_orig);
263 writer.writeln(&title, None);
264
265 for line in mismatch.lines {
266 match line {
267 DiffLine::Context(ref str) => {
268 writer.writeln(&format!(" {}{}", str, line_terminator), None)
269 }
270 DiffLine::Expected(ref str) => writer.writeln(
271 &format!("+{}{}", str, line_terminator),
272 Some(term::color::GREEN),
273 ),
274 DiffLine::Resulting(ref str) => writer.writeln(
275 &format!("-{}{}", str, line_terminator),
276 Some(term::color::RED),
277 ),
278 }
279 }
280 }
281 }
282
283 #[cfg(test)]
284 mod test {
285 use super::DiffLine::*;
286 use super::{make_diff, Mismatch};
287 use super::{ModifiedChunk, ModifiedLines};
288
289 #[test]
290 fn diff_simple() {
291 let src = "one\ntwo\nthree\nfour\nfive\n";
292 let dest = "one\ntwo\ntrois\nfour\nfive\n";
293 let diff = make_diff(src, dest, 1);
294 assert_eq!(
295 diff,
296 vec![Mismatch {
297 line_number: 2,
298 line_number_orig: 2,
299 lines: vec![
300 Context("two".to_owned()),
301 Resulting("three".to_owned()),
302 Expected("trois".to_owned()),
303 Context("four".to_owned()),
304 ],
305 }]
306 );
307 }
308
309 #[test]
310 fn diff_simple2() {
311 let src = "one\ntwo\nthree\nfour\nfive\nsix\nseven\n";
312 let dest = "one\ntwo\ntrois\nfour\ncinq\nsix\nseven\n";
313 let diff = make_diff(src, dest, 1);
314 assert_eq!(
315 diff,
316 vec![
317 Mismatch {
318 line_number: 2,
319 line_number_orig: 2,
320 lines: vec![
321 Context("two".to_owned()),
322 Resulting("three".to_owned()),
323 Expected("trois".to_owned()),
324 Context("four".to_owned()),
325 ],
326 },
327 Mismatch {
328 line_number: 5,
329 line_number_orig: 5,
330 lines: vec![
331 Resulting("five".to_owned()),
332 Expected("cinq".to_owned()),
333 Context("six".to_owned()),
334 ],
335 },
336 ]
337 );
338 }
339
340 #[test]
341 fn diff_zerocontext() {
342 let src = "one\ntwo\nthree\nfour\nfive\n";
343 let dest = "one\ntwo\ntrois\nfour\nfive\n";
344 let diff = make_diff(src, dest, 0);
345 assert_eq!(
346 diff,
347 vec![Mismatch {
348 line_number: 3,
349 line_number_orig: 3,
350 lines: vec![Resulting("three".to_owned()), Expected("trois".to_owned())],
351 }]
352 );
353 }
354
355 #[test]
356 fn diff_trailing_newline() {
357 let src = "one\ntwo\nthree\nfour\nfive";
358 let dest = "one\ntwo\nthree\nfour\nfive\n";
359 let diff = make_diff(src, dest, 1);
360 assert_eq!(
361 diff,
362 vec![Mismatch {
363 line_number: 5,
364 line_number_orig: 5,
365 lines: vec![Context("five".to_owned()), Expected("".to_owned())],
366 }]
367 );
368 }
369
370 #[test]
371 fn modified_lines_from_str() {
372 use std::str::FromStr;
373
374 let src = "1 6 2\nfn some() {}\nfn main() {}\n25 3 1\n struct Test {}";
375 let lines = ModifiedLines::from_str(src).unwrap();
376 assert_eq!(
377 lines,
378 ModifiedLines {
379 chunks: vec![
380 ModifiedChunk {
381 line_number_orig: 1,
382 lines_removed: 6,
383 lines: vec!["fn some() {}".to_owned(), "fn main() {}".to_owned(),]
384 },
385 ModifiedChunk {
386 line_number_orig: 25,
387 lines_removed: 3,
388 lines: vec![" struct Test {}".to_owned()]
389 }
390 ]
391 }
392 );
393
394 let src = "1 5 3";
395 assert_eq!(ModifiedLines::from_str(src), Err(()));
396
397 let src = "1 5 3\na\nb";
398 assert_eq!(ModifiedLines::from_str(src), Err(()));
399 }
400 }