1 //! This module contains types and functions to support formatting specific line ranges.
3 use std::collections::HashMap;
4 use std::path::PathBuf;
6 use std::{cmp, fmt, iter, str};
8 use serde::de::{Deserialize, Deserializer};
9 use serde::ser::{self, Serialize, Serializer};
10 use serde_json as json;
12 use syntax::source_map::{self, SourceFile};
14 /// A range of lines in a file, inclusive of both ends.
15 pub struct LineRange {
16 pub file: Rc<SourceFile>,
21 /// Defines the name of an input - either a file or stdin.
22 #[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
28 impl From<source_map::FileName> for FileName {
29 fn from(name: source_map::FileName) -> FileName {
31 source_map::FileName::Real(p) => FileName::Real(p),
32 source_map::FileName::Custom(ref f) if f == "stdin" => FileName::Stdin,
38 impl fmt::Display for FileName {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 FileName::Real(p) => write!(f, "{}", p.to_str().unwrap()),
42 FileName::Stdin => write!(f, "stdin"),
47 impl<'de> Deserialize<'de> for FileName {
48 fn deserialize<D>(deserializer: D) -> Result<FileName, D::Error>
52 let s = String::deserialize(deserializer)?;
56 Ok(FileName::Real(s.into()))
61 impl Serialize for FileName {
62 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
67 FileName::Stdin => Ok("stdin"),
68 FileName::Real(path) => path
70 .ok_or_else(|| ser::Error::custom("path can't be serialized as UTF-8 string")),
73 s.and_then(|s| serializer.serialize_str(s))
78 pub fn file_name(&self) -> FileName {
79 self.file.name.clone().into()
83 /// A range that is inclusive of both ends.
84 #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Deserialize)]
90 impl<'a> From<&'a LineRange> for Range {
91 fn from(range: &'a LineRange) -> Range {
92 Range::new(range.lo, range.hi)
97 pub fn new(lo: usize, hi: usize) -> Range {
101 fn is_empty(self) -> bool {
106 fn contains(self, other: Range) -> bool {
107 if other.is_empty() {
110 !self.is_empty() && self.lo <= other.lo && self.hi >= other.hi
114 fn intersects(self, other: Range) -> bool {
115 if self.is_empty() || other.is_empty() {
118 (self.lo <= other.hi && other.hi <= self.hi)
119 || (other.lo <= self.hi && self.hi <= other.hi)
123 fn adjacent_to(self, other: Range) -> bool {
124 if self.is_empty() || other.is_empty() {
127 self.hi + 1 == other.lo || other.hi + 1 == self.lo
131 /// Returns a new `Range` with lines from `self` and `other` if they were adjacent or
132 /// intersect; returns `None` otherwise.
133 fn merge(self, other: Range) -> Option<Range> {
134 if self.adjacent_to(other) || self.intersects(other) {
136 cmp::min(self.lo, other.lo),
137 cmp::max(self.hi, other.hi),
145 /// A set of lines in files.
147 /// It is represented as a multimap keyed on file names, with values a collection of
148 /// non-overlapping ranges sorted by their start point. An inner `None` is interpreted to mean all
149 /// lines in all files.
150 #[derive(Clone, Debug, Default, PartialEq)]
151 pub struct FileLines(Option<HashMap<FileName, Vec<Range>>>);
153 /// Normalizes the ranges so that the invariants for `FileLines` hold: ranges are non-overlapping,
154 /// and ordered by their start point.
155 fn normalize_ranges(ranges: &mut HashMap<FileName, Vec<Range>>) {
156 for ranges in ranges.values_mut() {
158 let mut result = vec![];
160 let mut iter = ranges.iter_mut().peekable();
161 while let Some(next) = iter.next() {
162 let mut next = *next;
163 while let Some(&&mut peek) = iter.peek() {
164 if let Some(merged) = next.merge(peek) {
165 iter.next().unwrap();
179 /// Creates a `FileLines` that contains all lines in all files.
180 pub(crate) fn all() -> FileLines {
184 /// Returns `true` if this `FileLines` contains all lines in all files.
185 pub(crate) fn is_all(&self) -> bool {
189 pub fn from_ranges(mut ranges: HashMap<FileName, Vec<Range>>) -> FileLines {
190 normalize_ranges(&mut ranges);
191 FileLines(Some(ranges))
194 /// Returns an iterator over the files contained in `self`.
195 pub fn files(&self) -> Files<'_> {
196 Files(self.0.as_ref().map(|m| m.keys()))
199 /// Returns JSON representation as accepted by the `--file-lines JSON` arg.
200 pub fn to_json_spans(&self) -> Vec<JsonSpan> {
203 Some(file_ranges) => file_ranges
205 .flat_map(|(file, ranges)| ranges.iter().map(move |r| (file, r)))
206 .map(|(file, range)| JsonSpan {
207 file: file.to_owned(),
208 range: (range.lo, range.hi),
214 /// Returns `true` if `self` includes all lines in all files. Otherwise runs `f` on all ranges
215 /// in the designated file (if any) and returns true if `f` ever does.
216 fn file_range_matches<F>(&self, file_name: &FileName, f: F) -> bool
218 F: FnMut(&Range) -> bool,
220 let map = match self.0 {
221 // `None` means "all lines in all files".
223 Some(ref map) => map,
226 match canonicalize_path_string(file_name).and_then(|file| map.get(&file)) {
227 Some(ranges) => ranges.iter().any(f),
232 /// Returns `true` if `range` is fully contained in `self`.
234 pub(crate) fn contains(&self, range: &LineRange) -> bool {
235 self.file_range_matches(&range.file_name(), |r| r.contains(Range::from(range)))
238 /// Returns `true` if any lines in `range` are in `self`.
239 pub(crate) fn intersects(&self, range: &LineRange) -> bool {
240 self.file_range_matches(&range.file_name(), |r| r.intersects(Range::from(range)))
243 /// Returns `true` if `line` from `file_name` is in `self`.
244 pub(crate) fn contains_line(&self, file_name: &FileName, line: usize) -> bool {
245 self.file_range_matches(file_name, |r| r.lo <= line && r.hi >= line)
248 /// Returns `true` if all the lines between `lo` and `hi` from `file_name` are in `self`.
249 pub(crate) fn contains_range(&self, file_name: &FileName, lo: usize, hi: usize) -> bool {
250 self.file_range_matches(file_name, |r| r.contains(Range::new(lo, hi)))
254 /// `FileLines` files iterator.
255 pub struct Files<'a>(Option<::std::collections::hash_map::Keys<'a, FileName, Vec<Range>>>);
257 impl<'a> iter::Iterator for Files<'a> {
258 type Item = &'a FileName;
260 fn next(&mut self) -> Option<&'a FileName> {
261 self.0.as_mut().and_then(Iterator::next)
265 fn canonicalize_path_string(file: &FileName) -> Option<FileName> {
267 FileName::Real(ref path) => path.canonicalize().ok().map(FileName::Real),
268 _ => Some(file.clone()),
272 // This impl is needed for `Config::override_value` to work for use in tests.
273 impl str::FromStr for FileLines {
276 fn from_str(s: &str) -> Result<FileLines, String> {
277 let v: Vec<JsonSpan> = json::from_str(s).map_err(|e| e.to_string())?;
278 let mut m = HashMap::new();
280 let (s, r) = JsonSpan::into_tuple(js)?;
281 m.entry(s).or_insert_with(|| vec![]).push(r);
283 Ok(FileLines::from_ranges(m))
287 // For JSON decoding.
288 #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]
289 pub struct JsonSpan {
291 range: (usize, usize),
295 fn into_tuple(self) -> Result<(FileName, Range), String> {
296 let (lo, hi) = self.range;
297 let canonical = canonicalize_path_string(&self.file)
298 .ok_or_else(|| format!("Can't canonicalize {}", &self.file))?;
299 Ok((canonical, Range::new(lo, hi)))
303 // This impl is needed for inclusion in the `Config` struct. We don't have a toml representation
304 // for `FileLines`, so it will just panic instead.
305 impl<'de> ::serde::de::Deserialize<'de> for FileLines {
306 fn deserialize<D>(_: D) -> Result<Self, D::Error>
308 D: ::serde::de::Deserializer<'de>,
311 "FileLines cannot be deserialized from a project rustfmt.toml file: please \
312 specify it via the `--file-lines` option instead"
317 // We also want to avoid attempting to serialize a FileLines to toml. The
318 // `Config` struct should ensure this impl is never reached.
319 impl ::serde::ser::Serialize for FileLines {
320 fn serialize<S>(&self, _: S) -> Result<S::Ok, S::Error>
322 S: ::serde::ser::Serializer,
324 unreachable!("FileLines cannot be serialized. This is a rustfmt bug.");
333 fn test_range_intersects() {
334 assert!(Range::new(1, 2).intersects(Range::new(1, 1)));
335 assert!(Range::new(1, 2).intersects(Range::new(2, 2)));
336 assert!(!Range::new(1, 2).intersects(Range::new(0, 0)));
337 assert!(!Range::new(1, 2).intersects(Range::new(3, 10)));
338 assert!(!Range::new(1, 3).intersects(Range::new(5, 5)));
342 fn test_range_adjacent_to() {
343 assert!(!Range::new(1, 2).adjacent_to(Range::new(1, 1)));
344 assert!(!Range::new(1, 2).adjacent_to(Range::new(2, 2)));
345 assert!(Range::new(1, 2).adjacent_to(Range::new(0, 0)));
346 assert!(Range::new(1, 2).adjacent_to(Range::new(3, 10)));
347 assert!(!Range::new(1, 3).adjacent_to(Range::new(5, 5)));
351 fn test_range_contains() {
352 assert!(Range::new(1, 2).contains(Range::new(1, 1)));
353 assert!(Range::new(1, 2).contains(Range::new(2, 2)));
354 assert!(!Range::new(1, 2).contains(Range::new(0, 0)));
355 assert!(!Range::new(1, 2).contains(Range::new(3, 10)));
359 fn test_range_merge() {
360 assert_eq!(None, Range::new(1, 3).merge(Range::new(5, 5)));
361 assert_eq!(None, Range::new(4, 7).merge(Range::new(0, 1)));
363 Some(Range::new(3, 7)),
364 Range::new(3, 5).merge(Range::new(4, 7))
367 Some(Range::new(3, 7)),
368 Range::new(3, 5).merge(Range::new(5, 7))
371 Some(Range::new(3, 7)),
372 Range::new(3, 5).merge(Range::new(6, 7))
375 Some(Range::new(3, 7)),
376 Range::new(3, 7).merge(Range::new(4, 5))
380 use super::json::{self, json};
381 use super::{FileLines, FileName};
382 use std::{collections::HashMap, path::PathBuf};
385 fn file_lines_to_json() {
386 let ranges: HashMap<FileName, Vec<Range>> = [
388 FileName::Real(PathBuf::from("src/main.rs")),
389 vec![Range::new(1, 3), Range::new(5, 7)],
392 FileName::Real(PathBuf::from("src/lib.rs")),
393 vec![Range::new(1, 7)],
400 let file_lines = FileLines::from_ranges(ranges);
401 let mut spans = file_lines.to_json_spans();
403 let json = json::to_value(&spans).unwrap();
407 {"file": "src/lib.rs", "range": [1, 7]},
408 {"file": "src/main.rs", "range": [1, 3]},
409 {"file": "src/main.rs", "range": [5, 7]},