pub mod confidence;
pub mod language_specific;
pub mod naming;
use crate::core::{FileMetrics, FunctionMetrics};
use crate::data_flow::DataFlowGraph;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ExtractablePattern {
AccumulationLoop {
iterator_binding: String,
accumulator: String,
operation: AccumulationOp,
filter: Option<Box<Expression>>,
transform: Option<Box<Expression>>,
start_line: usize,
end_line: usize,
},
GuardChainSequence {
checks: Vec<GuardCheck>,
early_return: ReturnType,
start_line: usize,
end_line: usize,
},
TransformationPipeline {
stages: Vec<TransformStage>,
input_binding: String,
output_type: String,
start_line: usize,
end_line: usize,
},
SimilarBranches {
condition_var: String,
common_operations: Vec<Statement>,
branch_specific: Vec<Vec<Statement>>,
start_line: usize,
end_line: usize,
},
NestedExtraction {
outer_scope: String,
inner_patterns: Vec<Box<ExtractablePattern>>,
start_line: usize,
end_line: usize,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AccumulationOp {
Sum,
Product,
Concatenation,
Collection,
Custom(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Expression {
pub code: String,
pub variables: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GuardCheck {
pub condition: String,
pub return_value: Option<String>,
pub line: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReturnType {
pub type_name: String,
pub is_early_return: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransformStage {
pub operation: String,
pub input: String,
pub output: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Statement {
pub code: String,
pub line: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MatchedPattern {
pub pattern: ExtractablePattern,
pub confidence: f32,
pub context: AnalysisContext,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnalysisContext {
pub function_name: String,
pub file_path: String,
pub language: String,
pub complexity_before: u32,
pub has_side_effects: bool,
pub data_dependencies: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExtractionSuggestion {
pub pattern_type: ExtractablePattern,
pub start_line: usize,
pub end_line: usize,
pub suggested_name: String,
pub confidence: f32,
pub parameters: Vec<Parameter>,
pub return_type: String,
pub complexity_reduction: ComplexityImpact,
pub example_transformation: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Parameter {
pub name: String,
pub type_hint: String,
pub is_mutable: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComplexityImpact {
pub current_cyclomatic: u32,
pub predicted_cyclomatic: u32,
pub current_cognitive: u32,
pub predicted_cognitive: u32,
pub extracted_function_complexity: u32,
}
pub trait PatternMatcher: Send + Sync {
fn match_patterns(&self, ast: &syn::File, context: &AnalysisContext) -> Vec<MatchedPattern>;
fn score_confidence(&self, pattern: &MatchedPattern, context: &AnalysisContext) -> f32;
fn generate_extraction(&self, pattern: &MatchedPattern) -> ExtractionSuggestion;
}
pub trait ExtractionAnalyzer {
fn analyze_function(
&self,
func: &FunctionMetrics,
file: &FileMetrics,
data_flow: Option<&DataFlowGraph>,
) -> Vec<ExtractionSuggestion>;
fn generate_recommendation(
&self,
suggestion: &ExtractionSuggestion,
verbosity: VerbosityLevel,
) -> String;
}
#[derive(Debug, Clone, Copy)]
pub enum VerbosityLevel {
Summary,
Normal,
Detailed,
}
pub struct UnifiedExtractionAnalyzer {
#[allow(dead_code)]
matchers: HashMap<String, Box<dyn PatternMatcher>>,
}
impl Default for UnifiedExtractionAnalyzer {
fn default() -> Self {
Self::new()
}
}
impl UnifiedExtractionAnalyzer {
pub fn new() -> Self {
let mut matchers: HashMap<String, Box<dyn PatternMatcher>> = HashMap::new();
matchers.insert(
"rust".to_string(),
Box::new(language_specific::RustPatternMatcher::new()),
);
matchers.insert(
"javascript".to_string(),
Box::new(language_specific::JavaScriptPatternMatcher::new()),
);
Self { matchers }
}
}
impl ExtractionAnalyzer for UnifiedExtractionAnalyzer {
fn analyze_function(
&self,
func: &FunctionMetrics,
_file: &FileMetrics,
data_flow: Option<&DataFlowGraph>,
) -> Vec<ExtractionSuggestion> {
create_analysis_context(func, data_flow)
.and_then(|context| build_analysis_pipeline(&context, func))
.and_then(|(context, ast, source)| {
execute_pattern_matching(&context, &ast, &source, func.line)
})
.unwrap_or_default()
}
fn generate_recommendation(
&self,
suggestion: &ExtractionSuggestion,
verbosity: VerbosityLevel,
) -> String {
match verbosity {
VerbosityLevel::Summary => {
format!(
"Extract '{}' (lines {}-{}) - confidence: {:.0}%, complexity reduction: {} -> {}",
suggestion.suggested_name,
suggestion.start_line,
suggestion.end_line,
suggestion.confidence * 100.0,
suggestion.complexity_reduction.current_cyclomatic,
suggestion.complexity_reduction.predicted_cyclomatic
)
}
VerbosityLevel::Normal => {
format!(
"Extract '{}' (lines {}-{})\n \
Confidence: {:.0}%\n \
Parameters: {}\n \
Returns: {}\n \
Complexity reduction: {} -> {} (cyclomatic), {} -> {} (cognitive)",
suggestion.suggested_name,
suggestion.start_line,
suggestion.end_line,
suggestion.confidence * 100.0,
format_parameters(&suggestion.parameters),
suggestion.return_type,
suggestion.complexity_reduction.current_cyclomatic,
suggestion.complexity_reduction.predicted_cyclomatic,
suggestion.complexity_reduction.current_cognitive,
suggestion.complexity_reduction.predicted_cognitive
)
}
VerbosityLevel::Detailed => {
format!(
"Extract '{}' (lines {}-{})\n\
Confidence: {:.0}%\n\
Parameters: {}\n\
Returns: {}\n\
Complexity Impact:\n\
- Current cyclomatic: {}\n\
- Predicted cyclomatic: {}\n\
- Current cognitive: {}\n\
- Predicted cognitive: {}\n\
- Extracted function complexity: {}\n\n\
Example transformation:\n{}\n",
suggestion.suggested_name,
suggestion.start_line,
suggestion.end_line,
suggestion.confidence * 100.0,
format_parameters(&suggestion.parameters),
suggestion.return_type,
suggestion.complexity_reduction.current_cyclomatic,
suggestion.complexity_reduction.predicted_cyclomatic,
suggestion.complexity_reduction.current_cognitive,
suggestion.complexity_reduction.predicted_cognitive,
suggestion
.complexity_reduction
.extracted_function_complexity,
suggestion.example_transformation
)
}
}
}
}
fn detect_language(path: &std::path::Path) -> String {
path.extension()
.and_then(|ext| ext.to_str())
.map(map_extension_to_language)
.unwrap_or_else(|| "unknown".to_string())
}
fn map_extension_to_language(ext: &str) -> String {
match ext {
"rs" => "rust",
"py" => "python",
"js" | "jsx" => "javascript",
"ts" | "tsx" => "typescript",
_ => "unknown",
}
.to_string()
}
fn detect_side_effects(func: &FunctionMetrics, data_flow: Option<&DataFlowGraph>) -> bool {
data_flow
.map(|flow| analyze_side_effects_from_dataflow(func, flow))
.unwrap_or_else(|| estimate_side_effects_from_complexity(func))
}
fn analyze_side_effects_from_dataflow(func: &FunctionMetrics, flow: &DataFlowGraph) -> bool {
let func_id = create_function_id(func);
flow.has_side_effects(&func_id)
}
fn estimate_side_effects_from_complexity(func: &FunctionMetrics) -> bool {
func.cyclomatic > 10
}
fn create_function_id(func: &FunctionMetrics) -> crate::priority::call_graph::FunctionId {
crate::priority::call_graph::FunctionId::new(func.file.clone(), func.name.clone(), func.line)
}
fn extract_dependencies(func: &FunctionMetrics, data_flow: Option<&DataFlowGraph>) -> Vec<String> {
data_flow
.and_then(|flow| extract_variable_dependencies(func, flow))
.unwrap_or_default()
}
fn extract_variable_dependencies(
func: &FunctionMetrics,
flow: &DataFlowGraph,
) -> Option<Vec<String>> {
let func_id = create_function_id(func);
flow.get_variable_dependencies(&func_id)
.map(|deps| deps.iter().cloned().collect())
}
fn parse_function_ast(func: &FunctionMetrics, language: &str) -> Option<syn::File> {
crate::io::read_file(&func.file)
.map_err(|e| {
eprintln!(
"Warning: Failed to read file {}: {}",
func.file.display(),
e
);
e
})
.ok()
.and_then(|source| parse_ast_by_language(&source, func, language))
}
fn parse_ast_by_language(
source: &str,
func: &FunctionMetrics,
language: &str,
) -> Option<syn::File> {
match language {
"rust" => extract_rust_function_ast(source, func),
"python" | "javascript" | "typescript" => None, _ => None,
}
}
fn extract_rust_function_ast(source: &str, func: &FunctionMetrics) -> Option<syn::File> {
use syn::{parse_str, Item};
let file = parse_str::<syn::File>(source).ok()?;
file.items.iter().find_map(|item| match item {
Item::Fn(item_fn) => try_match_toplevel_fn(item_fn, &func.name),
Item::Impl(item_impl) => try_match_impl_method(item_impl, &func.name),
_ => None,
})
}
fn try_match_toplevel_fn(item_fn: &syn::ItemFn, target_name: &str) -> Option<syn::File> {
(item_fn.sig.ident == target_name).then(|| wrap_fn_in_file(item_fn.clone()))
}
fn try_match_impl_method(item_impl: &syn::ItemImpl, target_name: &str) -> Option<syn::File> {
item_impl
.items
.iter()
.filter_map(|impl_item| match impl_item {
syn::ImplItem::Fn(method) if method.sig.ident == target_name => {
Some(wrap_fn_in_file(method_to_standalone_fn(method)))
}
_ => None,
})
.next()
}
fn method_to_standalone_fn(method: &syn::ImplItemFn) -> syn::ItemFn {
syn::ItemFn {
attrs: method.attrs.clone(),
vis: method.vis.clone(),
sig: method.sig.clone(),
block: Box::new(method.block.clone()),
}
}
fn wrap_fn_in_file(item_fn: syn::ItemFn) -> syn::File {
syn::File {
shebang: None,
attrs: vec![],
items: vec![syn::Item::Fn(item_fn)],
}
}
fn format_parameters(params: &[Parameter]) -> String {
match params.is_empty() {
true => "none".to_string(),
false => params
.iter()
.map(format_single_parameter)
.collect::<Vec<_>>()
.join(", "),
}
}
fn format_single_parameter(param: &Parameter) -> String {
let mutability = if param.is_mutable { "mut " } else { "" };
format!("{}{}: {}", mutability, param.name, param.type_hint)
}
fn create_analysis_context(
func: &FunctionMetrics,
data_flow: Option<&DataFlowGraph>,
) -> Result<AnalysisContext, AnalysisError> {
let language = detect_language(&func.file);
Ok(AnalysisContext {
function_name: func.name.clone(),
file_path: func.file.display().to_string(),
language,
complexity_before: func.cyclomatic,
has_side_effects: detect_side_effects(func, data_flow),
data_dependencies: extract_dependencies(func, data_flow),
})
}
fn build_analysis_pipeline(
context: &AnalysisContext,
func: &FunctionMetrics,
) -> Result<(AnalysisContext, syn::File, String), AnalysisError> {
let ast = parse_function_ast(func, &context.language).ok_or(AnalysisError::ParseError)?;
let source = crate::io::read_file(&func.file).map_err(|_| AnalysisError::IoError)?;
Ok((context.clone(), ast, source))
}
fn execute_pattern_matching(
context: &AnalysisContext,
ast: &syn::File,
source: &str,
function_line: usize,
) -> Result<Vec<ExtractionSuggestion>, AnalysisError> {
use crate::extraction_patterns::language_specific::RustPatternMatcher;
let matcher = RustPatternMatcher::with_source_context(source, function_line);
let patterns = matcher.match_patterns(ast, context);
Ok(patterns
.into_iter()
.map(|pattern| apply_confidence_scoring(pattern, &matcher, context))
.map(|pattern| matcher.generate_extraction(&pattern))
.collect())
}
fn apply_confidence_scoring(
mut pattern: MatchedPattern,
matcher: &crate::extraction_patterns::language_specific::RustPatternMatcher,
context: &AnalysisContext,
) -> MatchedPattern {
pattern.confidence = matcher.score_confidence(&pattern, context);
pattern
}
#[derive(Debug)]
#[allow(dead_code)]
enum AnalysisError {
ParseError,
IoError,
MatcherNotFound,
}
#[allow(dead_code)]
trait AnalysisResult<T> {
fn and_then_analysis<U, F>(self, f: F) -> Result<U, AnalysisError>
where
F: FnOnce(T) -> Result<U, AnalysisError>;
}
impl<T> AnalysisResult<T> for Result<T, AnalysisError> {
fn and_then_analysis<U, F>(self, f: F) -> Result<U, AnalysisError>
where
F: FnOnce(T) -> Result<U, AnalysisError>,
{
self.and_then(f)
}
}