2016-05-30 09:10:12 -05:00
|
|
|
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
|
|
|
|
// file at the top-level directory of this distribution and at
|
|
|
|
// http://rust-lang.org/COPYRIGHT.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
|
|
// option. This file may not be copied, modified, or distributed
|
|
|
|
// except according to those terms.
|
|
|
|
|
2016-05-26 07:17:20 -05:00
|
|
|
//! This module contains types and functions to support formatting specific line ranges.
|
2016-09-15 22:19:18 -05:00
|
|
|
|
2016-07-18 16:05:01 -05:00
|
|
|
use std::{cmp, iter, path, str};
|
2016-05-26 07:17:20 -05:00
|
|
|
|
|
|
|
use itertools::Itertools;
|
|
|
|
use multimap::MultiMap;
|
2017-05-03 03:23:50 -05:00
|
|
|
use serde_json as json;
|
2016-05-26 07:17:20 -05:00
|
|
|
|
|
|
|
use codemap::LineRange;
|
|
|
|
|
|
|
|
/// A range that is inclusive of both ends.
|
2017-05-03 03:23:50 -05:00
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize)]
|
2016-05-26 07:17:20 -05:00
|
|
|
struct Range {
|
|
|
|
pub lo: usize,
|
|
|
|
pub hi: usize,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> From<&'a LineRange> for Range {
|
|
|
|
fn from(range: &'a LineRange) -> Range {
|
|
|
|
Range::new(range.lo, range.hi)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Range {
|
|
|
|
fn new(lo: usize, hi: usize) -> Range {
|
|
|
|
Range { lo: lo, hi: hi }
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_empty(self) -> bool {
|
|
|
|
self.lo > self.hi
|
|
|
|
}
|
|
|
|
|
|
|
|
fn contains(self, other: Range) -> bool {
|
|
|
|
if other.is_empty() {
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
!self.is_empty() && self.lo <= other.lo && self.hi >= other.hi
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn intersects(self, other: Range) -> bool {
|
|
|
|
if self.is_empty() || other.is_empty() {
|
|
|
|
false
|
|
|
|
} else {
|
|
|
|
(self.lo <= other.hi && other.hi <= self.hi) ||
|
|
|
|
(other.lo <= self.hi && self.hi <= other.hi)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn adjacent_to(self, other: Range) -> bool {
|
|
|
|
if self.is_empty() || other.is_empty() {
|
|
|
|
false
|
|
|
|
} else {
|
|
|
|
self.hi + 1 == other.lo || other.hi + 1 == self.lo
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns a new `Range` with lines from `self` and `other` if they were adjacent or
|
|
|
|
/// intersect; returns `None` otherwise.
|
|
|
|
fn merge(self, other: Range) -> Option<Range> {
|
|
|
|
if self.adjacent_to(other) || self.intersects(other) {
|
|
|
|
Some(Range::new(cmp::min(self.lo, other.lo), cmp::max(self.hi, other.hi)))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A set of lines in files.
|
|
|
|
///
|
|
|
|
/// It is represented as a multimap keyed on file names, with values a collection of
|
|
|
|
/// non-overlapping ranges sorted by their start point. An inner `None` is interpreted to mean all
|
|
|
|
/// lines in all files.
|
|
|
|
#[derive(Clone, Debug, Default)]
|
|
|
|
pub struct FileLines(Option<MultiMap<String, Range>>);
|
|
|
|
|
|
|
|
/// Normalizes the ranges so that the invariants for `FileLines` hold: ranges are non-overlapping,
|
|
|
|
/// and ordered by their start point.
|
|
|
|
fn normalize_ranges(map: &mut MultiMap<String, Range>) {
|
|
|
|
for (_, ranges) in map.iter_all_mut() {
|
|
|
|
ranges.sort_by_key(|x| x.lo);
|
2017-03-27 17:14:47 -05:00
|
|
|
let merged = ranges
|
|
|
|
.drain(..)
|
|
|
|
.coalesce(|x, y| x.merge(y).ok_or((x, y)))
|
|
|
|
.collect();
|
2016-05-26 07:17:20 -05:00
|
|
|
*ranges = merged;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FileLines {
|
|
|
|
/// Creates a `FileLines` that contains all lines in all files.
|
|
|
|
pub fn all() -> FileLines {
|
|
|
|
FileLines(None)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Creates a `FileLines` from a `MultiMap`, ensuring that the invariants hold.
|
|
|
|
fn from_multimap(map: MultiMap<String, Range>) -> FileLines {
|
|
|
|
let mut map = map;
|
|
|
|
normalize_ranges(&mut map);
|
|
|
|
FileLines(Some(map))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns an iterator over the files contained in `self`.
|
|
|
|
pub fn files(&self) -> Files {
|
|
|
|
Files(self.0.as_ref().map(MultiMap::keys))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns true if `range` is fully contained in `self`.
|
|
|
|
pub fn contains(&self, range: &LineRange) -> bool {
|
|
|
|
let map = match self.0 {
|
|
|
|
// `None` means "all lines in all files".
|
|
|
|
None => return true,
|
|
|
|
Some(ref map) => map,
|
|
|
|
};
|
|
|
|
|
2017-05-02 21:26:25 -05:00
|
|
|
match canonicalize_path_string(range.file_name())
|
|
|
|
.and_then(|canonical| map.get_vec(&canonical).ok_or(())) {
|
2017-03-27 17:25:59 -05:00
|
|
|
Ok(ranges) => ranges.iter().any(|r| r.contains(Range::from(range))),
|
2016-07-18 16:05:01 -05:00
|
|
|
Err(_) => false,
|
2016-05-26 07:17:20 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns true if any lines in `range` are in `self`.
|
|
|
|
pub fn intersects(&self, range: &LineRange) -> bool {
|
|
|
|
let map = match self.0 {
|
|
|
|
// `None` means "all lines in all files".
|
|
|
|
None => return true,
|
|
|
|
Some(ref map) => map,
|
|
|
|
};
|
|
|
|
|
2017-05-02 21:26:25 -05:00
|
|
|
match canonicalize_path_string(range.file_name())
|
|
|
|
.and_then(|canonical| map.get_vec(&canonical).ok_or(())) {
|
2017-03-29 17:50:39 -05:00
|
|
|
Ok(ranges) => ranges.iter().any(|r| r.intersects(Range::from(range))),
|
|
|
|
Err(_) => false,
|
2016-05-26 07:17:20 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// FileLines files iterator.
|
|
|
|
pub struct Files<'a>(Option<::std::collections::hash_map::Keys<'a, String, Vec<Range>>>);
|
|
|
|
|
|
|
|
impl<'a> iter::Iterator for Files<'a> {
|
|
|
|
type Item = &'a String;
|
|
|
|
|
|
|
|
fn next(&mut self) -> Option<&'a String> {
|
|
|
|
self.0.as_mut().and_then(Iterator::next)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-18 16:05:01 -05:00
|
|
|
fn canonicalize_path_string(s: &str) -> Result<String, ()> {
|
2017-03-30 18:16:06 -05:00
|
|
|
if s == "stdin" {
|
|
|
|
return Ok(s.to_string());
|
|
|
|
}
|
|
|
|
|
2016-07-18 16:05:01 -05:00
|
|
|
match path::PathBuf::from(s).canonicalize() {
|
2017-03-27 17:25:59 -05:00
|
|
|
Ok(canonicalized) => canonicalized.to_str().map(|s| s.to_string()).ok_or(()),
|
2016-07-18 16:05:01 -05:00
|
|
|
_ => Err(()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-26 07:17:20 -05:00
|
|
|
// This impl is needed for `Config::override_value` to work for use in tests.
|
|
|
|
impl str::FromStr for FileLines {
|
|
|
|
type Err = String;
|
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<FileLines, String> {
|
2017-05-03 03:23:50 -05:00
|
|
|
let v: Vec<JsonSpan> = try!(json::from_str(s).map_err(|e| e.to_string()));
|
2017-03-27 17:25:59 -05:00
|
|
|
let m = try!(v.into_iter().map(JsonSpan::into_tuple).collect());
|
2016-05-26 07:17:20 -05:00
|
|
|
Ok(FileLines::from_multimap(m))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// For JSON decoding.
|
2017-05-03 03:23:50 -05:00
|
|
|
#[derive(Clone, Debug, Deserialize)]
|
2016-05-26 07:17:20 -05:00
|
|
|
struct JsonSpan {
|
|
|
|
file: String,
|
|
|
|
range: (usize, usize),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl JsonSpan {
|
|
|
|
// To allow `collect()`ing into a `MultiMap`.
|
2016-07-18 16:05:01 -05:00
|
|
|
fn into_tuple(self) -> Result<(String, Range), String> {
|
2016-05-26 07:17:20 -05:00
|
|
|
let (lo, hi) = self.range;
|
2017-04-17 08:57:07 -05:00
|
|
|
let canonical = try!(canonicalize_path_string(&self.file)
|
|
|
|
.map_err(|_| format!("Can't canonicalize {}", &self.file)));
|
2016-07-18 16:05:01 -05:00
|
|
|
Ok((canonical, Range::new(lo, hi)))
|
2016-05-26 07:17:20 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// This impl is needed for inclusion in the `Config` struct. We don't have a toml representation
|
|
|
|
// for `FileLines`, so it will just panic instead.
|
2017-05-03 03:23:50 -05:00
|
|
|
impl<'de> ::serde::de::Deserialize<'de> for FileLines {
|
|
|
|
fn deserialize<D>(_: D) -> Result<Self, D::Error>
|
|
|
|
where D: ::serde::de::Deserializer<'de>
|
|
|
|
{
|
2016-05-30 09:41:43 -05:00
|
|
|
panic!("FileLines cannot be deserialized from a project rustfmt.toml file: please \
|
|
|
|
specify it via the `--file-lines` option instead");
|
2016-05-26 07:17:20 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use super::Range;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_range_intersects() {
|
|
|
|
assert!(Range::new(1, 2).intersects(Range::new(1, 1)));
|
|
|
|
assert!(Range::new(1, 2).intersects(Range::new(2, 2)));
|
|
|
|
assert!(!Range::new(1, 2).intersects(Range::new(0, 0)));
|
|
|
|
assert!(!Range::new(1, 2).intersects(Range::new(3, 10)));
|
|
|
|
assert!(!Range::new(1, 3).intersects(Range::new(5, 5)));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_range_adjacent_to() {
|
|
|
|
assert!(!Range::new(1, 2).adjacent_to(Range::new(1, 1)));
|
|
|
|
assert!(!Range::new(1, 2).adjacent_to(Range::new(2, 2)));
|
|
|
|
assert!(Range::new(1, 2).adjacent_to(Range::new(0, 0)));
|
|
|
|
assert!(Range::new(1, 2).adjacent_to(Range::new(3, 10)));
|
|
|
|
assert!(!Range::new(1, 3).adjacent_to(Range::new(5, 5)));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_range_contains() {
|
|
|
|
assert!(Range::new(1, 2).contains(Range::new(1, 1)));
|
|
|
|
assert!(Range::new(1, 2).contains(Range::new(2, 2)));
|
|
|
|
assert!(!Range::new(1, 2).contains(Range::new(0, 0)));
|
|
|
|
assert!(!Range::new(1, 2).contains(Range::new(3, 10)));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_range_merge() {
|
|
|
|
assert_eq!(None, Range::new(1, 3).merge(Range::new(5, 5)));
|
|
|
|
assert_eq!(None, Range::new(4, 7).merge(Range::new(0, 1)));
|
|
|
|
assert_eq!(Some(Range::new(3, 7)),
|
|
|
|
Range::new(3, 5).merge(Range::new(4, 7)));
|
|
|
|
assert_eq!(Some(Range::new(3, 7)),
|
|
|
|
Range::new(3, 5).merge(Range::new(5, 7)));
|
|
|
|
assert_eq!(Some(Range::new(3, 7)),
|
|
|
|
Range::new(3, 5).merge(Range::new(6, 7)));
|
|
|
|
assert_eq!(Some(Range::new(3, 7)),
|
|
|
|
Range::new(3, 7).merge(Range::new(4, 5)));
|
|
|
|
}
|
|
|
|
}
|