diff options
Diffstat (limited to 'crates/hir_ty/src/diagnostics/decl_check.rs')
-rw-r--r-- | crates/hir_ty/src/diagnostics/decl_check.rs | 833 |
1 files changed, 833 insertions, 0 deletions
diff --git a/crates/hir_ty/src/diagnostics/decl_check.rs b/crates/hir_ty/src/diagnostics/decl_check.rs new file mode 100644 index 000000000..f987636fe --- /dev/null +++ b/crates/hir_ty/src/diagnostics/decl_check.rs | |||
@@ -0,0 +1,833 @@ | |||
1 | //! Provides validators for the item declarations. | ||
2 | //! | ||
3 | //! This includes the following items: | ||
4 | //! | ||
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(_)`) | ||
12 | |||
13 | mod case_conv; | ||
14 | |||
15 | use hir_def::{ | ||
16 | adt::VariantData, | ||
17 | expr::{Pat, PatId}, | ||
18 | src::HasSource, | ||
19 | AdtId, ConstId, EnumId, FunctionId, Lookup, ModuleDefId, StaticId, StructId, | ||
20 | }; | ||
21 | use hir_expand::{ | ||
22 | diagnostics::DiagnosticSink, | ||
23 | name::{AsName, Name}, | ||
24 | }; | ||
25 | use syntax::{ | ||
26 | ast::{self, NameOwner}, | ||
27 | AstNode, AstPtr, | ||
28 | }; | ||
29 | |||
30 | use crate::{ | ||
31 | db::HirDatabase, | ||
32 | diagnostics::{decl_check::case_conv::*, CaseType, IncorrectCase}, | ||
33 | }; | ||
34 | |||
35 | pub(super) struct DeclValidator<'a, 'b: 'a> { | ||
36 | owner: ModuleDefId, | ||
37 | sink: &'a mut DiagnosticSink<'b>, | ||
38 | } | ||
39 | |||
40 | #[derive(Debug)] | ||
41 | struct Replacement { | ||
42 | current_name: Name, | ||
43 | suggested_text: String, | ||
44 | expected_case: CaseType, | ||
45 | } | ||
46 | |||
47 | impl<'a, 'b> DeclValidator<'a, 'b> { | ||
48 | pub(super) fn new( | ||
49 | owner: ModuleDefId, | ||
50 | sink: &'a mut DiagnosticSink<'b>, | ||
51 | ) -> DeclValidator<'a, 'b> { | ||
52 | DeclValidator { owner, sink } | ||
53 | } | ||
54 | |||
55 | pub(super) fn validate_item(&mut self, db: &dyn HirDatabase) { | ||
56 | match self.owner { | ||
57 | ModuleDefId::FunctionId(func) => self.validate_func(db, func), | ||
58 | ModuleDefId::AdtId(adt) => self.validate_adt(db, adt), | ||
59 | ModuleDefId::ConstId(const_id) => self.validate_const(db, const_id), | ||
60 | ModuleDefId::StaticId(static_id) => self.validate_static(db, static_id), | ||
61 | _ => return, | ||
62 | } | ||
63 | } | ||
64 | |||
65 | fn validate_adt(&mut self, db: &dyn HirDatabase, adt: AdtId) { | ||
66 | match adt { | ||
67 | AdtId::StructId(struct_id) => self.validate_struct(db, struct_id), | ||
68 | AdtId::EnumId(enum_id) => self.validate_enum(db, enum_id), | ||
69 | AdtId::UnionId(_) => { | ||
70 | // Unions aren't yet supported by this validator. | ||
71 | } | ||
72 | } | ||
73 | } | ||
74 | |||
75 | fn validate_func(&mut self, db: &dyn HirDatabase, func: FunctionId) { | ||
76 | let data = db.function_data(func); | ||
77 | let body = db.body(func.into()); | ||
78 | |||
79 | // 1. Check the function name. | ||
80 | let function_name = data.name.to_string(); | ||
81 | let fn_name_replacement = if let Some(new_name) = to_lower_snake_case(&function_name) { | ||
82 | let replacement = Replacement { | ||
83 | current_name: data.name.clone(), | ||
84 | suggested_text: new_name, | ||
85 | expected_case: CaseType::LowerSnakeCase, | ||
86 | }; | ||
87 | Some(replacement) | ||
88 | } else { | ||
89 | None | ||
90 | }; | ||
91 | |||
92 | // 2. Check the param names. | ||
93 | let mut fn_param_replacements = Vec::new(); | ||
94 | |||
95 | for pat_id in body.params.iter().cloned() { | ||
96 | let pat = &body[pat_id]; | ||
97 | |||
98 | let param_name = match pat { | ||
99 | Pat::Bind { name, .. } => name, | ||
100 | _ => continue, | ||
101 | }; | ||
102 | |||
103 | let name = param_name.to_string(); | ||
104 | if let Some(new_name) = to_lower_snake_case(&name) { | ||
105 | let replacement = Replacement { | ||
106 | current_name: param_name.clone(), | ||
107 | suggested_text: new_name, | ||
108 | expected_case: CaseType::LowerSnakeCase, | ||
109 | }; | ||
110 | fn_param_replacements.push(replacement); | ||
111 | } | ||
112 | } | ||
113 | |||
114 | // 3. Check the patterns inside the function body. | ||
115 | let mut pats_replacements = Vec::new(); | ||
116 | |||
117 | for (pat_idx, pat) in body.pats.iter() { | ||
118 | if body.params.contains(&pat_idx) { | ||
119 | // We aren't interested in function parameters, we've processed them above. | ||
120 | continue; | ||
121 | } | ||
122 | |||
123 | let bind_name = match pat { | ||
124 | Pat::Bind { name, .. } => name, | ||
125 | _ => continue, | ||
126 | }; | ||
127 | |||
128 | let name = bind_name.to_string(); | ||
129 | if let Some(new_name) = to_lower_snake_case(&name) { | ||
130 | let replacement = Replacement { | ||
131 | current_name: bind_name.clone(), | ||
132 | suggested_text: new_name, | ||
133 | expected_case: CaseType::LowerSnakeCase, | ||
134 | }; | ||
135 | pats_replacements.push((pat_idx, replacement)); | ||
136 | } | ||
137 | } | ||
138 | |||
139 | // 4. If there is at least one element to spawn a warning on, go to the source map and generate a warning. | ||
140 | self.create_incorrect_case_diagnostic_for_func( | ||
141 | func, | ||
142 | db, | ||
143 | fn_name_replacement, | ||
144 | fn_param_replacements, | ||
145 | ); | ||
146 | self.create_incorrect_case_diagnostic_for_variables(func, db, pats_replacements); | ||
147 | |||
148 | // 5. Recursively validate inner scope items, such as static variables and constants. | ||
149 | for (item_id, _) in body.item_scope.values() { | ||
150 | let mut validator = DeclValidator::new(item_id, self.sink); | ||
151 | validator.validate_item(db); | ||
152 | } | ||
153 | } | ||
154 | |||
155 | /// Given the information about incorrect names in the function declaration, looks up into the source code | ||
156 | /// for exact locations and adds diagnostics into the sink. | ||
157 | fn create_incorrect_case_diagnostic_for_func( | ||
158 | &mut self, | ||
159 | func: FunctionId, | ||
160 | db: &dyn HirDatabase, | ||
161 | fn_name_replacement: Option<Replacement>, | ||
162 | fn_param_replacements: Vec<Replacement>, | ||
163 | ) { | ||
164 | // XXX: only look at sources if we do have incorrect names | ||
165 | if fn_name_replacement.is_none() && fn_param_replacements.is_empty() { | ||
166 | return; | ||
167 | } | ||
168 | |||
169 | let fn_loc = func.lookup(db.upcast()); | ||
170 | let fn_src = fn_loc.source(db.upcast()); | ||
171 | |||
172 | // 1. Diagnostic for function name. | ||
173 | if let Some(replacement) = fn_name_replacement { | ||
174 | let ast_ptr = match fn_src.value.name() { | ||
175 | Some(name) => name, | ||
176 | None => { | ||
177 | // We don't want rust-analyzer to panic over this, but it is definitely some kind of error in the logic. | ||
178 | log::error!( | ||
179 | "Replacement ({:?}) was generated for a function without a name: {:?}", | ||
180 | replacement, | ||
181 | fn_src | ||
182 | ); | ||
183 | return; | ||
184 | } | ||
185 | }; | ||
186 | |||
187 | let diagnostic = IncorrectCase { | ||
188 | file: fn_src.file_id, | ||
189 | ident_type: "Function".to_string(), | ||
190 | ident: AstPtr::new(&ast_ptr).into(), | ||
191 | expected_case: replacement.expected_case, | ||
192 | ident_text: replacement.current_name.to_string(), | ||
193 | suggested_text: replacement.suggested_text, | ||
194 | }; | ||
195 | |||
196 | self.sink.push(diagnostic); | ||
197 | } | ||
198 | |||
199 | // 2. Diagnostics for function params. | ||
200 | let fn_params_list = match fn_src.value.param_list() { | ||
201 | Some(params) => params, | ||
202 | None => { | ||
203 | if !fn_param_replacements.is_empty() { | ||
204 | log::error!( | ||
205 | "Replacements ({:?}) were generated for a function parameters which had no parameters list: {:?}", | ||
206 | fn_param_replacements, fn_src | ||
207 | ); | ||
208 | } | ||
209 | return; | ||
210 | } | ||
211 | }; | ||
212 | let mut fn_params_iter = fn_params_list.params(); | ||
213 | for param_to_rename in fn_param_replacements { | ||
214 | // We assume that parameters in replacement are in the same order as in the | ||
215 | // actual params list, but just some of them (ones that named correctly) are skipped. | ||
216 | let ast_ptr: ast::Name = loop { | ||
217 | match fn_params_iter.next() { | ||
218 | Some(element) | ||
219 | if pat_equals_to_name(element.pat(), ¶m_to_rename.current_name) => | ||
220 | { | ||
221 | if let ast::Pat::IdentPat(pat) = element.pat().unwrap() { | ||
222 | break pat.name().unwrap(); | ||
223 | } else { | ||
224 | // This is critical. If we consider this parameter the expected one, | ||
225 | // it **must** have a name. | ||
226 | panic!( | ||
227 | "Pattern {:?} equals to expected replacement {:?}, but has no name", | ||
228 | element, param_to_rename | ||
229 | ); | ||
230 | } | ||
231 | } | ||
232 | Some(_) => {} | ||
233 | None => { | ||
234 | log::error!( | ||
235 | "Replacement ({:?}) was generated for a function parameter which was not found: {:?}", | ||
236 | param_to_rename, fn_src | ||
237 | ); | ||
238 | return; | ||
239 | } | ||
240 | } | ||
241 | }; | ||
242 | |||
243 | let diagnostic = IncorrectCase { | ||
244 | file: fn_src.file_id, | ||
245 | ident_type: "Argument".to_string(), | ||
246 | ident: AstPtr::new(&ast_ptr).into(), | ||
247 | expected_case: param_to_rename.expected_case, | ||
248 | ident_text: param_to_rename.current_name.to_string(), | ||
249 | suggested_text: param_to_rename.suggested_text, | ||
250 | }; | ||
251 | |||
252 | self.sink.push(diagnostic); | ||
253 | } | ||
254 | } | ||
255 | |||
256 | /// Given the information about incorrect variable names, looks up into the source code | ||
257 | /// for exact locations and adds diagnostics into the sink. | ||
258 | fn create_incorrect_case_diagnostic_for_variables( | ||
259 | &mut self, | ||
260 | func: FunctionId, | ||
261 | db: &dyn HirDatabase, | ||
262 | pats_replacements: Vec<(PatId, Replacement)>, | ||
263 | ) { | ||
264 | // XXX: only look at source_map if we do have missing fields | ||
265 | if pats_replacements.is_empty() { | ||
266 | return; | ||
267 | } | ||
268 | |||
269 | let (_, source_map) = db.body_with_source_map(func.into()); | ||
270 | |||
271 | for (id, replacement) in pats_replacements { | ||
272 | if let Ok(source_ptr) = source_map.pat_syntax(id) { | ||
273 | if let Some(expr) = source_ptr.value.as_ref().left() { | ||
274 | let root = source_ptr.file_syntax(db.upcast()); | ||
275 | if let ast::Pat::IdentPat(ident_pat) = expr.to_node(&root) { | ||
276 | let parent = match ident_pat.syntax().parent() { | ||
277 | Some(parent) => parent, | ||
278 | None => continue, | ||
279 | }; | ||
280 | let name_ast = match ident_pat.name() { | ||
281 | Some(name_ast) => name_ast, | ||
282 | None => continue, | ||
283 | }; | ||
284 | |||
285 | // We have to check that it's either `let var = ...` or `var @ Variant(_)` statement, | ||
286 | // because e.g. match arms are patterns as well. | ||
287 | // In other words, we check that it's a named variable binding. | ||
288 | let is_binding = ast::LetStmt::cast(parent.clone()).is_some() | ||
289 | || (ast::MatchArm::cast(parent).is_some() | ||
290 | && ident_pat.at_token().is_some()); | ||
291 | if !is_binding { | ||
292 | // This pattern is not an actual variable declaration, e.g. `Some(val) => {..}` match arm. | ||
293 | continue; | ||
294 | } | ||
295 | |||
296 | let diagnostic = IncorrectCase { | ||
297 | file: source_ptr.file_id, | ||
298 | ident_type: "Variable".to_string(), | ||
299 | ident: AstPtr::new(&name_ast).into(), | ||
300 | expected_case: replacement.expected_case, | ||
301 | ident_text: replacement.current_name.to_string(), | ||
302 | suggested_text: replacement.suggested_text, | ||
303 | }; | ||
304 | |||
305 | self.sink.push(diagnostic); | ||
306 | } | ||
307 | } | ||
308 | } | ||
309 | } | ||
310 | } | ||
311 | |||
312 | fn validate_struct(&mut self, db: &dyn HirDatabase, struct_id: StructId) { | ||
313 | let data = db.struct_data(struct_id); | ||
314 | |||
315 | // 1. Check the structure name. | ||
316 | let struct_name = data.name.to_string(); | ||
317 | let struct_name_replacement = if let Some(new_name) = to_camel_case(&struct_name) { | ||
318 | let replacement = Replacement { | ||
319 | current_name: data.name.clone(), | ||
320 | suggested_text: new_name, | ||
321 | expected_case: CaseType::UpperCamelCase, | ||
322 | }; | ||
323 | Some(replacement) | ||
324 | } else { | ||
325 | None | ||
326 | }; | ||
327 | |||
328 | // 2. Check the field names. | ||
329 | let mut struct_fields_replacements = Vec::new(); | ||
330 | |||
331 | if let VariantData::Record(fields) = data.variant_data.as_ref() { | ||
332 | for (_, field) in fields.iter() { | ||
333 | let field_name = field.name.to_string(); | ||
334 | if let Some(new_name) = to_lower_snake_case(&field_name) { | ||
335 | let replacement = Replacement { | ||
336 | current_name: field.name.clone(), | ||
337 | suggested_text: new_name, | ||
338 | expected_case: CaseType::LowerSnakeCase, | ||
339 | }; | ||
340 | struct_fields_replacements.push(replacement); | ||
341 | } | ||
342 | } | ||
343 | } | ||
344 | |||
345 | // 3. If there is at least one element to spawn a warning on, go to the source map and generate a warning. | ||
346 | self.create_incorrect_case_diagnostic_for_struct( | ||
347 | struct_id, | ||
348 | db, | ||
349 | struct_name_replacement, | ||
350 | struct_fields_replacements, | ||
351 | ); | ||
352 | } | ||
353 | |||
354 | /// Given the information about incorrect names in the struct declaration, looks up into the source code | ||
355 | /// for exact locations and adds diagnostics into the sink. | ||
356 | fn create_incorrect_case_diagnostic_for_struct( | ||
357 | &mut self, | ||
358 | struct_id: StructId, | ||
359 | db: &dyn HirDatabase, | ||
360 | struct_name_replacement: Option<Replacement>, | ||
361 | struct_fields_replacements: Vec<Replacement>, | ||
362 | ) { | ||
363 | // XXX: only look at sources if we do have incorrect names | ||
364 | if struct_name_replacement.is_none() && struct_fields_replacements.is_empty() { | ||
365 | return; | ||
366 | } | ||
367 | |||
368 | let struct_loc = struct_id.lookup(db.upcast()); | ||
369 | let struct_src = struct_loc.source(db.upcast()); | ||
370 | |||
371 | if let Some(replacement) = struct_name_replacement { | ||
372 | let ast_ptr = match struct_src.value.name() { | ||
373 | Some(name) => name, | ||
374 | None => { | ||
375 | // We don't want rust-analyzer to panic over this, but it is definitely some kind of error in the logic. | ||
376 | log::error!( | ||
377 | "Replacement ({:?}) was generated for a structure without a name: {:?}", | ||
378 | replacement, | ||
379 | struct_src | ||
380 | ); | ||
381 | return; | ||
382 | } | ||
383 | }; | ||
384 | |||
385 | let diagnostic = IncorrectCase { | ||
386 | file: struct_src.file_id, | ||
387 | ident_type: "Structure".to_string(), | ||
388 | ident: AstPtr::new(&ast_ptr).into(), | ||
389 | expected_case: replacement.expected_case, | ||
390 | ident_text: replacement.current_name.to_string(), | ||
391 | suggested_text: replacement.suggested_text, | ||
392 | }; | ||
393 | |||
394 | self.sink.push(diagnostic); | ||
395 | } | ||
396 | |||
397 | let struct_fields_list = match struct_src.value.field_list() { | ||
398 | Some(ast::FieldList::RecordFieldList(fields)) => fields, | ||
399 | _ => { | ||
400 | if !struct_fields_replacements.is_empty() { | ||
401 | log::error!( | ||
402 | "Replacements ({:?}) were generated for a structure fields which had no fields list: {:?}", | ||
403 | struct_fields_replacements, struct_src | ||
404 | ); | ||
405 | } | ||
406 | return; | ||
407 | } | ||
408 | }; | ||
409 | let mut struct_fields_iter = struct_fields_list.fields(); | ||
410 | for field_to_rename in struct_fields_replacements { | ||
411 | // We assume that parameters in replacement are in the same order as in the | ||
412 | // actual params list, but just some of them (ones that named correctly) are skipped. | ||
413 | let ast_ptr = loop { | ||
414 | match struct_fields_iter.next() { | ||
415 | Some(element) if names_equal(element.name(), &field_to_rename.current_name) => { | ||
416 | break element.name().unwrap() | ||
417 | } | ||
418 | Some(_) => {} | ||
419 | None => { | ||
420 | log::error!( | ||
421 | "Replacement ({:?}) was generated for a structure field which was not found: {:?}", | ||
422 | field_to_rename, struct_src | ||
423 | ); | ||
424 | return; | ||
425 | } | ||
426 | } | ||
427 | }; | ||
428 | |||
429 | let diagnostic = IncorrectCase { | ||
430 | file: struct_src.file_id, | ||
431 | ident_type: "Field".to_string(), | ||
432 | ident: AstPtr::new(&ast_ptr).into(), | ||
433 | expected_case: field_to_rename.expected_case, | ||
434 | ident_text: field_to_rename.current_name.to_string(), | ||
435 | suggested_text: field_to_rename.suggested_text, | ||
436 | }; | ||
437 | |||
438 | self.sink.push(diagnostic); | ||
439 | } | ||
440 | } | ||
441 | |||
442 | fn validate_enum(&mut self, db: &dyn HirDatabase, enum_id: EnumId) { | ||
443 | let data = db.enum_data(enum_id); | ||
444 | |||
445 | // 1. Check the enum name. | ||
446 | let enum_name = data.name.to_string(); | ||
447 | let enum_name_replacement = if let Some(new_name) = to_camel_case(&enum_name) { | ||
448 | let replacement = Replacement { | ||
449 | current_name: data.name.clone(), | ||
450 | suggested_text: new_name, | ||
451 | expected_case: CaseType::UpperCamelCase, | ||
452 | }; | ||
453 | Some(replacement) | ||
454 | } else { | ||
455 | None | ||
456 | }; | ||
457 | |||
458 | // 2. Check the field names. | ||
459 | let mut enum_fields_replacements = Vec::new(); | ||
460 | |||
461 | for (_, variant) in data.variants.iter() { | ||
462 | let variant_name = variant.name.to_string(); | ||
463 | if let Some(new_name) = to_camel_case(&variant_name) { | ||
464 | let replacement = Replacement { | ||
465 | current_name: variant.name.clone(), | ||
466 | suggested_text: new_name, | ||
467 | expected_case: CaseType::UpperCamelCase, | ||
468 | }; | ||
469 | enum_fields_replacements.push(replacement); | ||
470 | } | ||
471 | } | ||
472 | |||
473 | // 3. If there is at least one element to spawn a warning on, go to the source map and generate a warning. | ||
474 | self.create_incorrect_case_diagnostic_for_enum( | ||
475 | enum_id, | ||
476 | db, | ||
477 | enum_name_replacement, | ||
478 | enum_fields_replacements, | ||
479 | ) | ||
480 | } | ||
481 | |||
482 | /// Given the information about incorrect names in the struct declaration, looks up into the source code | ||
483 | /// for exact locations and adds diagnostics into the sink. | ||
484 | fn create_incorrect_case_diagnostic_for_enum( | ||
485 | &mut self, | ||
486 | enum_id: EnumId, | ||
487 | db: &dyn HirDatabase, | ||
488 | enum_name_replacement: Option<Replacement>, | ||
489 | enum_variants_replacements: Vec<Replacement>, | ||
490 | ) { | ||
491 | // XXX: only look at sources if we do have incorrect names | ||
492 | if enum_name_replacement.is_none() && enum_variants_replacements.is_empty() { | ||
493 | return; | ||
494 | } | ||
495 | |||
496 | let enum_loc = enum_id.lookup(db.upcast()); | ||
497 | let enum_src = enum_loc.source(db.upcast()); | ||
498 | |||
499 | if let Some(replacement) = enum_name_replacement { | ||
500 | let ast_ptr = match enum_src.value.name() { | ||
501 | Some(name) => name, | ||
502 | None => { | ||
503 | // We don't want rust-analyzer to panic over this, but it is definitely some kind of error in the logic. | ||
504 | log::error!( | ||
505 | "Replacement ({:?}) was generated for a enum without a name: {:?}", | ||
506 | replacement, | ||
507 | enum_src | ||
508 | ); | ||
509 | return; | ||
510 | } | ||
511 | }; | ||
512 | |||
513 | let diagnostic = IncorrectCase { | ||
514 | file: enum_src.file_id, | ||
515 | ident_type: "Enum".to_string(), | ||
516 | ident: AstPtr::new(&ast_ptr).into(), | ||
517 | expected_case: replacement.expected_case, | ||
518 | ident_text: replacement.current_name.to_string(), | ||
519 | suggested_text: replacement.suggested_text, | ||
520 | }; | ||
521 | |||
522 | self.sink.push(diagnostic); | ||
523 | } | ||
524 | |||
525 | let enum_variants_list = match enum_src.value.variant_list() { | ||
526 | Some(variants) => variants, | ||
527 | _ => { | ||
528 | if !enum_variants_replacements.is_empty() { | ||
529 | log::error!( | ||
530 | "Replacements ({:?}) were generated for a enum variants which had no fields list: {:?}", | ||
531 | enum_variants_replacements, enum_src | ||
532 | ); | ||
533 | } | ||
534 | return; | ||
535 | } | ||
536 | }; | ||
537 | let mut enum_variants_iter = enum_variants_list.variants(); | ||
538 | for variant_to_rename in enum_variants_replacements { | ||
539 | // We assume that parameters in replacement are in the same order as in the | ||
540 | // actual params list, but just some of them (ones that named correctly) are skipped. | ||
541 | let ast_ptr = loop { | ||
542 | match enum_variants_iter.next() { | ||
543 | Some(variant) | ||
544 | if names_equal(variant.name(), &variant_to_rename.current_name) => | ||
545 | { | ||
546 | break variant.name().unwrap() | ||
547 | } | ||
548 | Some(_) => {} | ||
549 | None => { | ||
550 | log::error!( | ||
551 | "Replacement ({:?}) was generated for a enum variant which was not found: {:?}", | ||
552 | variant_to_rename, enum_src | ||
553 | ); | ||
554 | return; | ||
555 | } | ||
556 | } | ||
557 | }; | ||
558 | |||
559 | let diagnostic = IncorrectCase { | ||
560 | file: enum_src.file_id, | ||
561 | ident_type: "Variant".to_string(), | ||
562 | ident: AstPtr::new(&ast_ptr).into(), | ||
563 | expected_case: variant_to_rename.expected_case, | ||
564 | ident_text: variant_to_rename.current_name.to_string(), | ||
565 | suggested_text: variant_to_rename.suggested_text, | ||
566 | }; | ||
567 | |||
568 | self.sink.push(diagnostic); | ||
569 | } | ||
570 | } | ||
571 | |||
572 | fn validate_const(&mut self, db: &dyn HirDatabase, const_id: ConstId) { | ||
573 | let data = db.const_data(const_id); | ||
574 | |||
575 | let name = match &data.name { | ||
576 | Some(name) => name, | ||
577 | None => return, | ||
578 | }; | ||
579 | |||
580 | let const_name = name.to_string(); | ||
581 | let replacement = if let Some(new_name) = to_upper_snake_case(&const_name) { | ||
582 | Replacement { | ||
583 | current_name: name.clone(), | ||
584 | suggested_text: new_name, | ||
585 | expected_case: CaseType::UpperSnakeCase, | ||
586 | } | ||
587 | } else { | ||
588 | // Nothing to do here. | ||
589 | return; | ||
590 | }; | ||
591 | |||
592 | let const_loc = const_id.lookup(db.upcast()); | ||
593 | let const_src = const_loc.source(db.upcast()); | ||
594 | |||
595 | let ast_ptr = match const_src.value.name() { | ||
596 | Some(name) => name, | ||
597 | None => return, | ||
598 | }; | ||
599 | |||
600 | let diagnostic = IncorrectCase { | ||
601 | file: const_src.file_id, | ||
602 | ident_type: "Constant".to_string(), | ||
603 | ident: AstPtr::new(&ast_ptr).into(), | ||
604 | expected_case: replacement.expected_case, | ||
605 | ident_text: replacement.current_name.to_string(), | ||
606 | suggested_text: replacement.suggested_text, | ||
607 | }; | ||
608 | |||
609 | self.sink.push(diagnostic); | ||
610 | } | ||
611 | |||
612 | fn validate_static(&mut self, db: &dyn HirDatabase, static_id: StaticId) { | ||
613 | let data = db.static_data(static_id); | ||
614 | |||
615 | let name = match &data.name { | ||
616 | Some(name) => name, | ||
617 | None => return, | ||
618 | }; | ||
619 | |||
620 | let static_name = name.to_string(); | ||
621 | let replacement = if let Some(new_name) = to_upper_snake_case(&static_name) { | ||
622 | Replacement { | ||
623 | current_name: name.clone(), | ||
624 | suggested_text: new_name, | ||
625 | expected_case: CaseType::UpperSnakeCase, | ||
626 | } | ||
627 | } else { | ||
628 | // Nothing to do here. | ||
629 | return; | ||
630 | }; | ||
631 | |||
632 | let static_loc = static_id.lookup(db.upcast()); | ||
633 | let static_src = static_loc.source(db.upcast()); | ||
634 | |||
635 | let ast_ptr = match static_src.value.name() { | ||
636 | Some(name) => name, | ||
637 | None => return, | ||
638 | }; | ||
639 | |||
640 | let diagnostic = IncorrectCase { | ||
641 | file: static_src.file_id, | ||
642 | ident_type: "Static variable".to_string(), | ||
643 | ident: AstPtr::new(&ast_ptr).into(), | ||
644 | expected_case: replacement.expected_case, | ||
645 | ident_text: replacement.current_name.to_string(), | ||
646 | suggested_text: replacement.suggested_text, | ||
647 | }; | ||
648 | |||
649 | self.sink.push(diagnostic); | ||
650 | } | ||
651 | } | ||
652 | |||
653 | fn names_equal(left: Option<ast::Name>, right: &Name) -> bool { | ||
654 | if let Some(left) = left { | ||
655 | &left.as_name() == right | ||
656 | } else { | ||
657 | false | ||
658 | } | ||
659 | } | ||
660 | |||
661 | fn pat_equals_to_name(pat: Option<ast::Pat>, name: &Name) -> bool { | ||
662 | if let Some(ast::Pat::IdentPat(ident)) = pat { | ||
663 | ident.to_string() == name.to_string() | ||
664 | } else { | ||
665 | false | ||
666 | } | ||
667 | } | ||
668 | |||
669 | #[cfg(test)] | ||
670 | mod tests { | ||
671 | use crate::diagnostics::tests::check_diagnostics; | ||
672 | |||
673 | #[test] | ||
674 | fn incorrect_function_name() { | ||
675 | check_diagnostics( | ||
676 | r#" | ||
677 | fn NonSnakeCaseName() {} | ||
678 | // ^^^^^^^^^^^^^^^^ Function `NonSnakeCaseName` should have snake_case name, e.g. `non_snake_case_name` | ||
679 | "#, | ||
680 | ); | ||
681 | } | ||
682 | |||
683 | #[test] | ||
684 | fn incorrect_function_params() { | ||
685 | check_diagnostics( | ||
686 | r#" | ||
687 | fn foo(SomeParam: u8) {} | ||
688 | // ^^^^^^^^^ Argument `SomeParam` should have snake_case name, e.g. `some_param` | ||
689 | |||
690 | fn foo2(ok_param: &str, CAPS_PARAM: u8) {} | ||
691 | // ^^^^^^^^^^ Argument `CAPS_PARAM` should have snake_case name, e.g. `caps_param` | ||
692 | "#, | ||
693 | ); | ||
694 | } | ||
695 | |||
696 | #[test] | ||
697 | fn incorrect_variable_names() { | ||
698 | check_diagnostics( | ||
699 | r#" | ||
700 | fn foo() { | ||
701 | let SOME_VALUE = 10; | ||
702 | // ^^^^^^^^^^ Variable `SOME_VALUE` should have snake_case name, e.g. `some_value` | ||
703 | let AnotherValue = 20; | ||
704 | // ^^^^^^^^^^^^ Variable `AnotherValue` should have snake_case name, e.g. `another_value` | ||
705 | } | ||
706 | "#, | ||
707 | ); | ||
708 | } | ||
709 | |||
710 | #[test] | ||
711 | fn incorrect_struct_name() { | ||
712 | check_diagnostics( | ||
713 | r#" | ||
714 | struct non_camel_case_name {} | ||
715 | // ^^^^^^^^^^^^^^^^^^^ Structure `non_camel_case_name` should have CamelCase name, e.g. `NonCamelCaseName` | ||
716 | "#, | ||
717 | ); | ||
718 | } | ||
719 | |||
720 | #[test] | ||
721 | fn incorrect_struct_field() { | ||
722 | check_diagnostics( | ||
723 | r#" | ||
724 | struct SomeStruct { SomeField: u8 } | ||
725 | // ^^^^^^^^^ Field `SomeField` should have snake_case name, e.g. `some_field` | ||
726 | "#, | ||
727 | ); | ||
728 | } | ||
729 | |||
730 | #[test] | ||
731 | fn incorrect_enum_name() { | ||
732 | check_diagnostics( | ||
733 | r#" | ||
734 | enum some_enum { Val(u8) } | ||
735 | // ^^^^^^^^^ Enum `some_enum` should have CamelCase name, e.g. `SomeEnum` | ||
736 | "#, | ||
737 | ); | ||
738 | } | ||
739 | |||
740 | #[test] | ||
741 | fn incorrect_enum_variant_name() { | ||
742 | check_diagnostics( | ||
743 | r#" | ||
744 | enum SomeEnum { SOME_VARIANT(u8) } | ||
745 | // ^^^^^^^^^^^^ Variant `SOME_VARIANT` should have CamelCase name, e.g. `SomeVariant` | ||
746 | "#, | ||
747 | ); | ||
748 | } | ||
749 | |||
750 | #[test] | ||
751 | fn incorrect_const_name() { | ||
752 | check_diagnostics( | ||
753 | r#" | ||
754 | const some_weird_const: u8 = 10; | ||
755 | // ^^^^^^^^^^^^^^^^ Constant `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST` | ||
756 | |||
757 | fn func() { | ||
758 | const someConstInFunc: &str = "hi there"; | ||
759 | // ^^^^^^^^^^^^^^^ Constant `someConstInFunc` should have UPPER_SNAKE_CASE name, e.g. `SOME_CONST_IN_FUNC` | ||
760 | |||
761 | } | ||
762 | "#, | ||
763 | ); | ||
764 | } | ||
765 | |||
766 | #[test] | ||
767 | fn incorrect_static_name() { | ||
768 | check_diagnostics( | ||
769 | r#" | ||
770 | static some_weird_const: u8 = 10; | ||
771 | // ^^^^^^^^^^^^^^^^ Static variable `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST` | ||
772 | |||
773 | fn func() { | ||
774 | static someConstInFunc: &str = "hi there"; | ||
775 | // ^^^^^^^^^^^^^^^ Static variable `someConstInFunc` should have UPPER_SNAKE_CASE name, e.g. `SOME_CONST_IN_FUNC` | ||
776 | } | ||
777 | "#, | ||
778 | ); | ||
779 | } | ||
780 | |||
781 | #[test] | ||
782 | fn fn_inside_impl_struct() { | ||
783 | check_diagnostics( | ||
784 | r#" | ||
785 | struct someStruct; | ||
786 | // ^^^^^^^^^^ Structure `someStruct` should have CamelCase name, e.g. `SomeStruct` | ||
787 | |||
788 | impl someStruct { | ||
789 | fn SomeFunc(&self) { | ||
790 | // ^^^^^^^^ Function `SomeFunc` should have snake_case name, e.g. `some_func` | ||
791 | static someConstInFunc: &str = "hi there"; | ||
792 | // ^^^^^^^^^^^^^^^ Static variable `someConstInFunc` should have UPPER_SNAKE_CASE name, e.g. `SOME_CONST_IN_FUNC` | ||
793 | let WHY_VAR_IS_CAPS = 10; | ||
794 | // ^^^^^^^^^^^^^^^ Variable `WHY_VAR_IS_CAPS` should have snake_case name, e.g. `why_var_is_caps` | ||
795 | } | ||
796 | } | ||
797 | "#, | ||
798 | ); | ||
799 | } | ||
800 | |||
801 | #[test] | ||
802 | fn no_diagnostic_for_enum_varinats() { | ||
803 | check_diagnostics( | ||
804 | r#" | ||
805 | enum Option { Some, None } | ||
806 | |||
807 | fn main() { | ||
808 | match Option::None { | ||
809 | None => (), | ||
810 | Some => (), | ||
811 | } | ||
812 | } | ||
813 | "#, | ||
814 | ); | ||
815 | } | ||
816 | |||
817 | #[test] | ||
818 | fn non_let_bind() { | ||
819 | check_diagnostics( | ||
820 | r#" | ||
821 | enum Option { Some, None } | ||
822 | |||
823 | fn main() { | ||
824 | match Option::None { | ||
825 | SOME_VAR @ None => (), | ||
826 | // ^^^^^^^^ Variable `SOME_VAR` should have snake_case name, e.g. `some_var` | ||
827 | Some => (), | ||
828 | } | ||
829 | } | ||
830 | "#, | ||
831 | ); | ||
832 | } | ||
833 | } | ||