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},
35 diagnostics::{decl_check::case_conv::*, CaseType, IdentType, IncorrectCase},
39 pub(super) const NON_SNAKE_CASE: &str = "non_snake_case";
40 pub(super) const NON_UPPER_CASE_GLOBAL: &str = "non_upper_case_globals";
41 pub(super) const NON_CAMEL_CASE_TYPES: &str = "non_camel_case_types";
44 pub(super) struct DeclValidator<'a, 'b> {
45 db: &'a dyn HirDatabase,
47 sink: &'a mut DiagnosticSink<'b>,
53 suggested_text: String,
54 expected_case: CaseType,
57 impl<'a, 'b> DeclValidator<'a, 'b> {
59 db: &'a dyn HirDatabase,
61 sink: &'a mut DiagnosticSink<'b>,
62 ) -> DeclValidator<'a, 'b> {
63 DeclValidator { db, krate, sink }
66 pub(super) fn validate_item(&mut self, item: ModuleDefId) {
68 ModuleDefId::FunctionId(func) => self.validate_func(func),
69 ModuleDefId::AdtId(adt) => self.validate_adt(adt),
70 ModuleDefId::ConstId(const_id) => self.validate_const(const_id),
71 ModuleDefId::StaticId(static_id) => self.validate_static(static_id),
76 fn validate_adt(&mut self, adt: AdtId) {
78 AdtId::StructId(struct_id) => self.validate_struct(struct_id),
79 AdtId::EnumId(enum_id) => self.validate_enum(enum_id),
80 AdtId::UnionId(_) => {
81 // FIXME: Unions aren't yet supported by this validator.
86 /// Checks whether not following the convention is allowed for this item.
88 /// Currently this method doesn't check parent attributes.
89 fn allowed(&self, id: AttrDefId, allow_name: &str) -> bool {
90 self.db.attrs(id).by_key("allow").tt_values().any(|tt| tt.to_string().contains(allow_name))
93 fn validate_func(&mut self, func: FunctionId) {
94 let data = self.db.function_data(func);
96 mark::hit!(extern_func_incorrect_case_ignored);
100 let body = self.db.body(func.into());
102 // Recursively validate inner scope items, such as static variables and constants.
103 for (item_id, _) in body.item_scope.values() {
104 let mut validator = DeclValidator::new(self.db, self.krate, self.sink);
105 validator.validate_item(item_id);
108 // Check whether non-snake case identifiers are allowed for this function.
109 if self.allowed(func.into(), allow::NON_SNAKE_CASE) {
113 // Check the function name.
114 let function_name = data.name.to_string();
115 let fn_name_replacement = to_lower_snake_case(&function_name).map(|new_name| Replacement {
116 current_name: data.name.clone(),
117 suggested_text: new_name,
118 expected_case: CaseType::LowerSnakeCase,
121 // Check the param names.
122 let fn_param_replacements = body
125 .filter_map(|&id| match &body[id] {
126 Pat::Bind { name, .. } => Some(name),
129 .filter_map(|param_name| {
131 current_name: param_name.clone(),
132 suggested_text: to_lower_snake_case(¶m_name.to_string())?,
133 expected_case: CaseType::LowerSnakeCase,
138 // Check the patterns inside the function body.
139 let pats_replacements = body
142 // We aren't interested in function parameters, we've processed them above.
143 .filter(|(pat_idx, _)| !body.params.contains(&pat_idx))
144 .filter_map(|(id, pat)| match pat {
145 Pat::Bind { name, .. } => Some((id, name)),
148 .filter_map(|(id, bind_name)| {
152 current_name: bind_name.clone(),
153 suggested_text: to_lower_snake_case(&bind_name.to_string())?,
154 expected_case: CaseType::LowerSnakeCase,
160 // If there is at least one element to spawn a warning on, go to the source map and generate a warning.
161 self.create_incorrect_case_diagnostic_for_func(
164 fn_param_replacements,
166 self.create_incorrect_case_diagnostic_for_variables(func, pats_replacements);
169 /// Given the information about incorrect names in the function declaration, looks up into the source code
170 /// for exact locations and adds diagnostics into the sink.
171 fn create_incorrect_case_diagnostic_for_func(
174 fn_name_replacement: Option<Replacement>,
175 fn_param_replacements: Vec<Replacement>,
177 // XXX: only look at sources if we do have incorrect names
178 if fn_name_replacement.is_none() && fn_param_replacements.is_empty() {
182 let fn_loc = func.lookup(self.db.upcast());
183 let fn_src = fn_loc.source(self.db.upcast());
185 // Diagnostic for function name.
186 if let Some(replacement) = fn_name_replacement {
187 let ast_ptr = match fn_src.value.name() {
191 "Replacement ({:?}) was generated for a function without a name: {:?}",
199 let diagnostic = IncorrectCase {
200 file: fn_src.file_id,
201 ident_type: IdentType::Function,
202 ident: AstPtr::new(&ast_ptr).into(),
203 expected_case: replacement.expected_case,
204 ident_text: replacement.current_name.to_string(),
205 suggested_text: replacement.suggested_text,
208 self.sink.push(diagnostic);
211 // Diagnostics for function params.
212 let fn_params_list = match fn_src.value.param_list() {
213 Some(params) => params,
216 fn_param_replacements.is_empty(),
217 "Replacements ({:?}) were generated for a function parameters which had no parameters list: {:?}",
218 fn_param_replacements,
224 let mut fn_params_iter = fn_params_list.params();
225 for param_to_rename in fn_param_replacements {
226 // We assume that parameters in replacement are in the same order as in the
227 // actual params list, but just some of them (ones that named correctly) are skipped.
228 let ast_ptr: ast::Name = loop {
229 match fn_params_iter.next() {
231 if let Some(ast::Pat::IdentPat(pat)) = element.pat() {
232 if pat.to_string() == param_to_rename.current_name.to_string() {
233 if let Some(name) = pat.name() {
236 // This is critical. If we consider this parameter the expected one,
237 // it **must** have a name.
239 "Pattern {:?} equals to expected replacement {:?}, but has no name",
249 "Replacement ({:?}) was generated for a function parameter which was not found: {:?}",
250 param_to_rename, fn_src
257 let diagnostic = IncorrectCase {
258 file: fn_src.file_id,
259 ident_type: IdentType::Argument,
260 ident: AstPtr::new(&ast_ptr).into(),
261 expected_case: param_to_rename.expected_case,
262 ident_text: param_to_rename.current_name.to_string(),
263 suggested_text: param_to_rename.suggested_text,
266 self.sink.push(diagnostic);
270 /// Given the information about incorrect variable names, looks up into the source code
271 /// for exact locations and adds diagnostics into the sink.
272 fn create_incorrect_case_diagnostic_for_variables(
275 pats_replacements: Vec<(PatId, Replacement)>,
277 // XXX: only look at source_map if we do have missing fields
278 if pats_replacements.is_empty() {
282 let (_, source_map) = self.db.body_with_source_map(func.into());
284 for (id, replacement) in pats_replacements {
285 if let Ok(source_ptr) = source_map.pat_syntax(id) {
286 if let Some(expr) = source_ptr.value.as_ref().left() {
287 let root = source_ptr.file_syntax(self.db.upcast());
288 if let ast::Pat::IdentPat(ident_pat) = expr.to_node(&root) {
289 let parent = match ident_pat.syntax().parent() {
290 Some(parent) => parent,
293 let name_ast = match ident_pat.name() {
294 Some(name_ast) => name_ast,
298 // We have to check that it's either `let var = ...` or `var @ Variant(_)` statement,
299 // because e.g. match arms are patterns as well.
300 // In other words, we check that it's a named variable binding.
301 let is_binding = ast::LetStmt::can_cast(parent.kind())
302 || (ast::MatchArm::can_cast(parent.kind())
303 && ident_pat.at_token().is_some());
305 // This pattern is not an actual variable declaration, e.g. `Some(val) => {..}` match arm.
309 let diagnostic = IncorrectCase {
310 file: source_ptr.file_id,
311 ident_type: IdentType::Variable,
312 ident: AstPtr::new(&name_ast).into(),
313 expected_case: replacement.expected_case,
314 ident_text: replacement.current_name.to_string(),
315 suggested_text: replacement.suggested_text,
318 self.sink.push(diagnostic);
325 fn validate_struct(&mut self, struct_id: StructId) {
326 let data = self.db.struct_data(struct_id);
328 let non_camel_case_allowed = self.allowed(struct_id.into(), allow::NON_CAMEL_CASE_TYPES);
329 let non_snake_case_allowed = self.allowed(struct_id.into(), allow::NON_SNAKE_CASE);
331 // Check the structure name.
332 let struct_name = data.name.to_string();
333 let struct_name_replacement = if !non_camel_case_allowed {
334 to_camel_case(&struct_name).map(|new_name| Replacement {
335 current_name: data.name.clone(),
336 suggested_text: new_name,
337 expected_case: CaseType::UpperCamelCase,
343 // Check the field names.
344 let mut struct_fields_replacements = Vec::new();
346 if !non_snake_case_allowed {
347 if let VariantData::Record(fields) = data.variant_data.as_ref() {
348 for (_, field) in fields.iter() {
349 let field_name = field.name.to_string();
350 if let Some(new_name) = to_lower_snake_case(&field_name) {
351 let replacement = Replacement {
352 current_name: field.name.clone(),
353 suggested_text: new_name,
354 expected_case: CaseType::LowerSnakeCase,
356 struct_fields_replacements.push(replacement);
362 // If there is at least one element to spawn a warning on, go to the source map and generate a warning.
363 self.create_incorrect_case_diagnostic_for_struct(
365 struct_name_replacement,
366 struct_fields_replacements,
370 /// Given the information about incorrect names in the struct declaration, looks up into the source code
371 /// for exact locations and adds diagnostics into the sink.
372 fn create_incorrect_case_diagnostic_for_struct(
375 struct_name_replacement: Option<Replacement>,
376 struct_fields_replacements: Vec<Replacement>,
378 // XXX: only look at sources if we do have incorrect names
379 if struct_name_replacement.is_none() && struct_fields_replacements.is_empty() {
383 let struct_loc = struct_id.lookup(self.db.upcast());
384 let struct_src = struct_loc.source(self.db.upcast());
386 if let Some(replacement) = struct_name_replacement {
387 let ast_ptr = match struct_src.value.name() {
391 "Replacement ({:?}) was generated for a structure without a name: {:?}",
399 let diagnostic = IncorrectCase {
400 file: struct_src.file_id,
401 ident_type: IdentType::Structure,
402 ident: AstPtr::new(&ast_ptr).into(),
403 expected_case: replacement.expected_case,
404 ident_text: replacement.current_name.to_string(),
405 suggested_text: replacement.suggested_text,
408 self.sink.push(diagnostic);
411 let struct_fields_list = match struct_src.value.field_list() {
412 Some(ast::FieldList::RecordFieldList(fields)) => fields,
415 struct_fields_replacements.is_empty(),
416 "Replacements ({:?}) were generated for a structure fields which had no fields list: {:?}",
417 struct_fields_replacements,
423 let mut struct_fields_iter = struct_fields_list.fields();
424 for field_to_rename in struct_fields_replacements {
425 // We assume that parameters in replacement are in the same order as in the
426 // actual params list, but just some of them (ones that named correctly) are skipped.
428 match struct_fields_iter.next().and_then(|field| field.name()) {
429 Some(field_name) => {
430 if field_name.as_name() == field_to_rename.current_name {
436 "Replacement ({:?}) was generated for a structure field which was not found: {:?}",
437 field_to_rename, struct_src
444 let diagnostic = IncorrectCase {
445 file: struct_src.file_id,
446 ident_type: IdentType::Field,
447 ident: AstPtr::new(&ast_ptr).into(),
448 expected_case: field_to_rename.expected_case,
449 ident_text: field_to_rename.current_name.to_string(),
450 suggested_text: field_to_rename.suggested_text,
453 self.sink.push(diagnostic);
457 fn validate_enum(&mut self, enum_id: EnumId) {
458 let data = self.db.enum_data(enum_id);
460 // Check whether non-camel case names are allowed for this enum.
461 if self.allowed(enum_id.into(), allow::NON_CAMEL_CASE_TYPES) {
465 // Check the enum name.
466 let enum_name = data.name.to_string();
467 let enum_name_replacement = to_camel_case(&enum_name).map(|new_name| Replacement {
468 current_name: data.name.clone(),
469 suggested_text: new_name,
470 expected_case: CaseType::UpperCamelCase,
473 // Check the field names.
474 let enum_fields_replacements = data
477 .filter_map(|(_, variant)| {
479 current_name: variant.name.clone(),
480 suggested_text: to_camel_case(&variant.name.to_string())?,
481 expected_case: CaseType::UpperCamelCase,
486 // If there is at least one element to spawn a warning on, go to the source map and generate a warning.
487 self.create_incorrect_case_diagnostic_for_enum(
489 enum_name_replacement,
490 enum_fields_replacements,
494 /// Given the information about incorrect names in the struct declaration, looks up into the source code
495 /// for exact locations and adds diagnostics into the sink.
496 fn create_incorrect_case_diagnostic_for_enum(
499 enum_name_replacement: Option<Replacement>,
500 enum_variants_replacements: Vec<Replacement>,
502 // XXX: only look at sources if we do have incorrect names
503 if enum_name_replacement.is_none() && enum_variants_replacements.is_empty() {
507 let enum_loc = enum_id.lookup(self.db.upcast());
508 let enum_src = enum_loc.source(self.db.upcast());
510 if let Some(replacement) = enum_name_replacement {
511 let ast_ptr = match enum_src.value.name() {
515 "Replacement ({:?}) was generated for a enum without a name: {:?}",
523 let diagnostic = IncorrectCase {
524 file: enum_src.file_id,
525 ident_type: IdentType::Enum,
526 ident: AstPtr::new(&ast_ptr).into(),
527 expected_case: replacement.expected_case,
528 ident_text: replacement.current_name.to_string(),
529 suggested_text: replacement.suggested_text,
532 self.sink.push(diagnostic);
535 let enum_variants_list = match enum_src.value.variant_list() {
536 Some(variants) => variants,
539 enum_variants_replacements.is_empty(),
540 "Replacements ({:?}) were generated for a enum variants which had no fields list: {:?}",
541 enum_variants_replacements,
547 let mut enum_variants_iter = enum_variants_list.variants();
548 for variant_to_rename in enum_variants_replacements {
549 // We assume that parameters in replacement are in the same order as in the
550 // actual params list, but just some of them (ones that named correctly) are skipped.
552 match enum_variants_iter.next().and_then(|v| v.name()) {
553 Some(variant_name) => {
554 if variant_name.as_name() == variant_to_rename.current_name {
560 "Replacement ({:?}) was generated for a enum variant which was not found: {:?}",
561 variant_to_rename, enum_src
568 let diagnostic = IncorrectCase {
569 file: enum_src.file_id,
570 ident_type: IdentType::Variant,
571 ident: AstPtr::new(&ast_ptr).into(),
572 expected_case: variant_to_rename.expected_case,
573 ident_text: variant_to_rename.current_name.to_string(),
574 suggested_text: variant_to_rename.suggested_text,
577 self.sink.push(diagnostic);
581 fn validate_const(&mut self, const_id: ConstId) {
582 let data = self.db.const_data(const_id);
584 if self.allowed(const_id.into(), allow::NON_UPPER_CASE_GLOBAL) {
588 let name = match &data.name {
593 let const_name = name.to_string();
594 let replacement = if let Some(new_name) = to_upper_snake_case(&const_name) {
596 current_name: name.clone(),
597 suggested_text: new_name,
598 expected_case: CaseType::UpperSnakeCase,
601 // Nothing to do here.
605 let const_loc = const_id.lookup(self.db.upcast());
606 let const_src = const_loc.source(self.db.upcast());
608 let ast_ptr = match const_src.value.name() {
613 let diagnostic = IncorrectCase {
614 file: const_src.file_id,
615 ident_type: IdentType::Constant,
616 ident: AstPtr::new(&ast_ptr).into(),
617 expected_case: replacement.expected_case,
618 ident_text: replacement.current_name.to_string(),
619 suggested_text: replacement.suggested_text,
622 self.sink.push(diagnostic);
625 fn validate_static(&mut self, static_id: StaticId) {
626 let data = self.db.static_data(static_id);
628 mark::hit!(extern_static_incorrect_case_ignored);
632 if self.allowed(static_id.into(), allow::NON_UPPER_CASE_GLOBAL) {
636 let name = match &data.name {
641 let static_name = name.to_string();
642 let replacement = if let Some(new_name) = to_upper_snake_case(&static_name) {
644 current_name: name.clone(),
645 suggested_text: new_name,
646 expected_case: CaseType::UpperSnakeCase,
649 // Nothing to do here.
653 let static_loc = static_id.lookup(self.db.upcast());
654 let static_src = static_loc.source(self.db.upcast());
656 let ast_ptr = match static_src.value.name() {
661 let diagnostic = IncorrectCase {
662 file: static_src.file_id,
663 ident_type: IdentType::StaticVariable,
664 ident: AstPtr::new(&ast_ptr).into(),
665 expected_case: replacement.expected_case,
666 ident_text: replacement.current_name.to_string(),
667 suggested_text: replacement.suggested_text,
670 self.sink.push(diagnostic);
676 use test_utils::mark;
678 use crate::diagnostics::tests::check_diagnostics;
681 fn incorrect_function_name() {
684 fn NonSnakeCaseName() {}
685 // ^^^^^^^^^^^^^^^^ Function `NonSnakeCaseName` should have snake_case name, e.g. `non_snake_case_name`
691 fn incorrect_function_params() {
694 fn foo(SomeParam: u8) {}
695 // ^^^^^^^^^ Argument `SomeParam` should have snake_case name, e.g. `some_param`
697 fn foo2(ok_param: &str, CAPS_PARAM: u8) {}
698 // ^^^^^^^^^^ Argument `CAPS_PARAM` should have snake_case name, e.g. `caps_param`
704 fn incorrect_variable_names() {
709 // ^^^^^^^^^^ Variable `SOME_VALUE` should have snake_case name, e.g. `some_value`
710 let AnotherValue = 20;
711 // ^^^^^^^^^^^^ Variable `AnotherValue` should have snake_case name, e.g. `another_value`
718 fn incorrect_struct_names() {
721 struct non_camel_case_name {}
722 // ^^^^^^^^^^^^^^^^^^^ Structure `non_camel_case_name` should have CamelCase name, e.g. `NonCamelCaseName`
724 struct SCREAMING_CASE {}
725 // ^^^^^^^^^^^^^^ Structure `SCREAMING_CASE` should have CamelCase name, e.g. `ScreamingCase`
731 fn no_diagnostic_for_camel_cased_acronyms_in_struct_name() {
740 fn incorrect_struct_field() {
743 struct SomeStruct { SomeField: u8 }
744 // ^^^^^^^^^ Field `SomeField` should have snake_case name, e.g. `some_field`
750 fn incorrect_enum_names() {
753 enum some_enum { Val(u8) }
754 // ^^^^^^^^^ Enum `some_enum` should have CamelCase name, e.g. `SomeEnum`
757 // ^^^^^^^^^ Enum `SOME_ENUM` should have CamelCase name, e.g. `SomeEnum`
763 fn no_diagnostic_for_camel_cased_acronyms_in_enum_name() {
772 fn incorrect_enum_variant_name() {
775 enum SomeEnum { SOME_VARIANT(u8) }
776 // ^^^^^^^^^^^^ Variant `SOME_VARIANT` should have CamelCase name, e.g. `SomeVariant`
782 fn incorrect_const_name() {
785 const some_weird_const: u8 = 10;
786 // ^^^^^^^^^^^^^^^^ Constant `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST`
789 const someConstInFunc: &str = "hi there";
790 // ^^^^^^^^^^^^^^^ Constant `someConstInFunc` should have UPPER_SNAKE_CASE name, e.g. `SOME_CONST_IN_FUNC`
798 fn incorrect_static_name() {
801 static some_weird_const: u8 = 10;
802 // ^^^^^^^^^^^^^^^^ Static variable `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST`
805 static someConstInFunc: &str = "hi there";
806 // ^^^^^^^^^^^^^^^ Static variable `someConstInFunc` should have UPPER_SNAKE_CASE name, e.g. `SOME_CONST_IN_FUNC`
813 fn fn_inside_impl_struct() {
817 // ^^^^^^^^^^ Structure `someStruct` should have CamelCase name, e.g. `SomeStruct`
821 // ^^^^^^^^ Function `SomeFunc` should have snake_case name, e.g. `some_func`
822 static someConstInFunc: &str = "hi there";
823 // ^^^^^^^^^^^^^^^ Static variable `someConstInFunc` should have UPPER_SNAKE_CASE name, e.g. `SOME_CONST_IN_FUNC`
824 let WHY_VAR_IS_CAPS = 10;
825 // ^^^^^^^^^^^^^^^ Variable `WHY_VAR_IS_CAPS` should have snake_case name, e.g. `why_var_is_caps`
833 fn no_diagnostic_for_enum_varinats() {
836 enum Option { Some, None }
852 enum Option { Some, None }
856 SOME_VAR @ None => (),
857 // ^^^^^^^^ Variable `SOME_VAR` should have snake_case name, e.g. `some_var`
866 fn allow_attributes() {
869 #[allow(non_snake_case)]
870 fn NonSnakeCaseName(SOME_VAR: u8) -> u8{
871 let OtherVar = SOME_VAR + 1;
875 #[allow(non_snake_case, non_camel_case_types)]
876 pub struct some_type {
881 #[allow(non_upper_case_globals)]
882 pub const some_const: u8 = 10;
884 #[allow(non_upper_case_globals)]
885 pub static SomeStatic: u8 = 10;
891 fn ignores_extern_items() {
892 mark::check!(extern_func_incorrect_case_ignored);
893 mark::check!(extern_static_incorrect_case_ignored);
897 fn NonSnakeCaseName(SOME_VAR: u8) -> u8;
898 pub static SomeStatic: u8 = 10;