use crate::domain::detectors::detect_all;
use crate::domain::metrics::{CodeMetrics, ItemType, SmellDetection};
use crate::ports::parser::CodeParser;
use super::{
GenericParser, build_func_metrics_full, cached_regex, cached_regex_owned, calculate_cc_rust,
count_block_comment_lines, count_delegation_methods, count_doc_comment_lines,
count_javadoc_lines, count_line_comment_lines, count_loc, count_local_vars, count_overrides,
count_primitive_params_rust, find_matching_brace, line_number,
};
pub struct RustFullParser {
inner: GenericParser,
}
impl RustFullParser {
pub fn new() -> Self {
Self {
inner: super::generic::rust_parser(),
}
}
}
impl Default for RustFullParser {
fn default() -> Self {
Self::new()
}
}
impl CodeParser for RustFullParser {
fn parse_code(&self, code: &str, file_name: &str) -> Vec<SmellDetection> {
let cleaned = self.inner.strip_comments(code);
let mut detections: Vec<SmellDetection> = Vec::new();
let func_re = self.inner.get_func_re();
let mut raw_func_comments: std::collections::HashMap<String, (usize, usize)> =
std::collections::HashMap::new();
for cap in func_re.captures_iter(code) {
let name = cap[1].to_string();
if raw_func_comments.contains_key(&name) {
continue;
}
let full = cap.get(0).unwrap();
let start = full.start();
let Some(off) = code[start..].find('{') else {
continue;
};
let brace_pos = start + off;
let Some(end_pos) = find_matching_brace(code, brace_pos) else {
continue;
};
let raw_body = &code[start..=end_pos];
let comment_count =
count_line_comment_lines(raw_body, "//") + count_block_comment_lines(raw_body);
let doc_count = count_doc_comment_lines(raw_body, "//") + count_javadoc_lines(raw_body);
raw_func_comments.insert(name, (comment_count, doc_count));
}
for cap in func_re.captures_iter(&cleaned) {
let name = &cap[1];
let full = cap.get(0).unwrap();
let start = full.start();
let brace_pos = match cleaned[start..].find('{') {
Some(off) => start + off,
None => continue,
};
let end_pos = match find_matching_brace(&cleaned, brace_pos) {
Some(p) => p,
None => continue,
};
let body = &cleaned[start..=end_pos];
let sig = &cleaned[start..];
let (comment_count, doc_comment_count) =
*raw_func_comments.get(name).unwrap_or(&(0, 0));
let metrics = build_func_metrics_full(
body,
sig,
calculate_cc_rust,
count_local_vars,
count_primitive_params_rust,
comment_count,
doc_comment_count,
);
let location = format!("{}:{}", file_name, line_number(&cleaned, start));
detections.extend(detect_all(&metrics, &location, name));
}
let struct_re = cached_regex(r"(?m)struct\s+(\w+)\s*(?:\{|;|where)");
for cap in struct_re.captures_iter(&cleaned) {
let name = &cap[1];
let full = cap.get(0).unwrap();
let start = full.start();
let (loc, field_count) = if cleaned[start..].starts_with("struct") {
if let Some(off) = cleaned[start..].find('{') {
let brace_pos = start + off;
if let Some(end_pos) = find_matching_brace(&cleaned, brace_pos) {
let body = &cleaned[start..=end_pos];
let fields = body
.lines()
.filter(|l| {
let t = l.trim();
!t.is_empty()
&& t != "{"
&& t != "}"
&& !t.starts_with("//")
&& !t.starts_with("/*")
&& !t.starts_with('#')
})
.count();
(count_loc(body), fields)
} else {
(1, 0)
}
} else {
(1, 0)
}
} else {
(1, 0)
};
let impl_re = cached_regex_owned(&format!(
r"(?m)impl\s+(?:<[^>]*>\s*)?(?:\w+\s+for\s+)?{name}\s*(?:<[^>]*>\s*)?\{{"
));
let method_re = cached_regex(r"(?m)(?:pub\s+)?(?:(?:async|unsafe|const)\s+)*fn\s+\w+");
let mut method_count: usize = 0;
let mut impl_body_combined = String::new();
for m in impl_re.find_iter(&cleaned) {
let impl_start = m.start();
let brace_off = cleaned[impl_start..].find('{').unwrap_or(0);
let brace_pos = impl_start + brace_off;
if let Some(end) = find_matching_brace(&cleaned, brace_pos) {
method_count += method_re.find_iter(&cleaned[brace_pos..=end]).count();
impl_body_combined.push_str(&cleaned[brace_pos..=end]);
}
}
let delegation_methods = count_delegation_methods(&impl_body_combined);
let override_count = count_overrides(&impl_body_combined);
let metrics = CodeMetrics {
loc,
method_count,
field_count,
delegation_methods,
override_count,
item_type: ItemType::Class,
..Default::default()
};
let location = format!("{}:{}", file_name, line_number(&cleaned, start));
detections.extend(detect_all(&metrics, &location, name));
}
detections
}
fn supported_extensions(&self) -> &[&str] {
&["rs"]
}
}