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