1use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8use std::path::PathBuf;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
12#[serde(rename_all = "snake_case")]
13pub enum DeadCodeKind {
14 UnusedExport,
16 UnreachableFunction,
18 UnusedVariable,
20 UnusedImport,
22 ZombieFile,
24 UnusedType,
26 UnusedClass,
28 UnusedEnum,
30 DeadBranch,
32}
33
34impl std::fmt::Display for DeadCodeKind {
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 match self {
37 DeadCodeKind::UnusedExport => write!(f, "unused_export"),
38 DeadCodeKind::UnreachableFunction => write!(f, "unreachable_function"),
39 DeadCodeKind::UnusedVariable => write!(f, "unused_variable"),
40 DeadCodeKind::UnusedImport => write!(f, "unused_import"),
41 DeadCodeKind::ZombieFile => write!(f, "zombie_file"),
42 DeadCodeKind::UnusedType => write!(f, "unused_type"),
43 DeadCodeKind::UnusedClass => write!(f, "unused_class"),
44 DeadCodeKind::UnusedEnum => write!(f, "unused_enum"),
45 DeadCodeKind::DeadBranch => write!(f, "dead_branch"),
46 }
47 }
48}
49
50#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
52pub struct CodeSpan {
53 pub start: u32,
55 pub end: u32,
57 pub col_start: u32,
59 pub col_end: u32,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
65pub struct DeadCodeItem {
66 pub file_path: PathBuf,
68 pub relative_path: String,
70 pub span: CodeSpan,
72 pub code_snippet: String,
74 pub kind: DeadCodeKind,
76 pub name: String,
78 pub reason: String,
80 pub confidence: f64,
83 #[serde(skip_serializing_if = "Option::is_none")]
85 pub context: Option<DeadCodeContext>,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
90pub struct DeadCodeContext {
91 pub possibly_dynamic: bool,
93 pub in_test_file: bool,
95 pub public_api: bool,
97 pub partial_references: Vec<String>,
99 #[serde(skip_serializing_if = "Option::is_none")]
101 pub doc_comment: Option<String>,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
106pub struct ScanOutput {
107 pub version: String,
109 pub root: String,
111 pub timestamp: String,
113 pub dead_code: Vec<DeadCodeItem>,
115 pub total_files_scanned: u32,
117 pub total_lines: u64,
119 pub scan_duration_ms: u64,
121 pub summary: ScanSummary,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
127pub struct ScanSummary {
128 pub unused_exports: u32,
129 pub unreachable_functions: u32,
130 pub unused_variables: u32,
131 pub unused_imports: u32,
132 pub zombie_files: u32,
133 pub unused_types: u32,
134 pub total_issues: u32,
135 pub high_confidence_issues: u32,
136 pub low_confidence_issues: u32,
137}
138
139impl ScanSummary {
140 pub fn new() -> Self {
141 Self {
142 unused_exports: 0,
143 unreachable_functions: 0,
144 unused_variables: 0,
145 unused_imports: 0,
146 zombie_files: 0,
147 unused_types: 0,
148 total_issues: 0,
149 high_confidence_issues: 0,
150 low_confidence_issues: 0,
151 }
152 }
153
154 pub fn add(&mut self, item: &DeadCodeItem) {
155 self.total_issues += 1;
156
157 if item.confidence >= 0.8 {
158 self.high_confidence_issues += 1;
159 } else {
160 self.low_confidence_issues += 1;
161 }
162
163 match item.kind {
164 DeadCodeKind::UnusedExport => self.unused_exports += 1,
165 DeadCodeKind::UnreachableFunction => self.unreachable_functions += 1,
166 DeadCodeKind::UnusedVariable => self.unused_variables += 1,
167 DeadCodeKind::UnusedImport => self.unused_imports += 1,
168 DeadCodeKind::ZombieFile => self.zombie_files += 1,
169 DeadCodeKind::UnusedType | DeadCodeKind::UnusedClass | DeadCodeKind::UnusedEnum => {
170 self.unused_types += 1
171 }
172 DeadCodeKind::DeadBranch => {}
173 }
174 }
175}
176
177impl Default for ScanSummary {
178 fn default() -> Self {
179 Self::new()
180 }
181}
182
183#[derive(Debug, Clone, Serialize, Deserialize)]
185pub struct ClrConfig {
186 pub extensions: Vec<String>,
188 pub ignore_patterns: Vec<String>,
190 pub include_tests: bool,
192 pub confidence_threshold: f64,
194 pub output: OutputConfig,
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
199pub struct OutputConfig {
200 pub agent_md: bool,
202 pub claude_md: bool,
204 pub cursorrules: bool,
206}
207
208impl Default for ClrConfig {
209 fn default() -> Self {
210 Self {
211 extensions: vec![
212 "ts".into(),
213 "tsx".into(),
214 "js".into(),
215 "jsx".into(),
216 "mjs".into(),
217 "cjs".into(),
218 ],
219 ignore_patterns: vec![
220 "**/node_modules/**".into(),
221 "**/dist/**".into(),
222 "**/build/**".into(),
223 "**/.git/**".into(),
224 "**/coverage/**".into(),
225 "**/*.min.js".into(),
226 "**/*.bundle.js".into(),
227 ],
228 include_tests: false,
229 confidence_threshold: 0.5,
230 output: OutputConfig {
231 agent_md: true,
232 claude_md: true,
233 cursorrules: true,
234 },
235 }
236 }
237}
238
239#[derive(Debug, Clone)]
241pub struct ReferenceNode {
242 pub file_path: PathBuf,
243 pub exports: Vec<ExportedSymbol>,
244 pub imports: Vec<ImportedSymbol>,
245 pub internal_refs: Vec<String>,
246}
247
248#[derive(Debug, Clone)]
249pub struct ExportedSymbol {
250 pub name: String,
251 pub kind: SymbolKind,
252 pub span: CodeSpan,
253 pub is_default: bool,
254 pub is_reexport: bool,
255}
256
257#[derive(Debug, Clone)]
258pub struct ImportedSymbol {
259 pub name: String,
260 pub alias: Option<String>,
261 pub source: String,
262 pub is_type_only: bool,
263 pub span: CodeSpan,
264}
265
266#[derive(Debug, Clone, Copy, PartialEq, Eq)]
267pub enum SymbolKind {
268 Function,
269 Class,
270 Variable,
271 Type,
272 Interface,
273 Enum,
274 Const,
275 Let,
276 Namespace,
277}
278
279#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
281pub struct LlmJudgmentRequest {
282 pub items: Vec<DeadCodeItem>,
283 pub project_context: ProjectContext,
284}
285
286#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
287pub struct ProjectContext {
288 pub name: String,
289 pub framework: Option<String>,
290 pub package_json_main: Option<String>,
291 pub package_json_exports: Vec<String>,
292}
293
294#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
296pub struct LlmJudgmentResponse {
297 pub confirmed: Vec<ConfirmedDeadCode>,
298 pub rejected: Vec<RejectedItem>,
299}
300
301#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
302pub struct ConfirmedDeadCode {
303 pub file_path: String,
304 pub name: String,
305 pub action: RemovalAction,
306}
307
308#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
309#[serde(rename_all = "snake_case")]
310pub enum RemovalAction {
311 Delete,
312 CommentOut,
313 MoveToTrash,
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
317pub struct RejectedItem {
318 pub file_path: String,
319 pub name: String,
320 pub reason: String,
321}