diff options
Diffstat (limited to 'crates/ra_ide_api')
-rw-r--r-- | crates/ra_ide_api/src/diagnostics.rs | 215 |
1 files changed, 214 insertions, 1 deletions
diff --git a/crates/ra_ide_api/src/diagnostics.rs b/crates/ra_ide_api/src/diagnostics.rs index c2b959cb3..1a4882824 100644 --- a/crates/ra_ide_api/src/diagnostics.rs +++ b/crates/ra_ide_api/src/diagnostics.rs | |||
@@ -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); |
@@ -171,10 +184,11 @@ fn check_struct_shorthand_initialization( | |||
171 | #[cfg(test)] | 184 | #[cfg(test)] |
172 | mod tests { | 185 | mod 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 { |