diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/hir_ty/src/diagnostics.rs | 4 | ||||
-rw-r--r-- | crates/hir_ty/src/diagnostics/decl_check.rs | 97 |
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 | }; |
23 | use hir_expand::{diagnostics::DiagnosticSink, name::Name}; | 23 | use hir_expand::{diagnostics::DiagnosticSink, name::Name}; |
24 | use syntax::{ast::NameOwner, AstPtr}; | 24 | use syntax::{ |
25 | ast::{self, NameOwner}, | ||
26 | AstPtr, | ||
27 | }; | ||
25 | 28 | ||
26 | use crate::{ | 29 | use 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(), ¶m_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 | ||
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 | |||
150 | fn to_lower_snake_case(ident: &str) -> Option<String> { | 205 | 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. | ||
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 | ||
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 | |||
160 | #[cfg(test)] | 232 | #[cfg(test)] |
161 | mod tests { | 233 | mod 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#" |
168 | fn NonSnakeCaseName() {} | 240 | 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` | ||
170 | "#, | 255 | "#, |
171 | ); | 256 | ); |
172 | } | 257 | } |