1use std::path::PathBuf;
2
3use serde::Serialize;
4
5use crate::extract::MemberKind;
6
7#[derive(Debug, Default, Serialize)]
9pub struct AnalysisResults {
10 pub unused_files: Vec<UnusedFile>,
11 pub unused_exports: Vec<UnusedExport>,
12 pub unused_types: Vec<UnusedExport>,
13 pub unused_dependencies: Vec<UnusedDependency>,
14 pub unused_dev_dependencies: Vec<UnusedDependency>,
15 pub unused_enum_members: Vec<UnusedMember>,
16 pub unused_class_members: Vec<UnusedMember>,
17 pub unresolved_imports: Vec<UnresolvedImport>,
18 pub unlisted_dependencies: Vec<UnlistedDependency>,
19 pub duplicate_exports: Vec<DuplicateExport>,
20 pub type_only_dependencies: Vec<TypeOnlyDependency>,
23}
24
25impl AnalysisResults {
26 pub fn total_issues(&self) -> usize {
28 self.unused_files.len()
29 + self.unused_exports.len()
30 + self.unused_types.len()
31 + self.unused_dependencies.len()
32 + self.unused_dev_dependencies.len()
33 + self.unused_enum_members.len()
34 + self.unused_class_members.len()
35 + self.unresolved_imports.len()
36 + self.unlisted_dependencies.len()
37 + self.duplicate_exports.len()
38 + self.type_only_dependencies.len()
39 }
40
41 pub fn has_issues(&self) -> bool {
43 self.total_issues() > 0
44 }
45}
46
47#[derive(Debug, Serialize)]
49pub struct UnusedFile {
50 pub path: PathBuf,
51}
52
53#[derive(Debug, Serialize)]
55pub struct UnusedExport {
56 pub path: PathBuf,
57 pub export_name: String,
58 pub is_type_only: bool,
59 pub line: u32,
60 pub col: u32,
61 pub span_start: u32,
63 pub is_re_export: bool,
65}
66
67#[derive(Debug, Serialize)]
69pub struct UnusedDependency {
70 pub package_name: String,
71 pub location: DependencyLocation,
72 pub path: PathBuf,
75}
76
77#[derive(Debug, Serialize)]
79#[serde(rename_all = "camelCase")]
80pub enum DependencyLocation {
81 Dependencies,
82 DevDependencies,
83}
84
85#[derive(Debug, Serialize)]
87pub struct UnusedMember {
88 pub path: PathBuf,
89 pub parent_name: String,
90 pub member_name: String,
91 pub kind: MemberKind,
92 pub line: u32,
93 pub col: u32,
94}
95
96#[derive(Debug, Serialize)]
98pub struct UnresolvedImport {
99 pub path: PathBuf,
100 pub specifier: String,
101 pub line: u32,
102 pub col: u32,
103}
104
105#[derive(Debug, Serialize)]
107pub struct UnlistedDependency {
108 pub package_name: String,
109 pub imported_from: Vec<PathBuf>,
110}
111
112#[derive(Debug, Serialize)]
114pub struct DuplicateExport {
115 pub export_name: String,
116 pub locations: Vec<PathBuf>,
117}
118
119#[derive(Debug, Serialize)]
123pub struct TypeOnlyDependency {
124 pub package_name: String,
125 pub path: PathBuf,
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn empty_results_no_issues() {
135 let results = AnalysisResults::default();
136 assert_eq!(results.total_issues(), 0);
137 assert!(!results.has_issues());
138 }
139
140 #[test]
141 fn results_with_unused_file() {
142 let mut results = AnalysisResults::default();
143 results.unused_files.push(UnusedFile {
144 path: PathBuf::from("test.ts"),
145 });
146 assert_eq!(results.total_issues(), 1);
147 assert!(results.has_issues());
148 }
149
150 #[test]
151 fn results_with_unused_export() {
152 let mut results = AnalysisResults::default();
153 results.unused_exports.push(UnusedExport {
154 path: PathBuf::from("test.ts"),
155 export_name: "foo".to_string(),
156 is_type_only: false,
157 line: 1,
158 col: 0,
159 span_start: 0,
160 is_re_export: false,
161 });
162 assert_eq!(results.total_issues(), 1);
163 assert!(results.has_issues());
164 }
165
166 #[test]
167 fn results_total_counts_all_types() {
168 let mut results = AnalysisResults::default();
169 results.unused_files.push(UnusedFile {
170 path: PathBuf::from("a.ts"),
171 });
172 results.unused_exports.push(UnusedExport {
173 path: PathBuf::from("b.ts"),
174 export_name: "x".to_string(),
175 is_type_only: false,
176 line: 1,
177 col: 0,
178 span_start: 0,
179 is_re_export: false,
180 });
181 results.unused_types.push(UnusedExport {
182 path: PathBuf::from("c.ts"),
183 export_name: "T".to_string(),
184 is_type_only: true,
185 line: 1,
186 col: 0,
187 span_start: 0,
188 is_re_export: false,
189 });
190 results.unused_dependencies.push(UnusedDependency {
191 package_name: "dep".to_string(),
192 location: DependencyLocation::Dependencies,
193 path: PathBuf::from("package.json"),
194 });
195 results.unused_dev_dependencies.push(UnusedDependency {
196 package_name: "dev".to_string(),
197 location: DependencyLocation::DevDependencies,
198 path: PathBuf::from("package.json"),
199 });
200 results.unused_enum_members.push(UnusedMember {
201 path: PathBuf::from("d.ts"),
202 parent_name: "E".to_string(),
203 member_name: "A".to_string(),
204 kind: MemberKind::EnumMember,
205 line: 1,
206 col: 0,
207 });
208 results.unused_class_members.push(UnusedMember {
209 path: PathBuf::from("e.ts"),
210 parent_name: "C".to_string(),
211 member_name: "m".to_string(),
212 kind: MemberKind::ClassMethod,
213 line: 1,
214 col: 0,
215 });
216 results.unresolved_imports.push(UnresolvedImport {
217 path: PathBuf::from("f.ts"),
218 specifier: "./missing".to_string(),
219 line: 1,
220 col: 0,
221 });
222 results.unlisted_dependencies.push(UnlistedDependency {
223 package_name: "unlisted".to_string(),
224 imported_from: vec![PathBuf::from("g.ts")],
225 });
226 results.duplicate_exports.push(DuplicateExport {
227 export_name: "dup".to_string(),
228 locations: vec![PathBuf::from("h.ts"), PathBuf::from("i.ts")],
229 });
230
231 assert_eq!(results.total_issues(), 10);
232 assert!(results.has_issues());
233 }
234}