]> git.lizzy.rs Git - rust.git/blobdiff - src/config/file_lines.rs
add new flag to list names of misformatted files (#3747)
[rust.git] / src / config / file_lines.rs
index 436c7a83b81f6ade58945df03bfa593c4f676d04..f0dc6c66597033b7a0a6fb8b03729668beee9e5f 100644 (file)
@@ -1,28 +1,19 @@
-// 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.
-
 //! This module contains types and functions to support formatting specific line ranges.
 
+use itertools::Itertools;
 use std::collections::HashMap;
 use std::path::PathBuf;
 use std::rc::Rc;
 use std::{cmp, fmt, iter, str};
 
-use serde::de::{Deserialize, Deserializer};
+use serde::{ser, Deserialize, Deserializer, Serialize, Serializer};
 use serde_json as json;
 
-use syntax::codemap::{self, FileMap};
+use syntax::source_map::{self, SourceFile};
 
 /// A range of lines in a file, inclusive of both ends.
 pub struct LineRange {
-    pub file: Rc<FileMap>,
+    pub file: Rc<SourceFile>,
     pub lo: usize,
     pub hi: usize,
 }
@@ -34,18 +25,18 @@ pub enum FileName {
     Stdin,
 }
 
-impl From<codemap::FileName> for FileName {
-    fn from(name: codemap::FileName) -> FileName {
+impl From<source_map::FileName> for FileName {
+    fn from(name: source_map::FileName) -> FileName {
         match name {
-            codemap::FileName::Real(p) => FileName::Real(p),
-            codemap::FileName::Custom(ref f) if f == "stdin" => FileName::Stdin,
+            source_map::FileName::Real(p) => FileName::Real(p),
+            source_map::FileName::Custom(ref f) if f == "stdin" => FileName::Stdin,
             _ => unreachable!(),
         }
     }
 }
 
 impl fmt::Display for FileName {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
             FileName::Real(p) => write!(f, "{}", p.to_str().unwrap()),
             FileName::Stdin => write!(f, "stdin"),
@@ -53,6 +44,36 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
     }
 }
 
+impl<'de> Deserialize<'de> for FileName {
+    fn deserialize<D>(deserializer: D) -> Result<FileName, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let s = String::deserialize(deserializer)?;
+        if s == "stdin" {
+            Ok(FileName::Stdin)
+        } else {
+            Ok(FileName::Real(s.into()))
+        }
+    }
+}
+
+impl Serialize for FileName {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let s = match self {
+            FileName::Stdin => Ok("stdin"),
+            FileName::Real(path) => path
+                .to_str()
+                .ok_or_else(|| ser::Error::custom("path can't be serialized as UTF-8 string")),
+        };
+
+        s.and_then(|s| serializer.serialize_str(s))
+    }
+}
+
 impl LineRange {
     pub fn file_name(&self) -> FileName {
         self.file.name.clone().into()
@@ -72,6 +93,12 @@ fn from(range: &'a LineRange) -> Range {
     }
 }
 
+impl fmt::Display for Range {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}..{}", self.lo, self.hi)
+    }
+}
+
 impl Range {
     pub fn new(lo: usize, hi: usize) -> Range {
         Range { lo, hi }
@@ -129,26 +156,39 @@ fn merge(self, other: Range) -> Option<Range> {
 #[derive(Clone, Debug, Default, PartialEq)]
 pub struct FileLines(Option<HashMap<FileName, Vec<Range>>>);
 
+impl fmt::Display for FileLines {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match &self.0 {
+            None => write!(f, "None")?,
+            Some(map) => {
+                for (file_name, ranges) in map.iter() {
+                    write!(f, "{}: ", file_name)?;
+                    write!(f, "{}\n", ranges.iter().format(", "))?;
+                }
+            }
+        };
+        Ok(())
+    }
+}
+
 /// Normalizes the ranges so that the invariants for `FileLines` hold: ranges are non-overlapping,
 /// and ordered by their start point.
 fn normalize_ranges(ranges: &mut HashMap<FileName, Vec<Range>>) {
     for ranges in ranges.values_mut() {
         ranges.sort();
         let mut result = vec![];
-        {
-            let mut iter = ranges.into_iter().peekable();
-            while let Some(next) = iter.next() {
-                let mut next = *next;
-                while let Some(&&mut peek) = iter.peek() {
-                    if let Some(merged) = next.merge(peek) {
-                        iter.next().unwrap();
-                        next = merged;
-                    } else {
-                        break;
-                    }
+        let mut iter = ranges.iter_mut().peekable();
+        while let Some(next) = iter.next() {
+            let mut next = *next;
+            while let Some(&&mut peek) = iter.peek() {
+                if let Some(merged) = next.merge(peek) {
+                    iter.next().unwrap();
+                    next = merged;
+                } else {
+                    break;
                 }
-                result.push(next)
             }
+            result.push(next)
         }
         *ranges = result;
     }
@@ -160,7 +200,7 @@ pub(crate) fn all() -> FileLines {
         FileLines(None)
     }
 
-    /// Returns true if this `FileLines` contains all lines in all files.
+    /// Returns `true` if this `FileLines` contains all lines in all files.
     pub(crate) fn is_all(&self) -> bool {
         self.0.is_none()
     }
@@ -171,12 +211,27 @@ pub fn from_ranges(mut ranges: HashMap<FileName, Vec<Range>>) -> FileLines {
     }
 
     /// Returns an iterator over the files contained in `self`.
-    pub fn files(&self) -> Files {
-        Files(self.0.as_ref().map(|m| m.keys()))
+    pub fn files(&self) -> Files<'_> {
+        Files(self.0.as_ref().map(HashMap::keys))
+    }
+
+    /// Returns JSON representation as accepted by the `--file-lines JSON` arg.
+    pub fn to_json_spans(&self) -> Vec<JsonSpan> {
+        match &self.0 {
+            None => vec![],
+            Some(file_ranges) => file_ranges
+                .iter()
+                .flat_map(|(file, ranges)| ranges.iter().map(move |r| (file, r)))
+                .map(|(file, range)| JsonSpan {
+                    file: file.to_owned(),
+                    range: (range.lo, range.hi),
+                })
+                .collect(),
+        }
     }
 
-    /// Returns true if `self` includes all lines in all files. Otherwise runs `f` on all ranges in
-    /// the designated file (if any) and returns true if `f` ever does.
+    /// Returns `true` if `self` includes all lines in all files. Otherwise runs `f` on all ranges
+    /// in the designated file (if any) and returns true if `f` ever does.
     fn file_range_matches<F>(&self, file_name: &FileName, f: F) -> bool
     where
         F: FnMut(&Range) -> bool,
@@ -193,23 +248,23 @@ fn file_range_matches<F>(&self, file_name: &FileName, f: F) -> bool
         }
     }
 
-    /// Returns true if `range` is fully contained in `self`.
+    /// Returns `true` if `range` is fully contained in `self`.
     #[allow(dead_code)]
     pub(crate) fn contains(&self, range: &LineRange) -> bool {
         self.file_range_matches(&range.file_name(), |r| r.contains(Range::from(range)))
     }
 
-    /// Returns true if any lines in `range` are in `self`.
+    /// Returns `true` if any lines in `range` are in `self`.
     pub(crate) fn intersects(&self, range: &LineRange) -> bool {
         self.file_range_matches(&range.file_name(), |r| r.intersects(Range::from(range)))
     }
 
-    /// Returns true if `line` from `file_name` is in `self`.
+    /// Returns `true` if `line` from `file_name` is in `self`.
     pub(crate) fn contains_line(&self, file_name: &FileName, line: usize) -> bool {
         self.file_range_matches(file_name, |r| r.lo <= line && r.hi >= line)
     }
 
-    /// Returns true if all the lines between `lo` and `hi` from `file_name` are in `self`.
+    /// Returns `true` if all the lines between `lo` and `hi` from `file_name` are in `self`.
     pub(crate) fn contains_range(&self, file_name: &FileName, lo: usize, hi: usize) -> bool {
         self.file_range_matches(file_name, |r| r.contains(Range::new(lo, hi)))
     }
@@ -249,22 +304,12 @@ fn from_str(s: &str) -> Result<FileLines, String> {
 }
 
 // For JSON decoding.
-#[derive(Clone, Debug, Deserialize)]
-struct JsonSpan {
-    #[serde(deserialize_with = "deserialize_filename")]
+#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]
+pub struct JsonSpan {
     file: FileName,
     range: (usize, usize),
 }
 
-fn deserialize_filename<'de, D: Deserializer<'de>>(d: D) -> Result<FileName, D::Error> {
-    let s = String::deserialize(d)?;
-    if s == "stdin" {
-        Ok(FileName::Stdin)
-    } else {
-        Ok(FileName::Real(s.into()))
-    }
-}
-
 impl JsonSpan {
     fn into_tuple(self) -> Result<(FileName, Range), String> {
         let (lo, hi) = self.range;
@@ -350,4 +395,38 @@ fn test_range_merge() {
             Range::new(3, 7).merge(Range::new(4, 5))
         );
     }
+
+    use super::json::{self, json};
+    use super::{FileLines, FileName};
+    use std::{collections::HashMap, path::PathBuf};
+
+    #[test]
+    fn file_lines_to_json() {
+        let ranges: HashMap<FileName, Vec<Range>> = [
+            (
+                FileName::Real(PathBuf::from("src/main.rs")),
+                vec![Range::new(1, 3), Range::new(5, 7)],
+            ),
+            (
+                FileName::Real(PathBuf::from("src/lib.rs")),
+                vec![Range::new(1, 7)],
+            ),
+        ]
+        .iter()
+        .cloned()
+        .collect();
+
+        let file_lines = FileLines::from_ranges(ranges);
+        let mut spans = file_lines.to_json_spans();
+        spans.sort();
+        let json = json::to_value(&spans).unwrap();
+        assert_eq!(
+            json,
+            json! {[
+                {"file": "src/lib.rs",  "range": [1, 7]},
+                {"file": "src/main.rs", "range": [1, 3]},
+                {"file": "src/main.rs", "range": [5, 7]},
+            ]}
+        );
+    }
 }