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);
94 if data.is_in_extern_block() {
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.
102 for (_, block_def_map) in body.blocks(self.db.upcast()) {
103 for (_, module) in block_def_map.modules() {
104 for def_id in module.scope.declarations() {
105 let mut validator = DeclValidator::new(self.db, self.krate, self.sink);
106 validator.validate_item(def_id);
111 // Check whether non-snake case identifiers are allowed for this function.
112 if self.allowed(func.into(), allow::NON_SNAKE_CASE) {
116 // Check the function name.
117 let function_name = data.name.to_string();
118 let fn_name_replacement = to_lower_snake_case(&function_name).map(|new_name| Replacement {
119 current_name: data.name.clone(),
120 suggested_text: new_name,
121 expected_case: CaseType::LowerSnakeCase,
124 // Check the param names.
125 let fn_param_replacements = body
128 .filter_map(|&id| match &body[id] {
129 Pat::Bind { name, .. } => Some(name),
132 .filter_map(|param_name| {
134 current_name: param_name.clone(),
135 suggested_text: to_lower_snake_case(¶m_name.to_string())?,
136 expected_case: CaseType::LowerSnakeCase,
141 // Check the patterns inside the function body.
142 let pats_replacements = body
145 // We aren't interested in function parameters, we've processed them above.
146 .filter(|(pat_idx, _)| !body.params.contains(&pat_idx))
147 .filter_map(|(id, pat)| match pat {
148 Pat::Bind { name, .. } => Some((id, name)),
151 .filter_map(|(id, bind_name)| {
155 current_name: bind_name.clone(),
156 suggested_text: to_lower_snake_case(&bind_name.to_string())?,
157 expected_case: CaseType::LowerSnakeCase,
163 // If there is at least one element to spawn a warning on, go to the source map and generate a warning.
164 self.create_incorrect_case_diagnostic_for_func(
167 fn_param_replacements,
169 self.create_incorrect_case_diagnostic_for_variables(func, pats_replacements);
172 /// Given the information about incorrect names in the function declaration, looks up into the source code
173 /// for exact locations and adds diagnostics into the sink.
174 fn create_incorrect_case_diagnostic_for_func(
177 fn_name_replacement: Option<Replacement>,
178 fn_param_replacements: Vec<Replacement>,
180 // XXX: only look at sources if we do have incorrect names
181 if fn_name_replacement.is_none() && fn_param_replacements.is_empty() {
185 let fn_loc = func.lookup(self.db.upcast());
186 let fn_src = fn_loc.source(self.db.upcast());
188 // Diagnostic for function name.
189 if let Some(replacement) = fn_name_replacement {
190 let ast_ptr = match fn_src.value.name() {
194 "Replacement ({:?}) was generated for a function without a name: {:?}",
202 let diagnostic = IncorrectCase {
203 file: fn_src.file_id,
204 ident_type: IdentType::Function,
205 ident: AstPtr::new(&ast_ptr),
206 expected_case: replacement.expected_case,
207 ident_text: replacement.current_name.to_string(),
208 suggested_text: replacement.suggested_text,
211 self.sink.push(diagnostic);
214 // Diagnostics for function params.
215 let fn_params_list = match fn_src.value.param_list() {
216 Some(params) => params,
219 fn_param_replacements.is_empty(),
220 "Replacements ({:?}) were generated for a function parameters which had no parameters list: {:?}",
221 fn_param_replacements,
227 let mut fn_params_iter = fn_params_list.params();
228 for param_to_rename in fn_param_replacements {
229 // We assume that parameters in replacement are in the same order as in the
230 // actual params list, but just some of them (ones that named correctly) are skipped.
231 let ast_ptr: ast::Name = loop {
232 match fn_params_iter.next() {
234 if let Some(ast::Pat::IdentPat(pat)) = element.pat() {
235 if pat.to_string() == param_to_rename.current_name.to_string() {
236 if let Some(name) = pat.name() {
239 // This is critical. If we consider this parameter the expected one,
240 // it **must** have a name.
242 "Pattern {:?} equals to expected replacement {:?}, but has no name",
252 "Replacement ({:?}) was generated for a function parameter which was not found: {:?}",
253 param_to_rename, fn_src
260 let diagnostic = IncorrectCase {
261 file: fn_src.file_id,
262 ident_type: IdentType::Argument,
263 ident: AstPtr::new(&ast_ptr),
264 expected_case: param_to_rename.expected_case,
265 ident_text: param_to_rename.current_name.to_string(),
266 suggested_text: param_to_rename.suggested_text,
269 self.sink.push(diagnostic);
273 /// Given the information about incorrect variable names, looks up into the source code
274 /// for exact locations and adds diagnostics into the sink.
275 fn create_incorrect_case_diagnostic_for_variables(
278 pats_replacements: Vec<(PatId, Replacement)>,
280 // XXX: only look at source_map if we do have missing fields
281 if pats_replacements.is_empty() {
285 let (_, source_map) = self.db.body_with_source_map(func.into());
287 for (id, replacement) in pats_replacements {
288 if let Ok(source_ptr) = source_map.pat_syntax(id) {
289 if let Some(expr) = source_ptr.value.as_ref().left() {
290 let root = source_ptr.file_syntax(self.db.upcast());
291 if let ast::Pat::IdentPat(ident_pat) = expr.to_node(&root) {
292 let parent = match ident_pat.syntax().parent() {
293 Some(parent) => parent,
296 let name_ast = match ident_pat.name() {
297 Some(name_ast) => name_ast,
301 // We have to check that it's either `let var = ...` or `var @ Variant(_)` statement,
302 // because e.g. match arms are patterns as well.
303 // In other words, we check that it's a named variable binding.
304 let is_binding = ast::LetStmt::can_cast(parent.kind())
305 || (ast::MatchArm::can_cast(parent.kind())
306 && ident_pat.at_token().is_some());
308 // This pattern is not an actual variable declaration, e.g. `Some(val) => {..}` match arm.
312 let diagnostic = IncorrectCase {
313 file: source_ptr.file_id,
314 ident_type: IdentType::Variable,
315 ident: AstPtr::new(&name_ast),
316 expected_case: replacement.expected_case,
317 ident_text: replacement.current_name.to_string(),
318 suggested_text: replacement.suggested_text,
321 self.sink.push(diagnostic);
328 fn validate_struct(&mut self, struct_id: StructId) {
329 let data = self.db.struct_data(struct_id);
331 let non_camel_case_allowed = self.allowed(struct_id.into(), allow::NON_CAMEL_CASE_TYPES);
332 let non_snake_case_allowed = self.allowed(struct_id.into(), allow::NON_SNAKE_CASE);
334 // Check the structure name.
335 let struct_name = data.name.to_string();
336 let struct_name_replacement = if !non_camel_case_allowed {
337 to_camel_case(&struct_name).map(|new_name| Replacement {
338 current_name: data.name.clone(),
339 suggested_text: new_name,
340 expected_case: CaseType::UpperCamelCase,
346 // Check the field names.
347 let mut struct_fields_replacements = Vec::new();
349 if !non_snake_case_allowed {
350 if let VariantData::Record(fields) = data.variant_data.as_ref() {
351 for (_, field) in fields.iter() {
352 let field_name = field.name.to_string();
353 if let Some(new_name) = to_lower_snake_case(&field_name) {
354 let replacement = Replacement {
355 current_name: field.name.clone(),
356 suggested_text: new_name,
357 expected_case: CaseType::LowerSnakeCase,
359 struct_fields_replacements.push(replacement);
365 // If there is at least one element to spawn a warning on, go to the source map and generate a warning.
366 self.create_incorrect_case_diagnostic_for_struct(
368 struct_name_replacement,
369 struct_fields_replacements,
373 /// Given the information about incorrect names in the struct declaration, looks up into the source code
374 /// for exact locations and adds diagnostics into the sink.
375 fn create_incorrect_case_diagnostic_for_struct(
378 struct_name_replacement: Option<Replacement>,
379 struct_fields_replacements: Vec<Replacement>,
381 // XXX: only look at sources if we do have incorrect names
382 if struct_name_replacement.is_none() && struct_fields_replacements.is_empty() {
386 let struct_loc = struct_id.lookup(self.db.upcast());
387 let struct_src = struct_loc.source(self.db.upcast());
389 if let Some(replacement) = struct_name_replacement {
390 let ast_ptr = match struct_src.value.name() {
394 "Replacement ({:?}) was generated for a structure without a name: {:?}",
402 let diagnostic = IncorrectCase {
403 file: struct_src.file_id,
404 ident_type: IdentType::Structure,
405 ident: AstPtr::new(&ast_ptr),
406 expected_case: replacement.expected_case,
407 ident_text: replacement.current_name.to_string(),
408 suggested_text: replacement.suggested_text,
411 self.sink.push(diagnostic);
414 let struct_fields_list = match struct_src.value.field_list() {
415 Some(ast::FieldList::RecordFieldList(fields)) => fields,
418 struct_fields_replacements.is_empty(),
419 "Replacements ({:?}) were generated for a structure fields which had no fields list: {:?}",
420 struct_fields_replacements,
426 let mut struct_fields_iter = struct_fields_list.fields();
427 for field_to_rename in struct_fields_replacements {
428 // We assume that parameters in replacement are in the same order as in the
429 // actual params list, but just some of them (ones that named correctly) are skipped.
431 match struct_fields_iter.next().and_then(|field| field.name()) {
432 Some(field_name) => {
433 if field_name.as_name() == field_to_rename.current_name {
439 "Replacement ({:?}) was generated for a structure field which was not found: {:?}",
440 field_to_rename, struct_src
447 let diagnostic = IncorrectCase {
448 file: struct_src.file_id,
449 ident_type: IdentType::Field,
450 ident: AstPtr::new(&ast_ptr),
451 expected_case: field_to_rename.expected_case,
452 ident_text: field_to_rename.current_name.to_string(),
453 suggested_text: field_to_rename.suggested_text,
456 self.sink.push(diagnostic);
460 fn validate_enum(&mut self, enum_id: EnumId) {
461 let data = self.db.enum_data(enum_id);
463 // Check whether non-camel case names are allowed for this enum.
464 if self.allowed(enum_id.into(), allow::NON_CAMEL_CASE_TYPES) {
468 // Check the enum name.
469 let enum_name = data.name.to_string();
470 let enum_name_replacement = to_camel_case(&enum_name).map(|new_name| Replacement {
471 current_name: data.name.clone(),
472 suggested_text: new_name,
473 expected_case: CaseType::UpperCamelCase,
476 // Check the field names.
477 let enum_fields_replacements = data
480 .filter_map(|(_, variant)| {
482 current_name: variant.name.clone(),
483 suggested_text: to_camel_case(&variant.name.to_string())?,
484 expected_case: CaseType::UpperCamelCase,
489 // If there is at least one element to spawn a warning on, go to the source map and generate a warning.
490 self.create_incorrect_case_diagnostic_for_enum(
492 enum_name_replacement,
493 enum_fields_replacements,
497 /// Given the information about incorrect names in the struct declaration, looks up into the source code
498 /// for exact locations and adds diagnostics into the sink.
499 fn create_incorrect_case_diagnostic_for_enum(
502 enum_name_replacement: Option<Replacement>,
503 enum_variants_replacements: Vec<Replacement>,
505 // XXX: only look at sources if we do have incorrect names
506 if enum_name_replacement.is_none() && enum_variants_replacements.is_empty() {
510 let enum_loc = enum_id.lookup(self.db.upcast());
511 let enum_src = enum_loc.source(self.db.upcast());
513 if let Some(replacement) = enum_name_replacement {
514 let ast_ptr = match enum_src.value.name() {
518 "Replacement ({:?}) was generated for a enum without a name: {:?}",
526 let diagnostic = IncorrectCase {
527 file: enum_src.file_id,
528 ident_type: IdentType::Enum,
529 ident: AstPtr::new(&ast_ptr),
530 expected_case: replacement.expected_case,
531 ident_text: replacement.current_name.to_string(),
532 suggested_text: replacement.suggested_text,
535 self.sink.push(diagnostic);
538 let enum_variants_list = match enum_src.value.variant_list() {
539 Some(variants) => variants,
542 enum_variants_replacements.is_empty(),
543 "Replacements ({:?}) were generated for a enum variants which had no fields list: {:?}",
544 enum_variants_replacements,
550 let mut enum_variants_iter = enum_variants_list.variants();
551 for variant_to_rename in enum_variants_replacements {
552 // We assume that parameters in replacement are in the same order as in the
553 // actual params list, but just some of them (ones that named correctly) are skipped.
555 match enum_variants_iter.next().and_then(|v| v.name()) {
556 Some(variant_name) => {
557 if variant_name.as_name() == variant_to_rename.current_name {
563 "Replacement ({:?}) was generated for a enum variant which was not found: {:?}",
564 variant_to_rename, enum_src
571 let diagnostic = IncorrectCase {
572 file: enum_src.file_id,
573 ident_type: IdentType::Variant,
574 ident: AstPtr::new(&ast_ptr),
575 expected_case: variant_to_rename.expected_case,
576 ident_text: variant_to_rename.current_name.to_string(),
577 suggested_text: variant_to_rename.suggested_text,
580 self.sink.push(diagnostic);
584 fn validate_const(&mut self, const_id: ConstId) {
585 let data = self.db.const_data(const_id);
587 if self.allowed(const_id.into(), allow::NON_UPPER_CASE_GLOBAL) {
591 let name = match &data.name {
596 let const_name = name.to_string();
597 let replacement = if let Some(new_name) = to_upper_snake_case(&const_name) {
599 current_name: name.clone(),
600 suggested_text: new_name,
601 expected_case: CaseType::UpperSnakeCase,
604 // Nothing to do here.
608 let const_loc = const_id.lookup(self.db.upcast());
609 let const_src = const_loc.source(self.db.upcast());
611 let ast_ptr = match const_src.value.name() {
616 let diagnostic = IncorrectCase {
617 file: const_src.file_id,
618 ident_type: IdentType::Constant,
619 ident: AstPtr::new(&ast_ptr),
620 expected_case: replacement.expected_case,
621 ident_text: replacement.current_name.to_string(),
622 suggested_text: replacement.suggested_text,
625 self.sink.push(diagnostic);
628 fn validate_static(&mut self, static_id: StaticId) {
629 let data = self.db.static_data(static_id);
631 cov_mark::hit!(extern_static_incorrect_case_ignored);
635 if self.allowed(static_id.into(), allow::NON_UPPER_CASE_GLOBAL) {
639 let name = match &data.name {
644 let static_name = name.to_string();
645 let replacement = if let Some(new_name) = to_upper_snake_case(&static_name) {
647 current_name: name.clone(),
648 suggested_text: new_name,
649 expected_case: CaseType::UpperSnakeCase,
652 // Nothing to do here.
656 let static_loc = static_id.lookup(self.db.upcast());
657 let static_src = static_loc.source(self.db.upcast());
659 let ast_ptr = match static_src.value.name() {
664 let diagnostic = IncorrectCase {
665 file: static_src.file_id,
666 ident_type: IdentType::StaticVariable,
667 ident: AstPtr::new(&ast_ptr),
668 expected_case: replacement.expected_case,
669 ident_text: replacement.current_name.to_string(),
670 suggested_text: replacement.suggested_text,
673 self.sink.push(diagnostic);
679 use crate::diagnostics::tests::check_diagnostics;
682 fn incorrect_function_name() {
685 fn NonSnakeCaseName() {}
686 // ^^^^^^^^^^^^^^^^ Function `NonSnakeCaseName` should have snake_case name, e.g. `non_snake_case_name`
692 fn incorrect_function_params() {
695 fn foo(SomeParam: u8) {}
696 // ^^^^^^^^^ Argument `SomeParam` should have snake_case name, e.g. `some_param`
698 fn foo2(ok_param: &str, CAPS_PARAM: u8) {}
699 // ^^^^^^^^^^ Argument `CAPS_PARAM` should have snake_case name, e.g. `caps_param`
705 fn incorrect_variable_names() {
710 // ^^^^^^^^^^ Variable `SOME_VALUE` should have snake_case name, e.g. `some_value`
711 let AnotherValue = 20;
712 // ^^^^^^^^^^^^ Variable `AnotherValue` should have snake_case name, e.g. `another_value`
719 fn incorrect_struct_names() {
722 struct non_camel_case_name {}
723 // ^^^^^^^^^^^^^^^^^^^ Structure `non_camel_case_name` should have CamelCase name, e.g. `NonCamelCaseName`
725 struct SCREAMING_CASE {}
726 // ^^^^^^^^^^^^^^ Structure `SCREAMING_CASE` should have CamelCase name, e.g. `ScreamingCase`
732 fn no_diagnostic_for_camel_cased_acronyms_in_struct_name() {
741 fn incorrect_struct_field() {
744 struct SomeStruct { SomeField: u8 }
745 // ^^^^^^^^^ Field `SomeField` should have snake_case name, e.g. `some_field`
751 fn incorrect_enum_names() {
754 enum some_enum { Val(u8) }
755 // ^^^^^^^^^ Enum `some_enum` should have CamelCase name, e.g. `SomeEnum`
758 // ^^^^^^^^^ Enum `SOME_ENUM` should have CamelCase name, e.g. `SomeEnum`
764 fn no_diagnostic_for_camel_cased_acronyms_in_enum_name() {
773 fn incorrect_enum_variant_name() {
776 enum SomeEnum { SOME_VARIANT(u8) }
777 // ^^^^^^^^^^^^ Variant `SOME_VARIANT` should have CamelCase name, e.g. `SomeVariant`
783 fn incorrect_const_name() {
786 const some_weird_const: u8 = 10;
787 // ^^^^^^^^^^^^^^^^ Constant `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST`
790 const someConstInFunc: &str = "hi there";
791 // ^^^^^^^^^^^^^^^ Constant `someConstInFunc` should have UPPER_SNAKE_CASE name, e.g. `SOME_CONST_IN_FUNC`
799 fn incorrect_static_name() {
802 static some_weird_const: u8 = 10;
803 // ^^^^^^^^^^^^^^^^ Static variable `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST`
806 static someConstInFunc: &str = "hi there";
807 // ^^^^^^^^^^^^^^^ Static variable `someConstInFunc` should have UPPER_SNAKE_CASE name, e.g. `SOME_CONST_IN_FUNC`
814 fn fn_inside_impl_struct() {
818 // ^^^^^^^^^^ Structure `someStruct` should have CamelCase name, e.g. `SomeStruct`
822 // ^^^^^^^^ Function `SomeFunc` should have snake_case name, e.g. `some_func`
823 static someConstInFunc: &str = "hi there";
824 // ^^^^^^^^^^^^^^^ Static variable `someConstInFunc` should have UPPER_SNAKE_CASE name, e.g. `SOME_CONST_IN_FUNC`
825 let WHY_VAR_IS_CAPS = 10;
826 // ^^^^^^^^^^^^^^^ Variable `WHY_VAR_IS_CAPS` should have snake_case name, e.g. `why_var_is_caps`
834 fn no_diagnostic_for_enum_varinats() {
837 enum Option { Some, None }
853 enum Option { Some, None }
857 SOME_VAR @ None => (),
858 // ^^^^^^^^ Variable `SOME_VAR` should have snake_case name, e.g. `some_var`
867 fn allow_attributes() {
870 #[allow(non_snake_case)]
871 fn NonSnakeCaseName(SOME_VAR: u8) -> u8{
872 let OtherVar = SOME_VAR + 1;
876 #[allow(non_snake_case, non_camel_case_types)]
877 pub struct some_type {
882 #[allow(non_upper_case_globals)]
883 pub const some_const: u8 = 10;
885 #[allow(non_upper_case_globals)]
886 pub static SomeStatic: u8 = 10;
892 fn ignores_extern_items() {
893 cov_mark::check!(extern_func_incorrect_case_ignored);
894 cov_mark::check!(extern_static_incorrect_case_ignored);
898 fn NonSnakeCaseName(SOME_VAR: u8) -> u8;
899 pub static SomeStatic: u8 = 10;
906 fn infinite_loop_inner_items() {