use anyhow::Result;
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::Path;
use walkdir::WalkDir;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UsageLocation {
pub file: String,
pub line: usize,
pub content: String,
}
pub struct CodeScanner;
impl CodeScanner {
pub fn scan_for_usage(
project_path: &Path,
dependency_name: &str,
) -> Result<Vec<UsageLocation>> {
let mut locations = Vec::new();
let use_pattern = format!(r"\buse\s+{}(?:::|;|\s)", regex::escape(dependency_name));
let use_regex = Regex::new(&use_pattern)?;
let extern_pattern = format!(r"\bextern\s+crate\s+{}\b", regex::escape(dependency_name));
let extern_regex = Regex::new(&extern_pattern)?;
for entry in WalkDir::new(project_path)
.follow_links(true)
.into_iter()
.filter_map(|e| e.ok())
{
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) != Some("rs") {
continue;
}
if path.to_str().is_some_and(|s| s.contains("target/")) {
continue;
}
if let Ok(content) = fs::read_to_string(path) {
for (line_num, line) in content.lines().enumerate() {
if use_regex.is_match(line) || extern_regex.is_match(line) {
locations.push(UsageLocation {
file: path.display().to_string(),
line: line_num + 1,
content: line.trim().to_string(),
});
}
}
}
}
Ok(locations)
}
}