Skip to content

Commit fb96bba

Browse files
committed
Add diagnostics for enum names and variants
1 parent e24e22f commit fb96bba

File tree

3 files changed

+173
-2
lines changed

3 files changed

+173
-2
lines changed

crates/hir_ty/src/diagnostics.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ impl Diagnostic for IncorrectCase {
298298
}
299299

300300
fn is_experimental(&self) -> bool {
301-
false
301+
true
302302
}
303303
}
304304

crates/hir_ty/src/diagnostics/decl_check.rs

Lines changed: 146 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ impl<'a, 'b> DeclValidator<'a, 'b> {
315315
Some(_) => {}
316316
None => {
317317
log::error!(
318-
"Replacement ({:?}) was generated for a function parameter which was not found: {:?}",
318+
"Replacement ({:?}) was generated for a structure field which was not found: {:?}",
319319
field_to_rename, struct_src
320320
);
321321
return;
@@ -338,6 +338,131 @@ impl<'a, 'b> DeclValidator<'a, 'b> {
338338

339339
fn validate_enum(&mut self, db: &dyn HirDatabase, enum_id: EnumId) {
340340
let data = db.enum_data(enum_id);
341+
342+
// 1. Check the enum name.
343+
let enum_name = data.name.to_string();
344+
let enum_name_replacement = if let Some(new_name) = to_camel_case(&enum_name) {
345+
let replacement = Replacement {
346+
current_name: data.name.clone(),
347+
suggested_text: new_name,
348+
expected_case: CaseType::UpperCamelCase,
349+
};
350+
Some(replacement)
351+
} else {
352+
None
353+
};
354+
355+
// 2. Check the field names.
356+
let mut enum_fields_replacements = Vec::new();
357+
358+
for (_, variant) in data.variants.iter() {
359+
let variant_name = variant.name.to_string();
360+
if let Some(new_name) = to_camel_case(&variant_name) {
361+
let replacement = Replacement {
362+
current_name: variant.name.clone(),
363+
suggested_text: new_name,
364+
expected_case: CaseType::UpperCamelCase,
365+
};
366+
enum_fields_replacements.push(replacement);
367+
}
368+
}
369+
370+
// 3. If there is at least one element to spawn a warning on, go to the source map and generate a warning.
371+
self.create_incorrect_case_diagnostic_for_enum(
372+
enum_id,
373+
db,
374+
enum_name_replacement,
375+
enum_fields_replacements,
376+
)
377+
}
378+
379+
/// Given the information about incorrect names in the struct declaration, looks up into the source code
380+
/// for exact locations and adds diagnostics into the sink.
381+
fn create_incorrect_case_diagnostic_for_enum(
382+
&mut self,
383+
enum_id: EnumId,
384+
db: &dyn HirDatabase,
385+
enum_name_replacement: Option<Replacement>,
386+
enum_variants_replacements: Vec<Replacement>,
387+
) {
388+
// XXX: only look at sources if we do have incorrect names
389+
if enum_name_replacement.is_none() && enum_variants_replacements.is_empty() {
390+
return;
391+
}
392+
393+
let enum_loc = enum_id.lookup(db.upcast());
394+
let enum_src = enum_loc.source(db.upcast());
395+
396+
if let Some(replacement) = enum_name_replacement {
397+
let ast_ptr = if let Some(name) = enum_src.value.name() {
398+
name
399+
} else {
400+
// We don't want rust-analyzer to panic over this, but it is definitely some kind of error in the logic.
401+
log::error!(
402+
"Replacement ({:?}) was generated for a enum without a name: {:?}",
403+
replacement,
404+
enum_src
405+
);
406+
return;
407+
};
408+
409+
let diagnostic = IncorrectCase {
410+
file: enum_src.file_id,
411+
ident_type: "Enum".to_string(),
412+
ident: AstPtr::new(&ast_ptr).into(),
413+
expected_case: replacement.expected_case,
414+
ident_text: replacement.current_name.to_string(),
415+
suggested_text: replacement.suggested_text,
416+
};
417+
418+
self.sink.push(diagnostic);
419+
}
420+
421+
let enum_variants_list = match enum_src.value.variant_list() {
422+
Some(variants) => variants,
423+
_ => {
424+
if !enum_variants_replacements.is_empty() {
425+
log::error!(
426+
"Replacements ({:?}) were generated for a enum variants which had no fields list: {:?}",
427+
enum_variants_replacements, enum_src
428+
);
429+
}
430+
return;
431+
}
432+
};
433+
let mut enum_variants_iter = enum_variants_list.variants();
434+
for variant_to_rename in enum_variants_replacements {
435+
// We assume that parameters in replacement are in the same order as in the
436+
// actual params list, but just some of them (ones that named correctly) are skipped.
437+
let ast_ptr = loop {
438+
match enum_variants_iter.next() {
439+
Some(variant)
440+
if names_equal(variant.name(), &variant_to_rename.current_name) =>
441+
{
442+
break variant.name().unwrap()
443+
}
444+
Some(_) => {}
445+
None => {
446+
log::error!(
447+
"Replacement ({:?}) was generated for a enum variant which was not found: {:?}",
448+
variant_to_rename, enum_src
449+
);
450+
return;
451+
}
452+
}
453+
};
454+
455+
let diagnostic = IncorrectCase {
456+
file: enum_src.file_id,
457+
ident_type: "Variant".to_string(),
458+
ident: AstPtr::new(&ast_ptr).into(),
459+
expected_case: variant_to_rename.expected_case,
460+
ident_text: variant_to_rename.current_name.to_string(),
461+
suggested_text: variant_to_rename.suggested_text,
462+
};
463+
464+
self.sink.push(diagnostic);
465+
}
341466
}
342467
}
343468

@@ -400,6 +525,26 @@ struct non_camel_case_name {}
400525
r#"
401526
struct SomeStruct { SomeField: u8 }
402527
// ^^^^^^^^^ Field `SomeField` should have a snake_case name, e.g. `some_field`
528+
"#,
529+
);
530+
}
531+
532+
#[test]
533+
fn incorrect_enum_name() {
534+
check_diagnostics(
535+
r#"
536+
enum some_enum { Val(u8) }
537+
// ^^^^^^^^^ Enum `some_enum` should have a CamelCase name, e.g. `SomeEnum`
538+
"#,
539+
);
540+
}
541+
542+
#[test]
543+
fn incorrect_enum_variant_name() {
544+
check_diagnostics(
545+
r#"
546+
enum SomeEnum { SOME_VARIANT(u8) }
547+
// ^^^^^^^^^^^^ Variant `SOME_VARIANT` should have a CamelCase name, e.g. `SomeVariant`
403548
"#,
404549
);
405550
}

crates/ide/src/diagnostics.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -851,6 +851,32 @@ pub struct TestStruct { one: i32 }
851851
pub fn some_fn(val: TestStruct) -> TestStruct {
852852
TestStruct { one: val.one + 1 }
853853
}
854+
"#,
855+
);
856+
857+
check_fixes(
858+
r#"
859+
pub fn some_fn(NonSnakeCase<|>: u8) -> u8 {
860+
NonSnakeCase
861+
}
862+
"#,
863+
r#"
864+
pub fn some_fn(non_snake_case: u8) -> u8 {
865+
non_snake_case
866+
}
867+
"#,
868+
);
869+
870+
check_fixes(
871+
r#"
872+
pub fn SomeFn<|>(val: u8) -> u8 {
873+
if val != 0 { SomeFn(val - 1) } else { val }
874+
}
875+
"#,
876+
r#"
877+
pub fn some_fn(val: u8) -> u8 {
878+
if val != 0 { some_fn(val - 1) } else { val }
879+
}
854880
"#,
855881
);
856882
}

0 commit comments

Comments
 (0)