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