garbage_code_hunter/deps_shamer/
mod.rs1pub mod parser;
10pub mod report;
11pub mod rules;
12pub mod types;
13
14use anyhow::Result;
15use std::path::Path;
16
17use parser::detect_and_parse;
18use report::{format_json, format_terminal};
19use rules::check_dep_file;
20use types::DepFile;
21
22pub use crate::common::OutputFormat;
23
24pub fn run(project_path: &Path, format: &OutputFormat) -> Result<String> {
29 let dep_files = detect_and_parse(project_path)?;
30
31 if dep_files.is_empty() {
32 return match format {
33 OutputFormat::Terminal => Ok(format!(
34 "\n{}\n\n No dependency files found in {}\n\n Supported: Cargo.toml, package.json, go.mod, requirements.txt, pyproject.toml\n",
35 "\u{1f4e6} Dependency Shame Report \u{1f4e6}",
36 project_path.display()
37 )),
38 OutputFormat::Json => Ok(r#"{"score":100,"total_deps":0,"issues":[],"files":[]}"#.to_string()),
39 };
40 }
41
42 let all_issues: Vec<_> = dep_files.iter().flat_map(check_dep_file).collect();
43
44 let output = match format {
45 OutputFormat::Terminal => format_terminal(&dep_files, &all_issues),
46 OutputFormat::Json => format_json(&dep_files, &all_issues),
47 };
48
49 Ok(output)
50}
51
52pub fn analyze(project_path: &Path) -> Result<(Vec<DepFile>, Vec<types::DepIssue>)> {
54 let dep_files = detect_and_parse(project_path)?;
55 let all_issues: Vec<_> = dep_files.iter().flat_map(check_dep_file).collect();
56 Ok((dep_files, all_issues))
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62 use std::fs;
63 use tempfile::TempDir;
64
65 #[test]
66 fn test_run_no_dep_files() {
67 let dir = TempDir::new().unwrap();
68 let output = run(dir.path(), &OutputFormat::Terminal).unwrap();
69 assert!(output.contains("No dependency files found"));
70 }
71
72 #[test]
73 fn test_run_no_dep_files_json() {
74 let dir = TempDir::new().unwrap();
75 let output = run(dir.path(), &OutputFormat::Json).unwrap();
76 let parsed: serde_json::Value = serde_json::from_str(&output).unwrap();
77 assert_eq!(parsed["total_deps"], 0);
78 }
79
80 #[test]
81 fn test_run_with_cargo_toml() {
82 let dir = TempDir::new().unwrap();
83 fs::write(
84 dir.path().join("Cargo.toml"),
85 r#"
86[dependencies]
87serde = "1.0"
88my-git-crate = { git = "https://github.com/foo/bar" }
89wildcard = "*"
90"#,
91 )
92 .unwrap();
93
94 let output = run(dir.path(), &OutputFormat::Terminal).unwrap();
95 assert!(output.contains("Dependency Shame Report"));
96 assert!(output.contains("wildcard") || output.contains("git"));
98 }
99
100 #[test]
101 fn test_run_json_format() {
102 let dir = TempDir::new().unwrap();
103 fs::write(
104 dir.path().join("Cargo.toml"),
105 "[dependencies]\nserde = \"1.0\"\n",
106 )
107 .unwrap();
108
109 let output = run(dir.path(), &OutputFormat::Json).unwrap();
110 let parsed: serde_json::Value = serde_json::from_str(&output).unwrap();
111 assert!(parsed["score"].as_f64().unwrap() > 0.0);
112 }
113
114 #[test]
115 fn test_analyze_returns_structured_data() {
116 let dir = TempDir::new().unwrap();
117 fs::write(
118 dir.path().join("Cargo.toml"),
119 "[dependencies]\nserde = \"1.0\"\n",
120 )
121 .unwrap();
122
123 let (dep_files, issues) = analyze(dir.path()).unwrap();
124 assert_eq!(dep_files.len(), 1);
125 assert_eq!(dep_files[0].ecosystem, types::Ecosystem::Rust);
126 assert_eq!(dep_files[0].dependencies.len(), 1);
127 assert!(issues.is_empty());
129 }
130
131 #[test]
132 fn test_run_multiple_ecosystems() {
133 let dir = TempDir::new().unwrap();
134 fs::write(
135 dir.path().join("Cargo.toml"),
136 "[dependencies]\nserde = \"1.0\"\n",
137 )
138 .unwrap();
139 fs::write(
140 dir.path().join("package.json"),
141 r#"{"dependencies": {"express": "^4.0"}}"#,
142 )
143 .unwrap();
144
145 let (dep_files, _) = analyze(dir.path()).unwrap();
146 assert_eq!(dep_files.len(), 2);
147 assert!(dep_files
148 .iter()
149 .any(|f| f.ecosystem == types::Ecosystem::Rust));
150 assert!(dep_files
151 .iter()
152 .any(|f| f.ecosystem == types::Ecosystem::Node));
153 }
154}