diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:47:55 +0000 |
commit | 2aadc03ef15cb5ca5cc2af8a7c08e070742f0ac4 (patch) | |
tree | 033cc839730fda84ff08db877037977be94e5e3a /vendor/rustfix/src | |
parent | Initial commit. (diff) | |
download | cargo-upstream.tar.xz cargo-upstream.zip |
Adding upstream version 0.70.1+ds1.upstream/0.70.1+ds1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/rustfix/src')
-rw-r--r-- | vendor/rustfix/src/diagnostics.rs | 89 | ||||
-rw-r--r-- | vendor/rustfix/src/lib.rs | 263 | ||||
-rw-r--r-- | vendor/rustfix/src/replace.rs | 323 |
3 files changed, 675 insertions, 0 deletions
diff --git a/vendor/rustfix/src/diagnostics.rs b/vendor/rustfix/src/diagnostics.rs new file mode 100644 index 0000000..f745b0b --- /dev/null +++ b/vendor/rustfix/src/diagnostics.rs @@ -0,0 +1,89 @@ +//! Rustc Diagnostic JSON Output +//! +//! The following data types are copied from [rust-lang/rust](https://github.com/rust-lang/rust/blob/de78655bca47cac8e783dbb563e7e5c25c1fae40/src/libsyntax/json.rs) + +use serde::Deserialize; + +#[derive(Clone, Deserialize, Debug, Hash, Eq, PartialEq)] +pub struct Diagnostic { + /// The primary error message. + pub message: String, + pub code: Option<DiagnosticCode>, + /// "error: internal compiler error", "error", "warning", "note", "help". + level: String, + pub spans: Vec<DiagnosticSpan>, + /// Associated diagnostic messages. + pub children: Vec<Diagnostic>, + /// The message as rustc would render it. Currently this is only + /// `Some` for "suggestions", but eventually it will include all + /// snippets. + pub rendered: Option<String>, +} + +#[derive(Clone, Deserialize, Debug, Hash, Eq, PartialEq)] +pub struct DiagnosticSpan { + pub file_name: String, + pub byte_start: u32, + pub byte_end: u32, + /// 1-based. + pub line_start: usize, + pub line_end: usize, + /// 1-based, character offset. + pub column_start: usize, + pub column_end: usize, + /// Is this a "primary" span -- meaning the point, or one of the points, + /// where the error occurred? + pub is_primary: bool, + /// Source text from the start of line_start to the end of line_end. + pub text: Vec<DiagnosticSpanLine>, + /// Label that should be placed at this location (if any) + label: Option<String>, + /// If we are suggesting a replacement, this will contain text + /// that should be sliced in atop this span. You may prefer to + /// load the fully rendered version from the parent `Diagnostic`, + /// however. + pub suggested_replacement: Option<String>, + pub suggestion_applicability: Option<Applicability>, + /// Macro invocations that created the code at this span, if any. + expansion: Option<Box<DiagnosticSpanMacroExpansion>>, +} + +#[derive(Copy, Clone, Debug, PartialEq, Deserialize, Hash, Eq)] +pub enum Applicability { + MachineApplicable, + HasPlaceholders, + MaybeIncorrect, + Unspecified, +} + +#[derive(Clone, Deserialize, Debug, Eq, PartialEq, Hash)] +pub struct DiagnosticSpanLine { + pub text: String, + + /// 1-based, character offset in self.text. + pub highlight_start: usize, + + pub highlight_end: usize, +} + +#[derive(Clone, Deserialize, Debug, Eq, PartialEq, Hash)] +struct DiagnosticSpanMacroExpansion { + /// span where macro was applied to generate this code; note that + /// this may itself derive from a macro (if + /// `span.expansion.is_some()`) + span: DiagnosticSpan, + + /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]") + macro_decl_name: String, + + /// span where macro was defined (if known) + def_site_span: Option<DiagnosticSpan>, +} + +#[derive(Clone, Deserialize, Debug, Eq, PartialEq, Hash)] +pub struct DiagnosticCode { + /// The code itself. + pub code: String, + /// An explanation for the code. + explanation: Option<String>, +} 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() +} diff --git a/vendor/rustfix/src/replace.rs b/vendor/rustfix/src/replace.rs new file mode 100644 index 0000000..8deba5e --- /dev/null +++ b/vendor/rustfix/src/replace.rs @@ -0,0 +1,323 @@ +//! A small module giving you a simple container that allows easy and cheap +//! replacement of parts of its content, with the ability to prevent changing +//! the same parts multiple times. + +use anyhow::{anyhow, ensure, Error}; +use std::rc::Rc; + +#[derive(Debug, Clone, PartialEq, Eq)] +enum State { + Initial, + Replaced(Rc<[u8]>), + Inserted(Rc<[u8]>), +} + +impl State { + fn is_inserted(&self) -> bool { + matches!(*self, State::Inserted(..)) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct Span { + /// Start of this span in parent data + start: usize, + /// up to end including + end: usize, + data: State, +} + +/// A container that allows easily replacing chunks of its data +#[derive(Debug, Clone, Default)] +pub struct Data { + original: Vec<u8>, + parts: Vec<Span>, +} + +impl Data { + /// Create a new data container from a slice of bytes + pub fn new(data: &[u8]) -> Self { + Data { + original: data.into(), + parts: vec![Span { + data: State::Initial, + start: 0, + end: data.len().saturating_sub(1), + }], + } + } + + /// Render this data as a vector of bytes + pub fn to_vec(&self) -> Vec<u8> { + if self.original.is_empty() { + return Vec::new(); + } + + self.parts.iter().fold(Vec::new(), |mut acc, d| { + match d.data { + State::Initial => acc.extend_from_slice(&self.original[d.start..=d.end]), + State::Replaced(ref d) | State::Inserted(ref d) => acc.extend_from_slice(d), + }; + acc + }) + } + + /// Replace a chunk of data with the given slice, erroring when this part + /// was already changed previously. + pub fn replace_range( + &mut self, + from: usize, + up_to_and_including: usize, + data: &[u8], + ) -> Result<(), Error> { + let exclusive_end = up_to_and_including + 1; + + ensure!( + from <= exclusive_end, + "Invalid range {}...{}, start is larger than end", + from, + up_to_and_including + ); + + ensure!( + up_to_and_including <= self.original.len(), + "Invalid range {}...{} given, original data is only {} byte long", + from, + up_to_and_including, + self.original.len() + ); + + let insert_only = from == exclusive_end; + + // Since we error out when replacing an already replaced chunk of data, + // we can take some shortcuts here. For example, there can be no + // overlapping replacements -- we _always_ split a chunk of 'initial' + // data into three[^empty] parts, and there can't ever be two 'initial' + // parts touching. + // + // [^empty]: Leading and trailing ones might be empty if we replace + // the whole chunk. As an optimization and without loss of generality we + // don't add empty parts. + let new_parts = { + let index_of_part_to_split = self + .parts + .iter() + .position(|p| { + !p.data.is_inserted() && p.start <= from && p.end >= up_to_and_including + }) + .ok_or_else(|| { + use log::Level::Debug; + if log_enabled!(Debug) { + let slices = self + .parts + .iter() + .map(|p| { + ( + p.start, + p.end, + match p.data { + State::Initial => "initial", + State::Replaced(..) => "replaced", + State::Inserted(..) => "inserted", + }, + ) + }) + .collect::<Vec<_>>(); + debug!( + "no single slice covering {}...{}, current slices: {:?}", + from, up_to_and_including, slices, + ); + } + + anyhow!( + "Could not replace range {}...{} in file \ + -- maybe parts of it were already replaced?", + from, + up_to_and_including + ) + })?; + + let part_to_split = &self.parts[index_of_part_to_split]; + + // If this replacement matches exactly the part that we would + // otherwise split then we ignore this for now. This means that you + // can replace the exact same range with the exact same content + // multiple times and we'll process and allow it. + // + // This is currently done to alleviate issues like + // rust-lang/rust#51211 although this clause likely wants to be + // removed if that's fixed deeper in the compiler. + if part_to_split.start == from && part_to_split.end == up_to_and_including { + if let State::Replaced(ref replacement) = part_to_split.data { + if &**replacement == data { + return Ok(()); + } + } + } + + ensure!( + part_to_split.data == State::Initial, + "Cannot replace slice of data that was already replaced" + ); + + let mut new_parts = Vec::with_capacity(self.parts.len() + 2); + + // Previous parts + if let Some(ps) = self.parts.get(..index_of_part_to_split) { + new_parts.extend_from_slice(ps); + } + + // Keep initial data on left side of part + if from > part_to_split.start { + new_parts.push(Span { + start: part_to_split.start, + end: from.saturating_sub(1), + data: State::Initial, + }); + } + + // New part + new_parts.push(Span { + start: from, + end: up_to_and_including, + data: if insert_only { + State::Inserted(data.into()) + } else { + State::Replaced(data.into()) + }, + }); + + // Keep initial data on right side of part + if up_to_and_including < part_to_split.end { + new_parts.push(Span { + start: up_to_and_including + 1, + end: part_to_split.end, + data: State::Initial, + }); + } + + // Following parts + if let Some(ps) = self.parts.get(index_of_part_to_split + 1..) { + new_parts.extend_from_slice(ps); + } + + new_parts + }; + + self.parts = new_parts; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use proptest::prelude::*; + + fn str(i: &[u8]) -> &str { + ::std::str::from_utf8(i).unwrap() + } + + #[test] + fn replace_some_stuff() { + let mut d = Data::new(b"foo bar baz"); + d.replace_range(4, 6, b"lol").unwrap(); + assert_eq!("foo lol baz", str(&d.to_vec())); + } + + #[test] + fn replace_a_single_char() { + let mut d = Data::new(b"let y = true;"); + d.replace_range(4, 4, b"mut y").unwrap(); + assert_eq!("let mut y = true;", str(&d.to_vec())); + } + + #[test] + fn replace_multiple_lines() { + let mut d = Data::new(b"lorem\nipsum\ndolor"); + + d.replace_range(6, 10, b"lol").unwrap(); + assert_eq!("lorem\nlol\ndolor", str(&d.to_vec())); + + d.replace_range(12, 16, b"lol").unwrap(); + assert_eq!("lorem\nlol\nlol", str(&d.to_vec())); + } + + #[test] + fn replace_multiple_lines_with_insert_only() { + let mut d = Data::new(b"foo!"); + + d.replace_range(3, 2, b"bar").unwrap(); + assert_eq!("foobar!", str(&d.to_vec())); + + d.replace_range(0, 2, b"baz").unwrap(); + assert_eq!("bazbar!", str(&d.to_vec())); + + d.replace_range(3, 3, b"?").unwrap(); + assert_eq!("bazbar?", str(&d.to_vec())); + } + + #[test] + fn replace_invalid_range() { + let mut d = Data::new(b"foo!"); + + assert!(d.replace_range(2, 0, b"bar").is_err()); + assert!(d.replace_range(0, 2, b"bar").is_ok()); + } + + #[test] + fn empty_to_vec_roundtrip() { + let s = ""; + assert_eq!(s.as_bytes(), Data::new(s.as_bytes()).to_vec().as_slice()); + } + + #[test] + #[should_panic(expected = "Cannot replace slice of data that was already replaced")] + fn replace_overlapping_stuff_errs() { + let mut d = Data::new(b"foo bar baz"); + + d.replace_range(4, 6, b"lol").unwrap(); + assert_eq!("foo lol baz", str(&d.to_vec())); + + d.replace_range(4, 6, b"lol2").unwrap(); + } + + #[test] + #[should_panic(expected = "original data is only 3 byte long")] + fn broken_replacements() { + let mut d = Data::new(b"foo"); + d.replace_range(4, 7, b"lol").unwrap(); + } + + #[test] + fn replace_same_twice() { + let mut d = Data::new(b"foo"); + d.replace_range(0, 0, b"b").unwrap(); + d.replace_range(0, 0, b"b").unwrap(); + assert_eq!("boo", str(&d.to_vec())); + } + + proptest! { + #[test] + #[ignore] + fn new_to_vec_roundtrip(ref s in "\\PC*") { + assert_eq!(s.as_bytes(), Data::new(s.as_bytes()).to_vec().as_slice()); + } + + #[test] + #[ignore] + fn replace_random_chunks( + ref data in "\\PC*", + ref replacements in prop::collection::vec( + (any::<::std::ops::Range<usize>>(), any::<Vec<u8>>()), + 1..1337, + ) + ) { + let mut d = Data::new(data.as_bytes()); + for &(ref range, ref bytes) in replacements { + let _ = d.replace_range(range.start, range.end, bytes); + } + } + } +} |