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