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,
22 use hir_expand::name::{AsName, Name};
23 use stdx::{always, never};
25 ast::{self, NameOwner},
31 diagnostics::{decl_check::case_conv::*, CaseType, IdentType, IncorrectCase},
32 diagnostics_sink::DiagnosticSink,
36 pub(super) const BAD_STYLE: &str = "bad_style";
37 pub(super) const NONSTANDARD_STYLE: &str = "nonstandard_style";
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.
86 fn allowed(&self, id: AttrDefId, allow_name: &str, recursing: bool) -> bool {
87 let is_allowed = |def_id| {
88 let attrs = self.db.attrs(def_id);
89 // don't bug the user about directly no_mangle annotated stuff, they can't do anything about it
90 (!recursing && attrs.by_key("no_mangle").exists())
91 || attrs.by_key("allow").tt_values().any(|tt| {
92 let allows = tt.to_string();
93 allows.contains(allow_name)
94 || allows.contains(allow::BAD_STYLE)
95 || allows.contains(allow::NONSTANDARD_STYLE)
100 // go upwards one step or give up
102 AttrDefId::ModuleId(m) => m.containing_module(self.db.upcast()).map(|v| v.into()),
103 AttrDefId::FunctionId(f) => Some(f.lookup(self.db.upcast()).container.into()),
104 AttrDefId::StaticId(sid) => Some(sid.lookup(self.db.upcast()).container.into()),
105 AttrDefId::ConstId(cid) => Some(cid.lookup(self.db.upcast()).container.into()),
106 AttrDefId::TraitId(tid) => Some(tid.lookup(self.db.upcast()).container.into()),
107 AttrDefId::ImplId(iid) => Some(iid.lookup(self.db.upcast()).container.into()),
108 // These warnings should not explore macro definitions at all
109 AttrDefId::MacroDefId(_) => None,
110 // Will never occur under an enum/struct/union/type alias
111 AttrDefId::AdtId(_) => None,
112 AttrDefId::FieldId(_) => None,
113 AttrDefId::EnumVariantId(_) => None,
114 AttrDefId::TypeAliasId(_) => None,
115 AttrDefId::GenericParamId(_) => None,
117 .map(|mid| self.allowed(mid, allow_name, true))
121 fn validate_func(&mut self, func: FunctionId) {
122 let data = self.db.function_data(func);
123 if data.is_in_extern_block() {
124 cov_mark::hit!(extern_func_incorrect_case_ignored);
128 let body = self.db.body(func.into());
130 // Recursively validate inner scope items, such as static variables and constants.
131 for (_, block_def_map) in body.blocks(self.db.upcast()) {
132 for (_, module) in block_def_map.modules() {
133 for def_id in module.scope.declarations() {
134 let mut validator = DeclValidator::new(self.db, self.krate, self.sink);
135 validator.validate_item(def_id);
140 // Check whether non-snake case identifiers are allowed for this function.
141 if self.allowed(func.into(), allow::NON_SNAKE_CASE, false) {
145 // Check the function name.
146 let function_name = data.name.to_string();
147 let fn_name_replacement = to_lower_snake_case(&function_name).map(|new_name| Replacement {
148 current_name: data.name.clone(),
149 suggested_text: new_name,
150 expected_case: CaseType::LowerSnakeCase,
153 // Check the patterns inside the function body.
154 // This includes function parameters.
155 let pats_replacements = body
158 .filter_map(|(id, pat)| match pat {
159 Pat::Bind { name, .. } => Some((id, name)),
162 .filter_map(|(id, bind_name)| {
166 current_name: bind_name.clone(),
167 suggested_text: to_lower_snake_case(&bind_name.to_string())?,
168 expected_case: CaseType::LowerSnakeCase,
174 // If there is at least one element to spawn a warning on, go to the source map and generate a warning.
175 if let Some(fn_name_replacement) = fn_name_replacement {
176 self.create_incorrect_case_diagnostic_for_func(func, fn_name_replacement);
179 self.create_incorrect_case_diagnostic_for_variables(func, pats_replacements);
182 /// Given the information about incorrect names in the function declaration, looks up into the source code
183 /// for exact locations and adds diagnostics into the sink.
184 fn create_incorrect_case_diagnostic_for_func(
187 fn_name_replacement: Replacement,
189 let fn_loc = func.lookup(self.db.upcast());
190 let fn_src = fn_loc.source(self.db.upcast());
192 // Diagnostic for function name.
193 let ast_ptr = match fn_src.value.name() {
197 "Replacement ({:?}) was generated for a function without a name: {:?}",
205 let diagnostic = IncorrectCase {
206 file: fn_src.file_id,
207 ident_type: IdentType::Function,
208 ident: AstPtr::new(&ast_ptr),
209 expected_case: fn_name_replacement.expected_case,
210 ident_text: fn_name_replacement.current_name.to_string(),
211 suggested_text: fn_name_replacement.suggested_text,
214 self.sink.push(diagnostic);
217 /// Given the information about incorrect variable names, looks up into the source code
218 /// for exact locations and adds diagnostics into the sink.
219 fn create_incorrect_case_diagnostic_for_variables(
222 pats_replacements: Vec<(PatId, Replacement)>,
224 // XXX: only look at source_map if we do have missing fields
225 if pats_replacements.is_empty() {
229 let (_, source_map) = self.db.body_with_source_map(func.into());
231 for (id, replacement) in pats_replacements {
232 if let Ok(source_ptr) = source_map.pat_syntax(id) {
233 if let Some(expr) = source_ptr.value.as_ref().left() {
234 let root = source_ptr.file_syntax(self.db.upcast());
235 if let ast::Pat::IdentPat(ident_pat) = expr.to_node(&root) {
236 let parent = match ident_pat.syntax().parent() {
237 Some(parent) => parent,
240 let name_ast = match ident_pat.name() {
241 Some(name_ast) => name_ast,
245 let is_param = ast::Param::can_cast(parent.kind());
247 // We have to check that it's either `let var = ...` or `var @ Variant(_)` statement,
248 // because e.g. match arms are patterns as well.
249 // In other words, we check that it's a named variable binding.
250 let is_binding = ast::LetStmt::can_cast(parent.kind())
251 || (ast::MatchArm::can_cast(parent.kind())
252 && ident_pat.at_token().is_some());
253 if !(is_param || is_binding) {
254 // This pattern is not an actual variable declaration, e.g. `Some(val) => {..}` match arm.
259 if is_param { IdentType::Parameter } else { IdentType::Variable };
261 let diagnostic = IncorrectCase {
262 file: source_ptr.file_id,
264 ident: AstPtr::new(&name_ast),
265 expected_case: replacement.expected_case,
266 ident_text: replacement.current_name.to_string(),
267 suggested_text: replacement.suggested_text,
270 self.sink.push(diagnostic);
277 fn validate_struct(&mut self, struct_id: StructId) {
278 let data = self.db.struct_data(struct_id);
280 let non_camel_case_allowed =
281 self.allowed(struct_id.into(), allow::NON_CAMEL_CASE_TYPES, false);
282 let non_snake_case_allowed = self.allowed(struct_id.into(), allow::NON_SNAKE_CASE, false);
284 // Check the structure name.
285 let struct_name = data.name.to_string();
286 let struct_name_replacement = if !non_camel_case_allowed {
287 to_camel_case(&struct_name).map(|new_name| Replacement {
288 current_name: data.name.clone(),
289 suggested_text: new_name,
290 expected_case: CaseType::UpperCamelCase,
296 // Check the field names.
297 let mut struct_fields_replacements = Vec::new();
299 if !non_snake_case_allowed {
300 if let VariantData::Record(fields) = data.variant_data.as_ref() {
301 for (_, field) in fields.iter() {
302 let field_name = field.name.to_string();
303 if let Some(new_name) = to_lower_snake_case(&field_name) {
304 let replacement = Replacement {
305 current_name: field.name.clone(),
306 suggested_text: new_name,
307 expected_case: CaseType::LowerSnakeCase,
309 struct_fields_replacements.push(replacement);
315 // If there is at least one element to spawn a warning on, go to the source map and generate a warning.
316 self.create_incorrect_case_diagnostic_for_struct(
318 struct_name_replacement,
319 struct_fields_replacements,
323 /// Given the information about incorrect names in the struct declaration, looks up into the source code
324 /// for exact locations and adds diagnostics into the sink.
325 fn create_incorrect_case_diagnostic_for_struct(
328 struct_name_replacement: Option<Replacement>,
329 struct_fields_replacements: Vec<Replacement>,
331 // XXX: Only look at sources if we do have incorrect names.
332 if struct_name_replacement.is_none() && struct_fields_replacements.is_empty() {
336 let struct_loc = struct_id.lookup(self.db.upcast());
337 let struct_src = struct_loc.source(self.db.upcast());
339 if let Some(replacement) = struct_name_replacement {
340 let ast_ptr = match struct_src.value.name() {
344 "Replacement ({:?}) was generated for a structure without a name: {:?}",
352 let diagnostic = IncorrectCase {
353 file: struct_src.file_id,
354 ident_type: IdentType::Structure,
355 ident: AstPtr::new(&ast_ptr),
356 expected_case: replacement.expected_case,
357 ident_text: replacement.current_name.to_string(),
358 suggested_text: replacement.suggested_text,
361 self.sink.push(diagnostic);
364 let struct_fields_list = match struct_src.value.field_list() {
365 Some(ast::FieldList::RecordFieldList(fields)) => fields,
368 struct_fields_replacements.is_empty(),
369 "Replacements ({:?}) were generated for a structure fields which had no fields list: {:?}",
370 struct_fields_replacements,
376 let mut struct_fields_iter = struct_fields_list.fields();
377 for field_to_rename in struct_fields_replacements {
378 // We assume that parameters in replacement are in the same order as in the
379 // actual params list, but just some of them (ones that named correctly) are skipped.
381 match struct_fields_iter.next().and_then(|field| field.name()) {
382 Some(field_name) => {
383 if field_name.as_name() == field_to_rename.current_name {
389 "Replacement ({:?}) was generated for a structure field which was not found: {:?}",
390 field_to_rename, struct_src
397 let diagnostic = IncorrectCase {
398 file: struct_src.file_id,
399 ident_type: IdentType::Field,
400 ident: AstPtr::new(&ast_ptr),
401 expected_case: field_to_rename.expected_case,
402 ident_text: field_to_rename.current_name.to_string(),
403 suggested_text: field_to_rename.suggested_text,
406 self.sink.push(diagnostic);
410 fn validate_enum(&mut self, enum_id: EnumId) {
411 let data = self.db.enum_data(enum_id);
413 // Check whether non-camel case names are allowed for this enum.
414 if self.allowed(enum_id.into(), allow::NON_CAMEL_CASE_TYPES, false) {
418 // Check the enum name.
419 let enum_name = data.name.to_string();
420 let enum_name_replacement = to_camel_case(&enum_name).map(|new_name| Replacement {
421 current_name: data.name.clone(),
422 suggested_text: new_name,
423 expected_case: CaseType::UpperCamelCase,
426 // Check the field names.
427 let enum_fields_replacements = data
430 .filter_map(|(_, variant)| {
432 current_name: variant.name.clone(),
433 suggested_text: to_camel_case(&variant.name.to_string())?,
434 expected_case: CaseType::UpperCamelCase,
439 // If there is at least one element to spawn a warning on, go to the source map and generate a warning.
440 self.create_incorrect_case_diagnostic_for_enum(
442 enum_name_replacement,
443 enum_fields_replacements,
447 /// Given the information about incorrect names in the struct declaration, looks up into the source code
448 /// for exact locations and adds diagnostics into the sink.
449 fn create_incorrect_case_diagnostic_for_enum(
452 enum_name_replacement: Option<Replacement>,
453 enum_variants_replacements: Vec<Replacement>,
455 // XXX: only look at sources if we do have incorrect names
456 if enum_name_replacement.is_none() && enum_variants_replacements.is_empty() {
460 let enum_loc = enum_id.lookup(self.db.upcast());
461 let enum_src = enum_loc.source(self.db.upcast());
463 if let Some(replacement) = enum_name_replacement {
464 let ast_ptr = match enum_src.value.name() {
468 "Replacement ({:?}) was generated for a enum without a name: {:?}",
476 let diagnostic = IncorrectCase {
477 file: enum_src.file_id,
478 ident_type: IdentType::Enum,
479 ident: AstPtr::new(&ast_ptr),
480 expected_case: replacement.expected_case,
481 ident_text: replacement.current_name.to_string(),
482 suggested_text: replacement.suggested_text,
485 self.sink.push(diagnostic);
488 let enum_variants_list = match enum_src.value.variant_list() {
489 Some(variants) => variants,
492 enum_variants_replacements.is_empty(),
493 "Replacements ({:?}) were generated for a enum variants which had no fields list: {:?}",
494 enum_variants_replacements,
500 let mut enum_variants_iter = enum_variants_list.variants();
501 for variant_to_rename in enum_variants_replacements {
502 // We assume that parameters in replacement are in the same order as in the
503 // actual params list, but just some of them (ones that named correctly) are skipped.
505 match enum_variants_iter.next().and_then(|v| v.name()) {
506 Some(variant_name) => {
507 if variant_name.as_name() == variant_to_rename.current_name {
513 "Replacement ({:?}) was generated for a enum variant which was not found: {:?}",
514 variant_to_rename, enum_src
521 let diagnostic = IncorrectCase {
522 file: enum_src.file_id,
523 ident_type: IdentType::Variant,
524 ident: AstPtr::new(&ast_ptr),
525 expected_case: variant_to_rename.expected_case,
526 ident_text: variant_to_rename.current_name.to_string(),
527 suggested_text: variant_to_rename.suggested_text,
530 self.sink.push(diagnostic);
534 fn validate_const(&mut self, const_id: ConstId) {
535 let data = self.db.const_data(const_id);
537 if self.allowed(const_id.into(), allow::NON_UPPER_CASE_GLOBAL, false) {
541 let name = match &data.name {
546 let const_name = name.to_string();
547 let replacement = if let Some(new_name) = to_upper_snake_case(&const_name) {
549 current_name: name.clone(),
550 suggested_text: new_name,
551 expected_case: CaseType::UpperSnakeCase,
554 // Nothing to do here.
558 let const_loc = const_id.lookup(self.db.upcast());
559 let const_src = const_loc.source(self.db.upcast());
561 let ast_ptr = match const_src.value.name() {
566 let diagnostic = IncorrectCase {
567 file: const_src.file_id,
568 ident_type: IdentType::Constant,
569 ident: AstPtr::new(&ast_ptr),
570 expected_case: replacement.expected_case,
571 ident_text: replacement.current_name.to_string(),
572 suggested_text: replacement.suggested_text,
575 self.sink.push(diagnostic);
578 fn validate_static(&mut self, static_id: StaticId) {
579 let data = self.db.static_data(static_id);
581 cov_mark::hit!(extern_static_incorrect_case_ignored);
585 if self.allowed(static_id.into(), allow::NON_UPPER_CASE_GLOBAL, false) {
589 let name = match &data.name {
594 let static_name = name.to_string();
595 let replacement = if let Some(new_name) = to_upper_snake_case(&static_name) {
597 current_name: name.clone(),
598 suggested_text: new_name,
599 expected_case: CaseType::UpperSnakeCase,
602 // Nothing to do here.
606 let static_loc = static_id.lookup(self.db.upcast());
607 let static_src = static_loc.source(self.db.upcast());
609 let ast_ptr = match static_src.value.name() {
614 let diagnostic = IncorrectCase {
615 file: static_src.file_id,
616 ident_type: IdentType::StaticVariable,
617 ident: AstPtr::new(&ast_ptr),
618 expected_case: replacement.expected_case,
619 ident_text: replacement.current_name.to_string(),
620 suggested_text: replacement.suggested_text,
623 self.sink.push(diagnostic);
629 use crate::diagnostics::tests::check_diagnostics;
632 fn incorrect_function_name() {
635 fn NonSnakeCaseName() {}
636 // ^^^^^^^^^^^^^^^^ Function `NonSnakeCaseName` should have snake_case name, e.g. `non_snake_case_name`
642 fn incorrect_function_params() {
645 fn foo(SomeParam: u8) {}
646 // ^^^^^^^^^ Parameter `SomeParam` should have snake_case name, e.g. `some_param`
648 fn foo2(ok_param: &str, CAPS_PARAM: u8) {}
649 // ^^^^^^^^^^ Parameter `CAPS_PARAM` should have snake_case name, e.g. `caps_param`
655 fn incorrect_variable_names() {
660 // ^^^^^^^^^^ Variable `SOME_VALUE` should have snake_case name, e.g. `some_value`
661 let AnotherValue = 20;
662 // ^^^^^^^^^^^^ Variable `AnotherValue` should have snake_case name, e.g. `another_value`
669 fn incorrect_struct_names() {
672 struct non_camel_case_name {}
673 // ^^^^^^^^^^^^^^^^^^^ Structure `non_camel_case_name` should have CamelCase name, e.g. `NonCamelCaseName`
675 struct SCREAMING_CASE {}
676 // ^^^^^^^^^^^^^^ Structure `SCREAMING_CASE` should have CamelCase name, e.g. `ScreamingCase`
682 fn no_diagnostic_for_camel_cased_acronyms_in_struct_name() {
691 fn incorrect_struct_field() {
694 struct SomeStruct { SomeField: u8 }
695 // ^^^^^^^^^ Field `SomeField` should have snake_case name, e.g. `some_field`
701 fn incorrect_enum_names() {
704 enum some_enum { Val(u8) }
705 // ^^^^^^^^^ Enum `some_enum` should have CamelCase name, e.g. `SomeEnum`
708 // ^^^^^^^^^ Enum `SOME_ENUM` should have CamelCase name, e.g. `SomeEnum`
714 fn no_diagnostic_for_camel_cased_acronyms_in_enum_name() {
723 fn incorrect_enum_variant_name() {
726 enum SomeEnum { SOME_VARIANT(u8) }
727 // ^^^^^^^^^^^^ Variant `SOME_VARIANT` should have CamelCase name, e.g. `SomeVariant`
733 fn incorrect_const_name() {
736 const some_weird_const: u8 = 10;
737 // ^^^^^^^^^^^^^^^^ Constant `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST`
740 const someConstInFunc: &str = "hi there";
741 // ^^^^^^^^^^^^^^^ Constant `someConstInFunc` should have UPPER_SNAKE_CASE name, e.g. `SOME_CONST_IN_FUNC`
749 fn incorrect_static_name() {
752 static some_weird_const: u8 = 10;
753 // ^^^^^^^^^^^^^^^^ Static variable `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST`
756 static someConstInFunc: &str = "hi there";
757 // ^^^^^^^^^^^^^^^ Static variable `someConstInFunc` should have UPPER_SNAKE_CASE name, e.g. `SOME_CONST_IN_FUNC`
764 fn fn_inside_impl_struct() {
768 // ^^^^^^^^^^ Structure `someStruct` should have CamelCase name, e.g. `SomeStruct`
772 // ^^^^^^^^ Function `SomeFunc` should have snake_case name, e.g. `some_func`
773 static someConstInFunc: &str = "hi there";
774 // ^^^^^^^^^^^^^^^ Static variable `someConstInFunc` should have UPPER_SNAKE_CASE name, e.g. `SOME_CONST_IN_FUNC`
775 let WHY_VAR_IS_CAPS = 10;
776 // ^^^^^^^^^^^^^^^ Variable `WHY_VAR_IS_CAPS` should have snake_case name, e.g. `why_var_is_caps`
784 fn no_diagnostic_for_enum_varinats() {
787 enum Option { Some, None }
803 enum Option { Some, None }
807 SOME_VAR @ None => (),
808 // ^^^^^^^^ Variable `SOME_VAR` should have snake_case name, e.g. `some_var`
817 fn allow_attributes() {
820 #[allow(non_snake_case)]
821 fn NonSnakeCaseName(SOME_VAR: u8) -> u8{
822 // cov_flags generated output from elsewhere in this file
825 static lower_case: u8;
828 let OtherVar = SOME_VAR + 1;
832 #[allow(nonstandard_style)]
833 mod CheckNonstandardStyle {
834 fn HiImABadFnName() {}
839 fn HiImABadFnName() {}
843 #![allow(non_snake_case)]
844 fn CheckItWorksWithModAttr(BAD_NAME_HI: u8) {}
847 #[allow(non_snake_case, non_camel_case_types)]
848 pub struct some_type {
853 #[allow(non_upper_case_globals)]
854 pub const some_const: u8 = 10;
856 #[allow(non_upper_case_globals)]
857 pub static SomeStatic: u8 = 10;
863 fn allow_attributes_crate_attr() {
866 #![allow(non_snake_case)]
869 fn CheckItWorksWithCrateAttr(BAD_NAME_HI: u8) {}
877 fn bug_trait_inside_fn() {
879 // This is broken, and in fact, should not even be looked at by this
880 // lint in the first place. There's weird stuff going on in the
882 // It's currently being brought in by:
883 // * validate_func on `a` recursing into modules
884 // * then it finds the trait and then the function while iterating
886 // * then validate_func is called on Dirty
887 // * ... which then proceeds to look at some unknown module taking no
888 // attrs from either the impl or the fn a, and then finally to the root
891 // It should find the attribute on the trait, but it *doesn't even see
892 // the trait* as far as I can tell.
900 // this comes out of bitflags, mostly
901 #[allow(non_snake_case)]
903 const HiImAlsoBad: u8 = 2;
905 fn Dirty(&self) -> bool {
918 fn bug_traits_arent_checked() {
919 // FIXME: Traits and functions in traits aren't currently checked by
920 // r-a, even though rustc will complain about them.
924 // ^^^^^^^^^ Trait `BAD_TRAIT` should have CamelCase name, e.g. `BadTrait`
926 // ^^^^^^^^^^^^ Function `BAD_FUNCTION` should have snake_case name, e.g. `bad_function`
928 // ^^^^^^^^^^^^ Function `BadFunction` should have snake_case name, e.g. `bad_function`
935 fn ignores_extern_items() {
936 cov_mark::check!(extern_func_incorrect_case_ignored);
937 cov_mark::check!(extern_static_incorrect_case_ignored);
941 fn NonSnakeCaseName(SOME_VAR: u8) -> u8;
942 pub static SomeStatic: u8 = 10;
949 fn infinite_loop_inner_items() {
961 #[test] // Issue #8809.
962 fn parenthesized_parameter() {
963 check_diagnostics(r#"fn f((O): _) {}"#)