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