use crate::language::adapter::{adapter_for, FunctionNode};
use crate::language::Language;
use crate::treesitter::engine::ParsedFile;
use serde::Serialize;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub struct StyleIrThresholdSummary {
pub excessive_param_threshold: usize,
pub god_function_line_threshold: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct StyleIrSummary {
pub language: String,
pub line_count: usize,
pub function_count: usize,
pub god_function_count: usize,
pub panic_call_count: usize,
pub naming_violation_count: usize,
pub deeply_nested_block_count: usize,
pub debug_call_count: usize,
pub goroutine_spawn_count: usize,
pub defer_in_loop_count: usize,
pub go_convention_count: usize,
pub python_issue_count: usize,
pub java_issue_count: usize,
pub ruby_issue_count: usize,
pub c_issue_count: usize,
pub ts_issue_count: usize,
pub js_issue_count: usize,
pub swift_issue_count: usize,
pub dead_code_count: usize,
pub duplicate_import_count: usize,
pub excessive_param_count: usize,
pub unsafe_block_count: usize,
pub magic_number_count: usize,
pub commented_out_lines: usize,
pub todo_count: usize,
pub over_engineering_count: usize,
pub code_smell_count: usize,
pub is_clean_signal_baseline: bool,
pub thresholds: StyleIrThresholdSummary,
}
#[derive(Debug, Clone)]
pub struct StyleIr {
pub language: Language,
pub line_count: usize,
pub functions: Vec<FunctionNode>,
pub panic_call_count: usize,
pub naming_violation_count: usize,
pub deeply_nested_block_count: usize,
pub debug_call_count: usize,
pub excessive_param_count: usize,
pub unsafe_block_count: usize,
pub magic_number_count: usize,
pub commented_out_lines: usize,
pub todo_count: usize,
pub goroutine_spawn_count: usize,
pub defer_in_loop_count: usize,
pub go_convention_count: usize,
pub python_issue_count: usize,
pub java_issue_count: usize,
pub ruby_issue_count: usize,
pub c_issue_count: usize,
pub ts_issue_count: usize,
pub js_issue_count: usize,
pub swift_issue_count: usize,
pub dead_code_count: usize,
pub duplicate_import_count: usize,
}
impl StyleIr {
const EXCESSIVE_PARAM_THRESHOLD: usize = 5;
const GOD_FUNCTION_LINE_THRESHOLD: usize = 50;
pub fn from_parsed(file: &ParsedFile) -> Option<Self> {
let adapter = adapter_for(file.language)?;
let counts = adapter.compute_all(file);
Some(Self {
language: file.language,
line_count: file.content.lines().count(),
functions: counts.functions,
panic_call_count: counts.panic_calls,
naming_violation_count: counts.naming_violations,
deeply_nested_block_count: counts.deeply_nested_blocks,
debug_call_count: counts.debug_calls,
excessive_param_count: counts.excessive_params,
unsafe_block_count: counts.unsafe_blocks,
magic_number_count: counts.magic_numbers,
commented_out_lines: counts.commented_out_lines,
todo_count: counts.todo_markers,
goroutine_spawn_count: counts.goroutine_spawns,
defer_in_loop_count: counts.defer_in_loop,
go_convention_count: counts.go_conventions,
python_issue_count: counts.python_issues,
java_issue_count: counts.java_issues,
ruby_issue_count: counts.ruby_issues,
c_issue_count: counts.c_issues,
ts_issue_count: counts.ts_issues,
js_issue_count: counts.js_issues,
swift_issue_count: counts.swift_issues,
dead_code_count: counts.dead_code,
duplicate_import_count: counts.duplicate_imports,
})
}
pub fn god_function_count(&self) -> usize {
self.functions
.iter()
.filter(|function| {
function.end_line.saturating_sub(function.start_line)
> Self::GOD_FUNCTION_LINE_THRESHOLD
})
.count()
}
pub fn over_engineering_count(&self) -> usize {
self.god_function_count() + self.excessive_param_count + self.goroutine_spawn_count
}
pub fn code_smell_count(&self) -> usize {
self.unsafe_block_count * 2
+ self.magic_number_count
+ self.go_convention_count
+ self.python_issue_count
+ self.java_issue_count
+ self.ruby_issue_count
+ self.c_issue_count
+ self.ts_issue_count
+ self.js_issue_count
+ self.swift_issue_count
+ self.dead_code_count
+ self.duplicate_import_count
}
pub fn summary(&self) -> StyleIrSummary {
StyleIrSummary {
language: self.language.display_name().to_string(),
line_count: self.line_count,
function_count: self.functions.len(),
god_function_count: self.god_function_count(),
panic_call_count: self.panic_call_count,
naming_violation_count: self.naming_violation_count,
deeply_nested_block_count: self.deeply_nested_block_count,
debug_call_count: self.debug_call_count,
excessive_param_count: self.excessive_param_count,
unsafe_block_count: self.unsafe_block_count,
magic_number_count: self.magic_number_count,
commented_out_lines: self.commented_out_lines,
todo_count: self.todo_count,
goroutine_spawn_count: self.goroutine_spawn_count,
defer_in_loop_count: self.defer_in_loop_count,
go_convention_count: self.go_convention_count,
python_issue_count: self.python_issue_count,
java_issue_count: self.java_issue_count,
ruby_issue_count: self.ruby_issue_count,
c_issue_count: self.c_issue_count,
ts_issue_count: self.ts_issue_count,
js_issue_count: self.js_issue_count,
swift_issue_count: self.swift_issue_count,
dead_code_count: self.dead_code_count,
duplicate_import_count: self.duplicate_import_count,
over_engineering_count: self.over_engineering_count(),
code_smell_count: self.code_smell_count(),
is_clean_signal_baseline: self.is_clean_signal_baseline(),
thresholds: StyleIrThresholdSummary {
excessive_param_threshold: Self::EXCESSIVE_PARAM_THRESHOLD,
god_function_line_threshold: Self::GOD_FUNCTION_LINE_THRESHOLD,
},
}
}
pub fn is_clean_signal_baseline(&self) -> bool {
self.panic_call_count == 0
&& self.naming_violation_count == 0
&& self.deeply_nested_block_count == 0
&& self.debug_call_count == 0
&& self.excessive_param_count == 0
&& self.unsafe_block_count == 0
&& self.magic_number_count == 0
&& self.commented_out_lines == 0
&& self.todo_count == 0
&& self.goroutine_spawn_count == 0
&& self.defer_in_loop_count == 0
&& self.go_convention_count == 0
&& self.python_issue_count == 0
&& self.java_issue_count == 0
&& self.ruby_issue_count == 0
&& self.c_issue_count == 0
&& self.ts_issue_count == 0
&& self.js_issue_count == 0
&& self.swift_issue_count == 0
&& self.dead_code_count == 0
&& self.duplicate_import_count == 0
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::treesitter::engine::TreeSitterEngine;
use std::path::PathBuf;
fn parse_rust(code: &str) -> ParsedFile {
let engine = TreeSitterEngine::new();
engine
.parse_file(&PathBuf::from("sample.rs"), code)
.expect("Rust parser should parse valid source")
}
#[test]
fn test_style_ir_panic_count() {
let file = parse_rust("fn main() { value.unwrap(); panic!(\"boom\"); }");
let ir = StyleIr::from_parsed(&file).expect("Rust should have a style adapter");
assert_eq!(ir.language, Language::Rust);
assert_eq!(ir.line_count, 1);
assert_eq!(ir.panic_call_count, 2);
}
#[test]
fn test_style_ir_clean_baseline() {
let file = parse_rust("fn add(left: i32, right: i32) -> i32 { left + right }");
let ir = StyleIr::from_parsed(&file).expect("Rust should have a style adapter");
assert!(ir.is_clean_signal_baseline());
}
#[test]
fn test_style_ir_naming_count() {
let file = parse_rust("fn main() { let a = 1; }");
let ir = StyleIr::from_parsed(&file).expect("Rust should have a style adapter");
assert_eq!(ir.naming_violation_count, 1);
}
#[test]
fn test_style_ir_nested_count() {
let file = parse_rust(
r#"
fn main() {
if true {
if true {
if true {
if true {
if true {
if true {
let value = 1;
}
}
}
}
}
}
}
"#,
);
let ir = StyleIr::from_parsed(&file).expect("Rust should have a style adapter");
assert!(
ir.deeply_nested_block_count >= 1,
"deep nesting should produce at least one style fact"
);
}
#[test]
fn test_style_ir_debug_count() {
let file = parse_rust(
r#"
fn main() {
println!("hello");
dbg!(42);
}
"#,
);
let ir = StyleIr::from_parsed(&file).expect("Rust should have a style adapter");
assert_eq!(ir.debug_call_count, 2);
}
#[test]
fn test_style_ir_over_engineering_count() {
let file = parse_rust(
r#"
fn process(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32) -> i32 {
a + b + c + d + e + f
}
"#,
);
let ir = StyleIr::from_parsed(&file).expect("Rust should have a style adapter");
assert_eq!(ir.excessive_param_count, 1);
assert_eq!(ir.over_engineering_count(), 1);
}
#[test]
fn test_style_ir_code_smell_count() {
let file = parse_rust(
r#"
fn main() {
unsafe {
let ptr = 42 as *const i32;
let _ = *ptr;
}
foo(100);
}
"#,
);
let ir = StyleIr::from_parsed(&file).expect("Rust should have a style adapter");
assert!(ir.unsafe_block_count >= 1);
assert!(ir.magic_number_count >= 1);
assert_eq!(
ir.code_smell_count(),
ir.unsafe_block_count * 2 + ir.magic_number_count
);
}
#[test]
fn test_style_ir_summary_schema() {
let file = parse_rust(
r#"
fn process(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32) -> i32 {
unsafe {
let value = 42;
value + a + b + c + d + e + f
}
}
"#,
);
let ir = StyleIr::from_parsed(&file).expect("Rust should have a style adapter");
let summary = ir.summary();
assert_eq!(summary.language, "Rust");
assert_eq!(summary.line_count, ir.line_count);
assert_eq!(summary.function_count, ir.functions.len());
assert_eq!(summary.god_function_count, ir.god_function_count());
assert_eq!(summary.excessive_param_count, ir.excessive_param_count);
assert_eq!(summary.unsafe_block_count, ir.unsafe_block_count);
assert_eq!(summary.code_smell_count, ir.code_smell_count());
assert_eq!(summary.over_engineering_count, ir.over_engineering_count());
assert_eq!(summary.thresholds.excessive_param_threshold, 5);
assert_eq!(summary.thresholds.god_function_line_threshold, 50);
let json = serde_json::to_value(&summary).expect("summary should serialize");
assert!(
json.get("language").is_some(),
"summary JSON should include language"
);
assert!(
json.get("thresholds").is_some(),
"summary JSON should include thresholds"
);
}
}