]> git.lizzy.rs Git - rust.git/blob - crates/hir_ty/src/diagnostics/decl_check.rs
Merge #7505
[rust.git] / crates / hir_ty / src / diagnostics / decl_check.rs
1 //! Provides validators for the item declarations.
2 //!
3 //! This includes the following items:
4 //!
5 //! - variable bindings (e.g. `let x = foo();`)
6 //! - struct fields (e.g. `struct Foo { field: u8 }`)
7 //! - enum variants (e.g. `enum Foo { Variant { field: u8 } }`)
8 //! - function/method arguments (e.g. `fn foo(arg: u8)`)
9 //! - constants (e.g. `const FOO: u8 = 10;`)
10 //! - static items (e.g. `static FOO: u8 = 10;`)
11 //! - match arm bindings (e.g. `foo @ Some(_)`)
12
13 mod case_conv;
14
15 use base_db::CrateId;
16 use hir_def::{
17     adt::VariantData,
18     expr::{Pat, PatId},
19     src::HasSource,
20     AdtId, AttrDefId, ConstId, EnumId, FunctionId, Lookup, ModuleDefId, StaticId, StructId,
21 };
22 use hir_expand::{
23     diagnostics::DiagnosticSink,
24     name::{AsName, Name},
25 };
26 use stdx::{always, never};
27 use syntax::{
28     ast::{self, NameOwner},
29     AstNode, AstPtr,
30 };
31 use test_utils::mark;
32
33 use crate::{
34     db::HirDatabase,
35     diagnostics::{decl_check::case_conv::*, CaseType, IdentType, IncorrectCase},
36 };
37
38 mod allow {
39     pub(super) const NON_SNAKE_CASE: &str = "non_snake_case";
40     pub(super) const NON_UPPER_CASE_GLOBAL: &str = "non_upper_case_globals";
41     pub(super) const NON_CAMEL_CASE_TYPES: &str = "non_camel_case_types";
42 }
43
44 pub(super) struct DeclValidator<'a, 'b> {
45     db: &'a dyn HirDatabase,
46     krate: CrateId,
47     sink: &'a mut DiagnosticSink<'b>,
48 }
49
50 #[derive(Debug)]
51 struct Replacement {
52     current_name: Name,
53     suggested_text: String,
54     expected_case: CaseType,
55 }
56
57 impl<'a, 'b> DeclValidator<'a, 'b> {
58     pub(super) fn new(
59         db: &'a dyn HirDatabase,
60         krate: CrateId,
61         sink: &'a mut DiagnosticSink<'b>,
62     ) -> DeclValidator<'a, 'b> {
63         DeclValidator { db, krate, sink }
64     }
65
66     pub(super) fn validate_item(&mut self, item: ModuleDefId) {
67         match item {
68             ModuleDefId::FunctionId(func) => self.validate_func(func),
69             ModuleDefId::AdtId(adt) => self.validate_adt(adt),
70             ModuleDefId::ConstId(const_id) => self.validate_const(const_id),
71             ModuleDefId::StaticId(static_id) => self.validate_static(static_id),
72             _ => return,
73         }
74     }
75
76     fn validate_adt(&mut self, adt: AdtId) {
77         match adt {
78             AdtId::StructId(struct_id) => self.validate_struct(struct_id),
79             AdtId::EnumId(enum_id) => self.validate_enum(enum_id),
80             AdtId::UnionId(_) => {
81                 // FIXME: Unions aren't yet supported by this validator.
82             }
83         }
84     }
85
86     /// Checks whether not following the convention is allowed for this item.
87     ///
88     /// Currently this method doesn't check parent attributes.
89     fn allowed(&self, id: AttrDefId, allow_name: &str) -> bool {
90         self.db.attrs(id).by_key("allow").tt_values().any(|tt| tt.to_string().contains(allow_name))
91     }
92
93     fn validate_func(&mut self, func: FunctionId) {
94         let data = self.db.function_data(func);
95         if data.is_extern {
96             mark::hit!(extern_func_incorrect_case_ignored);
97             return;
98         }
99
100         let body = self.db.body(func.into());
101
102         // Recursively validate inner scope items, such as static variables and constants.
103         for (item_id, _) in body.item_scope.values() {
104             let mut validator = DeclValidator::new(self.db, self.krate, self.sink);
105             validator.validate_item(item_id);
106         }
107
108         // Check whether non-snake case identifiers are allowed for this function.
109         if self.allowed(func.into(), allow::NON_SNAKE_CASE) {
110             return;
111         }
112
113         // Check the function name.
114         let function_name = data.name.to_string();
115         let fn_name_replacement = to_lower_snake_case(&function_name).map(|new_name| Replacement {
116             current_name: data.name.clone(),
117             suggested_text: new_name,
118             expected_case: CaseType::LowerSnakeCase,
119         });
120
121         // Check the param names.
122         let fn_param_replacements = body
123             .params
124             .iter()
125             .filter_map(|&id| match &body[id] {
126                 Pat::Bind { name, .. } => Some(name),
127                 _ => None,
128             })
129             .filter_map(|param_name| {
130                 Some(Replacement {
131                     current_name: param_name.clone(),
132                     suggested_text: to_lower_snake_case(&param_name.to_string())?,
133                     expected_case: CaseType::LowerSnakeCase,
134                 })
135             })
136             .collect();
137
138         // Check the patterns inside the function body.
139         let pats_replacements = body
140             .pats
141             .iter()
142             // We aren't interested in function parameters, we've processed them above.
143             .filter(|(pat_idx, _)| !body.params.contains(&pat_idx))
144             .filter_map(|(id, pat)| match pat {
145                 Pat::Bind { name, .. } => Some((id, name)),
146                 _ => None,
147             })
148             .filter_map(|(id, bind_name)| {
149                 Some((
150                     id,
151                     Replacement {
152                         current_name: bind_name.clone(),
153                         suggested_text: to_lower_snake_case(&bind_name.to_string())?,
154                         expected_case: CaseType::LowerSnakeCase,
155                     },
156                 ))
157             })
158             .collect();
159
160         // If there is at least one element to spawn a warning on, go to the source map and generate a warning.
161         self.create_incorrect_case_diagnostic_for_func(
162             func,
163             fn_name_replacement,
164             fn_param_replacements,
165         );
166         self.create_incorrect_case_diagnostic_for_variables(func, pats_replacements);
167     }
168
169     /// Given the information about incorrect names in the function declaration, looks up into the source code
170     /// for exact locations and adds diagnostics into the sink.
171     fn create_incorrect_case_diagnostic_for_func(
172         &mut self,
173         func: FunctionId,
174         fn_name_replacement: Option<Replacement>,
175         fn_param_replacements: Vec<Replacement>,
176     ) {
177         // XXX: only look at sources if we do have incorrect names
178         if fn_name_replacement.is_none() && fn_param_replacements.is_empty() {
179             return;
180         }
181
182         let fn_loc = func.lookup(self.db.upcast());
183         let fn_src = fn_loc.source(self.db.upcast());
184
185         // Diagnostic for function name.
186         if let Some(replacement) = fn_name_replacement {
187             let ast_ptr = match fn_src.value.name() {
188                 Some(name) => name,
189                 None => {
190                     never!(
191                         "Replacement ({:?}) was generated for a function without a name: {:?}",
192                         replacement,
193                         fn_src
194                     );
195                     return;
196                 }
197             };
198
199             let diagnostic = IncorrectCase {
200                 file: fn_src.file_id,
201                 ident_type: IdentType::Function,
202                 ident: AstPtr::new(&ast_ptr).into(),
203                 expected_case: replacement.expected_case,
204                 ident_text: replacement.current_name.to_string(),
205                 suggested_text: replacement.suggested_text,
206             };
207
208             self.sink.push(diagnostic);
209         }
210
211         // Diagnostics for function params.
212         let fn_params_list = match fn_src.value.param_list() {
213             Some(params) => params,
214             None => {
215                 always!(
216                     fn_param_replacements.is_empty(),
217                     "Replacements ({:?}) were generated for a function parameters which had no parameters list: {:?}",
218                     fn_param_replacements,
219                     fn_src
220                 );
221                 return;
222             }
223         };
224         let mut fn_params_iter = fn_params_list.params();
225         for param_to_rename in fn_param_replacements {
226             // We assume that parameters in replacement are in the same order as in the
227             // actual params list, but just some of them (ones that named correctly) are skipped.
228             let ast_ptr: ast::Name = loop {
229                 match fn_params_iter.next() {
230                     Some(element) => {
231                         if let Some(ast::Pat::IdentPat(pat)) = element.pat() {
232                             if pat.to_string() == param_to_rename.current_name.to_string() {
233                                 if let Some(name) = pat.name() {
234                                     break name;
235                                 }
236                                 // This is critical. If we consider this parameter the expected one,
237                                 // it **must** have a name.
238                                 never!(
239                                     "Pattern {:?} equals to expected replacement {:?}, but has no name",
240                                     element,
241                                     param_to_rename
242                                 );
243                                 return;
244                             }
245                         }
246                     }
247                     None => {
248                         never!(
249                             "Replacement ({:?}) was generated for a function parameter which was not found: {:?}",
250                             param_to_rename, fn_src
251                         );
252                         return;
253                     }
254                 }
255             };
256
257             let diagnostic = IncorrectCase {
258                 file: fn_src.file_id,
259                 ident_type: IdentType::Argument,
260                 ident: AstPtr::new(&ast_ptr).into(),
261                 expected_case: param_to_rename.expected_case,
262                 ident_text: param_to_rename.current_name.to_string(),
263                 suggested_text: param_to_rename.suggested_text,
264             };
265
266             self.sink.push(diagnostic);
267         }
268     }
269
270     /// Given the information about incorrect variable names, looks up into the source code
271     /// for exact locations and adds diagnostics into the sink.
272     fn create_incorrect_case_diagnostic_for_variables(
273         &mut self,
274         func: FunctionId,
275         pats_replacements: Vec<(PatId, Replacement)>,
276     ) {
277         // XXX: only look at source_map if we do have missing fields
278         if pats_replacements.is_empty() {
279             return;
280         }
281
282         let (_, source_map) = self.db.body_with_source_map(func.into());
283
284         for (id, replacement) in pats_replacements {
285             if let Ok(source_ptr) = source_map.pat_syntax(id) {
286                 if let Some(expr) = source_ptr.value.as_ref().left() {
287                     let root = source_ptr.file_syntax(self.db.upcast());
288                     if let ast::Pat::IdentPat(ident_pat) = expr.to_node(&root) {
289                         let parent = match ident_pat.syntax().parent() {
290                             Some(parent) => parent,
291                             None => continue,
292                         };
293                         let name_ast = match ident_pat.name() {
294                             Some(name_ast) => name_ast,
295                             None => continue,
296                         };
297
298                         // We have to check that it's either `let var = ...` or `var @ Variant(_)` statement,
299                         // because e.g. match arms are patterns as well.
300                         // In other words, we check that it's a named variable binding.
301                         let is_binding = ast::LetStmt::can_cast(parent.kind())
302                             || (ast::MatchArm::can_cast(parent.kind())
303                                 && ident_pat.at_token().is_some());
304                         if !is_binding {
305                             // This pattern is not an actual variable declaration, e.g. `Some(val) => {..}` match arm.
306                             continue;
307                         }
308
309                         let diagnostic = IncorrectCase {
310                             file: source_ptr.file_id,
311                             ident_type: IdentType::Variable,
312                             ident: AstPtr::new(&name_ast).into(),
313                             expected_case: replacement.expected_case,
314                             ident_text: replacement.current_name.to_string(),
315                             suggested_text: replacement.suggested_text,
316                         };
317
318                         self.sink.push(diagnostic);
319                     }
320                 }
321             }
322         }
323     }
324
325     fn validate_struct(&mut self, struct_id: StructId) {
326         let data = self.db.struct_data(struct_id);
327
328         let non_camel_case_allowed = self.allowed(struct_id.into(), allow::NON_CAMEL_CASE_TYPES);
329         let non_snake_case_allowed = self.allowed(struct_id.into(), allow::NON_SNAKE_CASE);
330
331         // Check the structure name.
332         let struct_name = data.name.to_string();
333         let struct_name_replacement = if !non_camel_case_allowed {
334             to_camel_case(&struct_name).map(|new_name| Replacement {
335                 current_name: data.name.clone(),
336                 suggested_text: new_name,
337                 expected_case: CaseType::UpperCamelCase,
338             })
339         } else {
340             None
341         };
342
343         // Check the field names.
344         let mut struct_fields_replacements = Vec::new();
345
346         if !non_snake_case_allowed {
347             if let VariantData::Record(fields) = data.variant_data.as_ref() {
348                 for (_, field) in fields.iter() {
349                     let field_name = field.name.to_string();
350                     if let Some(new_name) = to_lower_snake_case(&field_name) {
351                         let replacement = Replacement {
352                             current_name: field.name.clone(),
353                             suggested_text: new_name,
354                             expected_case: CaseType::LowerSnakeCase,
355                         };
356                         struct_fields_replacements.push(replacement);
357                     }
358                 }
359             }
360         }
361
362         // If there is at least one element to spawn a warning on, go to the source map and generate a warning.
363         self.create_incorrect_case_diagnostic_for_struct(
364             struct_id,
365             struct_name_replacement,
366             struct_fields_replacements,
367         );
368     }
369
370     /// Given the information about incorrect names in the struct declaration, looks up into the source code
371     /// for exact locations and adds diagnostics into the sink.
372     fn create_incorrect_case_diagnostic_for_struct(
373         &mut self,
374         struct_id: StructId,
375         struct_name_replacement: Option<Replacement>,
376         struct_fields_replacements: Vec<Replacement>,
377     ) {
378         // XXX: only look at sources if we do have incorrect names
379         if struct_name_replacement.is_none() && struct_fields_replacements.is_empty() {
380             return;
381         }
382
383         let struct_loc = struct_id.lookup(self.db.upcast());
384         let struct_src = struct_loc.source(self.db.upcast());
385
386         if let Some(replacement) = struct_name_replacement {
387             let ast_ptr = match struct_src.value.name() {
388                 Some(name) => name,
389                 None => {
390                     never!(
391                         "Replacement ({:?}) was generated for a structure without a name: {:?}",
392                         replacement,
393                         struct_src
394                     );
395                     return;
396                 }
397             };
398
399             let diagnostic = IncorrectCase {
400                 file: struct_src.file_id,
401                 ident_type: IdentType::Structure,
402                 ident: AstPtr::new(&ast_ptr).into(),
403                 expected_case: replacement.expected_case,
404                 ident_text: replacement.current_name.to_string(),
405                 suggested_text: replacement.suggested_text,
406             };
407
408             self.sink.push(diagnostic);
409         }
410
411         let struct_fields_list = match struct_src.value.field_list() {
412             Some(ast::FieldList::RecordFieldList(fields)) => fields,
413             _ => {
414                 always!(
415                     struct_fields_replacements.is_empty(),
416                     "Replacements ({:?}) were generated for a structure fields which had no fields list: {:?}",
417                     struct_fields_replacements,
418                     struct_src
419                 );
420                 return;
421             }
422         };
423         let mut struct_fields_iter = struct_fields_list.fields();
424         for field_to_rename in struct_fields_replacements {
425             // We assume that parameters in replacement are in the same order as in the
426             // actual params list, but just some of them (ones that named correctly) are skipped.
427             let ast_ptr = loop {
428                 match struct_fields_iter.next().and_then(|field| field.name()) {
429                     Some(field_name) => {
430                         if field_name.as_name() == field_to_rename.current_name {
431                             break field_name;
432                         }
433                     }
434                     None => {
435                         never!(
436                             "Replacement ({:?}) was generated for a structure field which was not found: {:?}",
437                             field_to_rename, struct_src
438                         );
439                         return;
440                     }
441                 }
442             };
443
444             let diagnostic = IncorrectCase {
445                 file: struct_src.file_id,
446                 ident_type: IdentType::Field,
447                 ident: AstPtr::new(&ast_ptr).into(),
448                 expected_case: field_to_rename.expected_case,
449                 ident_text: field_to_rename.current_name.to_string(),
450                 suggested_text: field_to_rename.suggested_text,
451             };
452
453             self.sink.push(diagnostic);
454         }
455     }
456
457     fn validate_enum(&mut self, enum_id: EnumId) {
458         let data = self.db.enum_data(enum_id);
459
460         // Check whether non-camel case names are allowed for this enum.
461         if self.allowed(enum_id.into(), allow::NON_CAMEL_CASE_TYPES) {
462             return;
463         }
464
465         // Check the enum name.
466         let enum_name = data.name.to_string();
467         let enum_name_replacement = to_camel_case(&enum_name).map(|new_name| Replacement {
468             current_name: data.name.clone(),
469             suggested_text: new_name,
470             expected_case: CaseType::UpperCamelCase,
471         });
472
473         // Check the field names.
474         let enum_fields_replacements = data
475             .variants
476             .iter()
477             .filter_map(|(_, variant)| {
478                 Some(Replacement {
479                     current_name: variant.name.clone(),
480                     suggested_text: to_camel_case(&variant.name.to_string())?,
481                     expected_case: CaseType::UpperCamelCase,
482                 })
483             })
484             .collect();
485
486         // If there is at least one element to spawn a warning on, go to the source map and generate a warning.
487         self.create_incorrect_case_diagnostic_for_enum(
488             enum_id,
489             enum_name_replacement,
490             enum_fields_replacements,
491         )
492     }
493
494     /// Given the information about incorrect names in the struct declaration, looks up into the source code
495     /// for exact locations and adds diagnostics into the sink.
496     fn create_incorrect_case_diagnostic_for_enum(
497         &mut self,
498         enum_id: EnumId,
499         enum_name_replacement: Option<Replacement>,
500         enum_variants_replacements: Vec<Replacement>,
501     ) {
502         // XXX: only look at sources if we do have incorrect names
503         if enum_name_replacement.is_none() && enum_variants_replacements.is_empty() {
504             return;
505         }
506
507         let enum_loc = enum_id.lookup(self.db.upcast());
508         let enum_src = enum_loc.source(self.db.upcast());
509
510         if let Some(replacement) = enum_name_replacement {
511             let ast_ptr = match enum_src.value.name() {
512                 Some(name) => name,
513                 None => {
514                     never!(
515                         "Replacement ({:?}) was generated for a enum without a name: {:?}",
516                         replacement,
517                         enum_src
518                     );
519                     return;
520                 }
521             };
522
523             let diagnostic = IncorrectCase {
524                 file: enum_src.file_id,
525                 ident_type: IdentType::Enum,
526                 ident: AstPtr::new(&ast_ptr).into(),
527                 expected_case: replacement.expected_case,
528                 ident_text: replacement.current_name.to_string(),
529                 suggested_text: replacement.suggested_text,
530             };
531
532             self.sink.push(diagnostic);
533         }
534
535         let enum_variants_list = match enum_src.value.variant_list() {
536             Some(variants) => variants,
537             _ => {
538                 always!(
539                     enum_variants_replacements.is_empty(),
540                     "Replacements ({:?}) were generated for a enum variants which had no fields list: {:?}",
541                     enum_variants_replacements,
542                     enum_src
543                 );
544                 return;
545             }
546         };
547         let mut enum_variants_iter = enum_variants_list.variants();
548         for variant_to_rename in enum_variants_replacements {
549             // We assume that parameters in replacement are in the same order as in the
550             // actual params list, but just some of them (ones that named correctly) are skipped.
551             let ast_ptr = loop {
552                 match enum_variants_iter.next().and_then(|v| v.name()) {
553                     Some(variant_name) => {
554                         if variant_name.as_name() == variant_to_rename.current_name {
555                             break variant_name;
556                         }
557                     }
558                     None => {
559                         never!(
560                             "Replacement ({:?}) was generated for a enum variant which was not found: {:?}",
561                             variant_to_rename, enum_src
562                         );
563                         return;
564                     }
565                 }
566             };
567
568             let diagnostic = IncorrectCase {
569                 file: enum_src.file_id,
570                 ident_type: IdentType::Variant,
571                 ident: AstPtr::new(&ast_ptr).into(),
572                 expected_case: variant_to_rename.expected_case,
573                 ident_text: variant_to_rename.current_name.to_string(),
574                 suggested_text: variant_to_rename.suggested_text,
575             };
576
577             self.sink.push(diagnostic);
578         }
579     }
580
581     fn validate_const(&mut self, const_id: ConstId) {
582         let data = self.db.const_data(const_id);
583
584         if self.allowed(const_id.into(), allow::NON_UPPER_CASE_GLOBAL) {
585             return;
586         }
587
588         let name = match &data.name {
589             Some(name) => name,
590             None => return,
591         };
592
593         let const_name = name.to_string();
594         let replacement = if let Some(new_name) = to_upper_snake_case(&const_name) {
595             Replacement {
596                 current_name: name.clone(),
597                 suggested_text: new_name,
598                 expected_case: CaseType::UpperSnakeCase,
599             }
600         } else {
601             // Nothing to do here.
602             return;
603         };
604
605         let const_loc = const_id.lookup(self.db.upcast());
606         let const_src = const_loc.source(self.db.upcast());
607
608         let ast_ptr = match const_src.value.name() {
609             Some(name) => name,
610             None => return,
611         };
612
613         let diagnostic = IncorrectCase {
614             file: const_src.file_id,
615             ident_type: IdentType::Constant,
616             ident: AstPtr::new(&ast_ptr).into(),
617             expected_case: replacement.expected_case,
618             ident_text: replacement.current_name.to_string(),
619             suggested_text: replacement.suggested_text,
620         };
621
622         self.sink.push(diagnostic);
623     }
624
625     fn validate_static(&mut self, static_id: StaticId) {
626         let data = self.db.static_data(static_id);
627         if data.is_extern {
628             mark::hit!(extern_static_incorrect_case_ignored);
629             return;
630         }
631
632         if self.allowed(static_id.into(), allow::NON_UPPER_CASE_GLOBAL) {
633             return;
634         }
635
636         let name = match &data.name {
637             Some(name) => name,
638             None => return,
639         };
640
641         let static_name = name.to_string();
642         let replacement = if let Some(new_name) = to_upper_snake_case(&static_name) {
643             Replacement {
644                 current_name: name.clone(),
645                 suggested_text: new_name,
646                 expected_case: CaseType::UpperSnakeCase,
647             }
648         } else {
649             // Nothing to do here.
650             return;
651         };
652
653         let static_loc = static_id.lookup(self.db.upcast());
654         let static_src = static_loc.source(self.db.upcast());
655
656         let ast_ptr = match static_src.value.name() {
657             Some(name) => name,
658             None => return,
659         };
660
661         let diagnostic = IncorrectCase {
662             file: static_src.file_id,
663             ident_type: IdentType::StaticVariable,
664             ident: AstPtr::new(&ast_ptr).into(),
665             expected_case: replacement.expected_case,
666             ident_text: replacement.current_name.to_string(),
667             suggested_text: replacement.suggested_text,
668         };
669
670         self.sink.push(diagnostic);
671     }
672 }
673
674 #[cfg(test)]
675 mod tests {
676     use test_utils::mark;
677
678     use crate::diagnostics::tests::check_diagnostics;
679
680     #[test]
681     fn incorrect_function_name() {
682         check_diagnostics(
683             r#"
684 fn NonSnakeCaseName() {}
685 // ^^^^^^^^^^^^^^^^ Function `NonSnakeCaseName` should have snake_case name, e.g. `non_snake_case_name`
686 "#,
687         );
688     }
689
690     #[test]
691     fn incorrect_function_params() {
692         check_diagnostics(
693             r#"
694 fn foo(SomeParam: u8) {}
695     // ^^^^^^^^^ Argument `SomeParam` should have snake_case name, e.g. `some_param`
696
697 fn foo2(ok_param: &str, CAPS_PARAM: u8) {}
698                      // ^^^^^^^^^^ Argument `CAPS_PARAM` should have snake_case name, e.g. `caps_param`
699 "#,
700         );
701     }
702
703     #[test]
704     fn incorrect_variable_names() {
705         check_diagnostics(
706             r#"
707 fn foo() {
708     let SOME_VALUE = 10;
709      // ^^^^^^^^^^ Variable `SOME_VALUE` should have snake_case name, e.g. `some_value`
710     let AnotherValue = 20;
711      // ^^^^^^^^^^^^ Variable `AnotherValue` should have snake_case name, e.g. `another_value`
712 }
713 "#,
714         );
715     }
716
717     #[test]
718     fn incorrect_struct_names() {
719         check_diagnostics(
720             r#"
721 struct non_camel_case_name {}
722     // ^^^^^^^^^^^^^^^^^^^ Structure `non_camel_case_name` should have CamelCase name, e.g. `NonCamelCaseName`
723
724 struct SCREAMING_CASE {}
725     // ^^^^^^^^^^^^^^ Structure `SCREAMING_CASE` should have CamelCase name, e.g. `ScreamingCase`
726 "#,
727         );
728     }
729
730     #[test]
731     fn no_diagnostic_for_camel_cased_acronyms_in_struct_name() {
732         check_diagnostics(
733             r#"
734 struct AABB {}
735 "#,
736         );
737     }
738
739     #[test]
740     fn incorrect_struct_field() {
741         check_diagnostics(
742             r#"
743 struct SomeStruct { SomeField: u8 }
744                  // ^^^^^^^^^ Field `SomeField` should have snake_case name, e.g. `some_field`
745 "#,
746         );
747     }
748
749     #[test]
750     fn incorrect_enum_names() {
751         check_diagnostics(
752             r#"
753 enum some_enum { Val(u8) }
754   // ^^^^^^^^^ Enum `some_enum` should have CamelCase name, e.g. `SomeEnum`
755
756 enum SOME_ENUM
757   // ^^^^^^^^^ Enum `SOME_ENUM` should have CamelCase name, e.g. `SomeEnum`
758 "#,
759         );
760     }
761
762     #[test]
763     fn no_diagnostic_for_camel_cased_acronyms_in_enum_name() {
764         check_diagnostics(
765             r#"
766 enum AABB {}
767 "#,
768         );
769     }
770
771     #[test]
772     fn incorrect_enum_variant_name() {
773         check_diagnostics(
774             r#"
775 enum SomeEnum { SOME_VARIANT(u8) }
776              // ^^^^^^^^^^^^ Variant `SOME_VARIANT` should have CamelCase name, e.g. `SomeVariant`
777 "#,
778         );
779     }
780
781     #[test]
782     fn incorrect_const_name() {
783         check_diagnostics(
784             r#"
785 const some_weird_const: u8 = 10;
786    // ^^^^^^^^^^^^^^^^ Constant `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST`
787
788 fn func() {
789     const someConstInFunc: &str = "hi there";
790        // ^^^^^^^^^^^^^^^ Constant `someConstInFunc` should have UPPER_SNAKE_CASE name, e.g. `SOME_CONST_IN_FUNC`
791
792 }
793 "#,
794         );
795     }
796
797     #[test]
798     fn incorrect_static_name() {
799         check_diagnostics(
800             r#"
801 static some_weird_const: u8 = 10;
802     // ^^^^^^^^^^^^^^^^ Static variable `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST`
803
804 fn func() {
805     static someConstInFunc: &str = "hi there";
806         // ^^^^^^^^^^^^^^^ Static variable `someConstInFunc` should have UPPER_SNAKE_CASE name, e.g. `SOME_CONST_IN_FUNC`
807 }
808 "#,
809         );
810     }
811
812     #[test]
813     fn fn_inside_impl_struct() {
814         check_diagnostics(
815             r#"
816 struct someStruct;
817     // ^^^^^^^^^^ Structure `someStruct` should have CamelCase name, e.g. `SomeStruct`
818
819 impl someStruct {
820     fn SomeFunc(&self) {
821     // ^^^^^^^^ Function `SomeFunc` should have snake_case name, e.g. `some_func`
822         static someConstInFunc: &str = "hi there";
823             // ^^^^^^^^^^^^^^^ Static variable `someConstInFunc` should have UPPER_SNAKE_CASE name, e.g. `SOME_CONST_IN_FUNC`
824         let WHY_VAR_IS_CAPS = 10;
825          // ^^^^^^^^^^^^^^^ Variable `WHY_VAR_IS_CAPS` should have snake_case name, e.g. `why_var_is_caps`
826     }
827 }
828 "#,
829         );
830     }
831
832     #[test]
833     fn no_diagnostic_for_enum_varinats() {
834         check_diagnostics(
835             r#"
836 enum Option { Some, None }
837
838 fn main() {
839     match Option::None {
840         None => (),
841         Some => (),
842     }
843 }
844 "#,
845         );
846     }
847
848     #[test]
849     fn non_let_bind() {
850         check_diagnostics(
851             r#"
852 enum Option { Some, None }
853
854 fn main() {
855     match Option::None {
856         SOME_VAR @ None => (),
857      // ^^^^^^^^ Variable `SOME_VAR` should have snake_case name, e.g. `some_var`
858         Some => (),
859     }
860 }
861 "#,
862         );
863     }
864
865     #[test]
866     fn allow_attributes() {
867         check_diagnostics(
868             r#"
869             #[allow(non_snake_case)]
870     fn NonSnakeCaseName(SOME_VAR: u8) -> u8{
871         let OtherVar = SOME_VAR + 1;
872         OtherVar
873     }
874
875     #[allow(non_snake_case, non_camel_case_types)]
876     pub struct some_type {
877         SOME_FIELD: u8,
878         SomeField: u16,
879     }
880
881     #[allow(non_upper_case_globals)]
882     pub const some_const: u8 = 10;
883
884     #[allow(non_upper_case_globals)]
885     pub static SomeStatic: u8 = 10;
886     "#,
887         );
888     }
889
890     #[test]
891     fn ignores_extern_items() {
892         mark::check!(extern_func_incorrect_case_ignored);
893         mark::check!(extern_static_incorrect_case_ignored);
894         check_diagnostics(
895             r#"
896 extern {
897     fn NonSnakeCaseName(SOME_VAR: u8) -> u8;
898     pub static SomeStatic: u8 = 10;
899 }
900             "#,
901         );
902     }
903 }