]> git.lizzy.rs Git - rust.git/blob - src/config/file_lines.rs
deps: update rustc-ap to v642.0.0
[rust.git] / src / config / file_lines.rs
1 //! This module contains types and functions to support formatting specific line ranges.
2
3 use itertools::Itertools;
4 use std::collections::HashMap;
5 use std::path::PathBuf;
6 use std::rc::Rc;
7 use std::{cmp, fmt, iter, str};
8
9 use rustc_span::{self, SourceFile};
10 use serde::{ser, Deserialize, Deserializer, Serialize, Serializer};
11 use serde_json as json;
12
13 /// A range of lines in a file, inclusive of both ends.
14 pub struct LineRange {
15     pub file: Rc<SourceFile>,
16     pub lo: usize,
17     pub hi: usize,
18 }
19
20 /// Defines the name of an input - either a file or stdin.
21 #[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
22 pub enum FileName {
23     Real(PathBuf),
24     Stdin,
25 }
26
27 impl From<rustc_span::FileName> for FileName {
28     fn from(name: rustc_span::FileName) -> FileName {
29         match name {
30             rustc_span::FileName::Real(p) => FileName::Real(p),
31             rustc_span::FileName::Custom(ref f) if f == "stdin" => FileName::Stdin,
32             _ => unreachable!(),
33         }
34     }
35 }
36
37 impl fmt::Display for FileName {
38     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39         match self {
40             FileName::Real(p) => write!(f, "{}", p.to_str().unwrap()),
41             FileName::Stdin => write!(f, "stdin"),
42         }
43     }
44 }
45
46 impl<'de> Deserialize<'de> for FileName {
47     fn deserialize<D>(deserializer: D) -> Result<FileName, D::Error>
48     where
49         D: Deserializer<'de>,
50     {
51         let s = String::deserialize(deserializer)?;
52         if s == "stdin" {
53             Ok(FileName::Stdin)
54         } else {
55             Ok(FileName::Real(s.into()))
56         }
57     }
58 }
59
60 impl Serialize for FileName {
61     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
62     where
63         S: Serializer,
64     {
65         let s = match self {
66             FileName::Stdin => Ok("stdin"),
67             FileName::Real(path) => path
68                 .to_str()
69                 .ok_or_else(|| ser::Error::custom("path can't be serialized as UTF-8 string")),
70         };
71
72         s.and_then(|s| serializer.serialize_str(s))
73     }
74 }
75
76 impl LineRange {
77     pub fn file_name(&self) -> FileName {
78         self.file.name.clone().into()
79     }
80 }
81
82 /// A range that is inclusive of both ends.
83 #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Deserialize)]
84 pub struct Range {
85     lo: usize,
86     hi: usize,
87 }
88
89 impl<'a> From<&'a LineRange> for Range {
90     fn from(range: &'a LineRange) -> Range {
91         Range::new(range.lo, range.hi)
92     }
93 }
94
95 impl fmt::Display for Range {
96     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97         write!(f, "{}..{}", self.lo, self.hi)
98     }
99 }
100
101 impl Range {
102     pub fn new(lo: usize, hi: usize) -> Range {
103         Range { lo, hi }
104     }
105
106     fn is_empty(self) -> bool {
107         self.lo > self.hi
108     }
109
110     #[allow(dead_code)]
111     fn contains(self, other: Range) -> bool {
112         if other.is_empty() {
113             true
114         } else {
115             !self.is_empty() && self.lo <= other.lo && self.hi >= other.hi
116         }
117     }
118
119     fn intersects(self, other: Range) -> bool {
120         if self.is_empty() || other.is_empty() {
121             false
122         } else {
123             (self.lo <= other.hi && other.hi <= self.hi)
124                 || (other.lo <= self.hi && self.hi <= other.hi)
125         }
126     }
127
128     fn adjacent_to(self, other: Range) -> bool {
129         if self.is_empty() || other.is_empty() {
130             false
131         } else {
132             self.hi + 1 == other.lo || other.hi + 1 == self.lo
133         }
134     }
135
136     /// Returns a new `Range` with lines from `self` and `other` if they were adjacent or
137     /// intersect; returns `None` otherwise.
138     fn merge(self, other: Range) -> Option<Range> {
139         if self.adjacent_to(other) || self.intersects(other) {
140             Some(Range::new(
141                 cmp::min(self.lo, other.lo),
142                 cmp::max(self.hi, other.hi),
143             ))
144         } else {
145             None
146         }
147     }
148 }
149
150 /// A set of lines in files.
151 ///
152 /// It is represented as a multimap keyed on file names, with values a collection of
153 /// non-overlapping ranges sorted by their start point. An inner `None` is interpreted to mean all
154 /// lines in all files.
155 #[derive(Clone, Debug, Default, PartialEq)]
156 pub struct FileLines(Option<HashMap<FileName, Vec<Range>>>);
157
158 impl fmt::Display for FileLines {
159     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160         match &self.0 {
161             None => write!(f, "None")?,
162             Some(map) => {
163                 for (file_name, ranges) in map.iter() {
164                     write!(f, "{}: ", file_name)?;
165                     write!(f, "{}\n", ranges.iter().format(", "))?;
166                 }
167             }
168         };
169         Ok(())
170     }
171 }
172
173 /// Normalizes the ranges so that the invariants for `FileLines` hold: ranges are non-overlapping,
174 /// and ordered by their start point.
175 fn normalize_ranges(ranges: &mut HashMap<FileName, Vec<Range>>) {
176     for ranges in ranges.values_mut() {
177         ranges.sort();
178         let mut result = vec![];
179         let mut iter = ranges.iter_mut().peekable();
180         while let Some(next) = iter.next() {
181             let mut next = *next;
182             while let Some(&&mut peek) = iter.peek() {
183                 if let Some(merged) = next.merge(peek) {
184                     iter.next().unwrap();
185                     next = merged;
186                 } else {
187                     break;
188                 }
189             }
190             result.push(next)
191         }
192         *ranges = result;
193     }
194 }
195
196 impl FileLines {
197     /// Creates a `FileLines` that contains all lines in all files.
198     pub(crate) fn all() -> FileLines {
199         FileLines(None)
200     }
201
202     /// Returns `true` if this `FileLines` contains all lines in all files.
203     pub(crate) fn is_all(&self) -> bool {
204         self.0.is_none()
205     }
206
207     pub fn from_ranges(mut ranges: HashMap<FileName, Vec<Range>>) -> FileLines {
208         normalize_ranges(&mut ranges);
209         FileLines(Some(ranges))
210     }
211
212     /// Returns an iterator over the files contained in `self`.
213     pub fn files(&self) -> Files<'_> {
214         Files(self.0.as_ref().map(HashMap::keys))
215     }
216
217     /// Returns JSON representation as accepted by the `--file-lines JSON` arg.
218     pub fn to_json_spans(&self) -> Vec<JsonSpan> {
219         match &self.0 {
220             None => vec![],
221             Some(file_ranges) => file_ranges
222                 .iter()
223                 .flat_map(|(file, ranges)| ranges.iter().map(move |r| (file, r)))
224                 .map(|(file, range)| JsonSpan {
225                     file: file.to_owned(),
226                     range: (range.lo, range.hi),
227                 })
228                 .collect(),
229         }
230     }
231
232     /// Returns `true` if `self` includes all lines in all files. Otherwise runs `f` on all ranges
233     /// in the designated file (if any) and returns true if `f` ever does.
234     fn file_range_matches<F>(&self, file_name: &FileName, f: F) -> bool
235     where
236         F: FnMut(&Range) -> bool,
237     {
238         let map = match self.0 {
239             // `None` means "all lines in all files".
240             None => return true,
241             Some(ref map) => map,
242         };
243
244         match canonicalize_path_string(file_name).and_then(|file| map.get(&file)) {
245             Some(ranges) => ranges.iter().any(f),
246             None => false,
247         }
248     }
249
250     /// Returns `true` if `range` is fully contained in `self`.
251     #[allow(dead_code)]
252     pub(crate) fn contains(&self, range: &LineRange) -> bool {
253         self.file_range_matches(&range.file_name(), |r| r.contains(Range::from(range)))
254     }
255
256     /// Returns `true` if any lines in `range` are in `self`.
257     pub(crate) fn intersects(&self, range: &LineRange) -> bool {
258         self.file_range_matches(&range.file_name(), |r| r.intersects(Range::from(range)))
259     }
260
261     /// Returns `true` if `line` from `file_name` is in `self`.
262     pub(crate) fn contains_line(&self, file_name: &FileName, line: usize) -> bool {
263         self.file_range_matches(file_name, |r| r.lo <= line && r.hi >= line)
264     }
265
266     /// Returns `true` if all the lines between `lo` and `hi` from `file_name` are in `self`.
267     pub(crate) fn contains_range(&self, file_name: &FileName, lo: usize, hi: usize) -> bool {
268         self.file_range_matches(file_name, |r| r.contains(Range::new(lo, hi)))
269     }
270 }
271
272 /// `FileLines` files iterator.
273 pub struct Files<'a>(Option<::std::collections::hash_map::Keys<'a, FileName, Vec<Range>>>);
274
275 impl<'a> iter::Iterator for Files<'a> {
276     type Item = &'a FileName;
277
278     fn next(&mut self) -> Option<&'a FileName> {
279         self.0.as_mut().and_then(Iterator::next)
280     }
281 }
282
283 fn canonicalize_path_string(file: &FileName) -> Option<FileName> {
284     match *file {
285         FileName::Real(ref path) => path.canonicalize().ok().map(FileName::Real),
286         _ => Some(file.clone()),
287     }
288 }
289
290 // This impl is needed for `Config::override_value` to work for use in tests.
291 impl str::FromStr for FileLines {
292     type Err = String;
293
294     fn from_str(s: &str) -> Result<FileLines, String> {
295         let v: Vec<JsonSpan> = json::from_str(s).map_err(|e| e.to_string())?;
296         let mut m = HashMap::new();
297         for js in v {
298             let (s, r) = JsonSpan::into_tuple(js)?;
299             m.entry(s).or_insert_with(|| vec![]).push(r);
300         }
301         Ok(FileLines::from_ranges(m))
302     }
303 }
304
305 // For JSON decoding.
306 #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]
307 pub struct JsonSpan {
308     file: FileName,
309     range: (usize, usize),
310 }
311
312 impl JsonSpan {
313     fn into_tuple(self) -> Result<(FileName, Range), String> {
314         let (lo, hi) = self.range;
315         let canonical = canonicalize_path_string(&self.file)
316             .ok_or_else(|| format!("Can't canonicalize {}", &self.file))?;
317         Ok((canonical, Range::new(lo, hi)))
318     }
319 }
320
321 // This impl is needed for inclusion in the `Config` struct. We don't have a toml representation
322 // for `FileLines`, so it will just panic instead.
323 impl<'de> ::serde::de::Deserialize<'de> for FileLines {
324     fn deserialize<D>(_: D) -> Result<Self, D::Error>
325     where
326         D: ::serde::de::Deserializer<'de>,
327     {
328         panic!(
329             "FileLines cannot be deserialized from a project rustfmt.toml file: please \
330              specify it via the `--file-lines` option instead"
331         );
332     }
333 }
334
335 // We also want to avoid attempting to serialize a FileLines to toml. The
336 // `Config` struct should ensure this impl is never reached.
337 impl ::serde::ser::Serialize for FileLines {
338     fn serialize<S>(&self, _: S) -> Result<S::Ok, S::Error>
339     where
340         S: ::serde::ser::Serializer,
341     {
342         unreachable!("FileLines cannot be serialized. This is a rustfmt bug.");
343     }
344 }
345
346 #[cfg(test)]
347 mod test {
348     use super::Range;
349
350     #[test]
351     fn test_range_intersects() {
352         assert!(Range::new(1, 2).intersects(Range::new(1, 1)));
353         assert!(Range::new(1, 2).intersects(Range::new(2, 2)));
354         assert!(!Range::new(1, 2).intersects(Range::new(0, 0)));
355         assert!(!Range::new(1, 2).intersects(Range::new(3, 10)));
356         assert!(!Range::new(1, 3).intersects(Range::new(5, 5)));
357     }
358
359     #[test]
360     fn test_range_adjacent_to() {
361         assert!(!Range::new(1, 2).adjacent_to(Range::new(1, 1)));
362         assert!(!Range::new(1, 2).adjacent_to(Range::new(2, 2)));
363         assert!(Range::new(1, 2).adjacent_to(Range::new(0, 0)));
364         assert!(Range::new(1, 2).adjacent_to(Range::new(3, 10)));
365         assert!(!Range::new(1, 3).adjacent_to(Range::new(5, 5)));
366     }
367
368     #[test]
369     fn test_range_contains() {
370         assert!(Range::new(1, 2).contains(Range::new(1, 1)));
371         assert!(Range::new(1, 2).contains(Range::new(2, 2)));
372         assert!(!Range::new(1, 2).contains(Range::new(0, 0)));
373         assert!(!Range::new(1, 2).contains(Range::new(3, 10)));
374     }
375
376     #[test]
377     fn test_range_merge() {
378         assert_eq!(None, Range::new(1, 3).merge(Range::new(5, 5)));
379         assert_eq!(None, Range::new(4, 7).merge(Range::new(0, 1)));
380         assert_eq!(
381             Some(Range::new(3, 7)),
382             Range::new(3, 5).merge(Range::new(4, 7))
383         );
384         assert_eq!(
385             Some(Range::new(3, 7)),
386             Range::new(3, 5).merge(Range::new(5, 7))
387         );
388         assert_eq!(
389             Some(Range::new(3, 7)),
390             Range::new(3, 5).merge(Range::new(6, 7))
391         );
392         assert_eq!(
393             Some(Range::new(3, 7)),
394             Range::new(3, 7).merge(Range::new(4, 5))
395         );
396     }
397
398     use super::json::{self, json};
399     use super::{FileLines, FileName};
400     use std::{collections::HashMap, path::PathBuf};
401
402     #[test]
403     fn file_lines_to_json() {
404         let ranges: HashMap<FileName, Vec<Range>> = [
405             (
406                 FileName::Real(PathBuf::from("src/main.rs")),
407                 vec![Range::new(1, 3), Range::new(5, 7)],
408             ),
409             (
410                 FileName::Real(PathBuf::from("src/lib.rs")),
411                 vec![Range::new(1, 7)],
412             ),
413         ]
414         .iter()
415         .cloned()
416         .collect();
417
418         let file_lines = FileLines::from_ranges(ranges);
419         let mut spans = file_lines.to_json_spans();
420         spans.sort();
421         let json = json::to_value(&spans).unwrap();
422         assert_eq!(
423             json,
424             json! {[
425                 {"file": "src/lib.rs",  "range": [1, 7]},
426                 {"file": "src/main.rs", "range": [1, 3]},
427                 {"file": "src/main.rs", "range": [5, 7]},
428             ]}
429         );
430     }
431 }