]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/diagnostics.rs
Merge #8410
[rust.git] / crates / ide / src / diagnostics.rs
1 //! Collects diagnostics & fixits  for a single file.
2 //!
3 //! The tricky bit here is that diagnostics are produced by hir in terms of
4 //! macro-expanded files, but we need to present them to the users in terms of
5 //! original files. So we need to map the ranges.
6
7 mod fixes;
8 mod field_shorthand;
9 mod unlinked_file;
10
11 use std::cell::RefCell;
12
13 use hir::{
14     db::AstDatabase,
15     diagnostics::{Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder},
16     InFile, Semantics,
17 };
18 use ide_db::{base_db::SourceDatabase, RootDatabase};
19 use itertools::Itertools;
20 use rustc_hash::FxHashSet;
21 use syntax::{
22     ast::{self, AstNode},
23     SyntaxNode, SyntaxNodePtr, TextRange, TextSize,
24 };
25 use text_edit::TextEdit;
26 use unlinked_file::UnlinkedFile;
27
28 use crate::{FileId, Label, SourceChange};
29
30 use self::fixes::DiagnosticWithFix;
31
32 #[derive(Debug)]
33 pub struct Diagnostic {
34     // pub name: Option<String>,
35     pub message: String,
36     pub range: TextRange,
37     pub severity: Severity,
38     pub fix: Option<Fix>,
39     pub unused: bool,
40     pub code: Option<DiagnosticCode>,
41 }
42
43 impl Diagnostic {
44     fn error(range: TextRange, message: String) -> Self {
45         Self { message, range, severity: Severity::Error, fix: None, unused: false, code: None }
46     }
47
48     fn hint(range: TextRange, message: String) -> Self {
49         Self {
50             message,
51             range,
52             severity: Severity::WeakWarning,
53             fix: None,
54             unused: false,
55             code: None,
56         }
57     }
58
59     fn with_fix(self, fix: Option<Fix>) -> Self {
60         Self { fix, ..self }
61     }
62
63     fn with_unused(self, unused: bool) -> Self {
64         Self { unused, ..self }
65     }
66
67     fn with_code(self, code: Option<DiagnosticCode>) -> Self {
68         Self { code, ..self }
69     }
70 }
71
72 #[derive(Debug)]
73 pub struct Fix {
74     pub label: Label,
75     pub source_change: SourceChange,
76     /// Allows to trigger the fix only when the caret is in the range given
77     pub fix_trigger_range: TextRange,
78 }
79
80 impl Fix {
81     fn new(label: &str, source_change: SourceChange, fix_trigger_range: TextRange) -> Self {
82         let label = Label::new(label);
83         Self { label, source_change, fix_trigger_range }
84     }
85 }
86
87 #[derive(Debug, Copy, Clone)]
88 pub enum Severity {
89     Error,
90     WeakWarning,
91 }
92
93 #[derive(Default, Debug, Clone)]
94 pub struct DiagnosticsConfig {
95     pub disable_experimental: bool,
96     pub disabled: FxHashSet<String>,
97 }
98
99 pub(crate) fn diagnostics(
100     db: &RootDatabase,
101     config: &DiagnosticsConfig,
102     file_id: FileId,
103 ) -> Vec<Diagnostic> {
104     let _p = profile::span("diagnostics");
105     let sema = Semantics::new(db);
106     let parse = db.parse(file_id);
107     let mut res = Vec::new();
108
109     // [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily.
110     res.extend(
111         parse
112             .errors()
113             .iter()
114             .take(128)
115             .map(|err| Diagnostic::error(err.range(), format!("Syntax Error: {}", err))),
116     );
117
118     for node in parse.tree().syntax().descendants() {
119         check_unnecessary_braces_in_use_statement(&mut res, file_id, &node);
120         field_shorthand::check(&mut res, file_id, &node);
121     }
122     let res = RefCell::new(res);
123     let sink_builder = DiagnosticSinkBuilder::new()
124         .on::<hir::diagnostics::UnresolvedModule, _>(|d| {
125             res.borrow_mut().push(diagnostic_with_fix(d, &sema));
126         })
127         .on::<hir::diagnostics::MissingFields, _>(|d| {
128             res.borrow_mut().push(diagnostic_with_fix(d, &sema));
129         })
130         .on::<hir::diagnostics::MissingOkOrSomeInTailExpr, _>(|d| {
131             res.borrow_mut().push(diagnostic_with_fix(d, &sema));
132         })
133         .on::<hir::diagnostics::NoSuchField, _>(|d| {
134             res.borrow_mut().push(diagnostic_with_fix(d, &sema));
135         })
136         .on::<hir::diagnostics::RemoveThisSemicolon, _>(|d| {
137             res.borrow_mut().push(diagnostic_with_fix(d, &sema));
138         })
139         .on::<hir::diagnostics::IncorrectCase, _>(|d| {
140             res.borrow_mut().push(warning_with_fix(d, &sema));
141         })
142         .on::<hir::diagnostics::ReplaceFilterMapNextWithFindMap, _>(|d| {
143             res.borrow_mut().push(warning_with_fix(d, &sema));
144         })
145         .on::<hir::diagnostics::InactiveCode, _>(|d| {
146             // If there's inactive code somewhere in a macro, don't propagate to the call-site.
147             if d.display_source().file_id.expansion_info(db).is_some() {
148                 return;
149             }
150
151             // Override severity and mark as unused.
152             res.borrow_mut().push(
153                 Diagnostic::hint(
154                     sema.diagnostics_display_range(d.display_source()).range,
155                     d.message(),
156                 )
157                 .with_unused(true)
158                 .with_code(Some(d.code())),
159             );
160         })
161         .on::<UnlinkedFile, _>(|d| {
162             // Limit diagnostic to the first few characters in the file. This matches how VS Code
163             // renders it with the full span, but on other editors, and is less invasive.
164             let range = sema.diagnostics_display_range(d.display_source()).range;
165             let range = range.intersect(TextRange::up_to(TextSize::of("..."))).unwrap_or(range);
166
167             // Override severity and mark as unused.
168             res.borrow_mut().push(
169                 Diagnostic::hint(range, d.message())
170                     .with_fix(d.fix(&sema))
171                     .with_code(Some(d.code())),
172             );
173         })
174         .on::<hir::diagnostics::UnresolvedProcMacro, _>(|d| {
175             // Use more accurate position if available.
176             let display_range = d
177                 .precise_location
178                 .unwrap_or_else(|| sema.diagnostics_display_range(d.display_source()).range);
179
180             // FIXME: it would be nice to tell the user whether proc macros are currently disabled
181             res.borrow_mut()
182                 .push(Diagnostic::hint(display_range, d.message()).with_code(Some(d.code())));
183         })
184         .on::<hir::diagnostics::UnresolvedMacroCall, _>(|d| {
185             let last_path_segment = sema.db.parse_or_expand(d.file).and_then(|root| {
186                 d.node
187                     .to_node(&root)
188                     .path()
189                     .and_then(|it| it.segment())
190                     .and_then(|it| it.name_ref())
191                     .map(|it| InFile::new(d.file, SyntaxNodePtr::new(it.syntax())))
192             });
193             let diagnostics = last_path_segment.unwrap_or_else(|| d.display_source());
194             let display_range = sema.diagnostics_display_range(diagnostics).range;
195             res.borrow_mut()
196                 .push(Diagnostic::error(display_range, d.message()).with_code(Some(d.code())));
197         })
198         // Only collect experimental diagnostics when they're enabled.
199         .filter(|diag| !(diag.is_experimental() && config.disable_experimental))
200         .filter(|diag| !config.disabled.contains(diag.code().as_str()));
201
202     // Finalize the `DiagnosticSink` building process.
203     let mut sink = sink_builder
204         // Diagnostics not handled above get no fix and default treatment.
205         .build(|d| {
206             res.borrow_mut().push(
207                 Diagnostic::error(
208                     sema.diagnostics_display_range(d.display_source()).range,
209                     d.message(),
210                 )
211                 .with_code(Some(d.code())),
212             );
213         });
214
215     match sema.to_module_def(file_id) {
216         Some(m) => m.diagnostics(db, &mut sink),
217         None => {
218             sink.push(UnlinkedFile { file_id, node: SyntaxNodePtr::new(&parse.tree().syntax()) });
219         }
220     }
221
222     drop(sink);
223     res.into_inner()
224 }
225
226 fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic {
227     Diagnostic::error(sema.diagnostics_display_range(d.display_source()).range, d.message())
228         .with_fix(d.fix(&sema))
229         .with_code(Some(d.code()))
230 }
231
232 fn warning_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic {
233     Diagnostic::hint(sema.diagnostics_display_range(d.display_source()).range, d.message())
234         .with_fix(d.fix(&sema))
235         .with_code(Some(d.code()))
236 }
237
238 fn check_unnecessary_braces_in_use_statement(
239     acc: &mut Vec<Diagnostic>,
240     file_id: FileId,
241     node: &SyntaxNode,
242 ) -> Option<()> {
243     let use_tree_list = ast::UseTreeList::cast(node.clone())?;
244     if let Some((single_use_tree,)) = use_tree_list.use_trees().collect_tuple() {
245         // If there is a comment inside the bracketed `use`,
246         // assume it is a commented out module path and don't show diagnostic.
247         if use_tree_list.has_inner_comment() {
248             return Some(());
249         }
250
251         let use_range = use_tree_list.syntax().text_range();
252         let edit =
253             text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(&single_use_tree)
254                 .unwrap_or_else(|| {
255                     let to_replace = single_use_tree.syntax().text().to_string();
256                     let mut edit_builder = TextEdit::builder();
257                     edit_builder.delete(use_range);
258                     edit_builder.insert(use_range.start(), to_replace);
259                     edit_builder.finish()
260                 });
261
262         acc.push(
263             Diagnostic::hint(use_range, "Unnecessary braces in use statement".to_string())
264                 .with_fix(Some(Fix::new(
265                     "Remove unnecessary braces",
266                     SourceChange::from_text_edit(file_id, edit),
267                     use_range,
268                 ))),
269         );
270     }
271
272     Some(())
273 }
274
275 fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(
276     single_use_tree: &ast::UseTree,
277 ) -> Option<TextEdit> {
278     let use_tree_list_node = single_use_tree.syntax().parent()?;
279     if single_use_tree.path()?.segment()?.self_token().is_some() {
280         let start = use_tree_list_node.prev_sibling_or_token()?.text_range().start();
281         let end = use_tree_list_node.text_range().end();
282         return Some(TextEdit::delete(TextRange::new(start, end)));
283     }
284     None
285 }
286
287 #[cfg(test)]
288 mod tests {
289     use expect_test::{expect, Expect};
290     use stdx::trim_indent;
291     use test_utils::assert_eq_text;
292
293     use crate::{fixture, DiagnosticsConfig};
294
295     /// Takes a multi-file input fixture with annotated cursor positions,
296     /// and checks that:
297     ///  * a diagnostic is produced
298     ///  * this diagnostic fix trigger range touches the input cursor position
299     ///  * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied
300     pub(crate) fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) {
301         let after = trim_indent(ra_fixture_after);
302
303         let (analysis, file_position) = fixture::position(ra_fixture_before);
304         let diagnostic = analysis
305             .diagnostics(&DiagnosticsConfig::default(), file_position.file_id)
306             .unwrap()
307             .pop()
308             .unwrap();
309         let fix = diagnostic.fix.unwrap();
310         let actual = {
311             let file_id = *fix.source_change.source_file_edits.keys().next().unwrap();
312             let mut actual = analysis.file_text(file_id).unwrap().to_string();
313
314             for edit in fix.source_change.source_file_edits.values() {
315                 edit.apply(&mut actual);
316             }
317             actual
318         };
319
320         assert_eq_text!(&after, &actual);
321         assert!(
322             fix.fix_trigger_range.contains_inclusive(file_position.offset),
323             "diagnostic fix range {:?} does not touch cursor position {:?}",
324             fix.fix_trigger_range,
325             file_position.offset
326         );
327     }
328
329     /// Checks that there's a diagnostic *without* fix at `$0`.
330     fn check_no_fix(ra_fixture: &str) {
331         let (analysis, file_position) = fixture::position(ra_fixture);
332         let diagnostic = analysis
333             .diagnostics(&DiagnosticsConfig::default(), file_position.file_id)
334             .unwrap()
335             .pop()
336             .unwrap();
337         assert!(diagnostic.fix.is_none(), "got a fix when none was expected: {:?}", diagnostic);
338     }
339
340     /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics
341     /// apply to the file containing the cursor.
342     pub(crate) fn check_no_diagnostics(ra_fixture: &str) {
343         let (analysis, files) = fixture::files(ra_fixture);
344         let diagnostics = files
345             .into_iter()
346             .flat_map(|file_id| {
347                 analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap()
348             })
349             .collect::<Vec<_>>();
350         assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics);
351     }
352
353     fn check_expect(ra_fixture: &str, expect: Expect) {
354         let (analysis, file_id) = fixture::file(ra_fixture);
355         let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap();
356         expect.assert_debug_eq(&diagnostics)
357     }
358
359     #[test]
360     fn test_wrap_return_type_option() {
361         check_fix(
362             r#"
363 //- /main.rs crate:main deps:core
364 use core::option::Option::{self, Some, None};
365
366 fn div(x: i32, y: i32) -> Option<i32> {
367     if y == 0 {
368         return None;
369     }
370     x / y$0
371 }
372 //- /core/lib.rs crate:core
373 pub mod result {
374     pub enum Result<T, E> { Ok(T), Err(E) }
375 }
376 pub mod option {
377     pub enum Option<T> { Some(T), None }
378 }
379 "#,
380             r#"
381 use core::option::Option::{self, Some, None};
382
383 fn div(x: i32, y: i32) -> Option<i32> {
384     if y == 0 {
385         return None;
386     }
387     Some(x / y)
388 }
389 "#,
390         );
391     }
392
393     #[test]
394     fn test_wrap_return_type() {
395         check_fix(
396             r#"
397 //- /main.rs crate:main deps:core
398 use core::result::Result::{self, Ok, Err};
399
400 fn div(x: i32, y: i32) -> Result<i32, ()> {
401     if y == 0 {
402         return Err(());
403     }
404     x / y$0
405 }
406 //- /core/lib.rs crate:core
407 pub mod result {
408     pub enum Result<T, E> { Ok(T), Err(E) }
409 }
410 pub mod option {
411     pub enum Option<T> { Some(T), None }
412 }
413 "#,
414             r#"
415 use core::result::Result::{self, Ok, Err};
416
417 fn div(x: i32, y: i32) -> Result<i32, ()> {
418     if y == 0 {
419         return Err(());
420     }
421     Ok(x / y)
422 }
423 "#,
424         );
425     }
426
427     #[test]
428     fn test_wrap_return_type_handles_generic_functions() {
429         check_fix(
430             r#"
431 //- /main.rs crate:main deps:core
432 use core::result::Result::{self, Ok, Err};
433
434 fn div<T>(x: T) -> Result<T, i32> {
435     if x == 0 {
436         return Err(7);
437     }
438     $0x
439 }
440 //- /core/lib.rs crate:core
441 pub mod result {
442     pub enum Result<T, E> { Ok(T), Err(E) }
443 }
444 pub mod option {
445     pub enum Option<T> { Some(T), None }
446 }
447 "#,
448             r#"
449 use core::result::Result::{self, Ok, Err};
450
451 fn div<T>(x: T) -> Result<T, i32> {
452     if x == 0 {
453         return Err(7);
454     }
455     Ok(x)
456 }
457 "#,
458         );
459     }
460
461     #[test]
462     fn test_wrap_return_type_handles_type_aliases() {
463         check_fix(
464             r#"
465 //- /main.rs crate:main deps:core
466 use core::result::Result::{self, Ok, Err};
467
468 type MyResult<T> = Result<T, ()>;
469
470 fn div(x: i32, y: i32) -> MyResult<i32> {
471     if y == 0 {
472         return Err(());
473     }
474     x $0/ y
475 }
476 //- /core/lib.rs crate:core
477 pub mod result {
478     pub enum Result<T, E> { Ok(T), Err(E) }
479 }
480 pub mod option {
481     pub enum Option<T> { Some(T), None }
482 }
483 "#,
484             r#"
485 use core::result::Result::{self, Ok, Err};
486
487 type MyResult<T> = Result<T, ()>;
488
489 fn div(x: i32, y: i32) -> MyResult<i32> {
490     if y == 0 {
491         return Err(());
492     }
493     Ok(x / y)
494 }
495 "#,
496         );
497     }
498
499     #[test]
500     fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() {
501         check_no_diagnostics(
502             r#"
503 //- /main.rs crate:main deps:core
504 use core::result::Result::{self, Ok, Err};
505
506 fn foo() -> Result<(), i32> { 0 }
507
508 //- /core/lib.rs crate:core
509 pub mod result {
510     pub enum Result<T, E> { Ok(T), Err(E) }
511 }
512 pub mod option {
513     pub enum Option<T> { Some(T), None }
514 }
515 "#,
516         );
517     }
518
519     #[test]
520     fn test_wrap_return_type_not_applicable_when_return_type_is_not_result_or_option() {
521         check_no_diagnostics(
522             r#"
523 //- /main.rs crate:main deps:core
524 use core::result::Result::{self, Ok, Err};
525
526 enum SomeOtherEnum { Ok(i32), Err(String) }
527
528 fn foo() -> SomeOtherEnum { 0 }
529
530 //- /core/lib.rs crate:core
531 pub mod result {
532     pub enum Result<T, E> { Ok(T), Err(E) }
533 }
534 pub mod option {
535     pub enum Option<T> { Some(T), None }
536 }
537 "#,
538         );
539     }
540
541     #[test]
542     fn test_fill_struct_fields_empty() {
543         check_fix(
544             r#"
545 struct TestStruct { one: i32, two: i64 }
546
547 fn test_fn() {
548     let s = TestStruct {$0};
549 }
550 "#,
551             r#"
552 struct TestStruct { one: i32, two: i64 }
553
554 fn test_fn() {
555     let s = TestStruct { one: (), two: ()};
556 }
557 "#,
558         );
559     }
560
561     #[test]
562     fn test_fill_struct_fields_self() {
563         check_fix(
564             r#"
565 struct TestStruct { one: i32 }
566
567 impl TestStruct {
568     fn test_fn() { let s = Self {$0}; }
569 }
570 "#,
571             r#"
572 struct TestStruct { one: i32 }
573
574 impl TestStruct {
575     fn test_fn() { let s = Self { one: ()}; }
576 }
577 "#,
578         );
579     }
580
581     #[test]
582     fn test_fill_struct_fields_enum() {
583         check_fix(
584             r#"
585 enum Expr {
586     Bin { lhs: Box<Expr>, rhs: Box<Expr> }
587 }
588
589 impl Expr {
590     fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
591         Expr::Bin {$0 }
592     }
593 }
594 "#,
595             r#"
596 enum Expr {
597     Bin { lhs: Box<Expr>, rhs: Box<Expr> }
598 }
599
600 impl Expr {
601     fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
602         Expr::Bin { lhs: (), rhs: () }
603     }
604 }
605 "#,
606         );
607     }
608
609     #[test]
610     fn test_fill_struct_fields_partial() {
611         check_fix(
612             r#"
613 struct TestStruct { one: i32, two: i64 }
614
615 fn test_fn() {
616     let s = TestStruct{ two: 2$0 };
617 }
618 "#,
619             r"
620 struct TestStruct { one: i32, two: i64 }
621
622 fn test_fn() {
623     let s = TestStruct{ two: 2, one: () };
624 }
625 ",
626         );
627     }
628
629     #[test]
630     fn test_fill_struct_fields_no_diagnostic() {
631         check_no_diagnostics(
632             r"
633             struct TestStruct { one: i32, two: i64 }
634
635             fn test_fn() {
636                 let one = 1;
637                 let s = TestStruct{ one, two: 2 };
638             }
639         ",
640         );
641     }
642
643     #[test]
644     fn test_fill_struct_fields_no_diagnostic_on_spread() {
645         check_no_diagnostics(
646             r"
647             struct TestStruct { one: i32, two: i64 }
648
649             fn test_fn() {
650                 let one = 1;
651                 let s = TestStruct{ ..a };
652             }
653         ",
654         );
655     }
656
657     #[test]
658     fn test_unresolved_module_diagnostic() {
659         check_expect(
660             r#"mod foo;"#,
661             expect![[r#"
662                 [
663                     Diagnostic {
664                         message: "unresolved module",
665                         range: 0..8,
666                         severity: Error,
667                         fix: Some(
668                             Fix {
669                                 label: "Create module",
670                                 source_change: SourceChange {
671                                     source_file_edits: {},
672                                     file_system_edits: [
673                                         CreateFile {
674                                             dst: AnchoredPathBuf {
675                                                 anchor: FileId(
676                                                     0,
677                                                 ),
678                                                 path: "foo.rs",
679                                             },
680                                             initial_contents: "",
681                                         },
682                                     ],
683                                     is_snippet: false,
684                                 },
685                                 fix_trigger_range: 0..8,
686                             },
687                         ),
688                         unused: false,
689                         code: Some(
690                             DiagnosticCode(
691                                 "unresolved-module",
692                             ),
693                         ),
694                     },
695                 ]
696             "#]],
697         );
698     }
699
700     #[test]
701     fn test_unresolved_macro_range() {
702         check_expect(
703             r#"foo::bar!(92);"#,
704             expect![[r#"
705                 [
706                     Diagnostic {
707                         message: "unresolved macro call",
708                         range: 5..8,
709                         severity: Error,
710                         fix: None,
711                         unused: false,
712                         code: Some(
713                             DiagnosticCode(
714                                 "unresolved-macro-call",
715                             ),
716                         ),
717                     },
718                 ]
719             "#]],
720         );
721     }
722
723     #[test]
724     fn range_mapping_out_of_macros() {
725         // FIXME: this is very wrong, but somewhat tricky to fix.
726         check_fix(
727             r#"
728 fn some() {}
729 fn items() {}
730 fn here() {}
731
732 macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }
733
734 fn main() {
735     let _x = id![Foo { a: $042 }];
736 }
737
738 pub struct Foo { pub a: i32, pub b: i32 }
739 "#,
740             r#"
741 fn some(, b: ()) {}
742 fn items() {}
743 fn here() {}
744
745 macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }
746
747 fn main() {
748     let _x = id![Foo { a: 42 }];
749 }
750
751 pub struct Foo { pub a: i32, pub b: i32 }
752 "#,
753         );
754     }
755
756     #[test]
757     fn test_check_unnecessary_braces_in_use_statement() {
758         check_no_diagnostics(
759             r#"
760 use a;
761 use a::{c, d::e};
762
763 mod a {
764     mod c {}
765     mod d {
766         mod e {}
767     }
768 }
769 "#,
770         );
771         check_no_diagnostics(
772             r#"
773 use a;
774 use a::{
775     c,
776     // d::e
777 };
778
779 mod a {
780     mod c {}
781     mod d {
782         mod e {}
783     }
784 }
785 "#,
786         );
787         check_fix(
788             r"
789             mod b {}
790             use {$0b};
791             ",
792             r"
793             mod b {}
794             use b;
795             ",
796         );
797         check_fix(
798             r"
799             mod b {}
800             use {b$0};
801             ",
802             r"
803             mod b {}
804             use b;
805             ",
806         );
807         check_fix(
808             r"
809             mod a { mod c {} }
810             use a::{c$0};
811             ",
812             r"
813             mod a { mod c {} }
814             use a::c;
815             ",
816         );
817         check_fix(
818             r"
819             mod a {}
820             use a::{self$0};
821             ",
822             r"
823             mod a {}
824             use a;
825             ",
826         );
827         check_fix(
828             r"
829             mod a { mod c {} mod d { mod e {} } }
830             use a::{c, d::{e$0}};
831             ",
832             r"
833             mod a { mod c {} mod d { mod e {} } }
834             use a::{c, d::e};
835             ",
836         );
837     }
838
839     #[test]
840     fn test_add_field_from_usage() {
841         check_fix(
842             r"
843 fn main() {
844     Foo { bar: 3, baz$0: false};
845 }
846 struct Foo {
847     bar: i32
848 }
849 ",
850             r"
851 fn main() {
852     Foo { bar: 3, baz: false};
853 }
854 struct Foo {
855     bar: i32,
856     baz: bool
857 }
858 ",
859         )
860     }
861
862     #[test]
863     fn test_add_field_in_other_file_from_usage() {
864         check_fix(
865             r#"
866 //- /main.rs
867 mod foo;
868
869 fn main() {
870     foo::Foo { bar: 3, $0baz: false};
871 }
872 //- /foo.rs
873 struct Foo {
874     bar: i32
875 }
876 "#,
877             r#"
878 struct Foo {
879     bar: i32,
880     pub(crate) baz: bool
881 }
882 "#,
883         )
884     }
885
886     #[test]
887     fn test_disabled_diagnostics() {
888         let mut config = DiagnosticsConfig::default();
889         config.disabled.insert("unresolved-module".into());
890
891         let (analysis, file_id) = fixture::file(r#"mod foo;"#);
892
893         let diagnostics = analysis.diagnostics(&config, file_id).unwrap();
894         assert!(diagnostics.is_empty());
895
896         let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap();
897         assert!(!diagnostics.is_empty());
898     }
899
900     #[test]
901     fn test_rename_incorrect_case() {
902         check_fix(
903             r#"
904 pub struct test_struct$0 { one: i32 }
905
906 pub fn some_fn(val: test_struct) -> test_struct {
907     test_struct { one: val.one + 1 }
908 }
909 "#,
910             r#"
911 pub struct TestStruct { one: i32 }
912
913 pub fn some_fn(val: TestStruct) -> TestStruct {
914     TestStruct { one: val.one + 1 }
915 }
916 "#,
917         );
918
919         check_fix(
920             r#"
921 pub fn some_fn(NonSnakeCase$0: u8) -> u8 {
922     NonSnakeCase
923 }
924 "#,
925             r#"
926 pub fn some_fn(non_snake_case: u8) -> u8 {
927     non_snake_case
928 }
929 "#,
930         );
931
932         check_fix(
933             r#"
934 pub fn SomeFn$0(val: u8) -> u8 {
935     if val != 0 { SomeFn(val - 1) } else { val }
936 }
937 "#,
938             r#"
939 pub fn some_fn(val: u8) -> u8 {
940     if val != 0 { some_fn(val - 1) } else { val }
941 }
942 "#,
943         );
944
945         check_fix(
946             r#"
947 fn some_fn() {
948     let whatAWeird_Formatting$0 = 10;
949     another_func(whatAWeird_Formatting);
950 }
951 "#,
952             r#"
953 fn some_fn() {
954     let what_a_weird_formatting = 10;
955     another_func(what_a_weird_formatting);
956 }
957 "#,
958         );
959     }
960
961     #[test]
962     fn test_uppercase_const_no_diagnostics() {
963         check_no_diagnostics(
964             r#"
965 fn foo() {
966     const ANOTHER_ITEM$0: &str = "some_item";
967 }
968 "#,
969         );
970     }
971
972     #[test]
973     fn test_rename_incorrect_case_struct_method() {
974         check_fix(
975             r#"
976 pub struct TestStruct;
977
978 impl TestStruct {
979     pub fn SomeFn$0() -> TestStruct {
980         TestStruct
981     }
982 }
983 "#,
984             r#"
985 pub struct TestStruct;
986
987 impl TestStruct {
988     pub fn some_fn() -> TestStruct {
989         TestStruct
990     }
991 }
992 "#,
993         );
994     }
995
996     #[test]
997     fn test_single_incorrect_case_diagnostic_in_function_name_issue_6970() {
998         let input = r#"fn FOO$0() {}"#;
999         let expected = r#"fn foo() {}"#;
1000
1001         let (analysis, file_position) = fixture::position(input);
1002         let diagnostics =
1003             analysis.diagnostics(&DiagnosticsConfig::default(), file_position.file_id).unwrap();
1004         assert_eq!(diagnostics.len(), 1);
1005
1006         check_fix(input, expected);
1007     }
1008
1009     #[test]
1010     fn unlinked_file_prepend_first_item() {
1011         cov_mark::check!(unlinked_file_prepend_before_first_item);
1012         check_fix(
1013             r#"
1014 //- /main.rs
1015 fn f() {}
1016 //- /foo.rs
1017 $0
1018 "#,
1019             r#"
1020 mod foo;
1021
1022 fn f() {}
1023 "#,
1024         );
1025     }
1026
1027     #[test]
1028     fn unlinked_file_append_mod() {
1029         cov_mark::check!(unlinked_file_append_to_existing_mods);
1030         check_fix(
1031             r#"
1032 //- /main.rs
1033 //! Comment on top
1034
1035 mod preexisting;
1036
1037 mod preexisting2;
1038
1039 struct S;
1040
1041 mod preexisting_bottom;)
1042 //- /foo.rs
1043 $0
1044 "#,
1045             r#"
1046 //! Comment on top
1047
1048 mod preexisting;
1049
1050 mod preexisting2;
1051 mod foo;
1052
1053 struct S;
1054
1055 mod preexisting_bottom;)
1056 "#,
1057         );
1058     }
1059
1060     #[test]
1061     fn unlinked_file_insert_in_empty_file() {
1062         cov_mark::check!(unlinked_file_empty_file);
1063         check_fix(
1064             r#"
1065 //- /main.rs
1066 //- /foo.rs
1067 $0
1068 "#,
1069             r#"
1070 mod foo;
1071 "#,
1072         );
1073     }
1074
1075     #[test]
1076     fn unlinked_file_old_style_modrs() {
1077         check_fix(
1078             r#"
1079 //- /main.rs
1080 mod submod;
1081 //- /submod/mod.rs
1082 // in mod.rs
1083 //- /submod/foo.rs
1084 $0
1085 "#,
1086             r#"
1087 // in mod.rs
1088 mod foo;
1089 "#,
1090         );
1091     }
1092
1093     #[test]
1094     fn unlinked_file_new_style_mod() {
1095         check_fix(
1096             r#"
1097 //- /main.rs
1098 mod submod;
1099 //- /submod.rs
1100 //- /submod/foo.rs
1101 $0
1102 "#,
1103             r#"
1104 mod foo;
1105 "#,
1106         );
1107     }
1108
1109     #[test]
1110     fn unlinked_file_with_cfg_off() {
1111         cov_mark::check!(unlinked_file_skip_fix_when_mod_already_exists);
1112         check_no_fix(
1113             r#"
1114 //- /main.rs
1115 #[cfg(never)]
1116 mod foo;
1117
1118 //- /foo.rs
1119 $0
1120 "#,
1121         );
1122     }
1123
1124     #[test]
1125     fn unlinked_file_with_cfg_on() {
1126         check_no_diagnostics(
1127             r#"
1128 //- /main.rs
1129 #[cfg(not(never))]
1130 mod foo;
1131
1132 //- /foo.rs
1133 "#,
1134         );
1135     }
1136 }