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