diff --git a/crates/hir_ty/src/diagnostics.rs b/crates/hir_ty/src/diagnostics.rs index 24fff690a74..bd370e3b2d5 100644 --- a/crates/hir_ty/src/diagnostics.rs +++ b/crates/hir_ty/src/diagnostics.rs @@ -257,7 +257,7 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let repr = match self { CaseType::LowerSnakeCase => "snake_case", CaseType::UpperSnakeCase => "UPPER_SNAKE_CASE", - CaseType::UpperCamelCase => "UpperCamelCase", + CaseType::UpperCamelCase => "CamelCase", }; write!(f, "{}", repr) diff --git a/crates/hir_ty/src/diagnostics/decl_check.rs b/crates/hir_ty/src/diagnostics/decl_check.rs index 1a0906492d6..b7f511fd898 100644 --- a/crates/hir_ty/src/diagnostics/decl_check.rs +++ b/crates/hir_ty/src/diagnostics/decl_check.rs @@ -14,6 +14,7 @@ use std::sync::Arc; use hir_def::{ + adt::VariantData, body::Body, db::DefDatabase, expr::{Expr, ExprId, UnaryOp}, @@ -205,6 +206,133 @@ fn validate_adt(&mut self, db: &dyn HirDatabase, adt: AdtId) { fn validate_struct(&mut self, db: &dyn HirDatabase, struct_id: StructId) { let data = db.struct_data(struct_id); + + // 1. Check the structure name. + let struct_name = data.name.to_string(); + let struct_name_replacement = if let Some(new_name) = to_camel_case(&struct_name) { + let replacement = Replacement { + current_name: data.name.clone(), + suggested_text: new_name, + expected_case: CaseType::UpperCamelCase, + }; + Some(replacement) + } else { + None + }; + + // 2. Check the field names. + let mut struct_fields_replacements = Vec::new(); + + if let VariantData::Record(fields) = data.variant_data.as_ref() { + for (_, field) in fields.iter() { + let field_name = field.name.to_string(); + if let Some(new_name) = to_lower_snake_case(&field_name) { + let replacement = Replacement { + current_name: field.name.clone(), + suggested_text: new_name, + expected_case: CaseType::LowerSnakeCase, + }; + struct_fields_replacements.push(replacement); + } + } + } + + // 3. If there is at least one element to spawn a warning on, go to the source map and generate a warning. + self.create_incorrect_case_diagnostic_for_struct( + struct_id, + db, + struct_name_replacement, + struct_fields_replacements, + ) + } + + /// Given the information about incorrect names in the struct declaration, looks up into the source code + /// for exact locations and adds diagnostics into the sink. + fn create_incorrect_case_diagnostic_for_struct( + &mut self, + struct_id: StructId, + db: &dyn HirDatabase, + struct_name_replacement: Option, + struct_fields_replacements: Vec, + ) { + // XXX: only look at sources if we do have incorrect names + if struct_name_replacement.is_none() && struct_fields_replacements.is_empty() { + return; + } + + let struct_loc = struct_id.lookup(db.upcast()); + let struct_src = struct_loc.source(db.upcast()); + + if let Some(replacement) = struct_name_replacement { + let ast_ptr = if let Some(name) = struct_src.value.name() { + name + } else { + // We don't want rust-analyzer to panic over this, but it is definitely some kind of error in the logic. + log::error!( + "Replacement ({:?}) was generated for a structure without a name: {:?}", + replacement, + struct_src + ); + return; + }; + + let diagnostic = IncorrectCase { + file: struct_src.file_id, + ident_type: "Structure".to_string(), + ident: AstPtr::new(&ast_ptr).into(), + expected_case: replacement.expected_case, + ident_text: replacement.current_name.to_string(), + suggested_text: replacement.suggested_text, + }; + + self.sink.push(diagnostic); + } + + // let fn_params_list = match fn_src.value.param_list() { + // Some(params) => params, + // None => { + // if !fn_param_replacements.is_empty() { + // log::error!( + // "Replacements ({:?}) were generated for a function parameters which had no parameters list: {:?}", + // fn_param_replacements, fn_src + // ); + // } + // return; + // } + // }; + // let mut fn_params_iter = fn_params_list.params(); + // for param_to_rename in fn_param_replacements { + // // We assume that parameters in replacement are in the same order as in the + // // actual params list, but just some of them (ones that named correctly) are skipped. + // let ast_ptr = loop { + // match fn_params_iter.next() { + // Some(element) + // if pat_equals_to_name(element.pat(), ¶m_to_rename.current_name) => + // { + // break element.pat().unwrap() + // } + // Some(_) => {} + // None => { + // log::error!( + // "Replacement ({:?}) was generated for a function parameter which was not found: {:?}", + // param_to_rename, fn_src + // ); + // return; + // } + // } + // }; + + // let diagnostic = IncorrectCase { + // file: fn_src.file_id, + // ident_type: "Argument".to_string(), + // ident: AstPtr::new(&ast_ptr).into(), + // expected_case: param_to_rename.expected_case, + // ident_text: param_to_rename.current_name.to_string(), + // suggested_text: param_to_rename.suggested_text, + // }; + + // self.sink.push(diagnostic); + // } } fn validate_enum(&mut self, db: &dyn HirDatabase, enum_id: EnumId) { @@ -243,6 +371,16 @@ fn foo(SomeParam: u8) {} fn foo2(ok_param: &str, CAPS_PARAM: u8) {} // ^^^^^^^^^^ Argument `CAPS_PARAM` should have a snake_case name, e.g. `caps_param` +"#, + ); + } + + #[test] + fn incorrect_struct_name() { + check_diagnostics( + r#" +struct non_camel_case_name {} + // ^^^^^^^^^^^^^^^^^^^ Structure `non_camel_case_name` should have a CamelCase name, e.g. `NonCamelCaseName` "#, ); }