1#![allow(clippy::disallowed_types)]
27
28use std::collections::HashSet;
29use std::ops::Range;
30
31pub mod diagnostics;
32mod error;
33mod replace;
34
35use diagnostics::Diagnostic;
36use diagnostics::DiagnosticSpan;
37pub use error::Error;
38
39#[derive(Debug, Clone, Copy)]
41pub enum Filter {
42 MachineApplicableOnly,
44 Everything,
46}
47
48pub fn get_suggestions_from_json<S: ::std::hash::BuildHasher>(
54 input: &str,
55 only: &HashSet<String, S>,
56 filter: Filter,
57) -> serde_json::error::Result<Vec<Suggestion>> {
58 let mut result = Vec::new();
59 for cargo_msg in serde_json::Deserializer::from_str(input).into_iter::<Diagnostic>() {
60 result.extend(collect_suggestions(&cargo_msg?, only, filter));
62 }
63 Ok(result)
64}
65
66#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
67pub struct LinePosition {
68 pub line: usize,
69 pub column: usize,
70}
71
72impl std::fmt::Display for LinePosition {
73 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74 write!(f, "{}:{}", self.line, self.column)
75 }
76}
77
78#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
79pub struct LineRange {
80 pub start: LinePosition,
81 pub end: LinePosition,
82}
83
84impl std::fmt::Display for LineRange {
85 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 write!(f, "{}-{}", self.start, self.end)
87 }
88}
89
90#[derive(Debug, Clone, Hash, PartialEq, Eq)]
92pub struct Suggestion {
93 pub message: String,
94 pub snippets: Vec<Snippet>,
95 pub solutions: Vec<Solution>,
96}
97
98#[derive(Debug, Clone, Hash, PartialEq, Eq)]
100pub struct Solution {
101 pub message: String,
103 pub replacements: Vec<Replacement>,
105}
106
107#[derive(Debug, Clone, Hash, PartialEq, Eq)]
109pub struct Snippet {
110 pub file_name: String,
111 pub line_range: LineRange,
112 pub range: Range<usize>,
113}
114
115#[derive(Debug, Clone, Hash, PartialEq, Eq)]
117pub struct Replacement {
118 pub snippet: Snippet,
120 pub replacement: String,
122}
123
124fn span_to_snippet(span: &DiagnosticSpan) -> Snippet {
126 Snippet {
127 file_name: span.file_name.clone(),
128 line_range: LineRange {
129 start: LinePosition {
130 line: span.line_start,
131 column: span.column_start,
132 },
133 end: LinePosition {
134 line: span.line_end,
135 column: span.column_end,
136 },
137 },
138 range: (span.byte_start as usize)..(span.byte_end as usize),
139 }
140}
141
142fn collect_span(span: &DiagnosticSpan) -> Option<Replacement> {
144 let snippet = span_to_snippet(span);
145 let replacement = span.suggested_replacement.clone()?;
146 Some(Replacement {
147 snippet,
148 replacement,
149 })
150}
151
152pub fn collect_suggestions<S: ::std::hash::BuildHasher>(
156 diagnostic: &Diagnostic,
157 only: &HashSet<String, S>,
158 filter: Filter,
159) -> Option<Suggestion> {
160 if !only.is_empty() {
161 if let Some(ref code) = diagnostic.code {
162 if !only.contains(&code.code) {
163 return None;
165 }
166 } else {
167 return None;
169 }
170 }
171
172 let solutions: Vec<_> = diagnostic
173 .children
174 .iter()
175 .filter_map(|child| {
176 let replacements: Vec<_> = child
177 .spans
178 .iter()
179 .filter(|span| {
180 use crate::Filter::*;
181 use crate::diagnostics::Applicability::*;
182
183 match (filter, &span.suggestion_applicability) {
184 (MachineApplicableOnly, Some(MachineApplicable)) => true,
185 (MachineApplicableOnly, _) => false,
186 (Everything, _) => true,
187 }
188 })
189 .filter_map(collect_span)
190 .collect();
191 if !replacements.is_empty() {
192 Some(Solution {
193 message: child.message.clone(),
194 replacements,
195 })
196 } else {
197 None
198 }
199 })
200 .collect();
201
202 if solutions.is_empty() {
203 None
204 } else {
205 Some(Suggestion {
206 message: diagnostic.message.clone(),
207 snippets: diagnostic.spans.iter().map(span_to_snippet).collect(),
208 solutions,
209 })
210 }
211}
212
213#[derive(Clone)]
221pub struct CodeFix {
222 data: replace::Data,
223 modified: bool,
225}
226
227impl CodeFix {
228 pub fn new(s: &str) -> CodeFix {
230 CodeFix {
231 data: replace::Data::new(s.as_bytes()),
232 modified: false,
233 }
234 }
235
236 pub fn apply(&mut self, suggestion: &Suggestion) -> Result<(), Error> {
238 for solution in &suggestion.solutions {
239 for r in &solution.replacements {
240 self.data
241 .replace_range(r.snippet.range.clone(), r.replacement.as_bytes())
242 .inspect_err(|_| self.data.restore())?;
243 }
244 }
245 self.data.commit();
246 self.modified = true;
247 Ok(())
248 }
249
250 pub fn apply_solution(&mut self, solution: &Solution) -> Result<(), Error> {
252 for r in &solution.replacements {
253 self.data
254 .replace_range(r.snippet.range.clone(), r.replacement.as_bytes())
255 .inspect_err(|_| self.data.restore())?;
256 }
257 self.data.commit();
258 self.modified = true;
259 Ok(())
260 }
261
262 pub fn finish(&self) -> Result<String, Error> {
264 Ok(String::from_utf8(self.data.to_vec())?)
265 }
266
267 pub fn modified(&self) -> bool {
269 self.modified
270 }
271}
272
273pub fn apply_suggestions(code: &str, suggestions: &[Suggestion]) -> Result<String, Error> {
284 let mut fix = CodeFix::new(code);
285 for suggestion in suggestions.iter().rev() {
286 fix.apply(suggestion).or_else(|err| match err {
287 Error::AlreadyReplaced {
288 is_identical: true, ..
289 } => Ok(()),
290 _ => Err(err),
291 })?;
292 }
293 fix.finish()
294}