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