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