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