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