This commit is contained in:
148
app/techdebt-tracker-cli/src/core/analyzer.rs
Normal file
148
app/techdebt-tracker-cli/src/core/analyzer.rs
Normal file
@@ -0,0 +1,148 @@
|
||||
use anyhow::{Context, Result};
|
||||
use ignore::WalkBuilder;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::models::{
|
||||
AnalysisSummary, ByLanguage, ByPriority, CommentType, Config, FileLocation, Priority,
|
||||
TechDebtItem,
|
||||
};
|
||||
|
||||
pub struct Analyzer {
|
||||
path: PathBuf,
|
||||
config: Config,
|
||||
}
|
||||
|
||||
impl Analyzer {
|
||||
pub fn new(path: &PathBuf, config_path: &Option<PathBuf>) -> Result<Self> {
|
||||
let config = load_config(config_path)?;
|
||||
Ok(Self {
|
||||
path: path.clone(),
|
||||
config,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn analyze(&self) -> Result<Vec<TechDebtItem>> {
|
||||
let mut items = Vec::new();
|
||||
|
||||
let walker = WalkBuilder::new(&self.path)
|
||||
.hidden(true)
|
||||
.git_global(true)
|
||||
.git_ignore(true)
|
||||
.require_git(false)
|
||||
.build();
|
||||
|
||||
for result in walker {
|
||||
match result {
|
||||
Ok(entry) => {
|
||||
let path = entry.path();
|
||||
|
||||
if !self.should_include(path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(language) = Language::from_path(path) {
|
||||
match self.parse_file(path, &language) {
|
||||
Ok(mut file_items) => items.append(&mut file_items),
|
||||
Err(e) => {
|
||||
eprintln!("Warning: Failed to parse {}: {}", path.display(), e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!("Warning: Error walking directory: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
items.sort_by(|a, b| {
|
||||
b.priority
|
||||
.cmp(&a.priority)
|
||||
.then_with(|| a.location.line.cmp(&b.location.line))
|
||||
});
|
||||
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
fn should_include(&self, path: &PathBuf) -> bool {
|
||||
if !path.is_file() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(ext) = path.extension() {
|
||||
if let Some(ext_str) = ext.to_str() {
|
||||
let ext_with_dot = format!(".{}", ext_str);
|
||||
if !self.config.extensions.contains(&ext_with_dot) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for pattern in &self.config.ignore {
|
||||
if match_ignore_pattern(path, pattern) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn parse_file(
|
||||
&self,
|
||||
path: &PathBuf,
|
||||
language: &Language,
|
||||
) -> Result<Vec<TechDebtItem>> {
|
||||
let content = std::fs::read_to_string(path)
|
||||
.with_context(|| format!("Failed to read file: {}", path.display()))?;
|
||||
|
||||
let parser = LanguageParser::new(language.clone());
|
||||
parser.parse(&content, path, &self.config.patterns)
|
||||
}
|
||||
}
|
||||
|
||||
fn match_ignore_pattern(path: &PathBuf, pattern: &str) -> bool {
|
||||
if pattern.ends_with("/**") {
|
||||
let prefix = &pattern[..pattern.len() - 3];
|
||||
if let Some(path_str) = path.to_str() {
|
||||
return path_str.starts_with(prefix)
|
||||
|| path_str.contains(&format!("{}/", prefix));
|
||||
}
|
||||
} else if let Some(file_name) = path.file_name() {
|
||||
if let Some(file_name_str) = file_name.to_str() {
|
||||
return glob::Pattern::new(pattern)
|
||||
.ok()
|
||||
.map(|p| p.matches(file_name_str))
|
||||
.unwrap_or(false);
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn load_config(config_path: &Option<PathBuf>) -> Result<Config> {
|
||||
let config_path = if let Some(path) = config_path {
|
||||
path.clone()
|
||||
} else {
|
||||
std::env::current_dir()?
|
||||
.join("techdebt.yaml")
|
||||
};
|
||||
|
||||
if config_path.exists() {
|
||||
let content = std::fs::read_to_string(&config_path)?;
|
||||
let config: Config = serde_yaml::from_str(&content)?;
|
||||
return Ok(config);
|
||||
}
|
||||
|
||||
Ok(Config::default())
|
||||
}
|
||||
|
||||
pub fn summarize(items: &[TechDebtItem]) -> AnalysisSummary {
|
||||
let by_priority = ByPriority::from_items(items);
|
||||
let by_language = ByLanguage::from_items(items);
|
||||
let complexity_distribution =
|
||||
crate::models::ComplexityDistribution::from_items(items);
|
||||
|
||||
AnalysisSummary {
|
||||
total_items: items.len(),
|
||||
by_priority,
|
||||
by_language,
|
||||
complexity_distribution,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user