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}