diff --git a/src/config/file_lines.rs b/src/config/file_lines.rs index 436c7a83b81..58f65b3c1b6 100644 --- a/src/config/file_lines.rs +++ b/src/config/file_lines.rs @@ -16,6 +16,7 @@ use std::rc::Rc; use std::{cmp, fmt, iter, str}; use serde::de::{Deserialize, Deserializer}; +use serde::ser::{self, Serialize, Serializer}; use serde_json as json; use syntax::codemap::{self, FileMap}; @@ -53,6 +54,36 @@ impl fmt::Display for FileName { } } +impl<'de> Deserialize<'de> for FileName { + fn deserialize(deserializer: D) -> Result + 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(&self, serializer: S) -> Result + 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() @@ -175,6 +206,20 @@ impl FileLines { Files(self.0.as_ref().map(|m| m.keys())) } + /// Returns JSON representation as accepted by the `--file-lines JSON` arg. + pub fn to_json_spans(&self) -> Vec { + 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. fn file_range_matches(&self, file_name: &FileName, f: F) -> bool @@ -249,22 +294,12 @@ impl str::FromStr for FileLines { } // 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 { - 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 +385,38 @@ mod test { Range::new(3, 7).merge(Range::new(4, 5)) ); } + + use super::json::{self, json, json_internal}; + use super::{FileLines, FileName}; + use std::{collections::HashMap, path::PathBuf}; + + #[test] + fn file_lines_to_json() { + let ranges: HashMap> = [ + ( + 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]}, + ]} + ); + } }