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