mod c;
mod c_cpp_common;
mod cpp;
mod go;
mod helpers;
mod java;
mod js;
mod python;
mod ruby;
mod rust;
mod swift;
mod ts;
mod zig;
pub use self::c::CAdapter;
pub use self::cpp::CppAdapter;
pub use self::go::GoAdapter;
pub use self::java::JavaAdapter;
pub use self::js::JSAdapter;
pub use self::python::PythonAdapter;
pub use self::ruby::RubyAdapter;
pub use self::rust::RustAdapter;
pub use self::swift::SwiftAdapter;
pub use self::ts::TSAdapter;
pub use self::zig::ZigAdapter;
use crate::language::Language;
use crate::treesitter::engine::ParsedFile;
use crate::treesitter::query::{collect_captures_multi, QueryCapture};
#[derive(Debug, Clone)]
pub struct FunctionNode {
pub name: String,
pub start_line: usize,
pub end_line: usize,
pub nesting_depth: usize,
}
#[derive(Debug, Clone, Default)]
pub struct AdapterCounts {
pub functions: Vec<FunctionNode>,
pub panic_calls: usize,
pub naming_violations: usize,
pub deeply_nested_blocks: usize,
pub debug_calls: usize,
pub excessive_params: usize,
pub unsafe_blocks: usize,
pub magic_numbers: usize,
pub commented_out_lines: usize,
pub todo_markers: usize,
pub goroutine_spawns: usize,
pub defer_in_loop: usize,
pub go_conventions: usize,
pub python_issues: usize,
pub java_issues: usize,
pub ruby_issues: usize,
pub c_issues: usize,
pub ts_issues: usize,
pub js_issues: usize,
pub swift_issues: usize,
pub dead_code: usize,
pub duplicate_imports: usize,
}
pub trait LanguageAdapter: Send + Sync {
fn language(&self) -> Language;
fn count_panic_calls(&self, file: &ParsedFile) -> usize;
fn extract_functions(&self, file: &ParsedFile) -> Vec<FunctionNode>;
fn max_nesting_depth(&self, file: &ParsedFile) -> usize;
fn count_naming_violations(&self, file: &ParsedFile) -> usize;
fn count_deeply_nested_blocks(&self, file: &ParsedFile) -> usize;
fn count_debug_calls(&self, file: &ParsedFile) -> usize;
fn count_excessive_params(&self, file: &ParsedFile, threshold: usize) -> usize;
fn count_unsafe_blocks(&self, file: &ParsedFile) -> usize {
let _ = file;
0
}
fn count_magic_numbers(&self, file: &ParsedFile) -> usize {
let _ = file;
0
}
fn count_goroutine_spawns(&self, file: &ParsedFile) -> usize {
let _ = file;
0
}
fn count_defer_in_loop(&self, file: &ParsedFile) -> usize {
let _ = file;
0
}
fn count_go_convention_violations(&self, file: &ParsedFile) -> usize {
let _ = file;
0
}
fn count_python_issues(&self, file: &ParsedFile) -> usize {
let _ = file;
0
}
fn count_java_issues(&self, file: &ParsedFile) -> usize {
let _ = file;
0
}
fn count_ruby_issues(&self, file: &ParsedFile) -> usize {
let _ = file;
0
}
fn count_c_issues(&self, file: &ParsedFile) -> usize {
let _ = file;
0
}
fn count_ts_issues(&self, file: &ParsedFile) -> usize {
let _ = file;
0
}
fn count_js_issues(&self, file: &ParsedFile) -> usize {
let _ = file;
0
}
fn count_swift_issues(&self, file: &ParsedFile) -> usize {
let _ = file;
0
}
fn count_dead_code(&self, file: &ParsedFile) -> usize {
let _ = file;
0
}
fn count_duplicate_imports(&self, file: &ParsedFile) -> usize {
let _ = file;
0
}
fn count_commented_out_code(&self, file: &ParsedFile) -> usize {
let line_comment = file.language.line_comment();
let mut total = 0;
let mut block_size = 0;
for line in file.content.lines() {
let trimmed = line.trim();
if trimmed.starts_with(line_comment) {
if (trimmed.starts_with("///") && !trimmed.starts_with("////"))
|| trimmed.starts_with("/**")
{
if block_size > 0 {
total += block_size;
block_size = 0;
}
continue;
}
let text = trimmed.strip_prefix(line_comment).unwrap_or("").trim();
let is_code = CODEC_PATTERNS.iter().any(|p| text.contains(p));
if is_code || block_size > 0 {
block_size += 1;
}
} else if !trimmed.is_empty() {
if block_size >= 3 {
total += block_size;
}
block_size = 0;
}
}
if block_size >= 3 {
total += block_size;
}
total
}
fn count_todo_markers(&self, file: &ParsedFile) -> usize {
let line_comment = file.language.line_comment();
let mut count = 0;
for line in file.content.lines() {
let trimmed = line.trim();
if let Some(pos) = trimmed.find(line_comment) {
if pos > 0 {
let prev = trimmed.as_bytes()[pos - 1];
if prev != b' ' && prev != b'\t' {
continue;
}
}
let comment = trimmed[pos + line_comment.len()..].trim().to_uppercase();
if comment.starts_with("TODO")
|| comment.contains(" TODO ")
|| comment.starts_with("FIXME")
|| comment.contains(" FIXME ")
|| comment.starts_with("BUG")
|| comment.contains(" BUG ")
|| comment.starts_with("HACK")
|| comment.contains(" HACK ")
{
count += 1;
}
}
}
count
}
fn query_patterns(&self) -> &[&str] {
&[]
}
fn batch_captures<'a>(&self, file: &'a ParsedFile) -> Vec<Vec<QueryCapture<'a>>> {
let patterns = self.query_patterns();
if patterns.is_empty() {
return Vec::new();
}
collect_captures_multi(file, patterns).unwrap_or_default()
}
fn compute_all(&self, file: &ParsedFile) -> AdapterCounts {
let batch = self.batch_captures(file);
AdapterCounts {
functions: self.extract_functions_from_batch(file, &batch),
panic_calls: self.count_panic_from_batch(file, &batch),
naming_violations: self.count_naming_from_batch(file, &batch),
deeply_nested_blocks: self.count_deeply_nested_blocks(file),
debug_calls: self.count_debug_from_batch(file, &batch),
excessive_params: self.count_excessive_from_batch(file, &batch),
unsafe_blocks: self.count_unsafe_from_batch(file, &batch),
magic_numbers: self.count_magic_from_batch(file, &batch),
commented_out_lines: self.count_commented_out_code(file),
todo_markers: self.count_todo_markers(file),
goroutine_spawns: self.count_goroutine_from_batch(file, &batch),
defer_in_loop: self.count_defer_in_loop(file),
go_conventions: self.count_go_convention_from_batch(file, &batch),
python_issues: self.count_python_from_batch(file, &batch),
java_issues: self.count_java_from_batch(file, &batch),
ruby_issues: self.count_ruby_from_batch(file, &batch),
c_issues: self.count_c_from_batch(file, &batch),
ts_issues: self.count_ts_from_batch(file, &batch),
js_issues: self.count_js_from_batch(file, &batch),
swift_issues: self.count_swift_from_batch(file, &batch),
dead_code: self.count_dead_code(file),
duplicate_imports: self.count_duplicate_imports(file),
}
}
fn extract_functions_from_batch<'a>(
&self,
file: &ParsedFile,
_batch: &[Vec<QueryCapture<'a>>],
) -> Vec<FunctionNode> {
self.extract_functions(file)
}
fn count_panic_from_batch<'a>(
&self,
file: &ParsedFile,
_batch: &[Vec<QueryCapture<'a>>],
) -> usize {
self.count_panic_calls(file)
}
fn count_naming_from_batch<'a>(
&self,
file: &ParsedFile,
_batch: &[Vec<QueryCapture<'a>>],
) -> usize {
self.count_naming_violations(file)
}
fn count_debug_from_batch<'a>(
&self,
file: &ParsedFile,
_batch: &[Vec<QueryCapture<'a>>],
) -> usize {
self.count_debug_calls(file)
}
fn count_excessive_from_batch<'a>(
&self,
file: &ParsedFile,
_batch: &[Vec<QueryCapture<'a>>],
) -> usize {
self.count_excessive_params(file, 5)
}
fn count_unsafe_from_batch<'a>(
&self,
file: &ParsedFile,
_batch: &[Vec<QueryCapture<'a>>],
) -> usize {
self.count_unsafe_blocks(file)
}
fn count_magic_from_batch<'a>(
&self,
file: &ParsedFile,
_batch: &[Vec<QueryCapture<'a>>],
) -> usize {
self.count_magic_numbers(file)
}
fn count_goroutine_from_batch<'a>(
&self,
file: &ParsedFile,
_batch: &[Vec<QueryCapture<'a>>],
) -> usize {
self.count_goroutine_spawns(file)
}
fn count_go_convention_from_batch<'a>(
&self,
file: &ParsedFile,
_batch: &[Vec<QueryCapture<'a>>],
) -> usize {
self.count_go_convention_violations(file)
}
fn count_python_from_batch<'a>(
&self,
file: &ParsedFile,
_batch: &[Vec<QueryCapture<'a>>],
) -> usize {
self.count_python_issues(file)
}
fn count_java_from_batch<'a>(
&self,
file: &ParsedFile,
_batch: &[Vec<QueryCapture<'a>>],
) -> usize {
self.count_java_issues(file)
}
fn count_ruby_from_batch<'a>(
&self,
file: &ParsedFile,
_batch: &[Vec<QueryCapture<'a>>],
) -> usize {
self.count_ruby_issues(file)
}
fn count_c_from_batch<'a>(&self, file: &ParsedFile, _batch: &[Vec<QueryCapture<'a>>]) -> usize {
self.count_c_issues(file)
}
fn count_ts_from_batch<'a>(
&self,
file: &ParsedFile,
_batch: &[Vec<QueryCapture<'a>>],
) -> usize {
self.count_ts_issues(file)
}
fn count_js_from_batch<'a>(
&self,
file: &ParsedFile,
_batch: &[Vec<QueryCapture<'a>>],
) -> usize {
self.count_js_issues(file)
}
fn count_swift_from_batch<'a>(
&self,
file: &ParsedFile,
_batch: &[Vec<QueryCapture<'a>>],
) -> usize {
self.count_swift_issues(file)
}
}
const CODEC_PATTERNS: &[&str] = &[
"fn ", "if ", "else", "for ", "while ", "struct ", "enum ", "impl ", "let ", "return ", "use ",
"mod ", "break", "continue", "{", "}", "(", ")", "[", "]", ";", "=", "==", "!=", "&&", "||",
"->", "::",
];
pub(crate) fn count_dead_code_with(
file: &ParsedFile,
bare_terminators: &[&str],
prefix_terminators: &[&str],
line_comment: &str,
) -> usize {
let mut count = 0;
let mut dead_start: Option<usize> = None;
for (line_num, line) in file.content.lines().enumerate() {
let trimmed = line.trim();
if bare_terminators.contains(&trimmed)
|| prefix_terminators.iter().any(|p| trimmed.starts_with(p))
{
dead_start = Some(line_num + 2);
continue;
}
if let Some(start) = dead_start {
if trimmed.is_empty() || trimmed.starts_with(line_comment) {
continue;
}
if line_comment == "//" && (trimmed.starts_with("/*") || trimmed.starts_with("*")) {
continue;
}
if trimmed == "}" || trimmed.starts_with("} else") || trimmed.starts_with("} else if") {
dead_start = None;
continue;
}
if line_num + 1 >= start {
count += 1;
dead_start = None;
}
}
}
count
}
pub(crate) fn count_duplicate_imports_with(file: &ParsedFile, prefixes: &[&str]) -> usize {
let mut seen = std::collections::HashSet::new();
let mut count = 0;
for line in file.content.lines() {
let trimmed = line.trim();
if prefixes.iter().any(|p| trimmed.starts_with(p)) && !seen.insert(trimmed.to_string()) {
count += 1;
}
}
count
}
pub fn adapter_for(lang: Language) -> Option<&'static dyn LanguageAdapter> {
match lang {
Language::Rust => Some(&RustAdapter),
Language::Python => Some(&PythonAdapter),
Language::Go => Some(&GoAdapter),
Language::JavaScript => Some(&JSAdapter),
Language::Ruby => Some(&RubyAdapter),
Language::TypeScript => Some(&TSAdapter),
Language::Java => Some(&JavaAdapter),
Language::C => Some(&CAdapter),
Language::Cpp => Some(&CppAdapter),
Language::Swift => Some(&SwiftAdapter),
Language::Zig => Some(&ZigAdapter),
_ => None,
}
}
pub(crate) use helpers::{
count_block_ancestors, count_nested_blocks, count_params, is_boolean_or_null,
is_common_safe_number, is_inside_declaration, is_repeating_chars, max_scope_depth,
MEANINGLESS_NAMES,
};
#[cfg(test)]
pub(crate) use helpers::parse_code;