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,
26 use stdx::{always, never};
28 ast::{self, NameOwner},
34 diagnostics::{decl_check::case_conv::*, CaseType, IdentType, 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> {
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 // FIXME: 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 cov_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.
103 for block_def_map in body.block_scopes.iter().filter_map(|block| db.block_def_map(*block)) {
104 for (_, module) in block_def_map.modules() {
105 for (def_id, _) in module.scope.values() {
106 let mut validator = DeclValidator::new(self.db, self.krate, self.sink);
107 validator.validate_item(def_id);
112 // Check whether non-snake case identifiers are allowed for this function.
113 if self.allowed(func.into(), allow::NON_SNAKE_CASE) {
117 // Check the function name.
118 let function_name = data.name.to_string();
119 let fn_name_replacement = to_lower_snake_case(&function_name).map(|new_name| Replacement {
120 current_name: data.name.clone(),
121 suggested_text: new_name,
122 expected_case: CaseType::LowerSnakeCase,
125 // Check the param names.
126 let fn_param_replacements = body
129 .filter_map(|&id| match &body[id] {
130 Pat::Bind { name, .. } => Some(name),
133 .filter_map(|param_name| {
135 current_name: param_name.clone(),
136 suggested_text: to_lower_snake_case(¶m_name.to_string())?,
137 expected_case: CaseType::LowerSnakeCase,
142 // Check the patterns inside the function body.
143 let pats_replacements = body
146 // We aren't interested in function parameters, we've processed them above.
147 .filter(|(pat_idx, _)| !body.params.contains(&pat_idx))
148 .filter_map(|(id, pat)| match pat {
149 Pat::Bind { name, .. } => Some((id, name)),
152 .filter_map(|(id, bind_name)| {
156 current_name: bind_name.clone(),
157 suggested_text: to_lower_snake_case(&bind_name.to_string())?,
158 expected_case: CaseType::LowerSnakeCase,
164 // If there is at least one element to spawn a warning on, go to the source map and generate a warning.
165 self.create_incorrect_case_diagnostic_for_func(
168 fn_param_replacements,
170 self.create_incorrect_case_diagnostic_for_variables(func, pats_replacements);
173 /// Given the information about incorrect names in the function declaration, looks up into the source code
174 /// for exact locations and adds diagnostics into the sink.
175 fn create_incorrect_case_diagnostic_for_func(
178 fn_name_replacement: Option<Replacement>,
179 fn_param_replacements: Vec<Replacement>,
181 // XXX: only look at sources if we do have incorrect names
182 if fn_name_replacement.is_none() && fn_param_replacements.is_empty() {
186 let fn_loc = func.lookup(self.db.upcast());
187 let fn_src = fn_loc.source(self.db.upcast());
189 // Diagnostic for function name.
190 if let Some(replacement) = fn_name_replacement {
191 let ast_ptr = match fn_src.value.name() {
195 "Replacement ({:?}) was generated for a function without a name: {:?}",
203 let diagnostic = IncorrectCase {
204 file: fn_src.file_id,
205 ident_type: IdentType::Function,
206 ident: AstPtr::new(&ast_ptr).into(),
207 expected_case: replacement.expected_case,
208 ident_text: replacement.current_name.to_string(),
209 suggested_text: replacement.suggested_text,
212 self.sink.push(diagnostic);
215 // Diagnostics for function params.
216 let fn_params_list = match fn_src.value.param_list() {
217 Some(params) => params,
220 fn_param_replacements.is_empty(),
221 "Replacements ({:?}) were generated for a function parameters which had no parameters list: {:?}",
222 fn_param_replacements,
228 let mut fn_params_iter = fn_params_list.params();
229 for param_to_rename in fn_param_replacements {
230 // We assume that parameters in replacement are in the same order as in the
231 // actual params list, but just some of them (ones that named correctly) are skipped.
232 let ast_ptr: ast::Name = loop {
233 match fn_params_iter.next() {
235 if let Some(ast::Pat::IdentPat(pat)) = element.pat() {
236 if pat.to_string() == param_to_rename.current_name.to_string() {
237 if let Some(name) = pat.name() {
240 // This is critical. If we consider this parameter the expected one,
241 // it **must** have a name.
243 "Pattern {:?} equals to expected replacement {:?}, but has no name",
253 "Replacement ({:?}) was generated for a function parameter which was not found: {:?}",
254 param_to_rename, fn_src
261 let diagnostic = IncorrectCase {
262 file: fn_src.file_id,
263 ident_type: IdentType::Argument,
264 ident: AstPtr::new(&ast_ptr).into(),
265 expected_case: param_to_rename.expected_case,
266 ident_text: param_to_rename.current_name.to_string(),
267 suggested_text: param_to_rename.suggested_text,
270 self.sink.push(diagnostic);
274 /// Given the information about incorrect variable names, looks up into the source code
275 /// for exact locations and adds diagnostics into the sink.
276 fn create_incorrect_case_diagnostic_for_variables(
279 pats_replacements: Vec<(PatId, Replacement)>,
281 // XXX: only look at source_map if we do have missing fields
282 if pats_replacements.is_empty() {
286 let (_, source_map) = self.db.body_with_source_map(func.into());
288 for (id, replacement) in pats_replacements {
289 if let Ok(source_ptr) = source_map.pat_syntax(id) {
290 if let Some(expr) = source_ptr.value.as_ref().left() {
291 let root = source_ptr.file_syntax(self.db.upcast());
292 if let ast::Pat::IdentPat(ident_pat) = expr.to_node(&root) {
293 let parent = match ident_pat.syntax().parent() {
294 Some(parent) => parent,
297 let name_ast = match ident_pat.name() {
298 Some(name_ast) => name_ast,
302 // We have to check that it's either `let var = ...` or `var @ Variant(_)` statement,
303 // because e.g. match arms are patterns as well.
304 // In other words, we check that it's a named variable binding.
305 let is_binding = ast::LetStmt::can_cast(parent.kind())
306 || (ast::MatchArm::can_cast(parent.kind())
307 && ident_pat.at_token().is_some());
309 // This pattern is not an actual variable declaration, e.g. `Some(val) => {..}` match arm.
313 let diagnostic = IncorrectCase {
314 file: source_ptr.file_id,
315 ident_type: IdentType::Variable,
316 ident: AstPtr::new(&name_ast).into(),
317 expected_case: replacement.expected_case,
318 ident_text: replacement.current_name.to_string(),
319 suggested_text: replacement.suggested_text,
322 self.sink.push(diagnostic);
329 fn validate_struct(&mut self, struct_id: StructId) {
330 let data = self.db.struct_data(struct_id);
332 let non_camel_case_allowed = self.allowed(struct_id.into(), allow::NON_CAMEL_CASE_TYPES);
333 let non_snake_case_allowed = self.allowed(struct_id.into(), allow::NON_SNAKE_CASE);
335 // Check the structure name.
336 let struct_name = data.name.to_string();
337 let struct_name_replacement = if !non_camel_case_allowed {
338 to_camel_case(&struct_name).map(|new_name| Replacement {
339 current_name: data.name.clone(),
340 suggested_text: new_name,
341 expected_case: CaseType::UpperCamelCase,
347 // Check the field names.
348 let mut struct_fields_replacements = Vec::new();
350 if !non_snake_case_allowed {
351 if let VariantData::Record(fields) = data.variant_data.as_ref() {
352 for (_, field) in fields.iter() {
353 let field_name = field.name.to_string();
354 if let Some(new_name) = to_lower_snake_case(&field_name) {
355 let replacement = Replacement {
356 current_name: field.name.clone(),
357 suggested_text: new_name,
358 expected_case: CaseType::LowerSnakeCase,
360 struct_fields_replacements.push(replacement);
366 // If there is at least one element to spawn a warning on, go to the source map and generate a warning.
367 self.create_incorrect_case_diagnostic_for_struct(
369 struct_name_replacement,
370 struct_fields_replacements,
374 /// Given the information about incorrect names in the struct declaration, looks up into the source code
375 /// for exact locations and adds diagnostics into the sink.
376 fn create_incorrect_case_diagnostic_for_struct(
379 struct_name_replacement: Option<Replacement>,
380 struct_fields_replacements: Vec<Replacement>,
382 // XXX: only look at sources if we do have incorrect names
383 if struct_name_replacement.is_none() && struct_fields_replacements.is_empty() {
387 let struct_loc = struct_id.lookup(self.db.upcast());
388 let struct_src = struct_loc.source(self.db.upcast());
390 if let Some(replacement) = struct_name_replacement {
391 let ast_ptr = match struct_src.value.name() {
395 "Replacement ({:?}) was generated for a structure without a name: {:?}",
403 let diagnostic = IncorrectCase {
404 file: struct_src.file_id,
405 ident_type: IdentType::Structure,
406 ident: AstPtr::new(&ast_ptr).into(),
407 expected_case: replacement.expected_case,
408 ident_text: replacement.current_name.to_string(),
409 suggested_text: replacement.suggested_text,
412 self.sink.push(diagnostic);
415 let struct_fields_list = match struct_src.value.field_list() {
416 Some(ast::FieldList::RecordFieldList(fields)) => fields,
419 struct_fields_replacements.is_empty(),
420 "Replacements ({:?}) were generated for a structure fields which had no fields list: {:?}",
421 struct_fields_replacements,
427 let mut struct_fields_iter = struct_fields_list.fields();
428 for field_to_rename in struct_fields_replacements {
429 // We assume that parameters in replacement are in the same order as in the
430 // actual params list, but just some of them (ones that named correctly) are skipped.
432 match struct_fields_iter.next().and_then(|field| field.name()) {
433 Some(field_name) => {
434 if field_name.as_name() == field_to_rename.current_name {
440 "Replacement ({:?}) was generated for a structure field which was not found: {:?}",
441 field_to_rename, struct_src
448 let diagnostic = IncorrectCase {
449 file: struct_src.file_id,
450 ident_type: IdentType::Field,
451 ident: AstPtr::new(&ast_ptr).into(),
452 expected_case: field_to_rename.expected_case,
453 ident_text: field_to_rename.current_name.to_string(),
454 suggested_text: field_to_rename.suggested_text,
457 self.sink.push(diagnostic);
461 fn validate_enum(&mut self, enum_id: EnumId) {
462 let data = self.db.enum_data(enum_id);
464 // Check whether non-camel case names are allowed for this enum.
465 if self.allowed(enum_id.into(), allow::NON_CAMEL_CASE_TYPES) {
469 // Check the enum name.
470 let enum_name = data.name.to_string();
471 let enum_name_replacement = to_camel_case(&enum_name).map(|new_name| Replacement {
472 current_name: data.name.clone(),
473 suggested_text: new_name,
474 expected_case: CaseType::UpperCamelCase,
477 // Check the field names.
478 let enum_fields_replacements = data
481 .filter_map(|(_, variant)| {
483 current_name: variant.name.clone(),
484 suggested_text: to_camel_case(&variant.name.to_string())?,
485 expected_case: CaseType::UpperCamelCase,
490 // If there is at least one element to spawn a warning on, go to the source map and generate a warning.
491 self.create_incorrect_case_diagnostic_for_enum(
493 enum_name_replacement,
494 enum_fields_replacements,
498 /// Given the information about incorrect names in the struct declaration, looks up into the source code
499 /// for exact locations and adds diagnostics into the sink.
500 fn create_incorrect_case_diagnostic_for_enum(
503 enum_name_replacement: Option<Replacement>,
504 enum_variants_replacements: Vec<Replacement>,
506 // XXX: only look at sources if we do have incorrect names
507 if enum_name_replacement.is_none() && enum_variants_replacements.is_empty() {
511 let enum_loc = enum_id.lookup(self.db.upcast());
512 let enum_src = enum_loc.source(self.db.upcast());
514 if let Some(replacement) = enum_name_replacement {
515 let ast_ptr = match enum_src.value.name() {
519 "Replacement ({:?}) was generated for a enum without a name: {:?}",
527 let diagnostic = IncorrectCase {
528 file: enum_src.file_id,
529 ident_type: IdentType::Enum,
530 ident: AstPtr::new(&ast_ptr).into(),
531 expected_case: replacement.expected_case,
532 ident_text: replacement.current_name.to_string(),
533 suggested_text: replacement.suggested_text,
536 self.sink.push(diagnostic);
539 let enum_variants_list = match enum_src.value.variant_list() {
540 Some(variants) => variants,
543 enum_variants_replacements.is_empty(),
544 "Replacements ({:?}) were generated for a enum variants which had no fields list: {:?}",
545 enum_variants_replacements,
551 let mut enum_variants_iter = enum_variants_list.variants();
552 for variant_to_rename in enum_variants_replacements {
553 // We assume that parameters in replacement are in the same order as in the
554 // actual params list, but just some of them (ones that named correctly) are skipped.
556 match enum_variants_iter.next().and_then(|v| v.name()) {
557 Some(variant_name) => {
558 if variant_name.as_name() == variant_to_rename.current_name {
564 "Replacement ({:?}) was generated for a enum variant which was not found: {:?}",
565 variant_to_rename, enum_src
572 let diagnostic = IncorrectCase {
573 file: enum_src.file_id,
574 ident_type: IdentType::Variant,
575 ident: AstPtr::new(&ast_ptr).into(),
576 expected_case: variant_to_rename.expected_case,
577 ident_text: variant_to_rename.current_name.to_string(),
578 suggested_text: variant_to_rename.suggested_text,
581 self.sink.push(diagnostic);
585 fn validate_const(&mut self, const_id: ConstId) {
586 let data = self.db.const_data(const_id);
588 if self.allowed(const_id.into(), allow::NON_UPPER_CASE_GLOBAL) {
592 let name = match &data.name {
597 let const_name = name.to_string();
598 let replacement = if let Some(new_name) = to_upper_snake_case(&const_name) {
600 current_name: name.clone(),
601 suggested_text: new_name,
602 expected_case: CaseType::UpperSnakeCase,
605 // Nothing to do here.
609 let const_loc = const_id.lookup(self.db.upcast());
610 let const_src = const_loc.source(self.db.upcast());
612 let ast_ptr = match const_src.value.name() {
617 let diagnostic = IncorrectCase {
618 file: const_src.file_id,
619 ident_type: IdentType::Constant,
620 ident: AstPtr::new(&ast_ptr).into(),
621 expected_case: replacement.expected_case,
622 ident_text: replacement.current_name.to_string(),
623 suggested_text: replacement.suggested_text,
626 self.sink.push(diagnostic);
629 fn validate_static(&mut self, static_id: StaticId) {
630 let data = self.db.static_data(static_id);
632 cov_mark::hit!(extern_static_incorrect_case_ignored);
636 if self.allowed(static_id.into(), allow::NON_UPPER_CASE_GLOBAL) {
640 let name = match &data.name {
645 let static_name = name.to_string();
646 let replacement = if let Some(new_name) = to_upper_snake_case(&static_name) {
648 current_name: name.clone(),
649 suggested_text: new_name,
650 expected_case: CaseType::UpperSnakeCase,
653 // Nothing to do here.
657 let static_loc = static_id.lookup(self.db.upcast());
658 let static_src = static_loc.source(self.db.upcast());
660 let ast_ptr = match static_src.value.name() {
665 let diagnostic = IncorrectCase {
666 file: static_src.file_id,
667 ident_type: IdentType::StaticVariable,
668 ident: AstPtr::new(&ast_ptr).into(),
669 expected_case: replacement.expected_case,
670 ident_text: replacement.current_name.to_string(),
671 suggested_text: replacement.suggested_text,
674 self.sink.push(diagnostic);
680 use crate::diagnostics::tests::check_diagnostics;
683 fn incorrect_function_name() {
686 fn NonSnakeCaseName() {}
687 // ^^^^^^^^^^^^^^^^ Function `NonSnakeCaseName` should have snake_case name, e.g. `non_snake_case_name`
693 fn incorrect_function_params() {
696 fn foo(SomeParam: u8) {}
697 // ^^^^^^^^^ Argument `SomeParam` should have snake_case name, e.g. `some_param`
699 fn foo2(ok_param: &str, CAPS_PARAM: u8) {}
700 // ^^^^^^^^^^ Argument `CAPS_PARAM` should have snake_case name, e.g. `caps_param`
706 fn incorrect_variable_names() {
711 // ^^^^^^^^^^ Variable `SOME_VALUE` should have snake_case name, e.g. `some_value`
712 let AnotherValue = 20;
713 // ^^^^^^^^^^^^ Variable `AnotherValue` should have snake_case name, e.g. `another_value`
720 fn incorrect_struct_names() {
723 struct non_camel_case_name {}
724 // ^^^^^^^^^^^^^^^^^^^ Structure `non_camel_case_name` should have CamelCase name, e.g. `NonCamelCaseName`
726 struct SCREAMING_CASE {}
727 // ^^^^^^^^^^^^^^ Structure `SCREAMING_CASE` should have CamelCase name, e.g. `ScreamingCase`
733 fn no_diagnostic_for_camel_cased_acronyms_in_struct_name() {
742 fn incorrect_struct_field() {
745 struct SomeStruct { SomeField: u8 }
746 // ^^^^^^^^^ Field `SomeField` should have snake_case name, e.g. `some_field`
752 fn incorrect_enum_names() {
755 enum some_enum { Val(u8) }
756 // ^^^^^^^^^ Enum `some_enum` should have CamelCase name, e.g. `SomeEnum`
759 // ^^^^^^^^^ Enum `SOME_ENUM` should have CamelCase name, e.g. `SomeEnum`
765 fn no_diagnostic_for_camel_cased_acronyms_in_enum_name() {
774 fn incorrect_enum_variant_name() {
777 enum SomeEnum { SOME_VARIANT(u8) }
778 // ^^^^^^^^^^^^ Variant `SOME_VARIANT` should have CamelCase name, e.g. `SomeVariant`
784 fn incorrect_const_name() {
787 const some_weird_const: u8 = 10;
788 // ^^^^^^^^^^^^^^^^ Constant `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST`
791 const someConstInFunc: &str = "hi there";
792 // ^^^^^^^^^^^^^^^ Constant `someConstInFunc` should have UPPER_SNAKE_CASE name, e.g. `SOME_CONST_IN_FUNC`
800 fn incorrect_static_name() {
803 static some_weird_const: u8 = 10;
804 // ^^^^^^^^^^^^^^^^ Static variable `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST`
807 static someConstInFunc: &str = "hi there";
808 // ^^^^^^^^^^^^^^^ Static variable `someConstInFunc` should have UPPER_SNAKE_CASE name, e.g. `SOME_CONST_IN_FUNC`
815 fn fn_inside_impl_struct() {
819 // ^^^^^^^^^^ Structure `someStruct` should have CamelCase name, e.g. `SomeStruct`
823 // ^^^^^^^^ Function `SomeFunc` should have snake_case name, e.g. `some_func`
824 static someConstInFunc: &str = "hi there";
825 // ^^^^^^^^^^^^^^^ Static variable `someConstInFunc` should have UPPER_SNAKE_CASE name, e.g. `SOME_CONST_IN_FUNC`
826 let WHY_VAR_IS_CAPS = 10;
827 // ^^^^^^^^^^^^^^^ Variable `WHY_VAR_IS_CAPS` should have snake_case name, e.g. `why_var_is_caps`
835 fn no_diagnostic_for_enum_varinats() {
838 enum Option { Some, None }
854 enum Option { Some, None }
858 SOME_VAR @ None => (),
859 // ^^^^^^^^ Variable `SOME_VAR` should have snake_case name, e.g. `some_var`
868 fn allow_attributes() {
871 #[allow(non_snake_case)]
872 fn NonSnakeCaseName(SOME_VAR: u8) -> u8{
873 let OtherVar = SOME_VAR + 1;
877 #[allow(non_snake_case, non_camel_case_types)]
878 pub struct some_type {
883 #[allow(non_upper_case_globals)]
884 pub const some_const: u8 = 10;
886 #[allow(non_upper_case_globals)]
887 pub static SomeStatic: u8 = 10;
893 fn ignores_extern_items() {
894 cov_mark::check!(extern_func_incorrect_case_ignored);
895 cov_mark::check!(extern_static_incorrect_case_ignored);
899 fn NonSnakeCaseName(SOME_VAR: u8) -> u8;
900 pub static SomeStatic: u8 = 10;