1use itertools::Itertools;
4use std::collections::HashMap;
5use std::path::PathBuf;
6use std::sync::Arc;
7use std::{cmp, fmt, iter, str};
8
9use rustc_span::SourceFile;
10use serde::{Deserialize, Deserializer, Serialize, Serializer, ser};
11use serde_json as json;
12use thiserror::Error;
13
14pub struct LineRange {
16 pub(crate) file: Arc<SourceFile>,
17 pub(crate) lo: usize,
18 pub(crate) hi: usize,
19}
20
21#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
23pub enum FileName {
24 Real(PathBuf),
25 Stdin,
26}
27
28impl From<rustc_span::FileName> for FileName {
29 fn from(name: rustc_span::FileName) -> FileName {
30 match name {
31 rustc_span::FileName::Real(real) => {
32 if let Some(p) = real.into_local_path() {
33 FileName::Real(p)
34 } else {
35 unreachable!()
38 }
39 }
40 rustc_span::FileName::Custom(ref f) if f == "stdin" => FileName::Stdin,
41 _ => unreachable!(),
42 }
43 }
44}
45
46impl fmt::Display for FileName {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 match self {
49 FileName::Real(p) => write!(f, "{}", p.display()),
50 FileName::Stdin => write!(f, "<stdin>"),
51 }
52 }
53}
54
55impl<'de> Deserialize<'de> for FileName {
56 fn deserialize<D>(deserializer: D) -> Result<FileName, D::Error>
57 where
58 D: Deserializer<'de>,
59 {
60 let s = String::deserialize(deserializer)?;
61 if s == "stdin" {
62 Ok(FileName::Stdin)
63 } else {
64 Ok(FileName::Real(s.into()))
65 }
66 }
67}
68
69impl Serialize for FileName {
70 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
71 where
72 S: Serializer,
73 {
74 let s = match self {
75 FileName::Stdin => Ok("stdin"),
76 FileName::Real(path) => path
77 .to_str()
78 .ok_or_else(|| ser::Error::custom("path can't be serialized as UTF-8 string")),
79 };
80
81 s.and_then(|s| serializer.serialize_str(s))
82 }
83}
84
85impl LineRange {
86 pub(crate) fn file_name(&self) -> FileName {
87 self.file.name.clone().into()
88 }
89}
90
91#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Deserialize)]
93pub struct Range {
94 lo: usize,
95 hi: usize,
96}
97
98impl<'a> From<&'a LineRange> for Range {
99 fn from(range: &'a LineRange) -> Range {
100 Range::new(range.lo, range.hi)
101 }
102}
103
104impl fmt::Display for Range {
105 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106 write!(f, "{}..{}", self.lo, self.hi)
107 }
108}
109
110impl Range {
111 pub fn new(lo: usize, hi: usize) -> Range {
112 Range { lo, hi }
113 }
114
115 fn is_empty(self) -> bool {
116 self.lo > self.hi
117 }
118
119 #[allow(dead_code)]
120 fn contains(self, other: Range) -> bool {
121 if other.is_empty() {
122 true
123 } else {
124 !self.is_empty() && self.lo <= other.lo && self.hi >= other.hi
125 }
126 }
127
128 fn intersects(self, other: Range) -> bool {
129 if self.is_empty() || other.is_empty() {
130 false
131 } else {
132 (self.lo <= other.hi && other.hi <= self.hi)
133 || (other.lo <= self.hi && self.hi <= other.hi)
134 }
135 }
136
137 fn adjacent_to(self, other: Range) -> bool {
138 if self.is_empty() || other.is_empty() {
139 false
140 } else {
141 self.hi + 1 == other.lo || other.hi + 1 == self.lo
142 }
143 }
144
145 fn merge(self, other: Range) -> Option<Range> {
148 if self.adjacent_to(other) || self.intersects(other) {
149 Some(Range::new(
150 cmp::min(self.lo, other.lo),
151 cmp::max(self.hi, other.hi),
152 ))
153 } else {
154 None
155 }
156 }
157}
158
159#[derive(Clone, Debug, Default, PartialEq)]
165pub struct FileLines(Option<HashMap<FileName, Vec<Range>>>);
166
167impl fmt::Display for FileLines {
168 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169 match &self.0 {
170 None => write!(f, "None")?,
171 Some(map) => {
172 for (file_name, ranges) in map.iter() {
173 write!(f, "{file_name}: ")?;
174 write!(f, "{}\n", ranges.iter().format(", "))?;
175 }
176 }
177 };
178 Ok(())
179 }
180}
181
182fn normalize_ranges(ranges: &mut HashMap<FileName, Vec<Range>>) {
185 for ranges in ranges.values_mut() {
186 ranges.sort();
187 let mut result = vec![];
188 let mut iter = ranges.iter_mut().peekable();
189 while let Some(next) = iter.next() {
190 let mut next = *next;
191 while let Some(&&mut peek) = iter.peek() {
192 if let Some(merged) = next.merge(peek) {
193 iter.next().unwrap();
194 next = merged;
195 } else {
196 break;
197 }
198 }
199 result.push(next)
200 }
201 *ranges = result;
202 }
203}
204
205impl FileLines {
206 pub(crate) fn all() -> FileLines {
208 FileLines(None)
209 }
210
211 pub fn is_all(&self) -> bool {
213 self.0.is_none()
214 }
215
216 pub fn from_ranges(mut ranges: HashMap<FileName, Vec<Range>>) -> FileLines {
217 normalize_ranges(&mut ranges);
218 FileLines(Some(ranges))
219 }
220
221 pub fn files(&self) -> Files<'_> {
223 Files(self.0.as_ref().map(HashMap::keys))
224 }
225
226 pub fn to_json_spans(&self) -> Vec<JsonSpan> {
228 match &self.0 {
229 None => vec![],
230 Some(file_ranges) => file_ranges
231 .iter()
232 .flat_map(|(file, ranges)| ranges.iter().map(move |r| (file, r)))
233 .map(|(file, range)| JsonSpan {
234 file: file.to_owned(),
235 range: (range.lo, range.hi),
236 })
237 .collect(),
238 }
239 }
240
241 fn file_range_matches<F>(&self, file_name: &FileName, f: F) -> bool
244 where
245 F: FnMut(&Range) -> bool,
246 {
247 let map = match self.0 {
248 None => return true,
250 Some(ref map) => map,
251 };
252
253 match canonicalize_path_string(file_name).and_then(|file| map.get(&file)) {
254 Some(ranges) => ranges.iter().any(f),
255 None => false,
256 }
257 }
258
259 #[allow(dead_code)]
261 pub(crate) fn contains(&self, range: &LineRange) -> bool {
262 self.file_range_matches(&range.file_name(), |r| r.contains(Range::from(range)))
263 }
264
265 pub(crate) fn intersects(&self, range: &LineRange) -> bool {
267 self.file_range_matches(&range.file_name(), |r| r.intersects(Range::from(range)))
268 }
269
270 pub(crate) fn contains_line(&self, file_name: &FileName, line: usize) -> bool {
272 self.file_range_matches(file_name, |r| r.lo <= line && r.hi >= line)
273 }
274
275 pub(crate) fn contains_range(&self, file_name: &FileName, lo: usize, hi: usize) -> bool {
277 self.file_range_matches(file_name, |r| r.contains(Range::new(lo, hi)))
278 }
279}
280
281pub struct Files<'a>(Option<::std::collections::hash_map::Keys<'a, FileName, Vec<Range>>>);
283
284impl<'a> iter::Iterator for Files<'a> {
285 type Item = &'a FileName;
286
287 fn next(&mut self) -> Option<&'a FileName> {
288 self.0.as_mut().and_then(Iterator::next)
289 }
290}
291
292fn canonicalize_path_string(file: &FileName) -> Option<FileName> {
293 match *file {
294 FileName::Real(ref path) => path.canonicalize().ok().map(FileName::Real),
295 _ => Some(file.clone()),
296 }
297}
298
299#[derive(Error, Debug)]
300pub enum FileLinesError {
301 #[error("{0}")]
302 Json(json::Error),
303 #[error("Can't canonicalize {0}")]
304 CannotCanonicalize(FileName),
305}
306
307impl str::FromStr for FileLines {
309 type Err = FileLinesError;
310
311 fn from_str(s: &str) -> Result<FileLines, Self::Err> {
312 let v: Vec<JsonSpan> = json::from_str(s).map_err(FileLinesError::Json)?;
313 let mut m = HashMap::new();
314 for js in v {
315 let (s, r) = JsonSpan::into_tuple(js)?;
316 m.entry(s).or_insert_with(Vec::new).push(r);
317 }
318 Ok(FileLines::from_ranges(m))
319 }
320}
321
322#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]
324pub struct JsonSpan {
325 file: FileName,
326 range: (usize, usize),
327}
328
329impl JsonSpan {
330 fn into_tuple(self) -> Result<(FileName, Range), FileLinesError> {
331 let (lo, hi) = self.range;
332 let canonical = canonicalize_path_string(&self.file)
333 .ok_or(FileLinesError::CannotCanonicalize(self.file))?;
334 Ok((canonical, Range::new(lo, hi)))
335 }
336}
337
338impl<'de> ::serde::de::Deserialize<'de> for FileLines {
341 fn deserialize<D>(_: D) -> Result<Self, D::Error>
342 where
343 D: ::serde::de::Deserializer<'de>,
344 {
345 panic!(
346 "FileLines cannot be deserialized from a project rustfmt.toml file: please \
347 specify it via the `--file-lines` option instead"
348 );
349 }
350}
351
352impl ::serde::ser::Serialize for FileLines {
355 fn serialize<S>(&self, _: S) -> Result<S::Ok, S::Error>
356 where
357 S: ::serde::ser::Serializer,
358 {
359 unreachable!("FileLines cannot be serialized. This is a rustfmt bug.");
360 }
361}
362
363#[cfg(test)]
364mod test {
365 use super::Range;
366
367 #[test]
368 fn test_range_intersects() {
369 assert!(Range::new(1, 2).intersects(Range::new(1, 1)));
370 assert!(Range::new(1, 2).intersects(Range::new(2, 2)));
371 assert!(!Range::new(1, 2).intersects(Range::new(0, 0)));
372 assert!(!Range::new(1, 2).intersects(Range::new(3, 10)));
373 assert!(!Range::new(1, 3).intersects(Range::new(5, 5)));
374 }
375
376 #[test]
377 fn test_range_adjacent_to() {
378 assert!(!Range::new(1, 2).adjacent_to(Range::new(1, 1)));
379 assert!(!Range::new(1, 2).adjacent_to(Range::new(2, 2)));
380 assert!(Range::new(1, 2).adjacent_to(Range::new(0, 0)));
381 assert!(Range::new(1, 2).adjacent_to(Range::new(3, 10)));
382 assert!(!Range::new(1, 3).adjacent_to(Range::new(5, 5)));
383 }
384
385 #[test]
386 fn test_range_contains() {
387 assert!(Range::new(1, 2).contains(Range::new(1, 1)));
388 assert!(Range::new(1, 2).contains(Range::new(2, 2)));
389 assert!(!Range::new(1, 2).contains(Range::new(0, 0)));
390 assert!(!Range::new(1, 2).contains(Range::new(3, 10)));
391 }
392
393 #[test]
394 fn test_range_merge() {
395 assert_eq!(None, Range::new(1, 3).merge(Range::new(5, 5)));
396 assert_eq!(None, Range::new(4, 7).merge(Range::new(0, 1)));
397 assert_eq!(
398 Some(Range::new(3, 7)),
399 Range::new(3, 5).merge(Range::new(4, 7))
400 );
401 assert_eq!(
402 Some(Range::new(3, 7)),
403 Range::new(3, 5).merge(Range::new(5, 7))
404 );
405 assert_eq!(
406 Some(Range::new(3, 7)),
407 Range::new(3, 5).merge(Range::new(6, 7))
408 );
409 assert_eq!(
410 Some(Range::new(3, 7)),
411 Range::new(3, 7).merge(Range::new(4, 5))
412 );
413 }
414
415 use super::json::{self, json};
416 use super::{FileLines, FileName};
417 use std::{collections::HashMap, path::PathBuf};
418
419 #[test]
420 fn file_lines_to_json() {
421 let ranges: HashMap<FileName, Vec<Range>> = [
422 (
423 FileName::Real(PathBuf::from("src/main.rs")),
424 vec![Range::new(1, 3), Range::new(5, 7)],
425 ),
426 (
427 FileName::Real(PathBuf::from("src/lib.rs")),
428 vec![Range::new(1, 7)],
429 ),
430 ]
431 .iter()
432 .cloned()
433 .collect();
434
435 let file_lines = FileLines::from_ranges(ranges);
436 let mut spans = file_lines.to_json_spans();
437 spans.sort();
438 let json = json::to_value(&spans).unwrap();
439 assert_eq!(
440 json,
441 json! {[
442 {"file": "src/lib.rs", "range": [1, 7]},
443 {"file": "src/main.rs", "range": [1, 3]},
444 {"file": "src/main.rs", "range": [5, 7]},
445 ]}
446 );
447 }
448}