1 //! Provides validators for the item declarations.
3 //! This includes the following items:
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(_)`)
20 AdtId, AttrDefId, ConstId, EnumId, FunctionId, Lookup, ModuleDefId, StaticId, StructId,
23 diagnostics::DiagnosticSink,
27 ast::{self, NameOwner},
34 diagnostics::{decl_check::case_conv::*, CaseType, IncorrectCase},
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";
43 pub(super) struct DeclValidator<'a, 'b: 'a> {
44 db: &'a dyn HirDatabase,
46 sink: &'a mut DiagnosticSink<'b>,
52 suggested_text: String,
53 expected_case: CaseType,
56 impl<'a, 'b> DeclValidator<'a, 'b> {
58 db: &'a dyn HirDatabase,
60 sink: &'a mut DiagnosticSink<'b>,
61 ) -> DeclValidator<'a, 'b> {
62 DeclValidator { db, krate, sink }
65 pub(super) fn validate_item(&mut self, item: ModuleDefId) {
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),
75 fn validate_adt(&mut self, adt: AdtId) {
77 AdtId::StructId(struct_id) => self.validate_struct(struct_id),
78 AdtId::EnumId(enum_id) => self.validate_enum(enum_id),
79 AdtId::UnionId(_) => {
80 // Unions aren't yet supported by this validator.
85 /// Checks whether not following the convention is allowed for this item.
87 /// Currently this method doesn't check parent attributes.
88 fn allowed(&self, id: AttrDefId, allow_name: &str) -> bool {
89 self.db.attrs(id).by_key("allow").tt_values().any(|tt| tt.to_string().contains(allow_name))
92 fn validate_func(&mut self, func: FunctionId) {
93 let data = self.db.function_data(func);
95 mark::hit!(extern_func_incorrect_case_ignored);
99 let body = self.db.body(func.into());
101 // Recursively validate inner scope items, such as static variables and constants.
102 for (item_id, _) in body.item_scope.values() {
103 let mut validator = DeclValidator::new(self.db, self.krate, self.sink);
104 validator.validate_item(item_id);
107 // Check whether non-snake case identifiers are allowed for this function.
108 if self.allowed(func.into(), allow::NON_SNAKE_CASE) {
112 // Check the function name.
113 let function_name = data.name.to_string();
114 let fn_name_replacement = if let Some(new_name) = to_lower_snake_case(&function_name) {
115 let replacement = Replacement {
116 current_name: data.name.clone(),
117 suggested_text: new_name,
118 expected_case: CaseType::LowerSnakeCase,
125 // Check the param names.
126 let mut fn_param_replacements = Vec::new();
128 for pat_id in body.params.iter().cloned() {
129 let pat = &body[pat_id];
131 let param_name = match pat {
132 Pat::Bind { name, .. } => name,
136 let name = param_name.to_string();
137 if let Some(new_name) = to_lower_snake_case(&name) {
138 let replacement = Replacement {
139 current_name: param_name.clone(),
140 suggested_text: new_name,
141 expected_case: CaseType::LowerSnakeCase,
143 fn_param_replacements.push(replacement);
147 // Check the patterns inside the function body.
148 let mut pats_replacements = Vec::new();
150 for (pat_idx, pat) in body.pats.iter() {
151 if body.params.contains(&pat_idx) {
152 // We aren't interested in function parameters, we've processed them above.
156 let bind_name = match pat {
157 Pat::Bind { name, .. } => name,
161 let name = bind_name.to_string();
162 if let Some(new_name) = to_lower_snake_case(&name) {
163 let replacement = Replacement {
164 current_name: bind_name.clone(),
165 suggested_text: new_name,
166 expected_case: CaseType::LowerSnakeCase,
168 pats_replacements.push((pat_idx, replacement));
172 // If there is at least one element to spawn a warning on, go to the source map and generate a warning.
173 self.create_incorrect_case_diagnostic_for_func(
176 fn_param_replacements,
178 self.create_incorrect_case_diagnostic_for_variables(func, pats_replacements);
181 /// Given the information about incorrect names in the function declaration, looks up into the source code
182 /// for exact locations and adds diagnostics into the sink.
183 fn create_incorrect_case_diagnostic_for_func(
186 fn_name_replacement: Option<Replacement>,
187 fn_param_replacements: Vec<Replacement>,
189 // XXX: only look at sources if we do have incorrect names
190 if fn_name_replacement.is_none() && fn_param_replacements.is_empty() {
194 let fn_loc = func.lookup(self.db.upcast());
195 let fn_src = fn_loc.source(self.db.upcast());
197 // Diagnostic for function name.
198 if let Some(replacement) = fn_name_replacement {
199 let ast_ptr = match fn_src.value.name() {
202 // We don't want rust-analyzer to panic over this, but it is definitely some kind of error in the logic.
204 "Replacement ({:?}) was generated for a function without a name: {:?}",
212 let diagnostic = IncorrectCase {
213 file: fn_src.file_id,
214 ident_type: "Function".to_string(),
215 ident: AstPtr::new(&ast_ptr).into(),
216 expected_case: replacement.expected_case,
217 ident_text: replacement.current_name.to_string(),
218 suggested_text: replacement.suggested_text,
221 self.sink.push(diagnostic);
224 // Diagnostics for function params.
225 let fn_params_list = match fn_src.value.param_list() {
226 Some(params) => params,
228 if !fn_param_replacements.is_empty() {
230 "Replacements ({:?}) were generated for a function parameters which had no parameters list: {:?}",
231 fn_param_replacements, fn_src
237 let mut fn_params_iter = fn_params_list.params();
238 for param_to_rename in fn_param_replacements {
239 // We assume that parameters in replacement are in the same order as in the
240 // actual params list, but just some of them (ones that named correctly) are skipped.
241 let ast_ptr: ast::Name = loop {
242 match fn_params_iter.next() {
244 if pat_equals_to_name(element.pat(), ¶m_to_rename.current_name) =>
246 if let ast::Pat::IdentPat(pat) = element.pat().unwrap() {
247 break pat.name().unwrap();
249 // This is critical. If we consider this parameter the expected one,
250 // it **must** have a name.
252 "Pattern {:?} equals to expected replacement {:?}, but has no name",
253 element, param_to_rename
260 "Replacement ({:?}) was generated for a function parameter which was not found: {:?}",
261 param_to_rename, fn_src
268 let diagnostic = IncorrectCase {
269 file: fn_src.file_id,
270 ident_type: "Argument".to_string(),
271 ident: AstPtr::new(&ast_ptr).into(),
272 expected_case: param_to_rename.expected_case,
273 ident_text: param_to_rename.current_name.to_string(),
274 suggested_text: param_to_rename.suggested_text,
277 self.sink.push(diagnostic);
281 /// Given the information about incorrect variable names, looks up into the source code
282 /// for exact locations and adds diagnostics into the sink.
283 fn create_incorrect_case_diagnostic_for_variables(
286 pats_replacements: Vec<(PatId, Replacement)>,
288 // XXX: only look at source_map if we do have missing fields
289 if pats_replacements.is_empty() {
293 let (_, source_map) = self.db.body_with_source_map(func.into());
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(self.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,
304 let name_ast = match ident_pat.name() {
305 Some(name_ast) => name_ast,
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());
316 // This pattern is not an actual variable declaration, e.g. `Some(val) => {..}` match arm.
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,
329 self.sink.push(diagnostic);
336 fn validate_struct(&mut self, struct_id: StructId) {
337 let data = self.db.struct_data(struct_id);
339 let non_camel_case_allowed = self.allowed(struct_id.into(), allow::NON_CAMEL_CASE_TYPES);
340 let non_snake_case_allowed = self.allowed(struct_id.into(), allow::NON_SNAKE_CASE);
342 // Check the structure name.
343 let struct_name = data.name.to_string();
344 let struct_name_replacement = if let Some(new_name) = to_camel_case(&struct_name) {
345 let replacement = Replacement {
346 current_name: data.name.clone(),
347 suggested_text: new_name,
348 expected_case: CaseType::UpperCamelCase,
350 if non_camel_case_allowed {
359 // Check the field names.
360 let mut struct_fields_replacements = Vec::new();
362 if !non_snake_case_allowed {
363 if let VariantData::Record(fields) = data.variant_data.as_ref() {
364 for (_, field) in fields.iter() {
365 let field_name = field.name.to_string();
366 if let Some(new_name) = to_lower_snake_case(&field_name) {
367 let replacement = Replacement {
368 current_name: field.name.clone(),
369 suggested_text: new_name,
370 expected_case: CaseType::LowerSnakeCase,
372 struct_fields_replacements.push(replacement);
378 // If there is at least one element to spawn a warning on, go to the source map and generate a warning.
379 self.create_incorrect_case_diagnostic_for_struct(
381 struct_name_replacement,
382 struct_fields_replacements,
386 /// Given the information about incorrect names in the struct declaration, looks up into the source code
387 /// for exact locations and adds diagnostics into the sink.
388 fn create_incorrect_case_diagnostic_for_struct(
391 struct_name_replacement: Option<Replacement>,
392 struct_fields_replacements: Vec<Replacement>,
394 // XXX: only look at sources if we do have incorrect names
395 if struct_name_replacement.is_none() && struct_fields_replacements.is_empty() {
399 let struct_loc = struct_id.lookup(self.db.upcast());
400 let struct_src = struct_loc.source(self.db.upcast());
402 if let Some(replacement) = struct_name_replacement {
403 let ast_ptr = match struct_src.value.name() {
406 // We don't want rust-analyzer to panic over this, but it is definitely some kind of error in the logic.
408 "Replacement ({:?}) was generated for a structure without a name: {:?}",
416 let diagnostic = IncorrectCase {
417 file: struct_src.file_id,
418 ident_type: "Structure".to_string(),
419 ident: AstPtr::new(&ast_ptr).into(),
420 expected_case: replacement.expected_case,
421 ident_text: replacement.current_name.to_string(),
422 suggested_text: replacement.suggested_text,
425 self.sink.push(diagnostic);
428 let struct_fields_list = match struct_src.value.field_list() {
429 Some(ast::FieldList::RecordFieldList(fields)) => fields,
431 if !struct_fields_replacements.is_empty() {
433 "Replacements ({:?}) were generated for a structure fields which had no fields list: {:?}",
434 struct_fields_replacements, struct_src
440 let mut struct_fields_iter = struct_fields_list.fields();
441 for field_to_rename in struct_fields_replacements {
442 // We assume that parameters in replacement are in the same order as in the
443 // actual params list, but just some of them (ones that named correctly) are skipped.
445 match struct_fields_iter.next() {
446 Some(element) if names_equal(element.name(), &field_to_rename.current_name) => {
447 break element.name().unwrap()
452 "Replacement ({:?}) was generated for a structure field which was not found: {:?}",
453 field_to_rename, struct_src
460 let diagnostic = IncorrectCase {
461 file: struct_src.file_id,
462 ident_type: "Field".to_string(),
463 ident: AstPtr::new(&ast_ptr).into(),
464 expected_case: field_to_rename.expected_case,
465 ident_text: field_to_rename.current_name.to_string(),
466 suggested_text: field_to_rename.suggested_text,
469 self.sink.push(diagnostic);
473 fn validate_enum(&mut self, enum_id: EnumId) {
474 let data = self.db.enum_data(enum_id);
476 // Check whether non-camel case names are allowed for this enum.
477 if self.allowed(enum_id.into(), allow::NON_CAMEL_CASE_TYPES) {
481 // Check the enum name.
482 let enum_name = data.name.to_string();
483 let enum_name_replacement = if let Some(new_name) = to_camel_case(&enum_name) {
484 let replacement = Replacement {
485 current_name: data.name.clone(),
486 suggested_text: new_name,
487 expected_case: CaseType::UpperCamelCase,
494 // Check the field names.
495 let mut enum_fields_replacements = Vec::new();
497 for (_, variant) in data.variants.iter() {
498 let variant_name = variant.name.to_string();
499 if let Some(new_name) = to_camel_case(&variant_name) {
500 let replacement = Replacement {
501 current_name: variant.name.clone(),
502 suggested_text: new_name,
503 expected_case: CaseType::UpperCamelCase,
505 enum_fields_replacements.push(replacement);
509 // If there is at least one element to spawn a warning on, go to the source map and generate a warning.
510 self.create_incorrect_case_diagnostic_for_enum(
512 enum_name_replacement,
513 enum_fields_replacements,
517 /// Given the information about incorrect names in the struct declaration, looks up into the source code
518 /// for exact locations and adds diagnostics into the sink.
519 fn create_incorrect_case_diagnostic_for_enum(
522 enum_name_replacement: Option<Replacement>,
523 enum_variants_replacements: Vec<Replacement>,
525 // XXX: only look at sources if we do have incorrect names
526 if enum_name_replacement.is_none() && enum_variants_replacements.is_empty() {
530 let enum_loc = enum_id.lookup(self.db.upcast());
531 let enum_src = enum_loc.source(self.db.upcast());
533 if let Some(replacement) = enum_name_replacement {
534 let ast_ptr = match enum_src.value.name() {
537 // We don't want rust-analyzer to panic over this, but it is definitely some kind of error in the logic.
539 "Replacement ({:?}) was generated for a enum without a name: {:?}",
547 let diagnostic = IncorrectCase {
548 file: enum_src.file_id,
549 ident_type: "Enum".to_string(),
550 ident: AstPtr::new(&ast_ptr).into(),
551 expected_case: replacement.expected_case,
552 ident_text: replacement.current_name.to_string(),
553 suggested_text: replacement.suggested_text,
556 self.sink.push(diagnostic);
559 let enum_variants_list = match enum_src.value.variant_list() {
560 Some(variants) => variants,
562 if !enum_variants_replacements.is_empty() {
564 "Replacements ({:?}) were generated for a enum variants which had no fields list: {:?}",
565 enum_variants_replacements, enum_src
571 let mut enum_variants_iter = enum_variants_list.variants();
572 for variant_to_rename in enum_variants_replacements {
573 // We assume that parameters in replacement are in the same order as in the
574 // actual params list, but just some of them (ones that named correctly) are skipped.
576 match enum_variants_iter.next() {
578 if names_equal(variant.name(), &variant_to_rename.current_name) =>
580 break variant.name().unwrap()
585 "Replacement ({:?}) was generated for a enum variant which was not found: {:?}",
586 variant_to_rename, enum_src
593 let diagnostic = IncorrectCase {
594 file: enum_src.file_id,
595 ident_type: "Variant".to_string(),
596 ident: AstPtr::new(&ast_ptr).into(),
597 expected_case: variant_to_rename.expected_case,
598 ident_text: variant_to_rename.current_name.to_string(),
599 suggested_text: variant_to_rename.suggested_text,
602 self.sink.push(diagnostic);
606 fn validate_const(&mut self, const_id: ConstId) {
607 let data = self.db.const_data(const_id);
609 if self.allowed(const_id.into(), allow::NON_UPPER_CASE_GLOBAL) {
613 let name = match &data.name {
618 let const_name = name.to_string();
619 let replacement = if let Some(new_name) = to_upper_snake_case(&const_name) {
621 current_name: name.clone(),
622 suggested_text: new_name,
623 expected_case: CaseType::UpperSnakeCase,
626 // Nothing to do here.
630 let const_loc = const_id.lookup(self.db.upcast());
631 let const_src = const_loc.source(self.db.upcast());
633 let ast_ptr = match const_src.value.name() {
638 let diagnostic = IncorrectCase {
639 file: const_src.file_id,
640 ident_type: "Constant".to_string(),
641 ident: AstPtr::new(&ast_ptr).into(),
642 expected_case: replacement.expected_case,
643 ident_text: replacement.current_name.to_string(),
644 suggested_text: replacement.suggested_text,
647 self.sink.push(diagnostic);
650 fn validate_static(&mut self, static_id: StaticId) {
651 let data = self.db.static_data(static_id);
653 mark::hit!(extern_static_incorrect_case_ignored);
657 if self.allowed(static_id.into(), allow::NON_UPPER_CASE_GLOBAL) {
661 let name = match &data.name {
666 let static_name = name.to_string();
667 let replacement = if let Some(new_name) = to_upper_snake_case(&static_name) {
669 current_name: name.clone(),
670 suggested_text: new_name,
671 expected_case: CaseType::UpperSnakeCase,
674 // Nothing to do here.
678 let static_loc = static_id.lookup(self.db.upcast());
679 let static_src = static_loc.source(self.db.upcast());
681 let ast_ptr = match static_src.value.name() {
686 let diagnostic = IncorrectCase {
687 file: static_src.file_id,
688 ident_type: "Static variable".to_string(),
689 ident: AstPtr::new(&ast_ptr).into(),
690 expected_case: replacement.expected_case,
691 ident_text: replacement.current_name.to_string(),
692 suggested_text: replacement.suggested_text,
695 self.sink.push(diagnostic);
699 fn names_equal(left: Option<ast::Name>, right: &Name) -> bool {
700 if let Some(left) = left {
701 &left.as_name() == right
707 fn pat_equals_to_name(pat: Option<ast::Pat>, name: &Name) -> bool {
708 if let Some(ast::Pat::IdentPat(ident)) = pat {
709 ident.to_string() == name.to_string()
717 use test_utils::mark;
719 use crate::diagnostics::tests::check_diagnostics;
722 fn incorrect_function_name() {
725 fn NonSnakeCaseName() {}
726 // ^^^^^^^^^^^^^^^^ Function `NonSnakeCaseName` should have snake_case name, e.g. `non_snake_case_name`
732 fn incorrect_function_params() {
735 fn foo(SomeParam: u8) {}
736 // ^^^^^^^^^ Argument `SomeParam` should have snake_case name, e.g. `some_param`
738 fn foo2(ok_param: &str, CAPS_PARAM: u8) {}
739 // ^^^^^^^^^^ Argument `CAPS_PARAM` should have snake_case name, e.g. `caps_param`
745 fn incorrect_variable_names() {
750 // ^^^^^^^^^^ Variable `SOME_VALUE` should have snake_case name, e.g. `some_value`
751 let AnotherValue = 20;
752 // ^^^^^^^^^^^^ Variable `AnotherValue` should have snake_case name, e.g. `another_value`
759 fn incorrect_struct_names() {
762 struct non_camel_case_name {}
763 // ^^^^^^^^^^^^^^^^^^^ Structure `non_camel_case_name` should have CamelCase name, e.g. `NonCamelCaseName`
765 struct SCREAMING_CASE {}
766 // ^^^^^^^^^^^^^^ Structure `SCREAMING_CASE` should have CamelCase name, e.g. `ScreamingCase`
772 fn no_diagnostic_for_camel_cased_acronyms_in_struct_name() {
781 fn incorrect_struct_field() {
784 struct SomeStruct { SomeField: u8 }
785 // ^^^^^^^^^ Field `SomeField` should have snake_case name, e.g. `some_field`
791 fn incorrect_enum_names() {
794 enum some_enum { Val(u8) }
795 // ^^^^^^^^^ Enum `some_enum` should have CamelCase name, e.g. `SomeEnum`
798 // ^^^^^^^^^ Enum `SOME_ENUM` should have CamelCase name, e.g. `SomeEnum`
804 fn no_diagnostic_for_camel_cased_acronyms_in_enum_name() {
813 fn incorrect_enum_variant_name() {
816 enum SomeEnum { SOME_VARIANT(u8) }
817 // ^^^^^^^^^^^^ Variant `SOME_VARIANT` should have CamelCase name, e.g. `SomeVariant`
823 fn incorrect_const_name() {
826 const some_weird_const: u8 = 10;
827 // ^^^^^^^^^^^^^^^^ Constant `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST`
830 const someConstInFunc: &str = "hi there";
831 // ^^^^^^^^^^^^^^^ Constant `someConstInFunc` should have UPPER_SNAKE_CASE name, e.g. `SOME_CONST_IN_FUNC`
839 fn incorrect_static_name() {
842 static some_weird_const: u8 = 10;
843 // ^^^^^^^^^^^^^^^^ Static variable `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST`
846 static someConstInFunc: &str = "hi there";
847 // ^^^^^^^^^^^^^^^ Static variable `someConstInFunc` should have UPPER_SNAKE_CASE name, e.g. `SOME_CONST_IN_FUNC`
854 fn fn_inside_impl_struct() {
858 // ^^^^^^^^^^ Structure `someStruct` should have CamelCase name, e.g. `SomeStruct`
862 // ^^^^^^^^ Function `SomeFunc` should have snake_case name, e.g. `some_func`
863 static someConstInFunc: &str = "hi there";
864 // ^^^^^^^^^^^^^^^ Static variable `someConstInFunc` should have UPPER_SNAKE_CASE name, e.g. `SOME_CONST_IN_FUNC`
865 let WHY_VAR_IS_CAPS = 10;
866 // ^^^^^^^^^^^^^^^ Variable `WHY_VAR_IS_CAPS` should have snake_case name, e.g. `why_var_is_caps`
874 fn no_diagnostic_for_enum_varinats() {
877 enum Option { Some, None }
893 enum Option { Some, None }
897 SOME_VAR @ None => (),
898 // ^^^^^^^^ Variable `SOME_VAR` should have snake_case name, e.g. `some_var`
907 fn allow_attributes() {
910 #[allow(non_snake_case)]
911 fn NonSnakeCaseName(SOME_VAR: u8) -> u8{
912 let OtherVar = SOME_VAR + 1;
916 #[allow(non_snake_case, non_camel_case_types)]
917 pub struct some_type {
922 #[allow(non_upper_case_globals)]
923 pub const some_const: u8 = 10;
925 #[allow(non_upper_case_globals)]
926 pub static SomeStatic: u8 = 10;
932 fn ignores_extern_items() {
933 mark::check!(extern_func_incorrect_case_ignored);
934 mark::check!(extern_static_incorrect_case_ignored);
938 fn NonSnakeCaseName(SOME_VAR: u8) -> u8;
939 pub static SomeStatic: u8 = 10;