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}
21
22impl AnalysisResults {
23 pub fn total_issues(&self) -> usize {
25 self.unused_files.len()
26 + self.unused_exports.len()
27 + self.unused_types.len()
28 + self.unused_dependencies.len()
29 + self.unused_dev_dependencies.len()
30 + self.unused_enum_members.len()
31 + self.unused_class_members.len()
32 + self.unresolved_imports.len()
33 + self.unlisted_dependencies.len()
34 + self.duplicate_exports.len()
35 }
36
37 pub fn has_issues(&self) -> bool {
39 self.total_issues() > 0
40 }
41}
42
43#[derive(Debug, Serialize)]
45pub struct UnusedFile {
46 pub path: PathBuf,
47}
48
49#[derive(Debug, Serialize)]
51pub struct UnusedExport {
52 pub path: PathBuf,
53 pub export_name: String,
54 pub is_type_only: bool,
55 pub line: u32,
56 pub col: u32,
57 pub span_start: u32,
59}
60
61#[derive(Debug, Serialize)]
63pub struct UnusedDependency {
64 pub package_name: String,
65 pub location: DependencyLocation,
66}
67
68#[derive(Debug, Serialize)]
70#[serde(rename_all = "camelCase")]
71pub enum DependencyLocation {
72 Dependencies,
73 DevDependencies,
74}
75
76#[derive(Debug, Serialize)]
78pub struct UnusedMember {
79 pub path: PathBuf,
80 pub parent_name: String,
81 pub member_name: String,
82 pub kind: MemberKind,
83 pub line: u32,
84 pub col: u32,
85}
86
87#[derive(Debug, Serialize)]
89pub struct UnresolvedImport {
90 pub path: PathBuf,
91 pub specifier: String,
92 pub line: u32,
93 pub col: u32,
94}
95
96#[derive(Debug, Serialize)]
98pub struct UnlistedDependency {
99 pub package_name: String,
100 pub imported_from: Vec<PathBuf>,
101}
102
103#[derive(Debug, Serialize)]
105pub struct DuplicateExport {
106 pub export_name: String,
107 pub locations: Vec<PathBuf>,
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn empty_results_no_issues() {
116 let results = AnalysisResults::default();
117 assert_eq!(results.total_issues(), 0);
118 assert!(!results.has_issues());
119 }
120
121 #[test]
122 fn results_with_unused_file() {
123 let mut results = AnalysisResults::default();
124 results.unused_files.push(UnusedFile {
125 path: PathBuf::from("test.ts"),
126 });
127 assert_eq!(results.total_issues(), 1);
128 assert!(results.has_issues());
129 }
130
131 #[test]
132 fn results_with_unused_export() {
133 let mut results = AnalysisResults::default();
134 results.unused_exports.push(UnusedExport {
135 path: PathBuf::from("test.ts"),
136 export_name: "foo".to_string(),
137 is_type_only: false,
138 line: 1,
139 col: 0,
140 span_start: 0,
141 });
142 assert_eq!(results.total_issues(), 1);
143 assert!(results.has_issues());
144 }
145
146 #[test]
147 fn results_total_counts_all_types() {
148 let mut results = AnalysisResults::default();
149 results.unused_files.push(UnusedFile {
150 path: PathBuf::from("a.ts"),
151 });
152 results.unused_exports.push(UnusedExport {
153 path: PathBuf::from("b.ts"),
154 export_name: "x".to_string(),
155 is_type_only: false,
156 line: 1,
157 col: 0,
158 span_start: 0,
159 });
160 results.unused_types.push(UnusedExport {
161 path: PathBuf::from("c.ts"),
162 export_name: "T".to_string(),
163 is_type_only: true,
164 line: 1,
165 col: 0,
166 span_start: 0,
167 });
168 results.unused_dependencies.push(UnusedDependency {
169 package_name: "dep".to_string(),
170 location: DependencyLocation::Dependencies,
171 });
172 results.unused_dev_dependencies.push(UnusedDependency {
173 package_name: "dev".to_string(),
174 location: DependencyLocation::DevDependencies,
175 });
176 results.unused_enum_members.push(UnusedMember {
177 path: PathBuf::from("d.ts"),
178 parent_name: "E".to_string(),
179 member_name: "A".to_string(),
180 kind: MemberKind::EnumMember,
181 line: 1,
182 col: 0,
183 });
184 results.unused_class_members.push(UnusedMember {
185 path: PathBuf::from("e.ts"),
186 parent_name: "C".to_string(),
187 member_name: "m".to_string(),
188 kind: MemberKind::ClassMethod,
189 line: 1,
190 col: 0,
191 });
192 results.unresolved_imports.push(UnresolvedImport {
193 path: PathBuf::from("f.ts"),
194 specifier: "./missing".to_string(),
195 line: 1,
196 col: 0,
197 });
198 results.unlisted_dependencies.push(UnlistedDependency {
199 package_name: "unlisted".to_string(),
200 imported_from: vec![PathBuf::from("g.ts")],
201 });
202 results.duplicate_exports.push(DuplicateExport {
203 export_name: "dup".to_string(),
204 locations: vec![PathBuf::from("h.ts"), PathBuf::from("i.ts")],
205 });
206
207 assert_eq!(results.total_issues(), 10);
208 assert!(results.has_issues());
209 }
210}