diff options
Diffstat (limited to 'vendor/rustfix/src/lib.rs')
-rw-r--r-- | vendor/rustfix/src/lib.rs | 263 |
1 files changed, 263 insertions, 0 deletions
diff --git a/vendor/rustfix/src/lib.rs b/vendor/rustfix/src/lib.rs new file mode 100644 index 0000000..f832233 --- /dev/null +++ b/vendor/rustfix/src/lib.rs @@ -0,0 +1,263 @@ +#![warn(rust_2018_idioms)] + +#[macro_use] +extern crate log; +#[cfg(test)] +#[macro_use] +extern crate proptest; + +use std::collections::HashSet; +use std::ops::Range; + +use anyhow::Error; + +pub mod diagnostics; +use crate::diagnostics::{Diagnostic, DiagnosticSpan}; +mod replace; + +#[derive(Debug, Clone, Copy)] +pub enum Filter { + MachineApplicableOnly, + Everything, +} + +pub fn get_suggestions_from_json<S: ::std::hash::BuildHasher>( + input: &str, + only: &HashSet<String, S>, + filter: Filter, +) -> serde_json::error::Result<Vec<Suggestion>> { + let mut result = Vec::new(); + for cargo_msg in serde_json::Deserializer::from_str(input).into_iter::<Diagnostic>() { + // One diagnostic line might have multiple suggestions + result.extend(collect_suggestions(&cargo_msg?, only, filter)); + } + Ok(result) +} + +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub struct LinePosition { + pub line: usize, + pub column: usize, +} + +impl std::fmt::Display for LinePosition { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}", self.line, self.column) + } +} + +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub struct LineRange { + pub start: LinePosition, + pub end: LinePosition, +} + +impl std::fmt::Display for LineRange { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}-{}", self.start, self.end) + } +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +/// An error/warning and possible solutions for fixing it +pub struct Suggestion { + pub message: String, + pub snippets: Vec<Snippet>, + pub solutions: Vec<Solution>, +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct Solution { + pub message: String, + pub replacements: Vec<Replacement>, +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct Snippet { + pub file_name: String, + pub line_range: LineRange, + pub range: Range<usize>, + /// leading surrounding text, text to replace, trailing surrounding text + /// + /// This split is useful for higlighting the part that gets replaced + pub text: (String, String, String), +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct Replacement { + pub snippet: Snippet, + pub replacement: String, +} + +fn parse_snippet(span: &DiagnosticSpan) -> Option<Snippet> { + // unindent the snippet + let indent = span + .text + .iter() + .map(|line| { + let indent = line + .text + .chars() + .take_while(|&c| char::is_whitespace(c)) + .count(); + std::cmp::min(indent, line.highlight_start - 1) + }) + .min()?; + + let text_slice = span.text[0].text.chars().collect::<Vec<char>>(); + + // We subtract `1` because these highlights are 1-based + // Check the `min` so that it doesn't attempt to index out-of-bounds when + // the span points to the "end" of the line. For example, a line of + // "foo\n" with a highlight_start of 5 is intended to highlight *after* + // the line. This needs to compensate since the newline has been removed + // from the text slice. + let start = (span.text[0].highlight_start - 1).min(text_slice.len()); + let end = (span.text[0].highlight_end - 1).min(text_slice.len()); + let lead = text_slice[indent..start].iter().collect(); + let mut body: String = text_slice[start..end].iter().collect(); + + for line in span.text.iter().take(span.text.len() - 1).skip(1) { + body.push('\n'); + body.push_str(&line.text[indent..]); + } + let mut tail = String::new(); + let last = &span.text[span.text.len() - 1]; + + // If we get a DiagnosticSpanLine where highlight_end > text.len(), we prevent an 'out of + // bounds' access by making sure the index is within the array bounds. + // `saturating_sub` is used in case of an empty file + let last_tail_index = last.highlight_end.min(last.text.len()).saturating_sub(1); + let last_slice = last.text.chars().collect::<Vec<char>>(); + + if span.text.len() > 1 { + body.push('\n'); + body.push_str( + &last_slice[indent..last_tail_index] + .iter() + .collect::<String>(), + ); + } + tail.push_str(&last_slice[last_tail_index..].iter().collect::<String>()); + Some(Snippet { + file_name: span.file_name.clone(), + line_range: LineRange { + start: LinePosition { + line: span.line_start, + column: span.column_start, + }, + end: LinePosition { + line: span.line_end, + column: span.column_end, + }, + }, + range: (span.byte_start as usize)..(span.byte_end as usize), + text: (lead, body, tail), + }) +} + +fn collect_span(span: &DiagnosticSpan) -> Option<Replacement> { + let snippet = parse_snippet(span)?; + let replacement = span.suggested_replacement.clone()?; + Some(Replacement { + snippet, + replacement, + }) +} + +pub fn collect_suggestions<S: ::std::hash::BuildHasher>( + diagnostic: &Diagnostic, + only: &HashSet<String, S>, + filter: Filter, +) -> Option<Suggestion> { + if !only.is_empty() { + if let Some(ref code) = diagnostic.code { + if !only.contains(&code.code) { + // This is not the code we are looking for + return None; + } + } else { + // No code, probably a weird builtin warning/error + return None; + } + } + + let snippets = diagnostic.spans.iter().filter_map(parse_snippet).collect(); + + let solutions: Vec<_> = diagnostic + .children + .iter() + .filter_map(|child| { + let replacements: Vec<_> = child + .spans + .iter() + .filter(|span| { + use crate::diagnostics::Applicability::*; + use crate::Filter::*; + + match (filter, &span.suggestion_applicability) { + (MachineApplicableOnly, Some(MachineApplicable)) => true, + (MachineApplicableOnly, _) => false, + (Everything, _) => true, + } + }) + .filter_map(collect_span) + .collect(); + if !replacements.is_empty() { + Some(Solution { + message: child.message.clone(), + replacements, + }) + } else { + None + } + }) + .collect(); + + if solutions.is_empty() { + None + } else { + Some(Suggestion { + message: diagnostic.message.clone(), + snippets, + solutions, + }) + } +} + +pub struct CodeFix { + data: replace::Data, +} + +impl CodeFix { + pub fn new(s: &str) -> CodeFix { + CodeFix { + data: replace::Data::new(s.as_bytes()), + } + } + + pub fn apply(&mut self, suggestion: &Suggestion) -> Result<(), Error> { + for sol in &suggestion.solutions { + for r in &sol.replacements { + self.data.replace_range( + r.snippet.range.start, + r.snippet.range.end.saturating_sub(1), + r.replacement.as_bytes(), + )?; + } + } + Ok(()) + } + + pub fn finish(&self) -> Result<String, Error> { + Ok(String::from_utf8(self.data.to_vec())?) + } +} + +pub fn apply_suggestions(code: &str, suggestions: &[Suggestion]) -> Result<String, Error> { + let mut fix = CodeFix::new(code); + for suggestion in suggestions.iter().rev() { + fix.apply(suggestion)?; + } + fix.finish() +} |