aldrin_parser/warning/
unused_import.rs

1use super::Warning;
2use crate::ast::{
3    ArrayLen, ArrayLenValue, Definition, EnumDef, EnumVariant, EventDef, FunctionDef, FunctionPart,
4    ImportStmt, InlineEnum, InlineStruct, NamedRef, NamedRefKind, SchemaName, ServiceDef,
5    ServiceItem, StructDef, StructField, TypeName, TypeNameKind, TypeNameOrInline,
6};
7use crate::diag::{Diagnostic, DiagnosticKind, Formatted, Formatter};
8use crate::validate::Validate;
9use crate::{Parsed, Schema};
10
11#[derive(Debug)]
12pub struct UnusedImport {
13    schema_name: String,
14    import: ImportStmt,
15}
16
17impl UnusedImport {
18    pub(crate) fn validate(import: &ImportStmt, validate: &mut Validate) {
19        if Self::visit_schema(validate.get_current_schema(), import.schema_name()) {
20            return;
21        }
22
23        validate.add_warning(Self {
24            schema_name: validate.schema_name().to_owned(),
25            import: import.clone(),
26        });
27    }
28
29    fn visit_schema(schema: &Schema, schema_name: &SchemaName) -> bool {
30        schema
31            .definitions()
32            .iter()
33            .any(|def| Self::visit_def(def, schema_name))
34    }
35
36    fn visit_def(def: &Definition, schema_name: &SchemaName) -> bool {
37        match def {
38            Definition::Struct(d) => Self::visit_struct(d, schema_name),
39            Definition::Enum(d) => Self::visit_enum(d, schema_name),
40            Definition::Service(d) => Self::visit_service(d, schema_name),
41            Definition::Const(_) => false,
42        }
43    }
44
45    fn visit_struct(struct_def: &StructDef, schema_name: &SchemaName) -> bool {
46        Self::visit_struct_fields(struct_def.fields(), schema_name)
47    }
48
49    fn visit_inline_struct(inline_struct: &InlineStruct, schema_name: &SchemaName) -> bool {
50        Self::visit_struct_fields(inline_struct.fields(), schema_name)
51    }
52
53    fn visit_struct_fields(fields: &[StructField], schema_name: &SchemaName) -> bool {
54        fields
55            .iter()
56            .any(|field| Self::visit_struct_field(field, schema_name))
57    }
58
59    fn visit_struct_field(field: &StructField, schema_name: &SchemaName) -> bool {
60        Self::visit_type_name(field.field_type(), schema_name)
61    }
62
63    fn visit_enum(enum_def: &EnumDef, schema_name: &SchemaName) -> bool {
64        Self::visit_enum_variants(enum_def.variants(), schema_name)
65    }
66
67    fn visit_inline_enum(inline_enum: &InlineEnum, schema_name: &SchemaName) -> bool {
68        Self::visit_enum_variants(inline_enum.variants(), schema_name)
69    }
70
71    fn visit_enum_variants(vars: &[EnumVariant], schema_name: &SchemaName) -> bool {
72        vars.iter()
73            .any(|var| Self::visit_enum_variant(var, schema_name))
74    }
75
76    fn visit_enum_variant(var: &EnumVariant, schema_name: &SchemaName) -> bool {
77        match var.variant_type() {
78            Some(var_type) => Self::visit_type_name(var_type, schema_name),
79            None => false,
80        }
81    }
82
83    fn visit_service(service_def: &ServiceDef, schema_name: &SchemaName) -> bool {
84        service_def
85            .items()
86            .iter()
87            .any(|item| Self::visit_service_item(item, schema_name))
88    }
89
90    fn visit_service_item(item: &ServiceItem, schema_name: &SchemaName) -> bool {
91        match item {
92            ServiceItem::Function(func) => Self::visit_function(func, schema_name),
93            ServiceItem::Event(ev) => Self::visit_event(ev, schema_name),
94        }
95    }
96
97    fn visit_function(func: &FunctionDef, schema_name: &SchemaName) -> bool {
98        if let Some(args) = func.args() {
99            if Self::visit_function_part(args, schema_name) {
100                return true;
101            }
102        }
103
104        if let Some(ok) = func.ok() {
105            if Self::visit_function_part(ok, schema_name) {
106                return true;
107            }
108        }
109
110        if let Some(err) = func.err() {
111            if Self::visit_function_part(err, schema_name) {
112                return true;
113            }
114        }
115
116        false
117    }
118
119    fn visit_function_part(part: &FunctionPart, schema_name: &SchemaName) -> bool {
120        Self::visit_type_name_or_inline(part.part_type(), schema_name)
121    }
122
123    fn visit_event(ev: &EventDef, schema_name: &SchemaName) -> bool {
124        match ev.event_type() {
125            Some(event_type) => Self::visit_type_name_or_inline(event_type, schema_name),
126            None => false,
127        }
128    }
129
130    fn visit_type_name_or_inline(ty: &TypeNameOrInline, schema_name: &SchemaName) -> bool {
131        match ty {
132            TypeNameOrInline::TypeName(ty) => Self::visit_type_name(ty, schema_name),
133            TypeNameOrInline::Struct(s) => Self::visit_inline_struct(s, schema_name),
134            TypeNameOrInline::Enum(e) => Self::visit_inline_enum(e, schema_name),
135        }
136    }
137
138    fn visit_type_name(ty: &TypeName, schema_name: &SchemaName) -> bool {
139        match ty.kind() {
140            TypeNameKind::Option(ty)
141            | TypeNameKind::Box(ty)
142            | TypeNameKind::Vec(ty)
143            | TypeNameKind::Map(_, ty)
144            | TypeNameKind::Sender(ty)
145            | TypeNameKind::Receiver(ty) => Self::visit_type_name(ty, schema_name),
146
147            TypeNameKind::Array(ty, len) => {
148                Self::visit_type_name(ty, schema_name) || Self::visit_array_len(len, schema_name)
149            }
150
151            TypeNameKind::Result(ok, err) => {
152                Self::visit_type_name(ok, schema_name) || Self::visit_type_name(err, schema_name)
153            }
154
155            TypeNameKind::Ref(ty) => Self::visit_named_ref(ty, schema_name),
156
157            TypeNameKind::Bool
158            | TypeNameKind::U8
159            | TypeNameKind::I8
160            | TypeNameKind::U16
161            | TypeNameKind::I16
162            | TypeNameKind::U32
163            | TypeNameKind::I32
164            | TypeNameKind::U64
165            | TypeNameKind::I64
166            | TypeNameKind::F32
167            | TypeNameKind::F64
168            | TypeNameKind::String
169            | TypeNameKind::Uuid
170            | TypeNameKind::ObjectId
171            | TypeNameKind::ServiceId
172            | TypeNameKind::Value
173            | TypeNameKind::Bytes
174            | TypeNameKind::Set(_)
175            | TypeNameKind::Lifetime
176            | TypeNameKind::Unit => false,
177        }
178    }
179
180    fn visit_named_ref(ty: &NamedRef, schema_name: &SchemaName) -> bool {
181        match ty.kind() {
182            NamedRefKind::Intern(_) => false,
183            NamedRefKind::Extern(schema, _) => schema.value() == schema_name.value(),
184        }
185    }
186
187    fn visit_array_len(len: &ArrayLen, schema_name: &SchemaName) -> bool {
188        match len.value() {
189            ArrayLenValue::Literal(_) => false,
190            ArrayLenValue::Ref(ty) => Self::visit_named_ref(ty, schema_name),
191        }
192    }
193
194    pub fn import(&self) -> &ImportStmt {
195        &self.import
196    }
197}
198
199impl Diagnostic for UnusedImport {
200    fn kind(&self) -> DiagnosticKind {
201        DiagnosticKind::Warning
202    }
203
204    fn schema_name(&self) -> &str {
205        &self.schema_name
206    }
207
208    fn format<'a>(&'a self, parsed: &'a Parsed) -> Formatted<'a> {
209        let mut fmt = Formatter::new(
210            self,
211            format!("unused import `{}`", self.import.schema_name().value()),
212        );
213
214        if let Some(schema) = parsed.get_schema(&self.schema_name) {
215            fmt.main_block(schema, self.import.span().from, self.import.span(), "")
216                .help("remove the import statement");
217        }
218
219        fmt.format()
220    }
221}
222
223impl From<UnusedImport> for Warning {
224    fn from(w: UnusedImport) -> Self {
225        Self::UnusedImport(w)
226    }
227}