aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIgor Aleksanov <[email protected]>2020-10-03 11:39:10 +0100
committerIgor Aleksanov <[email protected]>2020-10-12 08:59:54 +0100
commitf5cea35986a0c8182ca427f10e20bc97ec564315 (patch)
tree80179332f09f6ad3bde913c9b6814ef2f2159dec
parent4039176ec63e5c75d76398f2debe26ac6fa59cbc (diff)
Add checks for function parameters
-rw-r--r--crates/hir_ty/src/diagnostics.rs4
-rw-r--r--crates/hir_ty/src/diagnostics/decl_check.rs97
2 files changed, 94 insertions, 7 deletions
diff --git a/crates/hir_ty/src/diagnostics.rs b/crates/hir_ty/src/diagnostics.rs
index 7227e7010..24fff690a 100644
--- a/crates/hir_ty/src/diagnostics.rs
+++ b/crates/hir_ty/src/diagnostics.rs
@@ -269,6 +269,7 @@ pub struct IncorrectCase {
269 pub file: HirFileId, 269 pub file: HirFileId,
270 pub ident: SyntaxNodePtr, 270 pub ident: SyntaxNodePtr,
271 pub expected_case: CaseType, 271 pub expected_case: CaseType,
272 pub ident_type: String,
272 pub ident_text: String, 273 pub ident_text: String,
273 pub suggested_text: String, 274 pub suggested_text: String,
274} 275}
@@ -280,7 +281,8 @@ impl Diagnostic for IncorrectCase {
280 281
281 fn message(&self) -> String { 282 fn message(&self) -> String {
282 format!( 283 format!(
283 "Argument `{}` should have a {} name, e.g. `{}`", 284 "{} `{}` should have a {} name, e.g. `{}`",
285 self.ident_type,
284 self.ident_text, 286 self.ident_text,
285 self.expected_case.to_string(), 287 self.expected_case.to_string(),
286 self.suggested_text 288 self.suggested_text
diff --git a/crates/hir_ty/src/diagnostics/decl_check.rs b/crates/hir_ty/src/diagnostics/decl_check.rs
index 6c3cd65c5..083df3772 100644
--- a/crates/hir_ty/src/diagnostics/decl_check.rs
+++ b/crates/hir_ty/src/diagnostics/decl_check.rs
@@ -21,7 +21,10 @@ use hir_def::{
21 AdtId, FunctionId, Lookup, ModuleDefId, 21 AdtId, FunctionId, Lookup, ModuleDefId,
22}; 22};
23use hir_expand::{diagnostics::DiagnosticSink, name::Name}; 23use hir_expand::{diagnostics::DiagnosticSink, name::Name};
24use syntax::{ast::NameOwner, AstPtr}; 24use syntax::{
25 ast::{self, NameOwner},
26 AstPtr,
27};
25 28
26use crate::{ 29use crate::{
27 db::HirDatabase, 30 db::HirDatabase,
@@ -122,7 +125,8 @@ impl<'a, 'b> DeclValidator<'a, 'b> {
122 } else { 125 } else {
123 // We don't want rust-analyzer to panic over this, but it is definitely some kind of error in the logic. 126 // We don't want rust-analyzer to panic over this, but it is definitely some kind of error in the logic.
124 log::error!( 127 log::error!(
125 "Replacement was generated for a function without a name: {:?}", 128 "Replacement ({:?}) was generated for a function without a name: {:?}",
129 replacement,
126 fn_src 130 fn_src
127 ); 131 );
128 return; 132 return;
@@ -130,6 +134,7 @@ impl<'a, 'b> DeclValidator<'a, 'b> {
130 134
131 let diagnostic = IncorrectCase { 135 let diagnostic = IncorrectCase {
132 file: fn_src.file_id, 136 file: fn_src.file_id,
137 ident_type: "Function".to_string(),
133 ident: AstPtr::new(&ast_ptr).into(), 138 ident: AstPtr::new(&ast_ptr).into(),
134 expected_case: replacement.expected_case, 139 expected_case: replacement.expected_case,
135 ident_text: replacement.current_name.to_string(), 140 ident_text: replacement.current_name.to_string(),
@@ -139,15 +144,71 @@ impl<'a, 'b> DeclValidator<'a, 'b> {
139 self.sink.push(diagnostic); 144 self.sink.push(diagnostic);
140 } 145 }
141 146
142 // let item_tree = db.item_tree(loc.id.file_id); 147 let fn_params_list = match fn_src.value.param_list() {
143 // let fn_def = &item_tree[fn_loc.id.value]; 148 Some(params) => params,
144 // let (_, source_map) = db.body_with_source_map(func.into()); 149 None => {
150 if !fn_param_replacements.is_empty() {
151 log::error!(
152 "Replacements ({:?}) were generated for a function parameters which had no parameters list: {:?}",
153 fn_param_replacements, fn_src
154 );
155 }
156 return;
157 }
158 };
159 let mut fn_params_iter = fn_params_list.params();
160 for param_to_rename in fn_param_replacements {
161 // We assume that parameters in replacement are in the same order as in the
162 // actual params list, but just some of them (ones that named correctly) are skipped.
163 let ast_ptr = loop {
164 match fn_params_iter.next() {
165 Some(element)
166 if pat_equals_to_name(element.pat(), &param_to_rename.current_name) =>
167 {
168 break element.pat().unwrap()
169 }
170 Some(_) => {}
171 None => {
172 log::error!(
173 "Replacement ({:?}) was generated for a function parameter which was not found: {:?}",
174 param_to_rename, fn_src
175 );
176 return;
177 }
178 }
179 };
180
181 let diagnostic = IncorrectCase {
182 file: fn_src.file_id,
183 ident_type: "Argument".to_string(),
184 ident: AstPtr::new(&ast_ptr).into(),
185 expected_case: param_to_rename.expected_case,
186 ident_text: param_to_rename.current_name.to_string(),
187 suggested_text: param_to_rename.suggested_text,
188 };
189
190 self.sink.push(diagnostic);
191 }
145 } 192 }
146 193
147 fn validate_adt(&mut self, db: &dyn HirDatabase, adt: AdtId) {} 194 fn validate_adt(&mut self, db: &dyn HirDatabase, adt: AdtId) {}
148} 195}
149 196
197fn pat_equals_to_name(pat: Option<ast::Pat>, name: &Name) -> bool {
198 if let Some(ast::Pat::IdentPat(ident)) = pat {
199 ident.to_string() == name.to_string()
200 } else {
201 false
202 }
203}
204
150fn to_lower_snake_case(ident: &str) -> Option<String> { 205fn to_lower_snake_case(ident: &str) -> Option<String> {
206 // First, assume that it's UPPER_SNAKE_CASE.
207 if let Some(normalized) = to_lower_snake_case_from_upper_snake_case(ident) {
208 return Some(normalized);
209 }
210
211 // Otherwise, assume that it's CamelCase.
151 let lower_snake_case = stdx::to_lower_snake_case(ident); 212 let lower_snake_case = stdx::to_lower_snake_case(ident);
152 213
153 if lower_snake_case == ident { 214 if lower_snake_case == ident {
@@ -157,6 +218,17 @@ fn to_lower_snake_case(ident: &str) -> Option<String> {
157 } 218 }
158} 219}
159 220
221fn to_lower_snake_case_from_upper_snake_case(ident: &str) -> Option<String> {
222 let is_upper_snake_case = ident.chars().all(|c| c.is_ascii_uppercase() || c == '_');
223
224 if is_upper_snake_case {
225 let string = ident.chars().map(|c| c.to_ascii_lowercase()).collect();
226 Some(string)
227 } else {
228 None
229 }
230}
231
160#[cfg(test)] 232#[cfg(test)]
161mod tests { 233mod tests {
162 use crate::diagnostics::tests::check_diagnostics; 234 use crate::diagnostics::tests::check_diagnostics;
@@ -166,7 +238,20 @@ mod tests {
166 check_diagnostics( 238 check_diagnostics(
167 r#" 239 r#"
168fn NonSnakeCaseName() {} 240fn NonSnakeCaseName() {}
169// ^^^^^^^^^^^^^^^^ Argument `NonSnakeCaseName` should have a snake_case name, e.g. `non_snake_case_name` 241// ^^^^^^^^^^^^^^^^ Function `NonSnakeCaseName` should have a snake_case name, e.g. `non_snake_case_name`
242"#,
243 );
244 }
245
246 #[test]
247 fn incorrect_function_params() {
248 check_diagnostics(
249 r#"
250fn foo(SomeParam: u8) {}
251 // ^^^^^^^^^ Argument `SomeParam` should have a snake_case name, e.g. `some_param`
252
253fn foo2(ok_param: &str, CAPS_PARAM: u8) {}
254 // ^^^^^^^^^^ Argument `CAPS_PARAM` should have a snake_case name, e.g. `caps_param`
170"#, 255"#,
171 ); 256 );
172 } 257 }