diff options
Diffstat (limited to 'crates/ra_ide/src/diagnostics.rs')
-rw-r--r-- | crates/ra_ide/src/diagnostics.rs | 733 |
1 files changed, 321 insertions, 412 deletions
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs index 46f8c31c7..e029af0dc 100644 --- a/crates/ra_ide/src/diagnostics.rs +++ b/crates/ra_ide/src/diagnostics.rs | |||
@@ -35,7 +35,8 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
35 | let parse = db.parse(file_id); | 35 | let parse = db.parse(file_id); |
36 | let mut res = Vec::new(); | 36 | let mut res = Vec::new(); |
37 | 37 | ||
38 | res.extend(parse.errors().iter().map(|err| Diagnostic { | 38 | // [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily. |
39 | res.extend(parse.errors().iter().take(128).map(|err| Diagnostic { | ||
39 | range: err.range(), | 40 | range: err.range(), |
40 | message: format!("Syntax Error: {}", err), | 41 | message: format!("Syntax Error: {}", err), |
41 | severity: Severity::Error, | 42 | severity: Severity::Error, |
@@ -99,14 +100,6 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
99 | fix, | 100 | fix, |
100 | }) | 101 | }) |
101 | }) | 102 | }) |
102 | .on::<hir::diagnostics::MissingMatchArms, _>(|d| { | ||
103 | res.borrow_mut().push(Diagnostic { | ||
104 | range: sema.diagnostics_range(d).range, | ||
105 | message: d.message(), | ||
106 | severity: Severity::Error, | ||
107 | fix: None, | ||
108 | }) | ||
109 | }) | ||
110 | .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| { | 103 | .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| { |
111 | let node = d.ast(db); | 104 | let node = d.ast(db); |
112 | let replacement = format!("Ok({})", node.syntax()); | 105 | let replacement = format!("Ok({})", node.syntax()); |
@@ -138,7 +131,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
138 | 131 | ||
139 | fn missing_struct_field_fix( | 132 | fn missing_struct_field_fix( |
140 | sema: &Semantics<RootDatabase>, | 133 | sema: &Semantics<RootDatabase>, |
141 | file_id: FileId, | 134 | usage_file_id: FileId, |
142 | d: &hir::diagnostics::NoSuchField, | 135 | d: &hir::diagnostics::NoSuchField, |
143 | ) -> Option<Fix> { | 136 | ) -> Option<Fix> { |
144 | let record_expr = sema.ast(d); | 137 | let record_expr = sema.ast(d); |
@@ -146,25 +139,30 @@ fn missing_struct_field_fix( | |||
146 | let record_lit = ast::RecordLit::cast(record_expr.syntax().parent()?.parent()?)?; | 139 | let record_lit = ast::RecordLit::cast(record_expr.syntax().parent()?.parent()?)?; |
147 | let def_id = sema.resolve_variant(record_lit)?; | 140 | let def_id = sema.resolve_variant(record_lit)?; |
148 | let module; | 141 | let module; |
142 | let def_file_id; | ||
149 | let record_fields = match VariantDef::from(def_id) { | 143 | let record_fields = match VariantDef::from(def_id) { |
150 | VariantDef::Struct(s) => { | 144 | VariantDef::Struct(s) => { |
151 | module = s.module(sema.db); | 145 | module = s.module(sema.db); |
152 | let source = s.source(sema.db); | 146 | let source = s.source(sema.db); |
147 | def_file_id = source.file_id; | ||
153 | let fields = source.value.field_def_list()?; | 148 | let fields = source.value.field_def_list()?; |
154 | record_field_def_list(fields)? | 149 | record_field_def_list(fields)? |
155 | } | 150 | } |
156 | VariantDef::Union(u) => { | 151 | VariantDef::Union(u) => { |
157 | module = u.module(sema.db); | 152 | module = u.module(sema.db); |
158 | let source = u.source(sema.db); | 153 | let source = u.source(sema.db); |
154 | def_file_id = source.file_id; | ||
159 | source.value.record_field_def_list()? | 155 | source.value.record_field_def_list()? |
160 | } | 156 | } |
161 | VariantDef::EnumVariant(e) => { | 157 | VariantDef::EnumVariant(e) => { |
162 | module = e.module(sema.db); | 158 | module = e.module(sema.db); |
163 | let source = e.source(sema.db); | 159 | let source = e.source(sema.db); |
160 | def_file_id = source.file_id; | ||
164 | let fields = source.value.field_def_list()?; | 161 | let fields = source.value.field_def_list()?; |
165 | record_field_def_list(fields)? | 162 | record_field_def_list(fields)? |
166 | } | 163 | } |
167 | }; | 164 | }; |
165 | let def_file_id = def_file_id.original_file(sema.db); | ||
168 | 166 | ||
169 | let new_field_type = sema.type_of_expr(&record_expr.expr()?)?; | 167 | let new_field_type = sema.type_of_expr(&record_expr.expr()?)?; |
170 | if new_field_type.is_unknown() { | 168 | if new_field_type.is_unknown() { |
@@ -179,15 +177,19 @@ fn missing_struct_field_fix( | |||
179 | let last_field_syntax = last_field.syntax(); | 177 | let last_field_syntax = last_field.syntax(); |
180 | let indent = IndentLevel::from_node(last_field_syntax); | 178 | let indent = IndentLevel::from_node(last_field_syntax); |
181 | 179 | ||
182 | let mut new_field = format!("\n{}{}", indent, new_field); | 180 | let mut new_field = new_field.to_string(); |
181 | if usage_file_id != def_file_id { | ||
182 | new_field = format!("pub(crate) {}", new_field); | ||
183 | } | ||
184 | new_field = format!("\n{}{}", indent, new_field); | ||
183 | 185 | ||
184 | let needs_comma = !last_field_syntax.to_string().ends_with(","); | 186 | let needs_comma = !last_field_syntax.to_string().ends_with(','); |
185 | if needs_comma { | 187 | if needs_comma { |
186 | new_field = format!(",{}", new_field); | 188 | new_field = format!(",{}", new_field); |
187 | } | 189 | } |
188 | 190 | ||
189 | let source_change = SourceFileEdit { | 191 | let source_change = SourceFileEdit { |
190 | file_id, | 192 | file_id: def_file_id, |
191 | edit: TextEdit::insert(last_field_syntax.text_range().end(), new_field), | 193 | edit: TextEdit::insert(last_field_syntax.text_range().end(), new_field), |
192 | }; | 194 | }; |
193 | let fix = Fix::new("Create field", source_change.into()); | 195 | let fix = Fix::new("Create field", source_change.into()); |
@@ -281,53 +283,21 @@ fn check_struct_shorthand_initialization( | |||
281 | 283 | ||
282 | #[cfg(test)] | 284 | #[cfg(test)] |
283 | mod tests { | 285 | mod tests { |
284 | use insta::assert_debug_snapshot; | ||
285 | use ra_syntax::SourceFile; | ||
286 | use stdx::trim_indent; | 286 | use stdx::trim_indent; |
287 | use test_utils::assert_eq_text; | 287 | use test_utils::assert_eq_text; |
288 | 288 | ||
289 | use crate::mock_analysis::{analysis_and_position, single_file}; | 289 | use crate::mock_analysis::{analysis_and_position, single_file, MockAnalysis}; |
290 | 290 | use expect::{expect, Expect}; | |
291 | use super::*; | ||
292 | |||
293 | type DiagnosticChecker = fn(&mut Vec<Diagnostic>, FileId, &SyntaxNode) -> Option<()>; | ||
294 | |||
295 | fn check_not_applicable(code: &str, func: DiagnosticChecker) { | ||
296 | let parse = SourceFile::parse(code); | ||
297 | let mut diagnostics = Vec::new(); | ||
298 | for node in parse.tree().syntax().descendants() { | ||
299 | func(&mut diagnostics, FileId(0), &node); | ||
300 | } | ||
301 | assert!(diagnostics.is_empty()); | ||
302 | } | ||
303 | |||
304 | fn check_apply(before: &str, after: &str, func: DiagnosticChecker) { | ||
305 | let parse = SourceFile::parse(before); | ||
306 | let mut diagnostics = Vec::new(); | ||
307 | for node in parse.tree().syntax().descendants() { | ||
308 | func(&mut diagnostics, FileId(0), &node); | ||
309 | } | ||
310 | let diagnostic = | ||
311 | diagnostics.pop().unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before)); | ||
312 | let mut fix = diagnostic.fix.unwrap(); | ||
313 | let edit = fix.source_change.source_file_edits.pop().unwrap().edit; | ||
314 | let actual = { | ||
315 | let mut actual = before.to_string(); | ||
316 | edit.apply(&mut actual); | ||
317 | actual | ||
318 | }; | ||
319 | assert_eq_text!(after, &actual); | ||
320 | } | ||
321 | 291 | ||
322 | /// Takes a multi-file input fixture with annotated cursor positions, | 292 | /// Takes a multi-file input fixture with annotated cursor positions, |
323 | /// and checks that: | 293 | /// and checks that: |
324 | /// * a diagnostic is produced | 294 | /// * a diagnostic is produced |
325 | /// * this diagnostic touches the input cursor position | 295 | /// * this diagnostic touches the input cursor position |
326 | /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied | 296 | /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied |
327 | fn check_apply_diagnostic_fix_from_position(ra_fixture: &str, after: &str) { | 297 | fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) { |
328 | let after = trim_indent(after); | 298 | let after = trim_indent(ra_fixture_after); |
329 | 299 | ||
330 | let (analysis, file_position) = analysis_and_position(ra_fixture); | 300 | let (analysis, file_position) = analysis_and_position(ra_fixture_before); |
331 | let diagnostic = analysis.diagnostics(file_position.file_id).unwrap().pop().unwrap(); | 301 | let diagnostic = analysis.diagnostics(file_position.file_id).unwrap().pop().unwrap(); |
332 | let mut fix = diagnostic.fix.unwrap(); | 302 | let mut fix = diagnostic.fix.unwrap(); |
333 | let edit = fix.source_change.source_file_edits.pop().unwrap().edit; | 303 | let edit = fix.source_change.source_file_edits.pop().unwrap().edit; |
@@ -348,16 +318,20 @@ mod tests { | |||
348 | ); | 318 | ); |
349 | } | 319 | } |
350 | 320 | ||
351 | fn check_apply_diagnostic_fix(ra_fixture_before: &str, ra_fixture_after: &str) { | 321 | /// Checks that a diagnostic applies to the file containing the `<|>` cursor marker |
322 | /// which has a fix that can apply to other files. | ||
323 | fn check_apply_diagnostic_fix_in_other_file(ra_fixture_before: &str, ra_fixture_after: &str) { | ||
352 | let ra_fixture_after = &trim_indent(ra_fixture_after); | 324 | let ra_fixture_after = &trim_indent(ra_fixture_after); |
353 | let (analysis, file_id) = single_file(ra_fixture_before); | 325 | let (analysis, file_pos) = analysis_and_position(ra_fixture_before); |
354 | let before = analysis.file_text(file_id).unwrap(); | 326 | let current_file_id = file_pos.file_id; |
355 | let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap(); | 327 | let diagnostic = analysis.diagnostics(current_file_id).unwrap().pop().unwrap(); |
356 | let mut fix = diagnostic.fix.unwrap(); | 328 | let mut fix = diagnostic.fix.unwrap(); |
357 | let edit = fix.source_change.source_file_edits.pop().unwrap().edit; | 329 | let edit = fix.source_change.source_file_edits.pop().unwrap(); |
330 | let changed_file_id = edit.file_id; | ||
331 | let before = analysis.file_text(changed_file_id).unwrap(); | ||
358 | let actual = { | 332 | let actual = { |
359 | let mut actual = before.to_string(); | 333 | let mut actual = before.to_string(); |
360 | edit.apply(&mut actual); | 334 | edit.edit.apply(&mut actual); |
361 | actual | 335 | actual |
362 | }; | 336 | }; |
363 | assert_eq_text!(ra_fixture_after, &actual); | 337 | assert_eq_text!(ra_fixture_after, &actual); |
@@ -365,267 +339,249 @@ mod tests { | |||
365 | 339 | ||
366 | /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics | 340 | /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics |
367 | /// apply to the file containing the cursor. | 341 | /// apply to the file containing the cursor. |
368 | fn check_no_diagnostic_for_target_file(ra_fixture: &str) { | 342 | fn check_no_diagnostics(ra_fixture: &str) { |
369 | let (analysis, file_position) = analysis_and_position(ra_fixture); | 343 | let mock = MockAnalysis::with_files(ra_fixture); |
370 | let diagnostics = analysis.diagnostics(file_position.file_id).unwrap(); | 344 | let files = mock.files().map(|(it, _)| it).collect::<Vec<_>>(); |
371 | assert_eq!(diagnostics.len(), 0); | 345 | let analysis = mock.analysis(); |
372 | } | 346 | let diagnostics = files |
373 | 347 | .into_iter() | |
374 | fn check_no_diagnostic(ra_fixture: &str) { | 348 | .flat_map(|file_id| analysis.diagnostics(file_id).unwrap()) |
349 | .collect::<Vec<_>>(); | ||
350 | assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); | ||
351 | } | ||
352 | |||
353 | fn check_expect(ra_fixture: &str, expect: Expect) { | ||
375 | let (analysis, file_id) = single_file(ra_fixture); | 354 | let (analysis, file_id) = single_file(ra_fixture); |
376 | let diagnostics = analysis.diagnostics(file_id).unwrap(); | 355 | let diagnostics = analysis.diagnostics(file_id).unwrap(); |
377 | assert_eq!(diagnostics.len(), 0, "expected no diagnostic, found one"); | 356 | expect.assert_debug_eq(&diagnostics) |
378 | } | 357 | } |
379 | 358 | ||
380 | #[test] | 359 | #[test] |
381 | fn test_wrap_return_type() { | 360 | fn test_wrap_return_type() { |
382 | let before = r#" | 361 | check_fix( |
383 | //- /main.rs | 362 | r#" |
384 | use core::result::Result::{self, Ok, Err}; | 363 | //- /main.rs |
364 | use core::result::Result::{self, Ok, Err}; | ||
385 | 365 | ||
386 | fn div(x: i32, y: i32) -> Result<i32, ()> { | 366 | fn div(x: i32, y: i32) -> Result<i32, ()> { |
387 | if y == 0 { | 367 | if y == 0 { |
388 | return Err(()); | 368 | return Err(()); |
389 | } | 369 | } |
390 | x / y<|> | 370 | x / y<|> |
391 | } | 371 | } |
392 | //- /core/lib.rs | 372 | //- /core/lib.rs |
393 | pub mod result { | 373 | pub mod result { |
394 | pub enum Result<T, E> { Ok(T), Err(E) } | 374 | pub enum Result<T, E> { Ok(T), Err(E) } |
395 | } | 375 | } |
396 | "#; | 376 | "#, |
397 | let after = r#" | 377 | r#" |
398 | use core::result::Result::{self, Ok, Err}; | 378 | use core::result::Result::{self, Ok, Err}; |
399 | 379 | ||
400 | fn div(x: i32, y: i32) -> Result<i32, ()> { | 380 | fn div(x: i32, y: i32) -> Result<i32, ()> { |
401 | if y == 0 { | 381 | if y == 0 { |
402 | return Err(()); | 382 | return Err(()); |
403 | } | 383 | } |
404 | Ok(x / y) | 384 | Ok(x / y) |
405 | } | 385 | } |
406 | "#; | 386 | "#, |
407 | check_apply_diagnostic_fix_from_position(before, after); | 387 | ); |
408 | } | 388 | } |
409 | 389 | ||
410 | #[test] | 390 | #[test] |
411 | fn test_wrap_return_type_handles_generic_functions() { | 391 | fn test_wrap_return_type_handles_generic_functions() { |
412 | let before = r#" | 392 | check_fix( |
413 | //- /main.rs | 393 | r#" |
414 | use core::result::Result::{self, Ok, Err}; | 394 | //- /main.rs |
395 | use core::result::Result::{self, Ok, Err}; | ||
415 | 396 | ||
416 | fn div<T>(x: T) -> Result<T, i32> { | 397 | fn div<T>(x: T) -> Result<T, i32> { |
417 | if x == 0 { | 398 | if x == 0 { |
418 | return Err(7); | 399 | return Err(7); |
419 | } | 400 | } |
420 | <|>x | 401 | <|>x |
421 | } | 402 | } |
422 | //- /core/lib.rs | 403 | //- /core/lib.rs |
423 | pub mod result { | 404 | pub mod result { |
424 | pub enum Result<T, E> { Ok(T), Err(E) } | 405 | pub enum Result<T, E> { Ok(T), Err(E) } |
425 | } | 406 | } |
426 | "#; | 407 | "#, |
427 | let after = r#" | 408 | r#" |
428 | use core::result::Result::{self, Ok, Err}; | 409 | use core::result::Result::{self, Ok, Err}; |
429 | 410 | ||
430 | fn div<T>(x: T) -> Result<T, i32> { | 411 | fn div<T>(x: T) -> Result<T, i32> { |
431 | if x == 0 { | 412 | if x == 0 { |
432 | return Err(7); | 413 | return Err(7); |
433 | } | 414 | } |
434 | Ok(x) | 415 | Ok(x) |
435 | } | 416 | } |
436 | "#; | 417 | "#, |
437 | check_apply_diagnostic_fix_from_position(before, after); | 418 | ); |
438 | } | 419 | } |
439 | 420 | ||
440 | #[test] | 421 | #[test] |
441 | fn test_wrap_return_type_handles_type_aliases() { | 422 | fn test_wrap_return_type_handles_type_aliases() { |
442 | let before = r#" | 423 | check_fix( |
443 | //- /main.rs | 424 | r#" |
444 | use core::result::Result::{self, Ok, Err}; | 425 | //- /main.rs |
426 | use core::result::Result::{self, Ok, Err}; | ||
445 | 427 | ||
446 | type MyResult<T> = Result<T, ()>; | 428 | type MyResult<T> = Result<T, ()>; |
447 | 429 | ||
448 | fn div(x: i32, y: i32) -> MyResult<i32> { | 430 | fn div(x: i32, y: i32) -> MyResult<i32> { |
449 | if y == 0 { | 431 | if y == 0 { |
450 | return Err(()); | 432 | return Err(()); |
451 | } | 433 | } |
452 | x <|>/ y | 434 | x <|>/ y |
453 | } | 435 | } |
454 | //- /core/lib.rs | 436 | //- /core/lib.rs |
455 | pub mod result { | 437 | pub mod result { |
456 | pub enum Result<T, E> { Ok(T), Err(E) } | 438 | pub enum Result<T, E> { Ok(T), Err(E) } |
457 | } | 439 | } |
458 | "#; | 440 | "#, |
459 | let after = r#" | 441 | r#" |
460 | use core::result::Result::{self, Ok, Err}; | 442 | use core::result::Result::{self, Ok, Err}; |
461 | 443 | ||
462 | type MyResult<T> = Result<T, ()>; | 444 | type MyResult<T> = Result<T, ()>; |
463 | 445 | ||
464 | fn div(x: i32, y: i32) -> MyResult<i32> { | 446 | fn div(x: i32, y: i32) -> MyResult<i32> { |
465 | if y == 0 { | 447 | if y == 0 { |
466 | return Err(()); | 448 | return Err(()); |
467 | } | 449 | } |
468 | Ok(x / y) | 450 | Ok(x / y) |
469 | } | 451 | } |
470 | "#; | 452 | "#, |
471 | check_apply_diagnostic_fix_from_position(before, after); | 453 | ); |
472 | } | 454 | } |
473 | 455 | ||
474 | #[test] | 456 | #[test] |
475 | fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() { | 457 | fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() { |
476 | check_no_diagnostic_for_target_file( | 458 | check_no_diagnostics( |
477 | r" | 459 | r#" |
478 | //- /main.rs | 460 | //- /main.rs |
479 | use core::result::Result::{self, Ok, Err}; | 461 | use core::result::Result::{self, Ok, Err}; |
480 | 462 | ||
481 | fn foo() -> Result<(), i32> { | 463 | fn foo() -> Result<(), i32> { 0 } |
482 | 0<|> | ||
483 | } | ||
484 | 464 | ||
485 | //- /core/lib.rs | 465 | //- /core/lib.rs |
486 | pub mod result { | 466 | pub mod result { |
487 | pub enum Result<T, E> { Ok(T), Err(E) } | 467 | pub enum Result<T, E> { Ok(T), Err(E) } |
488 | } | 468 | } |
489 | ", | 469 | "#, |
490 | ); | 470 | ); |
491 | } | 471 | } |
492 | 472 | ||
493 | #[test] | 473 | #[test] |
494 | fn test_wrap_return_type_not_applicable_when_return_type_is_not_result() { | 474 | fn test_wrap_return_type_not_applicable_when_return_type_is_not_result() { |
495 | check_no_diagnostic_for_target_file( | 475 | check_no_diagnostics( |
496 | r" | 476 | r#" |
497 | //- /main.rs | 477 | //- /main.rs |
498 | use core::result::Result::{self, Ok, Err}; | 478 | use core::result::Result::{self, Ok, Err}; |
499 | 479 | ||
500 | enum SomeOtherEnum { | 480 | enum SomeOtherEnum { Ok(i32), Err(String) } |
501 | Ok(i32), | ||
502 | Err(String), | ||
503 | } | ||
504 | 481 | ||
505 | fn foo() -> SomeOtherEnum { | 482 | fn foo() -> SomeOtherEnum { 0 } |
506 | 0<|> | ||
507 | } | ||
508 | 483 | ||
509 | //- /core/lib.rs | 484 | //- /core/lib.rs |
510 | pub mod result { | 485 | pub mod result { |
511 | pub enum Result<T, E> { Ok(T), Err(E) } | 486 | pub enum Result<T, E> { Ok(T), Err(E) } |
512 | } | 487 | } |
513 | ", | 488 | "#, |
514 | ); | 489 | ); |
515 | } | 490 | } |
516 | 491 | ||
517 | #[test] | 492 | #[test] |
518 | fn test_fill_struct_fields_empty() { | 493 | fn test_fill_struct_fields_empty() { |
519 | let before = r" | 494 | check_fix( |
520 | struct TestStruct { | 495 | r#" |
521 | one: i32, | 496 | struct TestStruct { one: i32, two: i64 } |
522 | two: i64, | ||
523 | } | ||
524 | 497 | ||
525 | fn test_fn() { | 498 | fn test_fn() { |
526 | let s = TestStruct{}; | 499 | let s = TestStruct {<|>}; |
527 | } | 500 | } |
528 | "; | 501 | "#, |
529 | let after = r" | 502 | r#" |
530 | struct TestStruct { | 503 | struct TestStruct { one: i32, two: i64 } |
531 | one: i32, | ||
532 | two: i64, | ||
533 | } | ||
534 | 504 | ||
535 | fn test_fn() { | 505 | fn test_fn() { |
536 | let s = TestStruct{ one: (), two: ()}; | 506 | let s = TestStruct { one: (), two: ()}; |
537 | } | 507 | } |
538 | "; | 508 | "#, |
539 | check_apply_diagnostic_fix(before, after); | 509 | ); |
540 | } | 510 | } |
541 | 511 | ||
542 | #[test] | 512 | #[test] |
543 | fn test_fill_struct_fields_self() { | 513 | fn test_fill_struct_fields_self() { |
544 | let before = r" | 514 | check_fix( |
545 | struct TestStruct { | 515 | r#" |
546 | one: i32, | 516 | struct TestStruct { one: i32 } |
547 | } | ||
548 | 517 | ||
549 | impl TestStruct { | 518 | impl TestStruct { |
550 | fn test_fn() { | 519 | fn test_fn() { let s = Self {<|>}; } |
551 | let s = Self {}; | 520 | } |
552 | } | 521 | "#, |
553 | } | 522 | r#" |
554 | "; | 523 | struct TestStruct { one: i32 } |
555 | let after = r" | ||
556 | struct TestStruct { | ||
557 | one: i32, | ||
558 | } | ||
559 | 524 | ||
560 | impl TestStruct { | 525 | impl TestStruct { |
561 | fn test_fn() { | 526 | fn test_fn() { let s = Self { one: ()}; } |
562 | let s = Self { one: ()}; | 527 | } |
563 | } | 528 | "#, |
564 | } | 529 | ); |
565 | "; | ||
566 | check_apply_diagnostic_fix(before, after); | ||
567 | } | 530 | } |
568 | 531 | ||
569 | #[test] | 532 | #[test] |
570 | fn test_fill_struct_fields_enum() { | 533 | fn test_fill_struct_fields_enum() { |
571 | let before = r" | 534 | check_fix( |
572 | enum Expr { | 535 | r#" |
573 | Bin { lhs: Box<Expr>, rhs: Box<Expr> } | 536 | enum Expr { |
574 | } | 537 | Bin { lhs: Box<Expr>, rhs: Box<Expr> } |
538 | } | ||
575 | 539 | ||
576 | impl Expr { | 540 | impl Expr { |
577 | fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr { | 541 | fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr { |
578 | Expr::Bin { } | 542 | Expr::Bin {<|> } |
579 | } | 543 | } |
580 | } | 544 | } |
581 | "; | 545 | "#, |
582 | let after = r" | 546 | r#" |
583 | enum Expr { | 547 | enum Expr { |
584 | Bin { lhs: Box<Expr>, rhs: Box<Expr> } | 548 | Bin { lhs: Box<Expr>, rhs: Box<Expr> } |
585 | } | 549 | } |
586 | 550 | ||
587 | impl Expr { | 551 | impl Expr { |
588 | fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr { | 552 | fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr { |
589 | Expr::Bin { lhs: (), rhs: () } | 553 | Expr::Bin { lhs: (), rhs: () } |
590 | } | 554 | } |
591 | } | 555 | } |
592 | "; | 556 | "#, |
593 | check_apply_diagnostic_fix(before, after); | 557 | ); |
594 | } | 558 | } |
595 | 559 | ||
596 | #[test] | 560 | #[test] |
597 | fn test_fill_struct_fields_partial() { | 561 | fn test_fill_struct_fields_partial() { |
598 | let before = r" | 562 | check_fix( |
599 | struct TestStruct { | 563 | r#" |
600 | one: i32, | 564 | struct TestStruct { one: i32, two: i64 } |
601 | two: i64, | ||
602 | } | ||
603 | 565 | ||
604 | fn test_fn() { | 566 | fn test_fn() { |
605 | let s = TestStruct{ two: 2 }; | 567 | let s = TestStruct{ two: 2<|> }; |
606 | } | 568 | } |
607 | "; | 569 | "#, |
608 | let after = r" | 570 | r" |
609 | struct TestStruct { | 571 | struct TestStruct { one: i32, two: i64 } |
610 | one: i32, | ||
611 | two: i64, | ||
612 | } | ||
613 | 572 | ||
614 | fn test_fn() { | 573 | fn test_fn() { |
615 | let s = TestStruct{ two: 2, one: () }; | 574 | let s = TestStruct{ two: 2, one: () }; |
616 | } | 575 | } |
617 | "; | 576 | ", |
618 | check_apply_diagnostic_fix(before, after); | 577 | ); |
619 | } | 578 | } |
620 | 579 | ||
621 | #[test] | 580 | #[test] |
622 | fn test_fill_struct_fields_no_diagnostic() { | 581 | fn test_fill_struct_fields_no_diagnostic() { |
623 | check_no_diagnostic( | 582 | check_no_diagnostics( |
624 | r" | 583 | r" |
625 | struct TestStruct { | 584 | struct TestStruct { one: i32, two: i64 } |
626 | one: i32, | ||
627 | two: i64, | ||
628 | } | ||
629 | 585 | ||
630 | fn test_fn() { | 586 | fn test_fn() { |
631 | let one = 1; | 587 | let one = 1; |
@@ -637,12 +593,9 @@ mod tests { | |||
637 | 593 | ||
638 | #[test] | 594 | #[test] |
639 | fn test_fill_struct_fields_no_diagnostic_on_spread() { | 595 | fn test_fill_struct_fields_no_diagnostic_on_spread() { |
640 | check_no_diagnostic( | 596 | check_no_diagnostics( |
641 | r" | 597 | r" |
642 | struct TestStruct { | 598 | struct TestStruct { one: i32, two: i64 } |
643 | one: i32, | ||
644 | two: i64, | ||
645 | } | ||
646 | 599 | ||
647 | fn test_fn() { | 600 | fn test_fn() { |
648 | let one = 1; | 601 | let one = 1; |
@@ -654,211 +607,143 @@ mod tests { | |||
654 | 607 | ||
655 | #[test] | 608 | #[test] |
656 | fn test_unresolved_module_diagnostic() { | 609 | fn test_unresolved_module_diagnostic() { |
657 | let (analysis, file_id) = single_file("mod foo;"); | 610 | check_expect( |
658 | let diagnostics = analysis.diagnostics(file_id).unwrap(); | 611 | r#"mod foo;"#, |
659 | assert_debug_snapshot!(diagnostics, @r###" | 612 | expect![[r#" |
660 | [ | 613 | [ |
661 | Diagnostic { | 614 | Diagnostic { |
662 | message: "unresolved module", | 615 | message: "unresolved module", |
663 | range: 0..8, | 616 | range: 0..8, |
664 | severity: Error, | 617 | severity: Error, |
665 | fix: Some( | 618 | fix: Some( |
666 | Fix { | 619 | Fix { |
667 | label: "Create module", | 620 | label: "Create module", |
668 | source_change: SourceChange { | 621 | source_change: SourceChange { |
669 | source_file_edits: [], | 622 | source_file_edits: [], |
670 | file_system_edits: [ | 623 | file_system_edits: [ |
671 | CreateFile { | 624 | CreateFile { |
672 | anchor: FileId( | 625 | anchor: FileId( |
673 | 1, | 626 | 1, |
674 | ), | 627 | ), |
675 | dst: "foo.rs", | 628 | dst: "foo.rs", |
629 | }, | ||
630 | ], | ||
631 | is_snippet: false, | ||
676 | }, | 632 | }, |
677 | ], | 633 | }, |
678 | is_snippet: false, | 634 | ), |
679 | }, | ||
680 | }, | 635 | }, |
681 | ), | 636 | ] |
682 | }, | 637 | "#]], |
683 | ] | 638 | ); |
684 | "###); | ||
685 | } | 639 | } |
686 | 640 | ||
687 | #[test] | 641 | #[test] |
688 | fn range_mapping_out_of_macros() { | 642 | fn range_mapping_out_of_macros() { |
689 | let (analysis, file_id) = single_file( | 643 | // FIXME: this is very wrong, but somewhat tricky to fix. |
690 | r" | 644 | check_fix( |
691 | fn some() {} | 645 | r#" |
692 | fn items() {} | 646 | fn some() {} |
693 | fn here() {} | 647 | fn items() {} |
648 | fn here() {} | ||
694 | 649 | ||
695 | macro_rules! id { | 650 | macro_rules! id { ($($tt:tt)*) => { $($tt)*}; } |
696 | ($($tt:tt)*) => { $($tt)*}; | ||
697 | } | ||
698 | 651 | ||
699 | fn main() { | 652 | fn main() { |
700 | let _x = id![Foo { a: 42 }]; | 653 | let _x = id![Foo { a: <|>42 }]; |
701 | } | 654 | } |
702 | 655 | ||
703 | pub struct Foo { | 656 | pub struct Foo { pub a: i32, pub b: i32 } |
704 | pub a: i32, | 657 | "#, |
705 | pub b: i32, | 658 | r#" |
706 | } | 659 | fn {a:42, b: ()} {} |
707 | ", | 660 | fn items() {} |
661 | fn here() {} | ||
662 | |||
663 | macro_rules! id { ($($tt:tt)*) => { $($tt)*}; } | ||
664 | |||
665 | fn main() { | ||
666 | let _x = id![Foo { a: 42 }]; | ||
667 | } | ||
668 | |||
669 | pub struct Foo { pub a: i32, pub b: i32 } | ||
670 | "#, | ||
708 | ); | 671 | ); |
709 | let diagnostics = analysis.diagnostics(file_id).unwrap(); | ||
710 | assert_debug_snapshot!(diagnostics, @r###" | ||
711 | [ | ||
712 | Diagnostic { | ||
713 | message: "Missing structure fields:\n- b\n", | ||
714 | range: 127..136, | ||
715 | severity: Error, | ||
716 | fix: Some( | ||
717 | Fix { | ||
718 | label: "Fill struct fields", | ||
719 | source_change: SourceChange { | ||
720 | source_file_edits: [ | ||
721 | SourceFileEdit { | ||
722 | file_id: FileId( | ||
723 | 1, | ||
724 | ), | ||
725 | edit: TextEdit { | ||
726 | indels: [ | ||
727 | Indel { | ||
728 | insert: "{a:42, b: ()}", | ||
729 | delete: 3..9, | ||
730 | }, | ||
731 | ], | ||
732 | }, | ||
733 | }, | ||
734 | ], | ||
735 | file_system_edits: [], | ||
736 | is_snippet: false, | ||
737 | }, | ||
738 | }, | ||
739 | ), | ||
740 | }, | ||
741 | ] | ||
742 | "###); | ||
743 | } | 672 | } |
744 | 673 | ||
745 | #[test] | 674 | #[test] |
746 | fn test_check_unnecessary_braces_in_use_statement() { | 675 | fn test_check_unnecessary_braces_in_use_statement() { |
747 | check_not_applicable( | 676 | check_no_diagnostics( |
748 | " | 677 | r#" |
749 | use a; | 678 | use a; |
750 | use a::{c, d::e}; | 679 | use a::{c, d::e}; |
751 | ", | 680 | "#, |
752 | check_unnecessary_braces_in_use_statement, | ||
753 | ); | ||
754 | check_apply("use {b};", "use b;", check_unnecessary_braces_in_use_statement); | ||
755 | check_apply("use a::{c};", "use a::c;", check_unnecessary_braces_in_use_statement); | ||
756 | check_apply("use a::{self};", "use a;", check_unnecessary_braces_in_use_statement); | ||
757 | check_apply( | ||
758 | "use a::{c, d::{e}};", | ||
759 | "use a::{c, d::e};", | ||
760 | check_unnecessary_braces_in_use_statement, | ||
761 | ); | 681 | ); |
682 | check_fix(r#"use {<|>b};"#, r#"use b;"#); | ||
683 | check_fix(r#"use {b<|>};"#, r#"use b;"#); | ||
684 | check_fix(r#"use a::{c<|>};"#, r#"use a::c;"#); | ||
685 | check_fix(r#"use a::{self<|>};"#, r#"use a;"#); | ||
686 | check_fix(r#"use a::{c, d::{e<|>}};"#, r#"use a::{c, d::e};"#); | ||
762 | } | 687 | } |
763 | 688 | ||
764 | #[test] | 689 | #[test] |
765 | fn test_check_struct_shorthand_initialization() { | 690 | fn test_check_struct_shorthand_initialization() { |
766 | check_not_applicable( | 691 | check_no_diagnostics( |
767 | r#" | 692 | r#" |
768 | struct A { | 693 | struct A { a: &'static str } |
769 | a: &'static str | 694 | fn main() { A { a: "hello" } } |
770 | } | 695 | "#, |
771 | |||
772 | fn main() { | ||
773 | A { | ||
774 | a: "hello" | ||
775 | } | ||
776 | } | ||
777 | "#, | ||
778 | check_struct_shorthand_initialization, | ||
779 | ); | 696 | ); |
780 | check_not_applicable( | 697 | check_no_diagnostics( |
781 | r#" | 698 | r#" |
782 | struct A(usize); | 699 | struct A(usize); |
783 | 700 | fn main() { A { 0: 0 } } | |
784 | fn main() { | 701 | "#, |
785 | A { | ||
786 | 0: 0 | ||
787 | } | ||
788 | } | ||
789 | "#, | ||
790 | check_struct_shorthand_initialization, | ||
791 | ); | 702 | ); |
792 | 703 | ||
793 | check_apply( | 704 | check_fix( |
794 | r#" | 705 | r#" |
795 | struct A { | 706 | struct A { a: &'static str } |
796 | a: &'static str | ||
797 | } | ||
798 | |||
799 | fn main() { | 707 | fn main() { |
800 | let a = "haha"; | 708 | let a = "haha"; |
801 | A { | 709 | A { a<|>: a } |
802 | a: a | ||
803 | } | ||
804 | } | 710 | } |
805 | "#, | 711 | "#, |
806 | r#" | 712 | r#" |
807 | struct A { | 713 | struct A { a: &'static str } |
808 | a: &'static str | ||
809 | } | ||
810 | |||
811 | fn main() { | 714 | fn main() { |
812 | let a = "haha"; | 715 | let a = "haha"; |
813 | A { | 716 | A { a } |
814 | a | ||
815 | } | ||
816 | } | 717 | } |
817 | "#, | 718 | "#, |
818 | check_struct_shorthand_initialization, | ||
819 | ); | 719 | ); |
820 | 720 | ||
821 | check_apply( | 721 | check_fix( |
822 | r#" | 722 | r#" |
823 | struct A { | 723 | struct A { a: &'static str, b: &'static str } |
824 | a: &'static str, | ||
825 | b: &'static str | ||
826 | } | ||
827 | |||
828 | fn main() { | 724 | fn main() { |
829 | let a = "haha"; | 725 | let a = "haha"; |
830 | let b = "bb"; | 726 | let b = "bb"; |
831 | A { | 727 | A { a<|>: a, b } |
832 | a: a, | ||
833 | b | ||
834 | } | ||
835 | } | 728 | } |
836 | "#, | 729 | "#, |
837 | r#" | 730 | r#" |
838 | struct A { | 731 | struct A { a: &'static str, b: &'static str } |
839 | a: &'static str, | ||
840 | b: &'static str | ||
841 | } | ||
842 | |||
843 | fn main() { | 732 | fn main() { |
844 | let a = "haha"; | 733 | let a = "haha"; |
845 | let b = "bb"; | 734 | let b = "bb"; |
846 | A { | 735 | A { a, b } |
847 | a, | ||
848 | b | ||
849 | } | ||
850 | } | 736 | } |
851 | "#, | 737 | "#, |
852 | check_struct_shorthand_initialization, | ||
853 | ); | 738 | ); |
854 | } | 739 | } |
855 | 740 | ||
856 | #[test] | 741 | #[test] |
857 | fn test_add_field_from_usage() { | 742 | fn test_add_field_from_usage() { |
858 | check_apply_diagnostic_fix( | 743 | check_fix( |
859 | r" | 744 | r" |
860 | fn main() { | 745 | fn main() { |
861 | Foo { bar: 3, baz: false}; | 746 | Foo { bar: 3, baz<|>: false}; |
862 | } | 747 | } |
863 | struct Foo { | 748 | struct Foo { |
864 | bar: i32 | 749 | bar: i32 |
@@ -875,4 +760,28 @@ struct Foo { | |||
875 | ", | 760 | ", |
876 | ) | 761 | ) |
877 | } | 762 | } |
763 | |||
764 | #[test] | ||
765 | fn test_add_field_in_other_file_from_usage() { | ||
766 | check_apply_diagnostic_fix_in_other_file( | ||
767 | r" | ||
768 | //- /main.rs | ||
769 | mod foo; | ||
770 | |||
771 | fn main() { | ||
772 | <|>foo::Foo { bar: 3, baz: false}; | ||
773 | } | ||
774 | //- /foo.rs | ||
775 | struct Foo { | ||
776 | bar: i32 | ||
777 | } | ||
778 | ", | ||
779 | r" | ||
780 | struct Foo { | ||
781 | bar: i32, | ||
782 | pub(crate) baz: bool | ||
783 | } | ||
784 | ", | ||
785 | ) | ||
786 | } | ||
878 | } | 787 | } |