]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/diagnostics.rs
Merge #8481
[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::{Assist, AssistId, AssistKind, 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<Assist>,
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<Assist>) -> 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, Copy, Clone)]
73 pub enum Severity {
74     Error,
75     WeakWarning,
76 }
77
78 #[derive(Default, Debug, Clone)]
79 pub struct DiagnosticsConfig {
80     pub disable_experimental: bool,
81     pub disabled: FxHashSet<String>,
82 }
83
84 pub(crate) fn diagnostics(
85     db: &RootDatabase,
86     config: &DiagnosticsConfig,
87     file_id: FileId,
88 ) -> Vec<Diagnostic> {
89     let _p = profile::span("diagnostics");
90     let sema = Semantics::new(db);
91     let parse = db.parse(file_id);
92     let mut res = Vec::new();
93
94     // [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily.
95     res.extend(
96         parse
97             .errors()
98             .iter()
99             .take(128)
100             .map(|err| Diagnostic::error(err.range(), format!("Syntax Error: {}", err))),
101     );
102
103     for node in parse.tree().syntax().descendants() {
104         check_unnecessary_braces_in_use_statement(&mut res, file_id, &node);
105         field_shorthand::check(&mut res, file_id, &node);
106     }
107     let res = RefCell::new(res);
108     let sink_builder = DiagnosticSinkBuilder::new()
109         .on::<hir::diagnostics::UnresolvedModule, _>(|d| {
110             res.borrow_mut().push(diagnostic_with_fix(d, &sema));
111         })
112         .on::<hir::diagnostics::MissingFields, _>(|d| {
113             res.borrow_mut().push(diagnostic_with_fix(d, &sema));
114         })
115         .on::<hir::diagnostics::MissingOkOrSomeInTailExpr, _>(|d| {
116             res.borrow_mut().push(diagnostic_with_fix(d, &sema));
117         })
118         .on::<hir::diagnostics::NoSuchField, _>(|d| {
119             res.borrow_mut().push(diagnostic_with_fix(d, &sema));
120         })
121         .on::<hir::diagnostics::RemoveThisSemicolon, _>(|d| {
122             res.borrow_mut().push(diagnostic_with_fix(d, &sema));
123         })
124         .on::<hir::diagnostics::IncorrectCase, _>(|d| {
125             res.borrow_mut().push(warning_with_fix(d, &sema));
126         })
127         .on::<hir::diagnostics::ReplaceFilterMapNextWithFindMap, _>(|d| {
128             res.borrow_mut().push(warning_with_fix(d, &sema));
129         })
130         .on::<hir::diagnostics::InactiveCode, _>(|d| {
131             // If there's inactive code somewhere in a macro, don't propagate to the call-site.
132             if d.display_source().file_id.expansion_info(db).is_some() {
133                 return;
134             }
135
136             // Override severity and mark as unused.
137             res.borrow_mut().push(
138                 Diagnostic::hint(
139                     sema.diagnostics_display_range(d.display_source()).range,
140                     d.message(),
141                 )
142                 .with_unused(true)
143                 .with_code(Some(d.code())),
144             );
145         })
146         .on::<UnlinkedFile, _>(|d| {
147             // Limit diagnostic to the first few characters in the file. This matches how VS Code
148             // renders it with the full span, but on other editors, and is less invasive.
149             let range = sema.diagnostics_display_range(d.display_source()).range;
150             let range = range.intersect(TextRange::up_to(TextSize::of("..."))).unwrap_or(range);
151
152             // Override severity and mark as unused.
153             res.borrow_mut().push(
154                 Diagnostic::hint(range, d.message())
155                     .with_fix(d.fix(&sema))
156                     .with_code(Some(d.code())),
157             );
158         })
159         .on::<hir::diagnostics::UnresolvedProcMacro, _>(|d| {
160             // Use more accurate position if available.
161             let display_range = d
162                 .precise_location
163                 .unwrap_or_else(|| sema.diagnostics_display_range(d.display_source()).range);
164
165             // FIXME: it would be nice to tell the user whether proc macros are currently disabled
166             res.borrow_mut()
167                 .push(Diagnostic::hint(display_range, d.message()).with_code(Some(d.code())));
168         })
169         .on::<hir::diagnostics::UnresolvedMacroCall, _>(|d| {
170             let last_path_segment = sema.db.parse_or_expand(d.file).and_then(|root| {
171                 d.node
172                     .to_node(&root)
173                     .path()
174                     .and_then(|it| it.segment())
175                     .and_then(|it| it.name_ref())
176                     .map(|it| InFile::new(d.file, SyntaxNodePtr::new(it.syntax())))
177             });
178             let diagnostics = last_path_segment.unwrap_or_else(|| d.display_source());
179             let display_range = sema.diagnostics_display_range(diagnostics).range;
180             res.borrow_mut()
181                 .push(Diagnostic::error(display_range, d.message()).with_code(Some(d.code())));
182         })
183         // Only collect experimental diagnostics when they're enabled.
184         .filter(|diag| !(diag.is_experimental() && config.disable_experimental))
185         .filter(|diag| !config.disabled.contains(diag.code().as_str()));
186
187     // Finalize the `DiagnosticSink` building process.
188     let mut sink = sink_builder
189         // Diagnostics not handled above get no fix and default treatment.
190         .build(|d| {
191             res.borrow_mut().push(
192                 Diagnostic::error(
193                     sema.diagnostics_display_range(d.display_source()).range,
194                     d.message(),
195                 )
196                 .with_code(Some(d.code())),
197             );
198         });
199
200     match sema.to_module_def(file_id) {
201         Some(m) => m.diagnostics(db, &mut sink),
202         None => {
203             sink.push(UnlinkedFile { file_id, node: SyntaxNodePtr::new(&parse.tree().syntax()) });
204         }
205     }
206
207     drop(sink);
208     res.into_inner()
209 }
210
211 fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic {
212     Diagnostic::error(sema.diagnostics_display_range(d.display_source()).range, d.message())
213         .with_fix(d.fix(&sema))
214         .with_code(Some(d.code()))
215 }
216
217 fn warning_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic {
218     Diagnostic::hint(sema.diagnostics_display_range(d.display_source()).range, d.message())
219         .with_fix(d.fix(&sema))
220         .with_code(Some(d.code()))
221 }
222
223 fn check_unnecessary_braces_in_use_statement(
224     acc: &mut Vec<Diagnostic>,
225     file_id: FileId,
226     node: &SyntaxNode,
227 ) -> Option<()> {
228     let use_tree_list = ast::UseTreeList::cast(node.clone())?;
229     if let Some((single_use_tree,)) = use_tree_list.use_trees().collect_tuple() {
230         // If there is a comment inside the bracketed `use`,
231         // assume it is a commented out module path and don't show diagnostic.
232         if use_tree_list.has_inner_comment() {
233             return Some(());
234         }
235
236         let use_range = use_tree_list.syntax().text_range();
237         let edit =
238             text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(&single_use_tree)
239                 .unwrap_or_else(|| {
240                     let to_replace = single_use_tree.syntax().text().to_string();
241                     let mut edit_builder = TextEdit::builder();
242                     edit_builder.delete(use_range);
243                     edit_builder.insert(use_range.start(), to_replace);
244                     edit_builder.finish()
245                 });
246
247         acc.push(
248             Diagnostic::hint(use_range, "Unnecessary braces in use statement".to_string())
249                 .with_fix(Some(fix(
250                     "remove_braces",
251                     "Remove unnecessary braces",
252                     SourceChange::from_text_edit(file_id, edit),
253                     use_range,
254                 ))),
255         );
256     }
257
258     Some(())
259 }
260
261 fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(
262     single_use_tree: &ast::UseTree,
263 ) -> Option<TextEdit> {
264     let use_tree_list_node = single_use_tree.syntax().parent()?;
265     if single_use_tree.path()?.segment()?.self_token().is_some() {
266         let start = use_tree_list_node.prev_sibling_or_token()?.text_range().start();
267         let end = use_tree_list_node.text_range().end();
268         return Some(TextEdit::delete(TextRange::new(start, end)));
269     }
270     None
271 }
272
273 fn fix(id: &'static str, label: &str, source_change: SourceChange, target: TextRange) -> Assist {
274     assert!(!id.contains(' '));
275     Assist {
276         id: AssistId(id, AssistKind::QuickFix),
277         label: Label::new(label),
278         group: None,
279         target,
280         source_change: Some(source_change),
281     }
282 }
283
284 #[cfg(test)]
285 mod tests {
286     use expect_test::{expect, Expect};
287     use stdx::trim_indent;
288     use test_utils::assert_eq_text;
289
290     use crate::{fixture, DiagnosticsConfig};
291
292     /// Takes a multi-file input fixture with annotated cursor positions,
293     /// and checks that:
294     ///  * a diagnostic is produced
295     ///  * this diagnostic fix trigger range touches the input cursor position
296     ///  * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied
297     pub(crate) fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) {
298         let after = trim_indent(ra_fixture_after);
299
300         let (analysis, file_position) = fixture::position(ra_fixture_before);
301         let diagnostic = analysis
302             .diagnostics(&DiagnosticsConfig::default(), file_position.file_id)
303             .unwrap()
304             .pop()
305             .unwrap();
306         let fix = diagnostic.fix.unwrap();
307         let actual = {
308             let source_change = fix.source_change.unwrap();
309             let file_id = *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 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.target.contains_inclusive(file_position.offset),
321             "diagnostic fix range {:?} does not touch cursor position {:?}",
322             fix.target,
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                             Assist {
667                                 id: AssistId(
668                                     "create_module",
669                                     QuickFix,
670                                 ),
671                                 label: "Create module",
672                                 group: None,
673                                 target: 0..8,
674                                 source_change: Some(
675                                     SourceChange {
676                                         source_file_edits: {},
677                                         file_system_edits: [
678                                             CreateFile {
679                                                 dst: AnchoredPathBuf {
680                                                     anchor: FileId(
681                                                         0,
682                                                     ),
683                                                     path: "foo.rs",
684                                                 },
685                                                 initial_contents: "",
686                                             },
687                                         ],
688                                         is_snippet: false,
689                                     },
690                                 ),
691                             },
692                         ),
693                         unused: false,
694                         code: Some(
695                             DiagnosticCode(
696                                 "unresolved-module",
697                             ),
698                         ),
699                     },
700                 ]
701             "#]],
702         );
703     }
704
705     #[test]
706     fn test_unresolved_macro_range() {
707         check_expect(
708             r#"foo::bar!(92);"#,
709             expect![[r#"
710                 [
711                     Diagnostic {
712                         message: "unresolved macro call",
713                         range: 5..8,
714                         severity: Error,
715                         fix: None,
716                         unused: false,
717                         code: Some(
718                             DiagnosticCode(
719                                 "unresolved-macro-call",
720                             ),
721                         ),
722                     },
723                 ]
724             "#]],
725         );
726     }
727
728     #[test]
729     fn range_mapping_out_of_macros() {
730         // FIXME: this is very wrong, but somewhat tricky to fix.
731         check_fix(
732             r#"
733 fn some() {}
734 fn items() {}
735 fn here() {}
736
737 macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }
738
739 fn main() {
740     let _x = id![Foo { a: $042 }];
741 }
742
743 pub struct Foo { pub a: i32, pub b: i32 }
744 "#,
745             r#"
746 fn some(, b: ()) {}
747 fn items() {}
748 fn here() {}
749
750 macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }
751
752 fn main() {
753     let _x = id![Foo { a: 42 }];
754 }
755
756 pub struct Foo { pub a: i32, pub b: i32 }
757 "#,
758         );
759     }
760
761     #[test]
762     fn test_check_unnecessary_braces_in_use_statement() {
763         check_no_diagnostics(
764             r#"
765 use a;
766 use a::{c, d::e};
767
768 mod a {
769     mod c {}
770     mod d {
771         mod e {}
772     }
773 }
774 "#,
775         );
776         check_no_diagnostics(
777             r#"
778 use a;
779 use a::{
780     c,
781     // d::e
782 };
783
784 mod a {
785     mod c {}
786     mod d {
787         mod e {}
788     }
789 }
790 "#,
791         );
792         check_fix(
793             r"
794             mod b {}
795             use {$0b};
796             ",
797             r"
798             mod b {}
799             use b;
800             ",
801         );
802         check_fix(
803             r"
804             mod b {}
805             use {b$0};
806             ",
807             r"
808             mod b {}
809             use b;
810             ",
811         );
812         check_fix(
813             r"
814             mod a { mod c {} }
815             use a::{c$0};
816             ",
817             r"
818             mod a { mod c {} }
819             use a::c;
820             ",
821         );
822         check_fix(
823             r"
824             mod a {}
825             use a::{self$0};
826             ",
827             r"
828             mod a {}
829             use a;
830             ",
831         );
832         check_fix(
833             r"
834             mod a { mod c {} mod d { mod e {} } }
835             use a::{c, d::{e$0}};
836             ",
837             r"
838             mod a { mod c {} mod d { mod e {} } }
839             use a::{c, d::e};
840             ",
841         );
842     }
843
844     #[test]
845     fn test_add_field_from_usage() {
846         check_fix(
847             r"
848 fn main() {
849     Foo { bar: 3, baz$0: false};
850 }
851 struct Foo {
852     bar: i32
853 }
854 ",
855             r"
856 fn main() {
857     Foo { bar: 3, baz: false};
858 }
859 struct Foo {
860     bar: i32,
861     baz: bool
862 }
863 ",
864         )
865     }
866
867     #[test]
868     fn test_add_field_in_other_file_from_usage() {
869         check_fix(
870             r#"
871 //- /main.rs
872 mod foo;
873
874 fn main() {
875     foo::Foo { bar: 3, $0baz: false};
876 }
877 //- /foo.rs
878 struct Foo {
879     bar: i32
880 }
881 "#,
882             r#"
883 struct Foo {
884     bar: i32,
885     pub(crate) baz: bool
886 }
887 "#,
888         )
889     }
890
891     #[test]
892     fn test_disabled_diagnostics() {
893         let mut config = DiagnosticsConfig::default();
894         config.disabled.insert("unresolved-module".into());
895
896         let (analysis, file_id) = fixture::file(r#"mod foo;"#);
897
898         let diagnostics = analysis.diagnostics(&config, file_id).unwrap();
899         assert!(diagnostics.is_empty());
900
901         let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap();
902         assert!(!diagnostics.is_empty());
903     }
904
905     #[test]
906     fn test_rename_incorrect_case() {
907         check_fix(
908             r#"
909 pub struct test_struct$0 { one: i32 }
910
911 pub fn some_fn(val: test_struct) -> test_struct {
912     test_struct { one: val.one + 1 }
913 }
914 "#,
915             r#"
916 pub struct TestStruct { one: i32 }
917
918 pub fn some_fn(val: TestStruct) -> TestStruct {
919     TestStruct { one: val.one + 1 }
920 }
921 "#,
922         );
923
924         check_fix(
925             r#"
926 pub fn some_fn(NonSnakeCase$0: u8) -> u8 {
927     NonSnakeCase
928 }
929 "#,
930             r#"
931 pub fn some_fn(non_snake_case: u8) -> u8 {
932     non_snake_case
933 }
934 "#,
935         );
936
937         check_fix(
938             r#"
939 pub fn SomeFn$0(val: u8) -> u8 {
940     if val != 0 { SomeFn(val - 1) } else { val }
941 }
942 "#,
943             r#"
944 pub fn some_fn(val: u8) -> u8 {
945     if val != 0 { some_fn(val - 1) } else { val }
946 }
947 "#,
948         );
949
950         check_fix(
951             r#"
952 fn some_fn() {
953     let whatAWeird_Formatting$0 = 10;
954     another_func(whatAWeird_Formatting);
955 }
956 "#,
957             r#"
958 fn some_fn() {
959     let what_a_weird_formatting = 10;
960     another_func(what_a_weird_formatting);
961 }
962 "#,
963         );
964     }
965
966     #[test]
967     fn test_uppercase_const_no_diagnostics() {
968         check_no_diagnostics(
969             r#"
970 fn foo() {
971     const ANOTHER_ITEM$0: &str = "some_item";
972 }
973 "#,
974         );
975     }
976
977     #[test]
978     fn test_rename_incorrect_case_struct_method() {
979         check_fix(
980             r#"
981 pub struct TestStruct;
982
983 impl TestStruct {
984     pub fn SomeFn$0() -> TestStruct {
985         TestStruct
986     }
987 }
988 "#,
989             r#"
990 pub struct TestStruct;
991
992 impl TestStruct {
993     pub fn some_fn() -> TestStruct {
994         TestStruct
995     }
996 }
997 "#,
998         );
999     }
1000
1001     #[test]
1002     fn test_single_incorrect_case_diagnostic_in_function_name_issue_6970() {
1003         let input = r#"fn FOO$0() {}"#;
1004         let expected = r#"fn foo() {}"#;
1005
1006         let (analysis, file_position) = fixture::position(input);
1007         let diagnostics =
1008             analysis.diagnostics(&DiagnosticsConfig::default(), file_position.file_id).unwrap();
1009         assert_eq!(diagnostics.len(), 1);
1010
1011         check_fix(input, expected);
1012     }
1013
1014     #[test]
1015     fn unlinked_file_prepend_first_item() {
1016         cov_mark::check!(unlinked_file_prepend_before_first_item);
1017         check_fix(
1018             r#"
1019 //- /main.rs
1020 fn f() {}
1021 //- /foo.rs
1022 $0
1023 "#,
1024             r#"
1025 mod foo;
1026
1027 fn f() {}
1028 "#,
1029         );
1030     }
1031
1032     #[test]
1033     fn unlinked_file_append_mod() {
1034         cov_mark::check!(unlinked_file_append_to_existing_mods);
1035         check_fix(
1036             r#"
1037 //- /main.rs
1038 //! Comment on top
1039
1040 mod preexisting;
1041
1042 mod preexisting2;
1043
1044 struct S;
1045
1046 mod preexisting_bottom;)
1047 //- /foo.rs
1048 $0
1049 "#,
1050             r#"
1051 //! Comment on top
1052
1053 mod preexisting;
1054
1055 mod preexisting2;
1056 mod foo;
1057
1058 struct S;
1059
1060 mod preexisting_bottom;)
1061 "#,
1062         );
1063     }
1064
1065     #[test]
1066     fn unlinked_file_insert_in_empty_file() {
1067         cov_mark::check!(unlinked_file_empty_file);
1068         check_fix(
1069             r#"
1070 //- /main.rs
1071 //- /foo.rs
1072 $0
1073 "#,
1074             r#"
1075 mod foo;
1076 "#,
1077         );
1078     }
1079
1080     #[test]
1081     fn unlinked_file_old_style_modrs() {
1082         check_fix(
1083             r#"
1084 //- /main.rs
1085 mod submod;
1086 //- /submod/mod.rs
1087 // in mod.rs
1088 //- /submod/foo.rs
1089 $0
1090 "#,
1091             r#"
1092 // in mod.rs
1093 mod foo;
1094 "#,
1095         );
1096     }
1097
1098     #[test]
1099     fn unlinked_file_new_style_mod() {
1100         check_fix(
1101             r#"
1102 //- /main.rs
1103 mod submod;
1104 //- /submod.rs
1105 //- /submod/foo.rs
1106 $0
1107 "#,
1108             r#"
1109 mod foo;
1110 "#,
1111         );
1112     }
1113
1114     #[test]
1115     fn unlinked_file_with_cfg_off() {
1116         cov_mark::check!(unlinked_file_skip_fix_when_mod_already_exists);
1117         check_no_fix(
1118             r#"
1119 //- /main.rs
1120 #[cfg(never)]
1121 mod foo;
1122
1123 //- /foo.rs
1124 $0
1125 "#,
1126         );
1127     }
1128
1129     #[test]
1130     fn unlinked_file_with_cfg_on() {
1131         check_no_diagnostics(
1132             r#"
1133 //- /main.rs
1134 #[cfg(not(never))]
1135 mod foo;
1136
1137 //- /foo.rs
1138 "#,
1139         );
1140     }
1141 }