use std::path::Path;
use crate::builtins::{BUILTINS_META, BuiltinMeta};
#[derive(Debug)]
pub struct Detection {
pub builtin: &'static BuiltinMeta,
pub reason: String,
}
pub fn detect_builtins(project_root: &Path) -> Vec<Detection> {
let mut detections = Vec::new();
for meta in BUILTINS_META {
if meta.project_indicators.is_empty() {
continue;
}
for indicator in meta.project_indicators {
if let Some(reason) = matches_indicator(project_root, indicator) {
detections.push(Detection {
builtin: meta,
reason,
});
break; }
}
}
detections
}
fn matches_indicator(
project_root: &Path,
indicator: &crate::builtins::ProjectIndicator,
) -> Option<String> {
if let Some(file) = indicator.file {
let path = project_root.join(file);
if !path.exists() {
return None;
}
if let Some(pattern) = indicator.contains {
if path.is_file()
&& let Ok(content) = std::fs::read_to_string(&path)
&& content.contains(pattern)
{
return Some(format!("{} contains {}", file, pattern));
}
return None;
}
return Some(file.to_string());
}
if let Some(glob_pattern) = indicator.glob
&& let Some(ext) = glob_pattern.strip_prefix("*.")
&& let Ok(entries) = std::fs::read_dir(project_root)
{
for entry in entries.flatten() {
if let Some(file_ext) = entry.path().extension()
&& file_ext == ext
{
return Some(format!("{} files", glob_pattern));
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detect_builtins_empty_dir() {
let tmp = tempfile::tempdir().unwrap();
let detections = detect_builtins(tmp.path());
assert!(detections.is_empty());
}
#[test]
fn test_detect_builtins_with_cargo_toml() {
let tmp = tempfile::tempdir().unwrap();
std::fs::write(tmp.path().join("Cargo.toml"), "[package]\nname = \"test\"").unwrap();
let detections = detect_builtins(tmp.path());
let names: Vec<_> = detections.iter().map(|d| d.builtin.name).collect();
assert!(names.contains(&"cargo_clippy"));
assert!(names.contains(&"cargo_fmt"));
}
#[test]
fn test_detect_builtins_with_package_json() {
let tmp = tempfile::tempdir().unwrap();
std::fs::write(tmp.path().join("package.json"), "{}").unwrap();
let detections = detect_builtins(tmp.path());
let names: Vec<_> = detections.iter().map(|d| d.builtin.name).collect();
assert!(names.contains(&"prettier"));
}
#[test]
fn test_detect_eslint_with_contains() {
let tmp = tempfile::tempdir().unwrap();
std::fs::write(
tmp.path().join("package.json"),
r#"{"devDependencies": {"eslint": "^8.0.0"}}"#,
)
.unwrap();
let detections = detect_builtins(tmp.path());
let names: Vec<_> = detections.iter().map(|d| d.builtin.name).collect();
assert!(names.contains(&"eslint"));
}
#[test]
fn test_detect_eslint_not_present() {
let tmp = tempfile::tempdir().unwrap();
std::fs::write(
tmp.path().join("package.json"),
r#"{"devDependencies": {"prettier": "^3.0.0"}}"#,
)
.unwrap();
let detections = detect_builtins(tmp.path());
let names: Vec<_> = detections.iter().map(|d| d.builtin.name).collect();
assert!(!names.contains(&"eslint"));
}
#[test]
fn test_detect_shell_scripts() {
let tmp = tempfile::tempdir().unwrap();
std::fs::write(tmp.path().join("test.sh"), "#!/bin/bash\necho hello").unwrap();
let detections = detect_builtins(tmp.path());
let names: Vec<_> = detections.iter().map(|d| d.builtin.name).collect();
assert!(names.contains(&"shellcheck"));
}
}