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