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