Skip to content

Commit f5cea35

Browse files
committed
Add checks for function parameters
1 parent 4039176 commit f5cea35

File tree

2 files changed

+94
-7
lines changed

2 files changed

+94
-7
lines changed

crates/hir_ty/src/diagnostics.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ pub struct IncorrectCase {
269269
pub file: HirFileId,
270270
pub ident: SyntaxNodePtr,
271271
pub expected_case: CaseType,
272+
pub ident_type: String,
272273
pub ident_text: String,
273274
pub suggested_text: String,
274275
}
@@ -280,7 +281,8 @@ impl Diagnostic for IncorrectCase {
280281

281282
fn message(&self) -> String {
282283
format!(
283-
"Argument `{}` should have a {} name, e.g. `{}`",
284+
"{} `{}` should have a {} name, e.g. `{}`",
285+
self.ident_type,
284286
self.ident_text,
285287
self.expected_case.to_string(),
286288
self.suggested_text

crates/hir_ty/src/diagnostics/decl_check.rs

Lines changed: 91 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ use hir_def::{
2121
AdtId, FunctionId, Lookup, ModuleDefId,
2222
};
2323
use hir_expand::{diagnostics::DiagnosticSink, name::Name};
24-
use syntax::{ast::NameOwner, AstPtr};
24+
use syntax::{
25+
ast::{self, NameOwner},
26+
AstPtr,
27+
};
2528

2629
use crate::{
2730
db::HirDatabase,
@@ -122,14 +125,16 @@ impl<'a, 'b> DeclValidator<'a, 'b> {
122125
} else {
123126
// We don't want rust-analyzer to panic over this, but it is definitely some kind of error in the logic.
124127
log::error!(
125-
"Replacement was generated for a function without a name: {:?}",
128+
"Replacement ({:?}) was generated for a function without a name: {:?}",
129+
replacement,
126130
fn_src
127131
);
128132
return;
129133
};
130134

131135
let diagnostic = IncorrectCase {
132136
file: fn_src.file_id,
137+
ident_type: "Function".to_string(),
133138
ident: AstPtr::new(&ast_ptr).into(),
134139
expected_case: replacement.expected_case,
135140
ident_text: replacement.current_name.to_string(),
@@ -139,15 +144,71 @@ impl<'a, 'b> DeclValidator<'a, 'b> {
139144
self.sink.push(diagnostic);
140145
}
141146

142-
// let item_tree = db.item_tree(loc.id.file_id);
143-
// let fn_def = &item_tree[fn_loc.id.value];
144-
// let (_, source_map) = db.body_with_source_map(func.into());
147+
let fn_params_list = match fn_src.value.param_list() {
148+
Some(params) => params,
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+
}
145192
}
146193

147194
fn validate_adt(&mut self, db: &dyn HirDatabase, adt: AdtId) {}
148195
}
149196

197+
fn 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+
150205
fn 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.
151212
let lower_snake_case = stdx::to_lower_snake_case(ident);
152213

153214
if lower_snake_case == ident {
@@ -157,6 +218,17 @@ fn to_lower_snake_case(ident: &str) -> Option<String> {
157218
}
158219
}
159220

221+
fn 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+
160232
#[cfg(test)]
161233
mod tests {
162234
use crate::diagnostics::tests::check_diagnostics;
@@ -166,7 +238,20 @@ mod tests {
166238
check_diagnostics(
167239
r#"
168240
fn 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#"
250+
fn foo(SomeParam: u8) {}
251+
// ^^^^^^^^^ Argument `SomeParam` should have a snake_case name, e.g. `some_param`
252+
253+
fn foo2(ok_param: &str, CAPS_PARAM: u8) {}
254+
// ^^^^^^^^^^ Argument `CAPS_PARAM` should have a snake_case name, e.g. `caps_param`
170255
"#,
171256
);
172257
}

0 commit comments

Comments
 (0)