aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide_api/src/diagnostics.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide_api/src/diagnostics.rs')
-rw-r--r--crates/ra_ide_api/src/diagnostics.rs233
1 files changed, 223 insertions, 10 deletions
diff --git a/crates/ra_ide_api/src/diagnostics.rs b/crates/ra_ide_api/src/diagnostics.rs
index 98b840b26..1a4882824 100644
--- a/crates/ra_ide_api/src/diagnostics.rs
+++ b/crates/ra_ide_api/src/diagnostics.rs
@@ -9,7 +9,7 @@ use ra_assists::ast_editor::{AstBuilder, AstEditor};
9use ra_db::SourceDatabase; 9use ra_db::SourceDatabase;
10use ra_prof::profile; 10use ra_prof::profile;
11use ra_syntax::{ 11use ra_syntax::{
12 ast::{self, AstNode, NamedField}, 12 ast::{self, AstNode, RecordField},
13 Location, SyntaxNode, TextRange, T, 13 Location, SyntaxNode, TextRange, T,
14}; 14};
15use ra_text_edit::{TextEdit, TextEditBuilder}; 15use ra_text_edit::{TextEdit, TextEditBuilder};
@@ -62,7 +62,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
62 let node = d.ast(db); 62 let node = d.ast(db);
63 let mut ast_editor = AstEditor::new(node); 63 let mut ast_editor = AstEditor::new(node);
64 for f in d.missed_fields.iter() { 64 for f in d.missed_fields.iter() {
65 ast_editor.append_field(&AstBuilder::<NamedField>::from_name(f)); 65 ast_editor.append_field(&AstBuilder::<RecordField>::from_name(f));
66 } 66 }
67 67
68 let mut builder = TextEditBuilder::default(); 68 let mut builder = TextEditBuilder::default();
@@ -75,6 +75,19 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
75 severity: Severity::Error, 75 severity: Severity::Error,
76 fix: Some(fix), 76 fix: Some(fix),
77 }) 77 })
78 })
79 .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| {
80 let node = d.ast(db);
81 let mut builder = TextEditBuilder::default();
82 let replacement = format!("Ok({})", node.syntax());
83 builder.replace(node.syntax().text_range(), replacement);
84 let fix = SourceChange::source_file_edit_from("wrap with ok", file_id, builder.finish());
85 res.borrow_mut().push(Diagnostic {
86 range: d.highlight_range(),
87 message: d.message(),
88 severity: Severity::Error,
89 fix: Some(fix),
90 })
78 }); 91 });
79 if let Some(m) = source_binder::module_from_file_id(db, file_id) { 92 if let Some(m) = source_binder::module_from_file_id(db, file_id) {
80 m.diagnostics(db, &mut sink); 93 m.diagnostics(db, &mut sink);
@@ -141,20 +154,20 @@ fn check_struct_shorthand_initialization(
141 file_id: FileId, 154 file_id: FileId,
142 node: &SyntaxNode, 155 node: &SyntaxNode,
143) -> Option<()> { 156) -> Option<()> {
144 let struct_lit = ast::StructLit::cast(node.clone())?; 157 let record_lit = ast::RecordLit::cast(node.clone())?;
145 let named_field_list = struct_lit.named_field_list()?; 158 let record_field_list = record_lit.record_field_list()?;
146 for named_field in named_field_list.fields() { 159 for record_field in record_field_list.fields() {
147 if let (Some(name_ref), Some(expr)) = (named_field.name_ref(), named_field.expr()) { 160 if let (Some(name_ref), Some(expr)) = (record_field.name_ref(), record_field.expr()) {
148 let field_name = name_ref.syntax().text().to_string(); 161 let field_name = name_ref.syntax().text().to_string();
149 let field_expr = expr.syntax().text().to_string(); 162 let field_expr = expr.syntax().text().to_string();
150 if field_name == field_expr { 163 if field_name == field_expr {
151 let mut edit_builder = TextEditBuilder::default(); 164 let mut edit_builder = TextEditBuilder::default();
152 edit_builder.delete(named_field.syntax().text_range()); 165 edit_builder.delete(record_field.syntax().text_range());
153 edit_builder.insert(named_field.syntax().text_range().start(), field_name); 166 edit_builder.insert(record_field.syntax().text_range().start(), field_name);
154 let edit = edit_builder.finish(); 167 let edit = edit_builder.finish();
155 168
156 acc.push(Diagnostic { 169 acc.push(Diagnostic {
157 range: named_field.syntax().text_range(), 170 range: record_field.syntax().text_range(),
158 message: "Shorthand struct initialization".to_string(), 171 message: "Shorthand struct initialization".to_string(),
159 severity: Severity::WeakWarning, 172 severity: Severity::WeakWarning,
160 fix: Some(SourceChange::source_file_edit( 173 fix: Some(SourceChange::source_file_edit(
@@ -171,10 +184,11 @@ fn check_struct_shorthand_initialization(
171#[cfg(test)] 184#[cfg(test)]
172mod tests { 185mod tests {
173 use insta::assert_debug_snapshot_matches; 186 use insta::assert_debug_snapshot_matches;
187 use join_to_string::join;
174 use ra_syntax::SourceFile; 188 use ra_syntax::SourceFile;
175 use test_utils::assert_eq_text; 189 use test_utils::assert_eq_text;
176 190
177 use crate::mock_analysis::single_file; 191 use crate::mock_analysis::{analysis_and_position, single_file};
178 192
179 use super::*; 193 use super::*;
180 194
@@ -203,6 +217,48 @@ mod tests {
203 assert_eq_text!(after, &actual); 217 assert_eq_text!(after, &actual);
204 } 218 }
205 219
220 /// Takes a multi-file input fixture with annotated cursor positions,
221 /// and checks that:
222 /// * a diagnostic is produced
223 /// * this diagnostic touches the input cursor position
224 /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied
225 fn check_apply_diagnostic_fix_from_position(fixture: &str, after: &str) {
226 let (analysis, file_position) = analysis_and_position(fixture);
227 let diagnostic = analysis.diagnostics(file_position.file_id).unwrap().pop().unwrap();
228 let mut fix = diagnostic.fix.unwrap();
229 let edit = fix.source_file_edits.pop().unwrap().edit;
230 let target_file_contents = analysis.file_text(file_position.file_id).unwrap();
231 let actual = edit.apply(&target_file_contents);
232
233 // Strip indent and empty lines from `after`, to match the behaviour of
234 // `parse_fixture` called from `analysis_and_position`.
235 let margin = fixture
236 .lines()
237 .filter(|it| it.trim_start().starts_with("//-"))
238 .map(|it| it.len() - it.trim_start().len())
239 .next()
240 .expect("empty fixture");
241 let after = join(after.lines().filter_map(|line| {
242 if line.len() > margin {
243 Some(&line[margin..])
244 } else {
245 None
246 }
247 }))
248 .separator("\n")
249 .suffix("\n")
250 .to_string();
251
252 assert_eq_text!(&after, &actual);
253 assert!(
254 diagnostic.range.start() <= file_position.offset
255 && diagnostic.range.end() >= file_position.offset,
256 "diagnostic range {} does not touch cursor position {}",
257 diagnostic.range,
258 file_position.offset
259 );
260 }
261
206 fn check_apply_diagnostic_fix(before: &str, after: &str) { 262 fn check_apply_diagnostic_fix(before: &str, after: &str) {
207 let (analysis, file_id) = single_file(before); 263 let (analysis, file_id) = single_file(before);
208 let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap(); 264 let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap();
@@ -212,6 +268,14 @@ mod tests {
212 assert_eq_text!(after, &actual); 268 assert_eq_text!(after, &actual);
213 } 269 }
214 270
271 /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics
272 /// apply to the file containing the cursor.
273 fn check_no_diagnostic_for_target_file(fixture: &str) {
274 let (analysis, file_position) = analysis_and_position(fixture);
275 let diagnostics = analysis.diagnostics(file_position.file_id).unwrap();
276 assert_eq!(diagnostics.len(), 0);
277 }
278
215 fn check_no_diagnostic(content: &str) { 279 fn check_no_diagnostic(content: &str) {
216 let (analysis, file_id) = single_file(content); 280 let (analysis, file_id) = single_file(content);
217 let diagnostics = analysis.diagnostics(file_id).unwrap(); 281 let diagnostics = analysis.diagnostics(file_id).unwrap();
@@ -219,6 +283,155 @@ mod tests {
219 } 283 }
220 284
221 #[test] 285 #[test]
286 fn test_wrap_return_type() {
287 let before = r#"
288 //- /main.rs
289 use std::{string::String, result::Result::{self, Ok, Err}};
290
291 fn div(x: i32, y: i32) -> Result<i32, String> {
292 if y == 0 {
293 return Err("div by zero".into());
294 }
295 x / y<|>
296 }
297
298 //- /std/lib.rs
299 pub mod string {
300 pub struct String { }
301 }
302 pub mod result {
303 pub enum Result<T, E> { Ok(T), Err(E) }
304 }
305 "#;
306 let after = r#"
307 use std::{string::String, result::Result::{self, Ok, Err}};
308
309 fn div(x: i32, y: i32) -> Result<i32, String> {
310 if y == 0 {
311 return Err("div by zero".into());
312 }
313 Ok(x / y)
314 }
315 "#;
316 check_apply_diagnostic_fix_from_position(before, after);
317 }
318
319 #[test]
320 fn test_wrap_return_type_handles_generic_functions() {
321 let before = r#"
322 //- /main.rs
323 use std::result::Result::{self, Ok, Err};
324
325 fn div<T>(x: T) -> Result<T, i32> {
326 if x == 0 {
327 return Err(7);
328 }
329 <|>x
330 }
331
332 //- /std/lib.rs
333 pub mod result {
334 pub enum Result<T, E> { Ok(T), Err(E) }
335 }
336 "#;
337 let after = r#"
338 use std::result::Result::{self, Ok, Err};
339
340 fn div<T>(x: T) -> Result<T, i32> {
341 if x == 0 {
342 return Err(7);
343 }
344 Ok(x)
345 }
346 "#;
347 check_apply_diagnostic_fix_from_position(before, after);
348 }
349
350 #[test]
351 fn test_wrap_return_type_handles_type_aliases() {
352 let before = r#"
353 //- /main.rs
354 use std::{string::String, result::Result::{self, Ok, Err}};
355
356 type MyResult<T> = Result<T, String>;
357
358 fn div(x: i32, y: i32) -> MyResult<i32> {
359 if y == 0 {
360 return Err("div by zero".into());
361 }
362 x <|>/ y
363 }
364
365 //- /std/lib.rs
366 pub mod string {
367 pub struct String { }
368 }
369 pub mod result {
370 pub enum Result<T, E> { Ok(T), Err(E) }
371 }
372 "#;
373 let after = r#"
374 use std::{string::String, result::Result::{self, Ok, Err}};
375
376 type MyResult<T> = Result<T, String>;
377 fn div(x: i32, y: i32) -> MyResult<i32> {
378 if y == 0 {
379 return Err("div by zero".into());
380 }
381 Ok(x / y)
382 }
383 "#;
384 check_apply_diagnostic_fix_from_position(before, after);
385 }
386
387 #[test]
388 fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() {
389 let content = r#"
390 //- /main.rs
391 use std::{string::String, result::Result::{self, Ok, Err}};
392
393 fn foo() -> Result<String, i32> {
394 0<|>
395 }
396
397 //- /std/lib.rs
398 pub mod string {
399 pub struct String { }
400 }
401 pub mod result {
402 pub enum Result<T, E> { Ok(T), Err(E) }
403 }
404 "#;
405 check_no_diagnostic_for_target_file(content);
406 }
407
408 #[test]
409 fn test_wrap_return_type_not_applicable_when_return_type_is_not_result() {
410 let content = r#"
411 //- /main.rs
412 use std::{string::String, result::Result::{self, Ok, Err}};
413
414 enum SomeOtherEnum {
415 Ok(i32),
416 Err(String),
417 }
418
419 fn foo() -> SomeOtherEnum {
420 0<|>
421 }
422
423 //- /std/lib.rs
424 pub mod string {
425 pub struct String { }
426 }
427 pub mod result {
428 pub enum Result<T, E> { Ok(T), Err(E) }
429 }
430 "#;
431 check_no_diagnostic_for_target_file(content);
432 }
433
434 #[test]
222 fn test_fill_struct_fields_empty() { 435 fn test_fill_struct_fields_empty() {
223 let before = r" 436 let before = r"
224 struct TestStruct { 437 struct TestStruct {