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