impl TdgAnalyzer {
fn estimate_cyclomatic_complexity(&self, lines: &[&str]) -> u32 {
let mut complexity = 1;
for line in lines {
let trimmed = line.trim();
complexity += count_control_flow_keywords(trimmed);
complexity += count_logical_operators(trimmed);
}
complexity
}
fn estimate_nesting_depth(&self, source: &str) -> usize {
let mut max_depth = 0;
let mut current_depth = 0;
for line in source.lines() {
let trimmed = line.trim();
if trimmed.contains('{') {
current_depth += trimmed.matches('{').count();
max_depth = max_depth.max(current_depth);
}
if trimmed.contains('}') {
current_depth = current_depth.saturating_sub(trimmed.matches('}').count());
}
}
max_depth
}
fn estimate_duplication_ratio(&self, source: &str) -> f32 {
let lines: Vec<&str> = source
.lines()
.map(str::trim)
.filter(|l| !l.is_empty() && !l.starts_with("//") && !l.starts_with("/*"))
.collect();
if lines.len() < 3 {
return 0.0;
}
let mut duplicates = 0;
for i in 0..lines.len() {
for j in i + 1..lines.len() {
if lines[i] == lines[j] && lines[i].len() > 10 {
duplicates += 1;
}
}
}
duplicates as f32 / lines.len() as f32
}
fn discover_files(&self, dir: &Path) -> Result<crate::tdg::file_discovery::Discovery> {
crate::tdg::file_discovery::discover(dir, crate::tdg::file_discovery::Policy::heuristic())
}
#[cfg(test)]
fn should_skip_directory(&self, path: &Path) -> bool {
crate::tdg::file_discovery::is_skipped_directory(path)
}
#[cfg(test)]
fn should_analyze_file(&self, path: &Path) -> bool {
crate::tdg::file_discovery::is_gradable_path(
path,
crate::tdg::file_discovery::Policy::heuristic(),
)
}
}
fn count_control_flow_keywords(trimmed: &str) -> u32 {
let mut count = 0;
if trimmed.starts_with("if ") || trimmed.contains(" if ") {
count += 1;
}
if trimmed.starts_with("for ") || trimmed.contains(" for ") {
count += 1;
}
if trimmed.starts_with("while ") || trimmed.contains(" while ") {
count += 1;
}
if trimmed.starts_with("match ") || trimmed.contains(" match ") {
count += 1;
}
count
}
fn count_logical_operators(trimmed: &str) -> u32 {
if trimmed.contains(" && ") || trimmed.contains(" || ") {
trimmed.matches(" && ").count() as u32 + trimmed.matches(" || ").count() as u32
} else {
0
}
}