]> git.lizzy.rs Git - rust.git/commitdiff
Add diagnostics for enum names and variants
authorIgor Aleksanov <popzxc@yandex.ru>
Sat, 3 Oct 2020 15:01:25 +0000 (18:01 +0300)
committerIgor Aleksanov <popzxc@yandex.ru>
Mon, 12 Oct 2020 08:05:00 +0000 (11:05 +0300)
crates/hir_ty/src/diagnostics.rs
crates/hir_ty/src/diagnostics/decl_check.rs
crates/ide/src/diagnostics.rs

index 66762b90e7adab0b7e63d757819d4f835a1aaa43..bd370e3b2d5d67ce7971c8687239234cf0135091 100644 (file)
@@ -298,7 +298,7 @@ fn as_any(&self) -> &(dyn Any + Send + 'static) {
     }
 
     fn is_experimental(&self) -> bool {
-        false
+        true
     }
 }
 
index 260aa9607ccf93570b533e10183d29131f5c32fb..7fc9c564e14c93352eabf91c94dc699351b8b45e 100644 (file)
@@ -315,7 +315,7 @@ fn create_incorrect_case_diagnostic_for_struct(
                     Some(_) => {}
                     None => {
                         log::error!(
-                            "Replacement ({:?}) was generated for a function parameter which was not found: {:?}",
+                            "Replacement ({:?}) was generated for a structure field which was not found: {:?}",
                             field_to_rename, struct_src
                         );
                         return;
@@ -338,6 +338,131 @@ fn create_incorrect_case_diagnostic_for_struct(
 
     fn validate_enum(&mut self, db: &dyn HirDatabase, enum_id: EnumId) {
         let data = db.enum_data(enum_id);
+
+        // 1. Check the enum name.
+        let enum_name = data.name.to_string();
+        let enum_name_replacement = if let Some(new_name) = to_camel_case(&enum_name) {
+            let replacement = Replacement {
+                current_name: data.name.clone(),
+                suggested_text: new_name,
+                expected_case: CaseType::UpperCamelCase,
+            };
+            Some(replacement)
+        } else {
+            None
+        };
+
+        // 2. Check the field names.
+        let mut enum_fields_replacements = Vec::new();
+
+        for (_, variant) in data.variants.iter() {
+            let variant_name = variant.name.to_string();
+            if let Some(new_name) = to_camel_case(&variant_name) {
+                let replacement = Replacement {
+                    current_name: variant.name.clone(),
+                    suggested_text: new_name,
+                    expected_case: CaseType::UpperCamelCase,
+                };
+                enum_fields_replacements.push(replacement);
+            }
+        }
+
+        // 3. If there is at least one element to spawn a warning on, go to the source map and generate a warning.
+        self.create_incorrect_case_diagnostic_for_enum(
+            enum_id,
+            db,
+            enum_name_replacement,
+            enum_fields_replacements,
+        )
+    }
+
+    /// Given the information about incorrect names in the struct declaration, looks up into the source code
+    /// for exact locations and adds diagnostics into the sink.
+    fn create_incorrect_case_diagnostic_for_enum(
+        &mut self,
+        enum_id: EnumId,
+        db: &dyn HirDatabase,
+        enum_name_replacement: Option<Replacement>,
+        enum_variants_replacements: Vec<Replacement>,
+    ) {
+        // XXX: only look at sources if we do have incorrect names
+        if enum_name_replacement.is_none() && enum_variants_replacements.is_empty() {
+            return;
+        }
+
+        let enum_loc = enum_id.lookup(db.upcast());
+        let enum_src = enum_loc.source(db.upcast());
+
+        if let Some(replacement) = enum_name_replacement {
+            let ast_ptr = if let Some(name) = enum_src.value.name() {
+                name
+            } else {
+                // We don't want rust-analyzer to panic over this, but it is definitely some kind of error in the logic.
+                log::error!(
+                    "Replacement ({:?}) was generated for a enum without a name: {:?}",
+                    replacement,
+                    enum_src
+                );
+                return;
+            };
+
+            let diagnostic = IncorrectCase {
+                file: enum_src.file_id,
+                ident_type: "Enum".to_string(),
+                ident: AstPtr::new(&ast_ptr).into(),
+                expected_case: replacement.expected_case,
+                ident_text: replacement.current_name.to_string(),
+                suggested_text: replacement.suggested_text,
+            };
+
+            self.sink.push(diagnostic);
+        }
+
+        let enum_variants_list = match enum_src.value.variant_list() {
+            Some(variants) => variants,
+            _ => {
+                if !enum_variants_replacements.is_empty() {
+                    log::error!(
+                        "Replacements ({:?}) were generated for a enum variants which had no fields list: {:?}",
+                        enum_variants_replacements, enum_src
+                    );
+                }
+                return;
+            }
+        };
+        let mut enum_variants_iter = enum_variants_list.variants();
+        for variant_to_rename in enum_variants_replacements {
+            // We assume that parameters in replacement are in the same order as in the
+            // actual params list, but just some of them (ones that named correctly) are skipped.
+            let ast_ptr = loop {
+                match enum_variants_iter.next() {
+                    Some(variant)
+                        if names_equal(variant.name(), &variant_to_rename.current_name) =>
+                    {
+                        break variant.name().unwrap()
+                    }
+                    Some(_) => {}
+                    None => {
+                        log::error!(
+                            "Replacement ({:?}) was generated for a enum variant which was not found: {:?}",
+                            variant_to_rename, enum_src
+                        );
+                        return;
+                    }
+                }
+            };
+
+            let diagnostic = IncorrectCase {
+                file: enum_src.file_id,
+                ident_type: "Variant".to_string(),
+                ident: AstPtr::new(&ast_ptr).into(),
+                expected_case: variant_to_rename.expected_case,
+                ident_text: variant_to_rename.current_name.to_string(),
+                suggested_text: variant_to_rename.suggested_text,
+            };
+
+            self.sink.push(diagnostic);
+        }
     }
 }
 
@@ -400,6 +525,26 @@ fn incorrect_struct_field() {
             r#"
 struct SomeStruct { SomeField: u8 }
                  // ^^^^^^^^^ Field `SomeField` should have a snake_case name, e.g. `some_field`
+"#,
+        );
+    }
+
+    #[test]
+    fn incorrect_enum_name() {
+        check_diagnostics(
+            r#"
+enum some_enum { Val(u8) }
+  // ^^^^^^^^^ Enum `some_enum` should have a CamelCase name, e.g. `SomeEnum`
+"#,
+        );
+    }
+
+    #[test]
+    fn incorrect_enum_variant_name() {
+        check_diagnostics(
+            r#"
+enum SomeEnum { SOME_VARIANT(u8) }
+             // ^^^^^^^^^^^^ Variant `SOME_VARIANT` should have a CamelCase name, e.g. `SomeVariant`
 "#,
         );
     }
index 71ab98c1f018cd2d0233d15182fcaa36d5d9b73f..ad1b265fdf84e05b8d7da7813c5a09dad3c52297 100644 (file)
@@ -851,6 +851,32 @@ pub struct TestStruct { one: i32 }
 pub fn some_fn(val: TestStruct) -> TestStruct {
     TestStruct { one: val.one + 1 }
 }
+"#,
+        );
+
+        check_fixes(
+            r#"
+pub fn some_fn(NonSnakeCase<|>: u8) -> u8 {
+    NonSnakeCase
+}
+"#,
+            r#"
+pub fn some_fn(non_snake_case: u8) -> u8 {
+    non_snake_case
+}
+"#,
+        );
+
+        check_fixes(
+            r#"
+pub fn SomeFn<|>(val: u8) -> u8 {
+    if val != 0 { SomeFn(val - 1) } else { val }
+}
+"#,
+            r#"
+pub fn some_fn(val: u8) -> u8 {
+    if val != 0 { some_fn(val - 1) } else { val }
+}
 "#,
         );
     }