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