aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/diagnostics.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src/diagnostics.rs')
-rw-r--r--crates/ra_ide/src/diagnostics.rs184
1 files changed, 32 insertions, 152 deletions
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs
index 73c0b8275..1046d7ab3 100644
--- a/crates/ra_ide/src/diagnostics.rs
+++ b/crates/ra_ide/src/diagnostics.rs
@@ -6,22 +6,21 @@
6 6
7use std::cell::RefCell; 7use std::cell::RefCell;
8 8
9use hir::{ 9use hir::{diagnostics::DiagnosticSinkBuilder, Semantics};
10 diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSinkBuilder},
11 HasSource, HirDisplay, Semantics, VariantDef,
12};
13use itertools::Itertools; 10use itertools::Itertools;
14use ra_db::SourceDatabase; 11use ra_db::SourceDatabase;
15use ra_ide_db::RootDatabase; 12use ra_ide_db::RootDatabase;
16use ra_prof::profile; 13use ra_prof::profile;
17use ra_syntax::{ 14use ra_syntax::{
18 algo, 15 ast::{self, AstNode},
19 ast::{self, edit::IndentLevel, make, AstNode},
20 SyntaxNode, TextRange, T, 16 SyntaxNode, TextRange, T,
21}; 17};
22use ra_text_edit::{TextEdit, TextEditBuilder}; 18use ra_text_edit::{TextEdit, TextEditBuilder};
23 19
24use crate::{Diagnostic, FileId, FileSystemEdit, Fix, SourceFileEdit}; 20use crate::{Diagnostic, FileId, Fix, SourceFileEdit};
21
22mod diagnostics_with_fix;
23use diagnostics_with_fix::DiagnosticWithFix;
25 24
26#[derive(Debug, Copy, Clone)] 25#[derive(Debug, Copy, Clone)]
27pub enum Severity { 26pub enum Severity {
@@ -54,73 +53,16 @@ pub(crate) fn diagnostics(
54 let res = RefCell::new(res); 53 let res = RefCell::new(res);
55 let mut sink = DiagnosticSinkBuilder::new() 54 let mut sink = DiagnosticSinkBuilder::new()
56 .on::<hir::diagnostics::UnresolvedModule, _>(|d| { 55 .on::<hir::diagnostics::UnresolvedModule, _>(|d| {
57 let original_file = d.source().file_id.original_file(db); 56 res.borrow_mut().push(diagnostic_with_fix(d, &sema));
58 let fix = Fix::new(
59 "Create module",
60 FileSystemEdit::CreateFile { anchor: original_file, dst: d.candidate.clone() }
61 .into(),
62 );
63 res.borrow_mut().push(Diagnostic {
64 range: sema.diagnostics_range(d).range,
65 message: d.message(),
66 severity: Severity::Error,
67 fix: Some(fix),
68 })
69 }) 57 })
70 .on::<hir::diagnostics::MissingFields, _>(|d| { 58 .on::<hir::diagnostics::MissingFields, _>(|d| {
71 // Note that although we could add a diagnostics to 59 res.borrow_mut().push(diagnostic_with_fix(d, &sema));
72 // fill the missing tuple field, e.g :
73 // `struct A(usize);`
74 // `let a = A { 0: () }`
75 // but it is uncommon usage and it should not be encouraged.
76 let fix = if d.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) {
77 None
78 } else {
79 let mut field_list = d.ast(db);
80 for f in d.missed_fields.iter() {
81 let field = make::record_expr_field(
82 make::name_ref(&f.to_string()),
83 Some(make::expr_unit()),
84 );
85 field_list = field_list.append_field(&field);
86 }
87
88 let edit = {
89 let mut builder = TextEditBuilder::default();
90 algo::diff(&d.ast(db).syntax(), &field_list.syntax())
91 .into_text_edit(&mut builder);
92 builder.finish()
93 };
94 Some(Fix::new("Fill struct fields", SourceFileEdit { file_id, edit }.into()))
95 };
96
97 res.borrow_mut().push(Diagnostic {
98 range: sema.diagnostics_range(d).range,
99 message: d.message(),
100 severity: Severity::Error,
101 fix,
102 })
103 }) 60 })
104 .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| { 61 .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| {
105 let node = d.ast(db); 62 res.borrow_mut().push(diagnostic_with_fix(d, &sema));
106 let replacement = format!("Ok({})", node.syntax());
107 let edit = TextEdit::replace(node.syntax().text_range(), replacement);
108 let source_change = SourceFileEdit { file_id, edit }.into();
109 let fix = Fix::new("Wrap with ok", source_change);
110 res.borrow_mut().push(Diagnostic {
111 range: sema.diagnostics_range(d).range,
112 message: d.message(),
113 severity: Severity::Error,
114 fix: Some(fix),
115 })
116 }) 63 })
117 .on::<hir::diagnostics::NoSuchField, _>(|d| { 64 .on::<hir::diagnostics::NoSuchField, _>(|d| {
118 res.borrow_mut().push(Diagnostic { 65 res.borrow_mut().push(diagnostic_with_fix(d, &sema));
119 range: sema.diagnostics_range(d).range,
120 message: d.message(),
121 severity: Severity::Error,
122 fix: missing_struct_field_fix(&sema, file_id, d),
123 })
124 }) 66 })
125 // Only collect experimental diagnostics when they're enabled. 67 // Only collect experimental diagnostics when they're enabled.
126 .filter(|diag| !diag.is_experimental() || enable_experimental) 68 .filter(|diag| !diag.is_experimental() || enable_experimental)
@@ -128,7 +70,7 @@ pub(crate) fn diagnostics(
128 .build(|d| { 70 .build(|d| {
129 res.borrow_mut().push(Diagnostic { 71 res.borrow_mut().push(Diagnostic {
130 message: d.message(), 72 message: d.message(),
131 range: sema.diagnostics_range(d).range, 73 range: sema.diagnostics_display_range(d).range,
132 severity: Severity::Error, 74 severity: Severity::Error,
133 fix: None, 75 fix: None,
134 }) 76 })
@@ -141,77 +83,12 @@ pub(crate) fn diagnostics(
141 res.into_inner() 83 res.into_inner()
142} 84}
143 85
144fn missing_struct_field_fix( 86fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic {
145 sema: &Semantics<RootDatabase>, 87 Diagnostic {
146 usage_file_id: FileId, 88 range: sema.diagnostics_display_range(d).range,
147 d: &hir::diagnostics::NoSuchField, 89 message: d.message(),
148) -> Option<Fix> { 90 severity: Severity::Error,
149 let record_expr = sema.ast(d); 91 fix: d.fix(&sema),
150
151 let record_lit = ast::RecordExpr::cast(record_expr.syntax().parent()?.parent()?)?;
152 let def_id = sema.resolve_variant(record_lit)?;
153 let module;
154 let def_file_id;
155 let record_fields = match VariantDef::from(def_id) {
156 VariantDef::Struct(s) => {
157 module = s.module(sema.db);
158 let source = s.source(sema.db);
159 def_file_id = source.file_id;
160 let fields = source.value.field_list()?;
161 record_field_list(fields)?
162 }
163 VariantDef::Union(u) => {
164 module = u.module(sema.db);
165 let source = u.source(sema.db);
166 def_file_id = source.file_id;
167 source.value.record_field_list()?
168 }
169 VariantDef::EnumVariant(e) => {
170 module = e.module(sema.db);
171 let source = e.source(sema.db);
172 def_file_id = source.file_id;
173 let fields = source.value.field_list()?;
174 record_field_list(fields)?
175 }
176 };
177 let def_file_id = def_file_id.original_file(sema.db);
178
179 let new_field_type = sema.type_of_expr(&record_expr.expr()?)?;
180 if new_field_type.is_unknown() {
181 return None;
182 }
183 let new_field = make::record_field(
184 record_expr.field_name()?,
185 make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
186 );
187
188 let last_field = record_fields.fields().last()?;
189 let last_field_syntax = last_field.syntax();
190 let indent = IndentLevel::from_node(last_field_syntax);
191
192 let mut new_field = new_field.to_string();
193 if usage_file_id != def_file_id {
194 new_field = format!("pub(crate) {}", new_field);
195 }
196 new_field = format!("\n{}{}", indent, new_field);
197
198 let needs_comma = !last_field_syntax.to_string().ends_with(',');
199 if needs_comma {
200 new_field = format!(",{}", new_field);
201 }
202
203 let source_change = SourceFileEdit {
204 file_id: def_file_id,
205 edit: TextEdit::insert(last_field_syntax.text_range().end(), new_field),
206 };
207 let fix = Fix::new("Create field", source_change.into());
208 return Some(fix);
209
210 fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
211 match field_def_list {
212 ast::FieldList::RecordFieldList(it) => Some(it),
213 ast::FieldList::TupleFieldList(_) => None,
214 }
215 } 92 }
216} 93}
217 94
@@ -222,24 +99,25 @@ fn check_unnecessary_braces_in_use_statement(
222) -> Option<()> { 99) -> Option<()> {
223 let use_tree_list = ast::UseTreeList::cast(node.clone())?; 100 let use_tree_list = ast::UseTreeList::cast(node.clone())?;
224 if let Some((single_use_tree,)) = use_tree_list.use_trees().collect_tuple() { 101 if let Some((single_use_tree,)) = use_tree_list.use_trees().collect_tuple() {
225 let range = use_tree_list.syntax().text_range(); 102 let use_range = use_tree_list.syntax().text_range();
226 let edit = 103 let edit =
227 text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(&single_use_tree) 104 text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(&single_use_tree)
228 .unwrap_or_else(|| { 105 .unwrap_or_else(|| {
229 let to_replace = single_use_tree.syntax().text().to_string(); 106 let to_replace = single_use_tree.syntax().text().to_string();
230 let mut edit_builder = TextEditBuilder::default(); 107 let mut edit_builder = TextEditBuilder::default();
231 edit_builder.delete(range); 108 edit_builder.delete(use_range);
232 edit_builder.insert(range.start(), to_replace); 109 edit_builder.insert(use_range.start(), to_replace);
233 edit_builder.finish() 110 edit_builder.finish()
234 }); 111 });
235 112
236 acc.push(Diagnostic { 113 acc.push(Diagnostic {
237 range, 114 range: use_range,
238 message: "Unnecessary braces in use statement".to_string(), 115 message: "Unnecessary braces in use statement".to_string(),
239 severity: Severity::WeakWarning, 116 severity: Severity::WeakWarning,
240 fix: Some(Fix::new( 117 fix: Some(Fix::new(
241 "Remove unnecessary braces", 118 "Remove unnecessary braces",
242 SourceFileEdit { file_id, edit }.into(), 119 SourceFileEdit { file_id, edit }.into(),
120 use_range,
243 )), 121 )),
244 }); 122 });
245 } 123 }
@@ -254,8 +132,7 @@ fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(
254 if single_use_tree.path()?.segment()?.syntax().first_child_or_token()?.kind() == T![self] { 132 if single_use_tree.path()?.segment()?.syntax().first_child_or_token()?.kind() == T![self] {
255 let start = use_tree_list_node.prev_sibling_or_token()?.text_range().start(); 133 let start = use_tree_list_node.prev_sibling_or_token()?.text_range().start();
256 let end = use_tree_list_node.text_range().end(); 134 let end = use_tree_list_node.text_range().end();
257 let range = TextRange::new(start, end); 135 return Some(TextEdit::delete(TextRange::new(start, end)));
258 return Some(TextEdit::delete(range));
259 } 136 }
260 None 137 None
261} 138}
@@ -278,13 +155,15 @@ fn check_struct_shorthand_initialization(
278 edit_builder.insert(record_field.syntax().text_range().start(), field_name); 155 edit_builder.insert(record_field.syntax().text_range().start(), field_name);
279 let edit = edit_builder.finish(); 156 let edit = edit_builder.finish();
280 157
158 let field_range = record_field.syntax().text_range();
281 acc.push(Diagnostic { 159 acc.push(Diagnostic {
282 range: record_field.syntax().text_range(), 160 range: field_range,
283 message: "Shorthand struct initialization".to_string(), 161 message: "Shorthand struct initialization".to_string(),
284 severity: Severity::WeakWarning, 162 severity: Severity::WeakWarning,
285 fix: Some(Fix::new( 163 fix: Some(Fix::new(
286 "Use struct shorthand initialization", 164 "Use struct shorthand initialization",
287 SourceFileEdit { file_id, edit }.into(), 165 SourceFileEdit { file_id, edit }.into(),
166 field_range,
288 )), 167 )),
289 }); 168 });
290 } 169 }
@@ -304,7 +183,7 @@ mod tests {
304 /// Takes a multi-file input fixture with annotated cursor positions, 183 /// Takes a multi-file input fixture with annotated cursor positions,
305 /// and checks that: 184 /// and checks that:
306 /// * a diagnostic is produced 185 /// * a diagnostic is produced
307 /// * this diagnostic touches the input cursor position 186 /// * this diagnostic fix trigger range touches the input cursor position
308 /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied 187 /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied
309 fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) { 188 fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) {
310 let after = trim_indent(ra_fixture_after); 189 let after = trim_indent(ra_fixture_after);
@@ -322,10 +201,10 @@ mod tests {
322 201
323 assert_eq_text!(&after, &actual); 202 assert_eq_text!(&after, &actual);
324 assert!( 203 assert!(
325 diagnostic.range.start() <= file_position.offset 204 fix.fix_trigger_range.start() <= file_position.offset
326 && diagnostic.range.end() >= file_position.offset, 205 && fix.fix_trigger_range.end() >= file_position.offset,
327 "diagnostic range {:?} does not touch cursor position {:?}", 206 "diagnostic fix range {:?} does not touch cursor position {:?}",
328 diagnostic.range, 207 fix.fix_trigger_range,
329 file_position.offset 208 file_position.offset
330 ); 209 );
331 } 210 }
@@ -642,6 +521,7 @@ fn test_fn() {
642 ], 521 ],
643 is_snippet: false, 522 is_snippet: false,
644 }, 523 },
524 fix_trigger_range: 0..8,
645 }, 525 },
646 ), 526 ),
647 }, 527 },