aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/diagnostics.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src/diagnostics.rs')
-rw-r--r--crates/ide/src/diagnostics.rs184
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
7mod fixes; 7mod fixes;
8mod field_shorthand;
8 9
9use std::cell::RefCell; 10use std::cell::RefCell;
10 11
11use base_db::SourceDatabase; 12use base_db::SourceDatabase;
12use hir::{diagnostics::DiagnosticSinkBuilder, Semantics}; 13use hir::{
14 diagnostics::{Diagnostic as _, DiagnosticSinkBuilder},
15 Semantics,
16};
13use ide_db::RootDatabase; 17use ide_db::RootDatabase;
14use itertools::Itertools; 18use itertools::Itertools;
15use rustc_hash::FxHashSet; 19use 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
40impl 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
126fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { 151fn 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
136fn warning_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { 155fn 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
145fn check_unnecessary_braces_in_use_statement( 159fn 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
191fn 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)]
228mod tests { 203mod 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#"
726struct A { a: &'static str }
727fn main() { A { a: "hello" } }
728"#,
729 );
730 check_no_diagnostics(
731 r#"
732struct A(usize);
733fn main() { A { 0: 0 } }
734"#,
735 );
736
737 check_fix(
738 r#"
739struct A { a: &'static str }
740fn main() {
741 let a = "haha";
742 A { a<|>: a }
743}
744"#,
745 r#"
746struct A { a: &'static str }
747fn main() {
748 let a = "haha";
749 A { a }
750}
751"#,
752 );
753
754 check_fix(
755 r#"
756struct A { a: &'static str, b: &'static str }
757fn main() {
758 let a = "haha";
759 let b = "bb";
760 A { a<|>: a, b }
761}
762"#,
763 r#"
764struct A { a: &'static str, b: &'static str }
765fn 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"