diff options
Diffstat (limited to 'crates/ide/src/diagnostics.rs')
-rw-r--r-- | crates/ide/src/diagnostics.rs | 184 |
1 files changed, 54 insertions, 130 deletions
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index b30cdb6ed..90574cb35 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs | |||
@@ -5,11 +5,15 @@ | |||
5 | //! original files. So we need to map the ranges. | 5 | //! original files. So we need to map the ranges. |
6 | 6 | ||
7 | mod fixes; | 7 | mod fixes; |
8 | mod field_shorthand; | ||
8 | 9 | ||
9 | use std::cell::RefCell; | 10 | use std::cell::RefCell; |
10 | 11 | ||
11 | use base_db::SourceDatabase; | 12 | use base_db::SourceDatabase; |
12 | use hir::{diagnostics::DiagnosticSinkBuilder, Semantics}; | 13 | use hir::{ |
14 | diagnostics::{Diagnostic as _, DiagnosticSinkBuilder}, | ||
15 | Semantics, | ||
16 | }; | ||
13 | use ide_db::RootDatabase; | 17 | use ide_db::RootDatabase; |
14 | use itertools::Itertools; | 18 | use itertools::Itertools; |
15 | use rustc_hash::FxHashSet; | 19 | use rustc_hash::FxHashSet; |
@@ -30,6 +34,25 @@ pub struct Diagnostic { | |||
30 | pub range: TextRange, | 34 | pub range: TextRange, |
31 | pub severity: Severity, | 35 | pub severity: Severity, |
32 | pub fix: Option<Fix>, | 36 | pub fix: Option<Fix>, |
37 | pub unused: bool, | ||
38 | } | ||
39 | |||
40 | impl Diagnostic { | ||
41 | fn error(range: TextRange, message: String) -> Self { | ||
42 | Self { message, range, severity: Severity::Error, fix: None, unused: false } | ||
43 | } | ||
44 | |||
45 | fn hint(range: TextRange, message: String) -> Self { | ||
46 | Self { message, range, severity: Severity::WeakWarning, fix: None, unused: false } | ||
47 | } | ||
48 | |||
49 | fn with_fix(self, fix: Option<Fix>) -> Self { | ||
50 | Self { fix, ..self } | ||
51 | } | ||
52 | |||
53 | fn with_unused(self, unused: bool) -> Self { | ||
54 | Self { unused, ..self } | ||
55 | } | ||
33 | } | 56 | } |
34 | 57 | ||
35 | #[derive(Debug)] | 58 | #[derive(Debug)] |
@@ -70,17 +93,17 @@ pub(crate) fn diagnostics( | |||
70 | let mut res = Vec::new(); | 93 | let mut res = Vec::new(); |
71 | 94 | ||
72 | // [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily. | 95 | // [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily. |
73 | res.extend(parse.errors().iter().take(128).map(|err| Diagnostic { | 96 | res.extend( |
74 | // name: None, | 97 | parse |
75 | range: err.range(), | 98 | .errors() |
76 | message: format!("Syntax Error: {}", err), | 99 | .iter() |
77 | severity: Severity::Error, | 100 | .take(128) |
78 | fix: None, | 101 | .map(|err| Diagnostic::error(err.range(), format!("Syntax Error: {}", err))), |
79 | })); | 102 | ); |
80 | 103 | ||
81 | for node in parse.tree().syntax().descendants() { | 104 | for node in parse.tree().syntax().descendants() { |
82 | check_unnecessary_braces_in_use_statement(&mut res, file_id, &node); | 105 | check_unnecessary_braces_in_use_statement(&mut res, file_id, &node); |
83 | check_struct_shorthand_initialization(&mut res, file_id, &node); | 106 | field_shorthand::check(&mut res, file_id, &node); |
84 | } | 107 | } |
85 | let res = RefCell::new(res); | 108 | let res = RefCell::new(res); |
86 | let sink_builder = DiagnosticSinkBuilder::new() | 109 | let sink_builder = DiagnosticSinkBuilder::new() |
@@ -99,6 +122,13 @@ pub(crate) fn diagnostics( | |||
99 | .on::<hir::diagnostics::IncorrectCase, _>(|d| { | 122 | .on::<hir::diagnostics::IncorrectCase, _>(|d| { |
100 | res.borrow_mut().push(warning_with_fix(d, &sema)); | 123 | res.borrow_mut().push(warning_with_fix(d, &sema)); |
101 | }) | 124 | }) |
125 | .on::<hir::diagnostics::InactiveCode, _>(|d| { | ||
126 | // Override severity and mark as unused. | ||
127 | res.borrow_mut().push( | ||
128 | Diagnostic::hint(sema.diagnostics_display_range(d).range, d.message()) | ||
129 | .with_unused(true), | ||
130 | ); | ||
131 | }) | ||
102 | // Only collect experimental diagnostics when they're enabled. | 132 | // Only collect experimental diagnostics when they're enabled. |
103 | .filter(|diag| !(diag.is_experimental() && config.disable_experimental)) | 133 | .filter(|diag| !(diag.is_experimental() && config.disable_experimental)) |
104 | .filter(|diag| !config.disabled.contains(diag.code().as_str())); | 134 | .filter(|diag| !config.disabled.contains(diag.code().as_str())); |
@@ -107,13 +137,8 @@ pub(crate) fn diagnostics( | |||
107 | let mut sink = sink_builder | 137 | let mut sink = sink_builder |
108 | // Diagnostics not handled above get no fix and default treatment. | 138 | // Diagnostics not handled above get no fix and default treatment. |
109 | .build(|d| { | 139 | .build(|d| { |
110 | res.borrow_mut().push(Diagnostic { | 140 | res.borrow_mut() |
111 | // name: Some(d.name().into()), | 141 | .push(Diagnostic::error(sema.diagnostics_display_range(d).range, d.message())); |
112 | message: d.message(), | ||
113 | range: sema.diagnostics_display_range(d).range, | ||
114 | severity: Severity::Error, | ||
115 | fix: None, | ||
116 | }) | ||
117 | }); | 142 | }); |
118 | 143 | ||
119 | if let Some(m) = sema.to_module_def(file_id) { | 144 | if let Some(m) = sema.to_module_def(file_id) { |
@@ -124,22 +149,11 @@ pub(crate) fn diagnostics( | |||
124 | } | 149 | } |
125 | 150 | ||
126 | fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { | 151 | fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { |
127 | Diagnostic { | 152 | Diagnostic::error(sema.diagnostics_display_range(d).range, d.message()).with_fix(d.fix(&sema)) |
128 | // name: Some(d.name().into()), | ||
129 | range: sema.diagnostics_display_range(d).range, | ||
130 | message: d.message(), | ||
131 | severity: Severity::Error, | ||
132 | fix: d.fix(&sema), | ||
133 | } | ||
134 | } | 153 | } |
135 | 154 | ||
136 | fn warning_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { | 155 | fn warning_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { |
137 | Diagnostic { | 156 | Diagnostic::hint(sema.diagnostics_display_range(d).range, d.message()).with_fix(d.fix(&sema)) |
138 | range: sema.diagnostics_display_range(d).range, | ||
139 | message: d.message(), | ||
140 | severity: Severity::WeakWarning, | ||
141 | fix: d.fix(&sema), | ||
142 | } | ||
143 | } | 157 | } |
144 | 158 | ||
145 | fn check_unnecessary_braces_in_use_statement( | 159 | fn check_unnecessary_braces_in_use_statement( |
@@ -160,17 +174,14 @@ fn check_unnecessary_braces_in_use_statement( | |||
160 | edit_builder.finish() | 174 | edit_builder.finish() |
161 | }); | 175 | }); |
162 | 176 | ||
163 | acc.push(Diagnostic { | 177 | acc.push( |
164 | // name: None, | 178 | Diagnostic::hint(use_range, "Unnecessary braces in use statement".to_string()) |
165 | range: use_range, | 179 | .with_fix(Some(Fix::new( |
166 | message: "Unnecessary braces in use statement".to_string(), | 180 | "Remove unnecessary braces", |
167 | severity: Severity::WeakWarning, | 181 | SourceFileEdit { file_id, edit }.into(), |
168 | fix: Some(Fix::new( | 182 | use_range, |
169 | "Remove unnecessary braces", | 183 | ))), |
170 | SourceFileEdit { file_id, edit }.into(), | 184 | ); |
171 | use_range, | ||
172 | )), | ||
173 | }); | ||
174 | } | 185 | } |
175 | 186 | ||
176 | Some(()) | 187 | Some(()) |
@@ -188,42 +199,6 @@ fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement( | |||
188 | None | 199 | None |
189 | } | 200 | } |
190 | 201 | ||
191 | fn check_struct_shorthand_initialization( | ||
192 | acc: &mut Vec<Diagnostic>, | ||
193 | file_id: FileId, | ||
194 | node: &SyntaxNode, | ||
195 | ) -> Option<()> { | ||
196 | let record_lit = ast::RecordExpr::cast(node.clone())?; | ||
197 | let record_field_list = record_lit.record_expr_field_list()?; | ||
198 | for record_field in record_field_list.fields() { | ||
199 | if let (Some(name_ref), Some(expr)) = (record_field.name_ref(), record_field.expr()) { | ||
200 | let field_name = name_ref.syntax().text().to_string(); | ||
201 | let field_expr = expr.syntax().text().to_string(); | ||
202 | let field_name_is_tup_index = name_ref.as_tuple_field().is_some(); | ||
203 | if field_name == field_expr && !field_name_is_tup_index { | ||
204 | let mut edit_builder = TextEdit::builder(); | ||
205 | edit_builder.delete(record_field.syntax().text_range()); | ||
206 | edit_builder.insert(record_field.syntax().text_range().start(), field_name); | ||
207 | let edit = edit_builder.finish(); | ||
208 | |||
209 | let field_range = record_field.syntax().text_range(); | ||
210 | acc.push(Diagnostic { | ||
211 | // name: None, | ||
212 | range: field_range, | ||
213 | message: "Shorthand struct initialization".to_string(), | ||
214 | severity: Severity::WeakWarning, | ||
215 | fix: Some(Fix::new( | ||
216 | "Use struct shorthand initialization", | ||
217 | SourceFileEdit { file_id, edit }.into(), | ||
218 | field_range, | ||
219 | )), | ||
220 | }); | ||
221 | } | ||
222 | } | ||
223 | } | ||
224 | Some(()) | ||
225 | } | ||
226 | |||
227 | #[cfg(test)] | 202 | #[cfg(test)] |
228 | mod tests { | 203 | mod tests { |
229 | use expect_test::{expect, Expect}; | 204 | use expect_test::{expect, Expect}; |
@@ -237,7 +212,7 @@ mod tests { | |||
237 | /// * a diagnostic is produced | 212 | /// * a diagnostic is produced |
238 | /// * this diagnostic fix trigger range touches the input cursor position | 213 | /// * this diagnostic fix trigger range touches the input cursor position |
239 | /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied | 214 | /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied |
240 | fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) { | 215 | pub(super) fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) { |
241 | let after = trim_indent(ra_fixture_after); | 216 | let after = trim_indent(ra_fixture_after); |
242 | 217 | ||
243 | let (analysis, file_position) = fixture::position(ra_fixture_before); | 218 | let (analysis, file_position) = fixture::position(ra_fixture_before); |
@@ -319,7 +294,7 @@ mod tests { | |||
319 | 294 | ||
320 | /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics | 295 | /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics |
321 | /// apply to the file containing the cursor. | 296 | /// apply to the file containing the cursor. |
322 | fn check_no_diagnostics(ra_fixture: &str) { | 297 | pub(crate) fn check_no_diagnostics(ra_fixture: &str) { |
323 | let (analysis, files) = fixture::files(ra_fixture); | 298 | let (analysis, files) = fixture::files(ra_fixture); |
324 | let diagnostics = files | 299 | let diagnostics = files |
325 | .into_iter() | 300 | .into_iter() |
@@ -613,6 +588,7 @@ fn test_fn() { | |||
613 | fix_trigger_range: 0..8, | 588 | fix_trigger_range: 0..8, |
614 | }, | 589 | }, |
615 | ), | 590 | ), |
591 | unused: false, | ||
616 | }, | 592 | }, |
617 | ] | 593 | ] |
618 | "#]], | 594 | "#]], |
@@ -720,58 +696,6 @@ mod a { | |||
720 | } | 696 | } |
721 | 697 | ||
722 | #[test] | 698 | #[test] |
723 | fn test_check_struct_shorthand_initialization() { | ||
724 | check_no_diagnostics( | ||
725 | r#" | ||
726 | struct A { a: &'static str } | ||
727 | fn main() { A { a: "hello" } } | ||
728 | "#, | ||
729 | ); | ||
730 | check_no_diagnostics( | ||
731 | r#" | ||
732 | struct A(usize); | ||
733 | fn main() { A { 0: 0 } } | ||
734 | "#, | ||
735 | ); | ||
736 | |||
737 | check_fix( | ||
738 | r#" | ||
739 | struct A { a: &'static str } | ||
740 | fn main() { | ||
741 | let a = "haha"; | ||
742 | A { a<|>: a } | ||
743 | } | ||
744 | "#, | ||
745 | r#" | ||
746 | struct A { a: &'static str } | ||
747 | fn main() { | ||
748 | let a = "haha"; | ||
749 | A { a } | ||
750 | } | ||
751 | "#, | ||
752 | ); | ||
753 | |||
754 | check_fix( | ||
755 | r#" | ||
756 | struct A { a: &'static str, b: &'static str } | ||
757 | fn main() { | ||
758 | let a = "haha"; | ||
759 | let b = "bb"; | ||
760 | A { a<|>: a, b } | ||
761 | } | ||
762 | "#, | ||
763 | r#" | ||
764 | struct A { a: &'static str, b: &'static str } | ||
765 | fn main() { | ||
766 | let a = "haha"; | ||
767 | let b = "bb"; | ||
768 | A { a, b } | ||
769 | } | ||
770 | "#, | ||
771 | ); | ||
772 | } | ||
773 | |||
774 | #[test] | ||
775 | fn test_add_field_from_usage() { | 699 | fn test_add_field_from_usage() { |
776 | check_fix( | 700 | check_fix( |
777 | r" | 701 | r" |