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(_)`)
19 AdtId, AttrDefId, ConstId, EnumId, FunctionId, Lookup, ModuleDefId, StaticId, StructId,
22 diagnostics::DiagnosticSink,
26 ast::{self, NameOwner},
33 diagnostics::{decl_check::case_conv::*, CaseType, IncorrectCase},
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";
42 pub(super) struct DeclValidator<'a, 'b: 'a> {
44 sink: &'a mut DiagnosticSink<'b>,
50 suggested_text: String,
51 expected_case: CaseType,
54 impl<'a, 'b> DeclValidator<'a, 'b> {
57 sink: &'a mut DiagnosticSink<'b>,
58 ) -> DeclValidator<'a, 'b> {
59 DeclValidator { owner, sink }
62 pub(super) fn validate_item(&mut self, db: &dyn HirDatabase) {
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),
72 fn validate_adt(&mut self, db: &dyn HirDatabase, adt: AdtId) {
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.
82 /// Checks whether not following the convention is allowed for this item.
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))
89 fn validate_func(&mut self, db: &dyn HirDatabase, func: FunctionId) {
90 let data = db.function_data(func);
92 mark::hit!(extern_func_incorrect_case_ignored);
96 let body = db.body(func.into());
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);
104 // Check whether non-snake case identifiers are allowed for this function.
105 if self.allowed(db, func.into(), allow::NON_SNAKE_CASE) {
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,
122 // Check the param names.
123 let mut fn_param_replacements = Vec::new();
125 for pat_id in body.params.iter().cloned() {
126 let pat = &body[pat_id];
128 let param_name = match pat {
129 Pat::Bind { name, .. } => name,
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,
140 fn_param_replacements.push(replacement);
144 // Check the patterns inside the function body.
145 let mut pats_replacements = Vec::new();
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.
153 let bind_name = match pat {
154 Pat::Bind { name, .. } => name,
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,
165 pats_replacements.push((pat_idx, replacement));
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(
174 fn_param_replacements,
176 self.create_incorrect_case_diagnostic_for_variables(func, db, pats_replacements);
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(
184 db: &dyn HirDatabase,
185 fn_name_replacement: Option<Replacement>,
186 fn_param_replacements: Vec<Replacement>,
188 // XXX: only look at sources if we do have incorrect names
189 if fn_name_replacement.is_none() && fn_param_replacements.is_empty() {
193 let fn_loc = func.lookup(db.upcast());
194 let fn_src = fn_loc.source(db.upcast());
196 // Diagnostic for function name.
197 if let Some(replacement) = fn_name_replacement {
198 let ast_ptr = match fn_src.value.name() {
201 // We don't want rust-analyzer to panic over this, but it is definitely some kind of error in the logic.
203 "Replacement ({:?}) was generated for a function without a name: {:?}",
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,
220 self.sink.push(diagnostic);
223 // Diagnostics for function params.
224 let fn_params_list = match fn_src.value.param_list() {
225 Some(params) => params,
227 if !fn_param_replacements.is_empty() {
229 "Replacements ({:?}) were generated for a function parameters which had no parameters list: {:?}",
230 fn_param_replacements, fn_src
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() {
243 if pat_equals_to_name(element.pat(), ¶m_to_rename.current_name) =>
245 if let ast::Pat::IdentPat(pat) = element.pat().unwrap() {
246 break pat.name().unwrap();
248 // This is critical. If we consider this parameter the expected one,
249 // it **must** have a name.
251 "Pattern {:?} equals to expected replacement {:?}, but has no name",
252 element, param_to_rename
259 "Replacement ({:?}) was generated for a function parameter which was not found: {:?}",
260 param_to_rename, fn_src
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,
276 self.sink.push(diagnostic);
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(
285 db: &dyn HirDatabase,
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) = 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(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, db: &dyn HirDatabase, struct_id: StructId) {
337 let data = db.struct_data(struct_id);
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);
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,
351 if non_camel_case_allowed {
360 // Check the field names.
361 let mut struct_fields_replacements = Vec::new();
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,
373 struct_fields_replacements.push(replacement);
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(
383 struct_name_replacement,
384 struct_fields_replacements,
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(
393 db: &dyn HirDatabase,
394 struct_name_replacement: Option<Replacement>,
395 struct_fields_replacements: Vec<Replacement>,
397 // XXX: only look at sources if we do have incorrect names
398 if struct_name_replacement.is_none() && struct_fields_replacements.is_empty() {
402 let struct_loc = struct_id.lookup(db.upcast());
403 let struct_src = struct_loc.source(db.upcast());
405 if let Some(replacement) = struct_name_replacement {
406 let ast_ptr = match struct_src.value.name() {
409 // We don't want rust-analyzer to panic over this, but it is definitely some kind of error in the logic.
411 "Replacement ({:?}) was generated for a structure without a name: {:?}",
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,
428 self.sink.push(diagnostic);
431 let struct_fields_list = match struct_src.value.field_list() {
432 Some(ast::FieldList::RecordFieldList(fields)) => fields,
434 if !struct_fields_replacements.is_empty() {
436 "Replacements ({:?}) were generated for a structure fields which had no fields list: {:?}",
437 struct_fields_replacements, struct_src
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.
448 match struct_fields_iter.next() {
449 Some(element) if names_equal(element.name(), &field_to_rename.current_name) => {
450 break element.name().unwrap()
455 "Replacement ({:?}) was generated for a structure field which was not found: {:?}",
456 field_to_rename, struct_src
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,
472 self.sink.push(diagnostic);
476 fn validate_enum(&mut self, db: &dyn HirDatabase, enum_id: EnumId) {
477 let data = db.enum_data(enum_id);
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) {
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,
497 // Check the field names.
498 let mut enum_fields_replacements = Vec::new();
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,
508 enum_fields_replacements.push(replacement);
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(
516 enum_name_replacement,
517 enum_fields_replacements,
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(
526 db: &dyn HirDatabase,
527 enum_name_replacement: Option<Replacement>,
528 enum_variants_replacements: Vec<Replacement>,
530 // XXX: only look at sources if we do have incorrect names
531 if enum_name_replacement.is_none() && enum_variants_replacements.is_empty() {
535 let enum_loc = enum_id.lookup(db.upcast());
536 let enum_src = enum_loc.source(db.upcast());
538 if let Some(replacement) = enum_name_replacement {
539 let ast_ptr = match enum_src.value.name() {
542 // We don't want rust-analyzer to panic over this, but it is definitely some kind of error in the logic.
544 "Replacement ({:?}) was generated for a enum without a name: {:?}",
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,
561 self.sink.push(diagnostic);
564 let enum_variants_list = match enum_src.value.variant_list() {
565 Some(variants) => variants,
567 if !enum_variants_replacements.is_empty() {
569 "Replacements ({:?}) were generated for a enum variants which had no fields list: {:?}",
570 enum_variants_replacements, enum_src
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.
581 match enum_variants_iter.next() {
583 if names_equal(variant.name(), &variant_to_rename.current_name) =>
585 break variant.name().unwrap()
590 "Replacement ({:?}) was generated for a enum variant which was not found: {:?}",
591 variant_to_rename, enum_src
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,
607 self.sink.push(diagnostic);
611 fn validate_const(&mut self, db: &dyn HirDatabase, const_id: ConstId) {
612 let data = db.const_data(const_id);
614 if self.allowed(db, const_id.into(), allow::NON_UPPER_CASE_GLOBAL) {
618 let name = match &data.name {
623 let const_name = name.to_string();
624 let replacement = if let Some(new_name) = to_upper_snake_case(&const_name) {
626 current_name: name.clone(),
627 suggested_text: new_name,
628 expected_case: CaseType::UpperSnakeCase,
631 // Nothing to do here.
635 let const_loc = const_id.lookup(db.upcast());
636 let const_src = const_loc.source(db.upcast());
638 let ast_ptr = match const_src.value.name() {
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,
652 self.sink.push(diagnostic);
655 fn validate_static(&mut self, db: &dyn HirDatabase, static_id: StaticId) {
656 let data = db.static_data(static_id);
658 mark::hit!(extern_static_incorrect_case_ignored);
662 if self.allowed(db, static_id.into(), allow::NON_UPPER_CASE_GLOBAL) {
666 let name = match &data.name {
671 let static_name = name.to_string();
672 let replacement = if let Some(new_name) = to_upper_snake_case(&static_name) {
674 current_name: name.clone(),
675 suggested_text: new_name,
676 expected_case: CaseType::UpperSnakeCase,
679 // Nothing to do here.
683 let static_loc = static_id.lookup(db.upcast());
684 let static_src = static_loc.source(db.upcast());
686 let ast_ptr = match static_src.value.name() {
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,
700 self.sink.push(diagnostic);
704 fn names_equal(left: Option<ast::Name>, right: &Name) -> bool {
705 if let Some(left) = left {
706 &left.as_name() == right
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()
722 use test_utils::mark;
724 use crate::diagnostics::tests::check_diagnostics;
727 fn incorrect_function_name() {
730 fn NonSnakeCaseName() {}
731 // ^^^^^^^^^^^^^^^^ Function `NonSnakeCaseName` should have snake_case name, e.g. `non_snake_case_name`
737 fn incorrect_function_params() {
740 fn foo(SomeParam: u8) {}
741 // ^^^^^^^^^ Argument `SomeParam` should have snake_case name, e.g. `some_param`
743 fn foo2(ok_param: &str, CAPS_PARAM: u8) {}
744 // ^^^^^^^^^^ Argument `CAPS_PARAM` should have snake_case name, e.g. `caps_param`
750 fn incorrect_variable_names() {
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`
764 fn incorrect_struct_names() {
767 struct non_camel_case_name {}
768 // ^^^^^^^^^^^^^^^^^^^ Structure `non_camel_case_name` should have CamelCase name, e.g. `NonCamelCaseName`
770 struct SCREAMING_CASE {}
771 // ^^^^^^^^^^^^^^ Structure `SCREAMING_CASE` should have CamelCase name, e.g. `ScreamingCase`
777 fn no_diagnostic_for_camel_cased_acronyms_in_struct_name() {
786 fn incorrect_struct_field() {
789 struct SomeStruct { SomeField: u8 }
790 // ^^^^^^^^^ Field `SomeField` should have snake_case name, e.g. `some_field`
796 fn incorrect_enum_names() {
799 enum some_enum { Val(u8) }
800 // ^^^^^^^^^ Enum `some_enum` should have CamelCase name, e.g. `SomeEnum`
803 // ^^^^^^^^^ Enum `SOME_ENUM` should have CamelCase name, e.g. `SomeEnum`
809 fn no_diagnostic_for_camel_cased_acronyms_in_enum_name() {
818 fn incorrect_enum_variant_name() {
821 enum SomeEnum { SOME_VARIANT(u8) }
822 // ^^^^^^^^^^^^ Variant `SOME_VARIANT` should have CamelCase name, e.g. `SomeVariant`
828 fn incorrect_const_name() {
831 const some_weird_const: u8 = 10;
832 // ^^^^^^^^^^^^^^^^ Constant `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST`
835 const someConstInFunc: &str = "hi there";
836 // ^^^^^^^^^^^^^^^ Constant `someConstInFunc` should have UPPER_SNAKE_CASE name, e.g. `SOME_CONST_IN_FUNC`
844 fn incorrect_static_name() {
847 static some_weird_const: u8 = 10;
848 // ^^^^^^^^^^^^^^^^ Static variable `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST`
851 static someConstInFunc: &str = "hi there";
852 // ^^^^^^^^^^^^^^^ Static variable `someConstInFunc` should have UPPER_SNAKE_CASE name, e.g. `SOME_CONST_IN_FUNC`
859 fn fn_inside_impl_struct() {
863 // ^^^^^^^^^^ Structure `someStruct` should have CamelCase name, e.g. `SomeStruct`
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`
879 fn no_diagnostic_for_enum_varinats() {
882 enum Option { Some, None }
898 enum Option { Some, None }
902 SOME_VAR @ None => (),
903 // ^^^^^^^^ Variable `SOME_VAR` should have snake_case name, e.g. `some_var`
912 fn allow_attributes() {
915 #[allow(non_snake_case)]
916 fn NonSnakeCaseName(SOME_VAR: u8) -> u8{
917 let OtherVar = SOME_VAR + 1;
921 #[allow(non_snake_case, non_camel_case_types)]
922 pub struct some_type {
927 #[allow(non_upper_case_globals)]
928 pub const some_const: u8 = 10;
930 #[allow(non_upper_case_globals)]
931 pub static SomeStatic: u8 = 10;
937 fn ignores_extern_items() {
938 mark::check!(extern_func_incorrect_case_ignored);
939 mark::check!(extern_static_incorrect_case_ignored);
943 fn NonSnakeCaseName(SOME_VAR: u8) -> u8;
944 pub static SomeStatic: u8 = 10;