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