Skip to main content

cairo_lint/lints/
empty_enum_brackets_variant.rs

1use cairo_lang_defs::{ids::ModuleItemId, plugin::PluginDiagnostic};
2use cairo_lang_diagnostics::Severity;
3use cairo_lang_semantic::items::enm::EnumSemantic;
4use cairo_lang_syntax::node::{
5    SyntaxNode, TypedStablePtr, TypedSyntaxNode,
6    ast::{self, OptionTypeClause},
7};
8
9use crate::{
10    context::{CairoLintKind, Lint},
11    fixer::InternalFix,
12};
13use salsa::Database;
14
15pub struct EmptyEnumBracketsVariant;
16
17/// ## What it does
18///
19/// Finds enum variants that are declared with empty brackets.
20///
21/// ## Example
22///
23/// ```cairo
24///  enum MyEnum {
25///     Data: u8,
26///     Empty: ()       // redundant parentheses
27///  }
28/// ```
29///
30/// Can be simplified to:
31///
32/// ```cairo
33///  enum MyEnum {
34///     Data(u8),
35///     Empty,
36///  }
37/// ```
38impl Lint for EmptyEnumBracketsVariant {
39    fn allowed_name(&self) -> &'static str {
40        "empty_enum_brackets_variant"
41    }
42
43    fn diagnostic_message(&self) -> &'static str {
44        "redundant parentheses in enum variant definition"
45    }
46
47    fn kind(&self) -> CairoLintKind {
48        CairoLintKind::EnumEmptyVariantBrackets
49    }
50
51    fn has_fixer(&self) -> bool {
52        true
53    }
54
55    fn fix<'db>(&self, db: &'db dyn Database, node: SyntaxNode<'db>) -> Option<InternalFix<'db>> {
56        fix_empty_enum_brackets_variant(db, node)
57    }
58
59    fn fix_message(&self) -> Option<&'static str> {
60        Some("Remove unit type definition from enum variant")
61    }
62}
63
64#[tracing::instrument(skip_all, level = "trace")]
65pub fn check_empty_enum_brackets_variant<'db>(
66    db: &'db dyn Database,
67    item: &ModuleItemId<'db>,
68    diagnostics: &mut Vec<PluginDiagnostic<'db>>,
69) {
70    let ModuleItemId::Enum(enum_id) = item else {
71        return;
72    };
73
74    let Ok(variants) = db.enum_variants(*enum_id) else {
75        return;
76    };
77
78    for variant in variants.values() {
79        let Ok(semantic_variant) = db.variant_semantic(*enum_id, *variant) else {
80            return;
81        };
82
83        // Check if the variant is of unit type `()`
84        if semantic_variant.ty.is_unit(db) {
85            let ast_variant = variant.stable_ptr(db).lookup(db);
86
87            // Determine if the variant includes a type clause, or if the type clause is empty
88            if let OptionTypeClause::TypeClause(_) = ast_variant.type_clause(db) {
89                diagnostics.push(PluginDiagnostic {
90                    stable_ptr: variant.stable_ptr(db).untyped(),
91                    message: EmptyEnumBracketsVariant.diagnostic_message().to_string(),
92                    severity: Severity::Warning,
93                    inner_span: None,
94                    error_code: None,
95                });
96            }
97        }
98    }
99}
100
101#[tracing::instrument(skip_all, level = "trace")]
102fn fix_empty_enum_brackets_variant<'db>(
103    db: &'db dyn Database,
104    node: SyntaxNode<'db>,
105) -> Option<InternalFix<'db>> {
106    let ast_variant = ast::Variant::from_syntax_node(db, node);
107
108    // Extract a clean type definition, to remove
109    let type_clause = ast_variant
110        .type_clause(db)
111        .as_syntax_node()
112        .get_text_without_trivia(db);
113
114    let variant_text = node.get_text(db);
115    let fixed = variant_text.replace(type_clause.long(db).as_str(), "");
116
117    Some(InternalFix {
118        node,
119        suggestion: fixed,
120        description: EmptyEnumBracketsVariant.fix_message().unwrap().to_string(),
121        import_addition_paths: None,
122    })
123}