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