1 #![warn(rust_2018_idioms)]
9 use std
::collections
::HashSet
;
15 use crate::diagnostics
::{Diagnostic, DiagnosticSpan}
;
18 #[derive(Debug, Clone, Copy)]
20 MachineApplicableOnly
,
24 pub fn get_suggestions_from_json
<S
: ::std
::hash
::BuildHasher
>(
26 only
: &HashSet
<String
, S
>,
28 ) -> serde_json
::error
::Result
<Vec
<Suggestion
>> {
29 let mut result
= Vec
::new();
30 for cargo_msg
in serde_json
::Deserializer
::from_str(input
).into_iter
::<Diagnostic
>() {
31 // One diagnostic line might have multiple suggestions
32 result
.extend(collect_suggestions(&cargo_msg?
, only
, filter
));
37 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
38 pub struct LinePosition
{
43 impl std
::fmt
::Display
for LinePosition
{
44 fn fmt(&self, f
: &mut std
::fmt
::Formatter
<'_
>) -> std
::fmt
::Result
{
45 write
!(f
, "{}:{}", self.line
, self.column
)
49 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
50 pub struct LineRange
{
51 pub start
: LinePosition
,
52 pub end
: LinePosition
,
55 impl std
::fmt
::Display
for LineRange
{
56 fn fmt(&self, f
: &mut std
::fmt
::Formatter
<'_
>) -> std
::fmt
::Result
{
57 write
!(f
, "{}-{}", self.start
, self.end
)
61 #[derive(Debug, Clone, Hash, PartialEq, Eq)]
62 /// An error/warning and possible solutions for fixing it
63 pub struct Suggestion
{
65 pub snippets
: Vec
<Snippet
>,
66 pub solutions
: Vec
<Solution
>,
69 #[derive(Debug, Clone, Hash, PartialEq, Eq)]
72 pub replacements
: Vec
<Replacement
>,
75 #[derive(Debug, Clone, Hash, PartialEq, Eq)]
77 pub file_name
: String
,
78 pub line_range
: LineRange
,
79 pub range
: Range
<usize>,
80 /// leading surrounding text, text to replace, trailing surrounding text
82 /// This split is useful for higlighting the part that gets replaced
83 pub text
: (String
, String
, String
),
86 #[derive(Debug, Clone, Hash, PartialEq, Eq)]
87 pub struct Replacement
{
89 pub replacement
: String
,
92 fn parse_snippet(span
: &DiagnosticSpan
) -> Option
<Snippet
> {
93 // unindent the snippet
101 .take_while(|&c
| char::is_whitespace(c
))
103 std
::cmp
::min(indent
, line
.highlight_start
- 1)
107 let text_slice
= span
.text
[0].text
.chars().collect
::<Vec
<char>>();
109 // We subtract `1` because these highlights are 1-based
110 // Check the `min` so that it doesn't attempt to index out-of-bounds when
111 // the span points to the "end" of the line. For example, a line of
112 // "foo\n" with a highlight_start of 5 is intended to highlight *after*
113 // the line. This needs to compensate since the newline has been removed
114 // from the text slice.
115 let start
= (span
.text
[0].highlight_start
- 1).min(text_slice
.len());
116 let end
= (span
.text
[0].highlight_end
- 1).min(text_slice
.len());
117 let lead
= text_slice
[indent
..start
].iter().collect();
118 let mut body
: String
= text_slice
[start
..end
].iter().collect();
120 for line
in span
.text
.iter().take(span
.text
.len() - 1).skip(1) {
122 body
.push_str(&line
.text
[indent
..]);
124 let mut tail
= String
::new();
125 let last
= &span
.text
[span
.text
.len() - 1];
127 // If we get a DiagnosticSpanLine where highlight_end > text.len(), we prevent an 'out of
128 // bounds' access by making sure the index is within the array bounds.
129 // `saturating_sub` is used in case of an empty file
130 let last_tail_index
= last
.highlight_end
.min(last
.text
.len()).saturating_sub(1);
131 let last_slice
= last
.text
.chars().collect
::<Vec
<char>>();
133 if span
.text
.len() > 1 {
136 &last_slice
[indent
..last_tail_index
]
138 .collect
::<String
>(),
141 tail
.push_str(&last_slice
[last_tail_index
..].iter().collect
::<String
>());
143 file_name
: span
.file_name
.clone(),
144 line_range
: LineRange
{
145 start
: LinePosition
{
146 line
: span
.line_start
,
147 column
: span
.column_start
,
151 column
: span
.column_end
,
154 range
: (span
.byte_start
as usize)..(span
.byte_end
as usize),
155 text
: (lead
, body
, tail
),
159 fn collect_span(span
: &DiagnosticSpan
) -> Option
<Replacement
> {
160 let snippet
= parse_snippet(span
)?
;
161 let replacement
= span
.suggested_replacement
.clone()?
;
168 pub fn collect_suggestions
<S
: ::std
::hash
::BuildHasher
>(
169 diagnostic
: &Diagnostic
,
170 only
: &HashSet
<String
, S
>,
172 ) -> Option
<Suggestion
> {
173 if !only
.is_empty() {
174 if let Some(ref code
) = diagnostic
.code
{
175 if !only
.contains(&code
.code
) {
176 // This is not the code we are looking for
180 // No code, probably a weird builtin warning/error
185 let snippets
= diagnostic
.spans
.iter().filter_map(parse_snippet
).collect();
187 let solutions
: Vec
<_
> = diagnostic
190 .filter_map(|child
| {
191 let replacements
: Vec
<_
> = child
195 use crate::diagnostics
::Applicability
::*;
196 use crate::Filter
::*;
198 match (filter
, &span
.suggestion_applicability
) {
199 (MachineApplicableOnly
, Some(MachineApplicable
)) => true,
200 (MachineApplicableOnly
, _
) => false,
201 (Everything
, _
) => true,
204 .filter_map(collect_span
)
206 if !replacements
.is_empty() {
208 message
: child
.message
.clone(),
217 if solutions
.is_empty() {
221 message
: diagnostic
.message
.clone(),
233 pub fn new(s
: &str) -> CodeFix
{
235 data
: replace
::Data
::new(s
.as_bytes()),
239 pub fn apply(&mut self, suggestion
: &Suggestion
) -> Result
<(), Error
> {
240 for sol
in &suggestion
.solutions
{
241 for r
in &sol
.replacements
{
242 self.data
.replace_range(
243 r
.snippet
.range
.start
,
244 r
.snippet
.range
.end
.saturating_sub(1),
245 r
.replacement
.as_bytes(),
252 pub fn finish(&self) -> Result
<String
, Error
> {
253 Ok(String
::from_utf8(self.data
.to_vec())?
)
257 pub fn apply_suggestions(code
: &str, suggestions
: &[Suggestion
]) -> Result
<String
, Error
> {
258 let mut fix
= CodeFix
::new(code
);
259 for suggestion
in suggestions
.iter().rev() {
260 fix
.apply(suggestion
)?
;