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