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.
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 = to_lower_snake_case(&function_name).map(|new_name| Replacement {
115 current_name: data.name.clone(),
116 suggested_text: new_name,
117 expected_case: CaseType::LowerSnakeCase,
120 // Check the param names.
121 let fn_param_replacements = body
124 .filter_map(|&id| match &body[id] {
125 Pat::Bind { name, .. } => Some(name),
128 .filter_map(|param_name| {
130 current_name: param_name.clone(),
131 suggested_text: to_lower_snake_case(¶m_name.to_string())?,
132 expected_case: CaseType::LowerSnakeCase,
137 // Check the patterns inside the function body.
138 let pats_replacements = body
141 // We aren't interested in function parameters, we've processed them above.
142 .filter(|(pat_idx, _)| !body.params.contains(&pat_idx))
143 .filter_map(|(id, pat)| match pat {
144 Pat::Bind { name, .. } => Some((id, name)),
147 .filter_map(|(id, bind_name)| {
151 current_name: bind_name.clone(),
152 suggested_text: to_lower_snake_case(&bind_name.to_string())?,
153 expected_case: CaseType::LowerSnakeCase,
159 // If there is at least one element to spawn a warning on, go to the source map and generate a warning.
160 self.create_incorrect_case_diagnostic_for_func(
163 fn_param_replacements,
165 self.create_incorrect_case_diagnostic_for_variables(func, pats_replacements);
168 /// Given the information about incorrect names in the function declaration, looks up into the source code
169 /// for exact locations and adds diagnostics into the sink.
170 fn create_incorrect_case_diagnostic_for_func(
173 fn_name_replacement: Option<Replacement>,
174 fn_param_replacements: Vec<Replacement>,
176 // XXX: only look at sources if we do have incorrect names
177 if fn_name_replacement.is_none() && fn_param_replacements.is_empty() {
181 let fn_loc = func.lookup(self.db.upcast());
182 let fn_src = fn_loc.source(self.db.upcast());
184 // Diagnostic for function name.
185 if let Some(replacement) = fn_name_replacement {
186 let ast_ptr = match fn_src.value.name() {
190 "Replacement ({:?}) was generated for a function without a name: {:?}",
198 let diagnostic = IncorrectCase {
199 file: fn_src.file_id,
200 ident_type: IdentType::Function,
201 ident: AstPtr::new(&ast_ptr).into(),
202 expected_case: replacement.expected_case,
203 ident_text: replacement.current_name.to_string(),
204 suggested_text: replacement.suggested_text,
207 self.sink.push(diagnostic);
210 // Diagnostics for function params.
211 let fn_params_list = match fn_src.value.param_list() {
212 Some(params) => params,
215 fn_param_replacements.is_empty(),
216 "Replacements ({:?}) were generated for a function parameters which had no parameters list: {:?}",
217 fn_param_replacements,
223 let mut fn_params_iter = fn_params_list.params();
224 for param_to_rename in fn_param_replacements {
225 // We assume that parameters in replacement are in the same order as in the
226 // actual params list, but just some of them (ones that named correctly) are skipped.
227 let ast_ptr: ast::Name = loop {
228 match fn_params_iter.next() {
230 if let Some(ast::Pat::IdentPat(pat)) = element.pat() {
231 if pat.to_string() == param_to_rename.current_name.to_string() {
232 if let Some(name) = pat.name() {
235 // This is critical. If we consider this parameter the expected one,
236 // it **must** have a name.
238 "Pattern {:?} equals to expected replacement {:?}, but has no name",
248 "Replacement ({:?}) was generated for a function parameter which was not found: {:?}",
249 param_to_rename, fn_src
256 let diagnostic = IncorrectCase {
257 file: fn_src.file_id,
258 ident_type: IdentType::Argument,
259 ident: AstPtr::new(&ast_ptr).into(),
260 expected_case: param_to_rename.expected_case,
261 ident_text: param_to_rename.current_name.to_string(),
262 suggested_text: param_to_rename.suggested_text,
265 self.sink.push(diagnostic);
269 /// Given the information about incorrect variable names, looks up into the source code
270 /// for exact locations and adds diagnostics into the sink.
271 fn create_incorrect_case_diagnostic_for_variables(
274 pats_replacements: Vec<(PatId, Replacement)>,
276 // XXX: only look at source_map if we do have missing fields
277 if pats_replacements.is_empty() {
281 let (_, source_map) = self.db.body_with_source_map(func.into());
283 for (id, replacement) in pats_replacements {
284 if let Ok(source_ptr) = source_map.pat_syntax(id) {
285 if let Some(expr) = source_ptr.value.as_ref().left() {
286 let root = source_ptr.file_syntax(self.db.upcast());
287 if let ast::Pat::IdentPat(ident_pat) = expr.to_node(&root) {
288 let parent = match ident_pat.syntax().parent() {
289 Some(parent) => parent,
292 let name_ast = match ident_pat.name() {
293 Some(name_ast) => name_ast,
297 // We have to check that it's either `let var = ...` or `var @ Variant(_)` statement,
298 // because e.g. match arms are patterns as well.
299 // In other words, we check that it's a named variable binding.
300 let is_binding = ast::LetStmt::can_cast(parent.kind())
301 || (ast::MatchArm::can_cast(parent.kind())
302 && ident_pat.at_token().is_some());
304 // This pattern is not an actual variable declaration, e.g. `Some(val) => {..}` match arm.
308 let diagnostic = IncorrectCase {
309 file: source_ptr.file_id,
310 ident_type: IdentType::Variable,
311 ident: AstPtr::new(&name_ast).into(),
312 expected_case: replacement.expected_case,
313 ident_text: replacement.current_name.to_string(),
314 suggested_text: replacement.suggested_text,
317 self.sink.push(diagnostic);
324 fn validate_struct(&mut self, struct_id: StructId) {
325 let data = self.db.struct_data(struct_id);
327 let non_camel_case_allowed = self.allowed(struct_id.into(), allow::NON_CAMEL_CASE_TYPES);
328 let non_snake_case_allowed = self.allowed(struct_id.into(), allow::NON_SNAKE_CASE);
330 // Check the structure name.
331 let struct_name = data.name.to_string();
332 let struct_name_replacement = if !non_camel_case_allowed {
333 to_camel_case(&struct_name).map(|new_name| Replacement {
334 current_name: data.name.clone(),
335 suggested_text: new_name,
336 expected_case: CaseType::UpperCamelCase,
342 // Check the field names.
343 let mut struct_fields_replacements = Vec::new();
345 if !non_snake_case_allowed {
346 if let VariantData::Record(fields) = data.variant_data.as_ref() {
347 for (_, field) in fields.iter() {
348 let field_name = field.name.to_string();
349 if let Some(new_name) = to_lower_snake_case(&field_name) {
350 let replacement = Replacement {
351 current_name: field.name.clone(),
352 suggested_text: new_name,
353 expected_case: CaseType::LowerSnakeCase,
355 struct_fields_replacements.push(replacement);
361 // If there is at least one element to spawn a warning on, go to the source map and generate a warning.
362 self.create_incorrect_case_diagnostic_for_struct(
364 struct_name_replacement,
365 struct_fields_replacements,
369 /// Given the information about incorrect names in the struct declaration, looks up into the source code
370 /// for exact locations and adds diagnostics into the sink.
371 fn create_incorrect_case_diagnostic_for_struct(
374 struct_name_replacement: Option<Replacement>,
375 struct_fields_replacements: Vec<Replacement>,
377 // XXX: only look at sources if we do have incorrect names
378 if struct_name_replacement.is_none() && struct_fields_replacements.is_empty() {
382 let struct_loc = struct_id.lookup(self.db.upcast());
383 let struct_src = struct_loc.source(self.db.upcast());
385 if let Some(replacement) = struct_name_replacement {
386 let ast_ptr = match struct_src.value.name() {
390 "Replacement ({:?}) was generated for a structure without a name: {:?}",
398 let diagnostic = IncorrectCase {
399 file: struct_src.file_id,
400 ident_type: IdentType::Structure,
401 ident: AstPtr::new(&ast_ptr).into(),
402 expected_case: replacement.expected_case,
403 ident_text: replacement.current_name.to_string(),
404 suggested_text: replacement.suggested_text,
407 self.sink.push(diagnostic);
410 let struct_fields_list = match struct_src.value.field_list() {
411 Some(ast::FieldList::RecordFieldList(fields)) => fields,
414 struct_fields_replacements.is_empty(),
415 "Replacements ({:?}) were generated for a structure fields which had no fields list: {:?}",
416 struct_fields_replacements,
422 let mut struct_fields_iter = struct_fields_list.fields();
423 for field_to_rename in struct_fields_replacements {
424 // We assume that parameters in replacement are in the same order as in the
425 // actual params list, but just some of them (ones that named correctly) are skipped.
427 match struct_fields_iter.next().and_then(|field| field.name()) {
428 Some(field_name) => {
429 if field_name.as_name() == field_to_rename.current_name {
435 "Replacement ({:?}) was generated for a structure field which was not found: {:?}",
436 field_to_rename, struct_src
443 let diagnostic = IncorrectCase {
444 file: struct_src.file_id,
445 ident_type: IdentType::Field,
446 ident: AstPtr::new(&ast_ptr).into(),
447 expected_case: field_to_rename.expected_case,
448 ident_text: field_to_rename.current_name.to_string(),
449 suggested_text: field_to_rename.suggested_text,
452 self.sink.push(diagnostic);
456 fn validate_enum(&mut self, enum_id: EnumId) {
457 let data = self.db.enum_data(enum_id);
459 // Check whether non-camel case names are allowed for this enum.
460 if self.allowed(enum_id.into(), allow::NON_CAMEL_CASE_TYPES) {
464 // Check the enum name.
465 let enum_name = data.name.to_string();
466 let enum_name_replacement = to_camel_case(&enum_name).map(|new_name| Replacement {
467 current_name: data.name.clone(),
468 suggested_text: new_name,
469 expected_case: CaseType::UpperCamelCase,
472 // Check the field names.
473 let enum_fields_replacements = data
476 .filter_map(|(_, variant)| {
478 current_name: variant.name.clone(),
479 suggested_text: to_camel_case(&variant.name.to_string())?,
480 expected_case: CaseType::UpperCamelCase,
485 // If there is at least one element to spawn a warning on, go to the source map and generate a warning.
486 self.create_incorrect_case_diagnostic_for_enum(
488 enum_name_replacement,
489 enum_fields_replacements,
493 /// Given the information about incorrect names in the struct declaration, looks up into the source code
494 /// for exact locations and adds diagnostics into the sink.
495 fn create_incorrect_case_diagnostic_for_enum(
498 enum_name_replacement: Option<Replacement>,
499 enum_variants_replacements: Vec<Replacement>,
501 // XXX: only look at sources if we do have incorrect names
502 if enum_name_replacement.is_none() && enum_variants_replacements.is_empty() {
506 let enum_loc = enum_id.lookup(self.db.upcast());
507 let enum_src = enum_loc.source(self.db.upcast());
509 if let Some(replacement) = enum_name_replacement {
510 let ast_ptr = match enum_src.value.name() {
514 "Replacement ({:?}) was generated for a enum without a name: {:?}",
522 let diagnostic = IncorrectCase {
523 file: enum_src.file_id,
524 ident_type: IdentType::Enum,
525 ident: AstPtr::new(&ast_ptr).into(),
526 expected_case: replacement.expected_case,
527 ident_text: replacement.current_name.to_string(),
528 suggested_text: replacement.suggested_text,
531 self.sink.push(diagnostic);
534 let enum_variants_list = match enum_src.value.variant_list() {
535 Some(variants) => variants,
538 enum_variants_replacements.is_empty(),
539 "Replacements ({:?}) were generated for a enum variants which had no fields list: {:?}",
540 enum_variants_replacements,
546 let mut enum_variants_iter = enum_variants_list.variants();
547 for variant_to_rename in enum_variants_replacements {
548 // We assume that parameters in replacement are in the same order as in the
549 // actual params list, but just some of them (ones that named correctly) are skipped.
551 match enum_variants_iter.next().and_then(|v| v.name()) {
552 Some(variant_name) => {
553 if variant_name.as_name() == variant_to_rename.current_name {
559 "Replacement ({:?}) was generated for a enum variant which was not found: {:?}",
560 variant_to_rename, enum_src
567 let diagnostic = IncorrectCase {
568 file: enum_src.file_id,
569 ident_type: IdentType::Variant,
570 ident: AstPtr::new(&ast_ptr).into(),
571 expected_case: variant_to_rename.expected_case,
572 ident_text: variant_to_rename.current_name.to_string(),
573 suggested_text: variant_to_rename.suggested_text,
576 self.sink.push(diagnostic);
580 fn validate_const(&mut self, const_id: ConstId) {
581 let data = self.db.const_data(const_id);
583 if self.allowed(const_id.into(), allow::NON_UPPER_CASE_GLOBAL) {
587 let name = match &data.name {
592 let const_name = name.to_string();
593 let replacement = if let Some(new_name) = to_upper_snake_case(&const_name) {
595 current_name: name.clone(),
596 suggested_text: new_name,
597 expected_case: CaseType::UpperSnakeCase,
600 // Nothing to do here.
604 let const_loc = const_id.lookup(self.db.upcast());
605 let const_src = const_loc.source(self.db.upcast());
607 let ast_ptr = match const_src.value.name() {
612 let diagnostic = IncorrectCase {
613 file: const_src.file_id,
614 ident_type: IdentType::Constant,
615 ident: AstPtr::new(&ast_ptr).into(),
616 expected_case: replacement.expected_case,
617 ident_text: replacement.current_name.to_string(),
618 suggested_text: replacement.suggested_text,
621 self.sink.push(diagnostic);
624 fn validate_static(&mut self, static_id: StaticId) {
625 let data = self.db.static_data(static_id);
627 cov_mark::hit!(extern_static_incorrect_case_ignored);
631 if self.allowed(static_id.into(), allow::NON_UPPER_CASE_GLOBAL) {
635 let name = match &data.name {
640 let static_name = name.to_string();
641 let replacement = if let Some(new_name) = to_upper_snake_case(&static_name) {
643 current_name: name.clone(),
644 suggested_text: new_name,
645 expected_case: CaseType::UpperSnakeCase,
648 // Nothing to do here.
652 let static_loc = static_id.lookup(self.db.upcast());
653 let static_src = static_loc.source(self.db.upcast());
655 let ast_ptr = match static_src.value.name() {
660 let diagnostic = IncorrectCase {
661 file: static_src.file_id,
662 ident_type: IdentType::StaticVariable,
663 ident: AstPtr::new(&ast_ptr).into(),
664 expected_case: replacement.expected_case,
665 ident_text: replacement.current_name.to_string(),
666 suggested_text: replacement.suggested_text,
669 self.sink.push(diagnostic);
675 use crate::diagnostics::tests::check_diagnostics;
678 fn incorrect_function_name() {
681 fn NonSnakeCaseName() {}
682 // ^^^^^^^^^^^^^^^^ Function `NonSnakeCaseName` should have snake_case name, e.g. `non_snake_case_name`
688 fn incorrect_function_params() {
691 fn foo(SomeParam: u8) {}
692 // ^^^^^^^^^ Argument `SomeParam` should have snake_case name, e.g. `some_param`
694 fn foo2(ok_param: &str, CAPS_PARAM: u8) {}
695 // ^^^^^^^^^^ Argument `CAPS_PARAM` should have snake_case name, e.g. `caps_param`
701 fn incorrect_variable_names() {
706 // ^^^^^^^^^^ Variable `SOME_VALUE` should have snake_case name, e.g. `some_value`
707 let AnotherValue = 20;
708 // ^^^^^^^^^^^^ Variable `AnotherValue` should have snake_case name, e.g. `another_value`
715 fn incorrect_struct_names() {
718 struct non_camel_case_name {}
719 // ^^^^^^^^^^^^^^^^^^^ Structure `non_camel_case_name` should have CamelCase name, e.g. `NonCamelCaseName`
721 struct SCREAMING_CASE {}
722 // ^^^^^^^^^^^^^^ Structure `SCREAMING_CASE` should have CamelCase name, e.g. `ScreamingCase`
728 fn no_diagnostic_for_camel_cased_acronyms_in_struct_name() {
737 fn incorrect_struct_field() {
740 struct SomeStruct { SomeField: u8 }
741 // ^^^^^^^^^ Field `SomeField` should have snake_case name, e.g. `some_field`
747 fn incorrect_enum_names() {
750 enum some_enum { Val(u8) }
751 // ^^^^^^^^^ Enum `some_enum` should have CamelCase name, e.g. `SomeEnum`
754 // ^^^^^^^^^ Enum `SOME_ENUM` should have CamelCase name, e.g. `SomeEnum`
760 fn no_diagnostic_for_camel_cased_acronyms_in_enum_name() {
769 fn incorrect_enum_variant_name() {
772 enum SomeEnum { SOME_VARIANT(u8) }
773 // ^^^^^^^^^^^^ Variant `SOME_VARIANT` should have CamelCase name, e.g. `SomeVariant`
779 fn incorrect_const_name() {
782 const some_weird_const: u8 = 10;
783 // ^^^^^^^^^^^^^^^^ Constant `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST`
786 const someConstInFunc: &str = "hi there";
787 // ^^^^^^^^^^^^^^^ Constant `someConstInFunc` should have UPPER_SNAKE_CASE name, e.g. `SOME_CONST_IN_FUNC`
795 fn incorrect_static_name() {
798 static some_weird_const: u8 = 10;
799 // ^^^^^^^^^^^^^^^^ Static variable `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST`
802 static someConstInFunc: &str = "hi there";
803 // ^^^^^^^^^^^^^^^ Static variable `someConstInFunc` should have UPPER_SNAKE_CASE name, e.g. `SOME_CONST_IN_FUNC`
810 fn fn_inside_impl_struct() {
814 // ^^^^^^^^^^ Structure `someStruct` should have CamelCase name, e.g. `SomeStruct`
818 // ^^^^^^^^ Function `SomeFunc` should have snake_case name, e.g. `some_func`
819 static someConstInFunc: &str = "hi there";
820 // ^^^^^^^^^^^^^^^ Static variable `someConstInFunc` should have UPPER_SNAKE_CASE name, e.g. `SOME_CONST_IN_FUNC`
821 let WHY_VAR_IS_CAPS = 10;
822 // ^^^^^^^^^^^^^^^ Variable `WHY_VAR_IS_CAPS` should have snake_case name, e.g. `why_var_is_caps`
830 fn no_diagnostic_for_enum_varinats() {
833 enum Option { Some, None }
849 enum Option { Some, None }
853 SOME_VAR @ None => (),
854 // ^^^^^^^^ Variable `SOME_VAR` should have snake_case name, e.g. `some_var`
863 fn allow_attributes() {
866 #[allow(non_snake_case)]
867 fn NonSnakeCaseName(SOME_VAR: u8) -> u8{
868 let OtherVar = SOME_VAR + 1;
872 #[allow(non_snake_case, non_camel_case_types)]
873 pub struct some_type {
878 #[allow(non_upper_case_globals)]
879 pub const some_const: u8 = 10;
881 #[allow(non_upper_case_globals)]
882 pub static SomeStatic: u8 = 10;
888 fn ignores_extern_items() {
889 cov_mark::check!(extern_func_incorrect_case_ignored);
890 cov_mark::check!(extern_static_incorrect_case_ignored);
894 fn NonSnakeCaseName(SOME_VAR: u8) -> u8;
895 pub static SomeStatic: u8 = 10;