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.
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.
11 //! This module contains types and functions to support formatting specific line ranges.
13 use std::collections::HashMap;
14 use std::path::PathBuf;
16 use std::{cmp, fmt, iter, str};
18 use serde::de::{Deserialize, Deserializer};
19 use serde_json as json;
21 use syntax::codemap::{self, FileMap};
23 /// A range of lines in a file, inclusive of both ends.
24 pub struct LineRange {
25 pub file: Rc<FileMap>,
30 #[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
36 impl From<codemap::FileName> for FileName {
37 fn from(name: codemap::FileName) -> FileName {
39 codemap::FileName::Real(p) => FileName::Real(p),
40 codemap::FileName::Custom(ref f) if f == "stdin" => FileName::Stdin,
46 impl fmt::Display for FileName {
47 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
49 FileName::Real(p) => write!(f, "{}", p.to_str().unwrap()),
50 FileName::Stdin => write!(f, "stdin"),
56 pub fn file_name(&self) -> FileName {
57 self.file.name.clone().into()
61 /// A range that is inclusive of both ends.
62 #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Deserialize)]
68 impl<'a> From<&'a LineRange> for Range {
69 fn from(range: &'a LineRange) -> Range {
70 Range::new(range.lo, range.hi)
75 pub fn new(lo: usize, hi: usize) -> Range {
79 fn is_empty(self) -> bool {
84 fn contains(self, other: Range) -> bool {
88 !self.is_empty() && self.lo <= other.lo && self.hi >= other.hi
92 fn intersects(self, other: Range) -> bool {
93 if self.is_empty() || other.is_empty() {
96 (self.lo <= other.hi && other.hi <= self.hi)
97 || (other.lo <= self.hi && self.hi <= other.hi)
101 fn adjacent_to(self, other: Range) -> bool {
102 if self.is_empty() || other.is_empty() {
105 self.hi + 1 == other.lo || other.hi + 1 == self.lo
109 /// Returns a new `Range` with lines from `self` and `other` if they were adjacent or
110 /// intersect; returns `None` otherwise.
111 fn merge(self, other: Range) -> Option<Range> {
112 if self.adjacent_to(other) || self.intersects(other) {
114 cmp::min(self.lo, other.lo),
115 cmp::max(self.hi, other.hi),
123 /// A set of lines in files.
125 /// It is represented as a multimap keyed on file names, with values a collection of
126 /// non-overlapping ranges sorted by their start point. An inner `None` is interpreted to mean all
127 /// lines in all files.
128 #[derive(Clone, Debug, Default)]
129 pub struct FileLines(Option<HashMap<FileName, Vec<Range>>>);
131 /// Normalizes the ranges so that the invariants for `FileLines` hold: ranges are non-overlapping,
132 /// and ordered by their start point.
133 fn normalize_ranges(ranges: &mut HashMap<FileName, Vec<Range>>) {
134 for ranges in ranges.values_mut() {
136 let mut result = vec![];
138 let mut iter = ranges.into_iter().peekable();
139 while let Some(next) = iter.next() {
140 let mut next = *next;
141 while let Some(&&mut peek) = iter.peek() {
142 if let Some(merged) = next.merge(peek) {
143 iter.next().unwrap();
157 /// Creates a `FileLines` that contains all lines in all files.
158 pub(crate) fn all() -> FileLines {
162 /// Returns true if this `FileLines` contains all lines in all files.
163 pub(crate) fn is_all(&self) -> bool {
167 pub fn from_ranges(mut ranges: HashMap<FileName, Vec<Range>>) -> FileLines {
168 normalize_ranges(&mut ranges);
169 FileLines(Some(ranges))
172 /// Returns an iterator over the files contained in `self`.
173 pub fn files(&self) -> Files {
174 Files(self.0.as_ref().map(|m| m.keys()))
177 /// Returns true if `self` includes all lines in all files. Otherwise runs `f` on all ranges in
178 /// the designated file (if any) and returns true if `f` ever does.
179 fn file_range_matches<F>(&self, file_name: &FileName, f: F) -> bool
181 F: FnMut(&Range) -> bool,
183 let map = match self.0 {
184 // `None` means "all lines in all files".
186 Some(ref map) => map,
189 match canonicalize_path_string(file_name).and_then(|file| map.get(&file)) {
190 Some(ranges) => ranges.iter().any(f),
195 /// Returns true if `range` is fully contained in `self`.
197 pub(crate) fn contains(&self, range: &LineRange) -> bool {
198 self.file_range_matches(&range.file_name(), |r| r.contains(Range::from(range)))
201 /// Returns true if any lines in `range` are in `self`.
202 pub(crate) fn intersects(&self, range: &LineRange) -> bool {
203 self.file_range_matches(&range.file_name(), |r| r.intersects(Range::from(range)))
206 /// Returns true if `line` from `file_name` is in `self`.
207 pub(crate) fn contains_line(&self, file_name: &FileName, line: usize) -> bool {
208 self.file_range_matches(file_name, |r| r.lo <= line && r.hi >= line)
211 /// Returns true if any of the lines between `lo` and `hi` from `file_name` are in `self`.
212 pub(crate) fn intersects_range(&self, file_name: &FileName, lo: usize, hi: usize) -> bool {
213 self.file_range_matches(file_name, |r| r.intersects(Range::new(lo, hi)))
217 /// `FileLines` files iterator.
218 pub struct Files<'a>(Option<::std::collections::hash_map::Keys<'a, FileName, Vec<Range>>>);
220 impl<'a> iter::Iterator for Files<'a> {
221 type Item = &'a FileName;
223 fn next(&mut self) -> Option<&'a FileName> {
224 self.0.as_mut().and_then(Iterator::next)
228 fn canonicalize_path_string(file: &FileName) -> Option<FileName> {
230 FileName::Real(ref path) => path.canonicalize().ok().map(FileName::Real),
231 _ => Some(file.clone()),
235 // This impl is needed for `Config::override_value` to work for use in tests.
236 impl str::FromStr for FileLines {
239 fn from_str(s: &str) -> Result<FileLines, String> {
240 let v: Vec<JsonSpan> = json::from_str(s).map_err(|e| e.to_string())?;
241 let mut m = HashMap::new();
243 let (s, r) = JsonSpan::into_tuple(js)?;
244 m.entry(s).or_insert_with(|| vec![]).push(r);
246 Ok(FileLines::from_ranges(m))
250 // For JSON decoding.
251 #[derive(Clone, Debug, Deserialize)]
253 #[serde(deserialize_with = "deserialize_filename")]
255 range: (usize, usize),
258 fn deserialize_filename<'de, D: Deserializer<'de>>(d: D) -> Result<FileName, D::Error> {
259 let s = String::deserialize(d)?;
263 Ok(FileName::Real(s.into()))
268 fn into_tuple(self) -> Result<(FileName, Range), String> {
269 let (lo, hi) = self.range;
270 let canonical = canonicalize_path_string(&self.file)
271 .ok_or_else(|| format!("Can't canonicalize {}", &self.file))?;
272 Ok((canonical, Range::new(lo, hi)))
276 // This impl is needed for inclusion in the `Config` struct. We don't have a toml representation
277 // for `FileLines`, so it will just panic instead.
278 impl<'de> ::serde::de::Deserialize<'de> for FileLines {
279 fn deserialize<D>(_: D) -> Result<Self, D::Error>
281 D: ::serde::de::Deserializer<'de>,
284 "FileLines cannot be deserialized from a project rustfmt.toml file: please \
285 specify it via the `--file-lines` option instead"
290 // We also want to avoid attempting to serialize a FileLines to toml. The
291 // `Config` struct should ensure this impl is never reached.
292 impl ::serde::ser::Serialize for FileLines {
293 fn serialize<S>(&self, _: S) -> Result<S::Ok, S::Error>
295 S: ::serde::ser::Serializer,
297 unreachable!("FileLines cannot be serialized. This is a rustfmt bug.");
306 fn test_range_intersects() {
307 assert!(Range::new(1, 2).intersects(Range::new(1, 1)));
308 assert!(Range::new(1, 2).intersects(Range::new(2, 2)));
309 assert!(!Range::new(1, 2).intersects(Range::new(0, 0)));
310 assert!(!Range::new(1, 2).intersects(Range::new(3, 10)));
311 assert!(!Range::new(1, 3).intersects(Range::new(5, 5)));
315 fn test_range_adjacent_to() {
316 assert!(!Range::new(1, 2).adjacent_to(Range::new(1, 1)));
317 assert!(!Range::new(1, 2).adjacent_to(Range::new(2, 2)));
318 assert!(Range::new(1, 2).adjacent_to(Range::new(0, 0)));
319 assert!(Range::new(1, 2).adjacent_to(Range::new(3, 10)));
320 assert!(!Range::new(1, 3).adjacent_to(Range::new(5, 5)));
324 fn test_range_contains() {
325 assert!(Range::new(1, 2).contains(Range::new(1, 1)));
326 assert!(Range::new(1, 2).contains(Range::new(2, 2)));
327 assert!(!Range::new(1, 2).contains(Range::new(0, 0)));
328 assert!(!Range::new(1, 2).contains(Range::new(3, 10)));
332 fn test_range_merge() {
333 assert_eq!(None, Range::new(1, 3).merge(Range::new(5, 5)));
334 assert_eq!(None, Range::new(4, 7).merge(Range::new(0, 1)));
336 Some(Range::new(3, 7)),
337 Range::new(3, 5).merge(Range::new(4, 7))
340 Some(Range::new(3, 7)),
341 Range::new(3, 5).merge(Range::new(5, 7))
344 Some(Range::new(3, 7)),
345 Range::new(3, 5).merge(Range::new(6, 7))
348 Some(Range::new(3, 7)),
349 Range::new(3, 7).merge(Range::new(4, 5))