use crate::extraction_patterns::{
AccumulationOp, AnalysisContext, ComplexityImpact, ExtractablePattern, ExtractionSuggestion,
MatchedPattern, Parameter, PatternMatcher,
};
use syn::File;
pub struct JavaScriptPatternMatcher;
impl Default for JavaScriptPatternMatcher {
fn default() -> Self {
Self::new()
}
}
impl JavaScriptPatternMatcher {
pub fn new() -> Self {
Self
}
#[allow(dead_code)]
fn detect_array_methods(&self) -> Vec<ExtractablePattern> {
Vec::new()
}
#[allow(dead_code)]
fn detect_promise_chains(&self) -> Vec<ExtractablePattern> {
Vec::new()
}
#[allow(dead_code)]
fn detect_callback_patterns(&self) -> Vec<ExtractablePattern> {
Vec::new()
}
}
impl PatternMatcher for JavaScriptPatternMatcher {
fn match_patterns(&self, _ast: &File, _context: &AnalysisContext) -> Vec<MatchedPattern> {
Vec::new()
}
fn score_confidence(&self, pattern: &MatchedPattern, context: &AnalysisContext) -> f32 {
use crate::extraction_patterns::confidence::ConfidenceScorer;
ConfidenceScorer::score_pattern(pattern, context)
}
fn generate_extraction(&self, pattern: &MatchedPattern) -> ExtractionSuggestion {
use crate::extraction_patterns::naming::FunctionNameInferrer;
let suggested_name = FunctionNameInferrer::infer_name(&pattern.pattern, "javascript");
let (start_line, end_line) = match &pattern.pattern {
ExtractablePattern::AccumulationLoop {
start_line,
end_line,
..
}
| ExtractablePattern::GuardChainSequence {
start_line,
end_line,
..
}
| ExtractablePattern::TransformationPipeline {
start_line,
end_line,
..
}
| ExtractablePattern::SimilarBranches {
start_line,
end_line,
..
}
| ExtractablePattern::NestedExtraction {
start_line,
end_line,
..
} => (*start_line, *end_line),
};
let parameters = self.extract_parameters(&pattern.pattern);
let return_type = self.infer_return_type(&pattern.pattern);
let complexity_reduction =
self.calculate_complexity_reduction(&pattern.pattern, &pattern.context);
let example = self.generate_example(&pattern.pattern, &suggested_name);
ExtractionSuggestion {
pattern_type: pattern.pattern.clone(),
start_line,
end_line,
suggested_name,
confidence: pattern.confidence,
parameters,
return_type,
complexity_reduction,
example_transformation: example,
}
}
}
impl JavaScriptPatternMatcher {
fn extract_parameters(&self, pattern: &ExtractablePattern) -> Vec<Parameter> {
match pattern {
ExtractablePattern::AccumulationLoop {
iterator_binding, ..
} => {
vec![Parameter {
name: format!("{}Array", iterator_binding),
type_hint: "Array".to_string(),
is_mutable: false,
}]
}
ExtractablePattern::GuardChainSequence { .. } => {
vec![Parameter {
name: "value".to_string(),
type_hint: "any".to_string(),
is_mutable: false,
}]
}
ExtractablePattern::TransformationPipeline { input_binding, .. } => {
vec![Parameter {
name: input_binding.clone(),
type_hint: "any".to_string(),
is_mutable: false,
}]
}
_ => Vec::new(),
}
}
fn infer_return_type(&self, pattern: &ExtractablePattern) -> String {
match pattern {
ExtractablePattern::AccumulationLoop { operation, .. } => {
Self::classify_accumulation_return_type(operation)
}
ExtractablePattern::GuardChainSequence { .. } => "boolean".to_string(),
ExtractablePattern::TransformationPipeline { output_type, .. } => output_type.clone(),
_ => "void".to_string(),
}
}
fn classify_accumulation_return_type(operation: &AccumulationOp) -> String {
match operation {
AccumulationOp::Sum | AccumulationOp::Product => "number".to_string(),
AccumulationOp::Concatenation => "string".to_string(),
AccumulationOp::Collection => "Array".to_string(),
AccumulationOp::Custom(_) => "any".to_string(),
}
}
fn calculate_complexity_reduction(
&self,
pattern: &ExtractablePattern,
context: &AnalysisContext,
) -> ComplexityImpact {
let extracted_complexity = match pattern {
ExtractablePattern::AccumulationLoop {
filter, transform, ..
} => {
let base = 2;
let filter_complexity = if filter.is_some() { 1 } else { 0 };
let transform_complexity = if transform.is_some() { 1 } else { 0 };
base + filter_complexity + transform_complexity
}
ExtractablePattern::GuardChainSequence { checks, .. } => checks.len() as u32,
ExtractablePattern::TransformationPipeline { stages, .. } => stages.len() as u32 * 2,
_ => 3,
};
ComplexityImpact {
current_cyclomatic: context.complexity_before,
predicted_cyclomatic: context
.complexity_before
.saturating_sub(extracted_complexity),
current_cognitive: context.complexity_before * 2,
predicted_cognitive: (context.complexity_before * 2)
.saturating_sub(extracted_complexity * 2),
extracted_function_complexity: extracted_complexity,
}
}
fn generate_example(&self, pattern: &ExtractablePattern, function_name: &str) -> String {
match pattern {
ExtractablePattern::AccumulationLoop {
iterator_binding, ..
} => {
format!(
"// Before:\n\
let result = 0;\n\
for (const {} of items) {{\n\
result += {};\n\
}}\n\n\
// After:\n\
const result = {}(items);",
iterator_binding, iterator_binding, function_name
)
}
ExtractablePattern::GuardChainSequence { .. } => {
format!(
"// Before:\n\
if (!condition1) {{ throw new Error('Invalid'); }}\n\
if (!condition2) {{ throw new Error('Invalid'); }}\n\n\
// After:\n\
{}(value);",
function_name
)
}
ExtractablePattern::TransformationPipeline { .. } => {
format!(
"// Before:\n\
const doubled = items.map(x => x * 2);\n\
const filtered = doubled.filter(x => x > 10);\n\
const result = filtered.sort();\n\n\
// After:\n\
const result = {}(items);",
function_name
)
}
_ => "// Example transformation".to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::extraction_patterns::{AccumulationOp, ExtractablePattern, ReturnType};
#[test]
fn test_classify_accumulation_return_type() {
assert_eq!(
JavaScriptPatternMatcher::classify_accumulation_return_type(&AccumulationOp::Sum),
"number"
);
assert_eq!(
JavaScriptPatternMatcher::classify_accumulation_return_type(&AccumulationOp::Product),
"number"
);
assert_eq!(
JavaScriptPatternMatcher::classify_accumulation_return_type(
&AccumulationOp::Concatenation
),
"string"
);
assert_eq!(
JavaScriptPatternMatcher::classify_accumulation_return_type(
&AccumulationOp::Collection
),
"Array"
);
assert_eq!(
JavaScriptPatternMatcher::classify_accumulation_return_type(&AccumulationOp::Custom(
"custom".to_string()
)),
"any"
);
}
#[test]
fn test_infer_return_type_guard_chain() {
let matcher = JavaScriptPatternMatcher;
let pattern = ExtractablePattern::GuardChainSequence {
checks: vec![],
early_return: ReturnType {
type_name: "default".to_string(),
is_early_return: true,
},
start_line: 1,
end_line: 10,
};
assert_eq!(matcher.infer_return_type(&pattern), "boolean");
}
#[test]
fn test_infer_return_type_transformation_pipeline() {
let matcher = JavaScriptPatternMatcher;
let pattern = ExtractablePattern::TransformationPipeline {
input_binding: "input".to_string(),
stages: vec![],
output_type: "CustomType".to_string(),
start_line: 1,
end_line: 10,
};
assert_eq!(matcher.infer_return_type(&pattern), "CustomType");
}
#[test]
fn test_infer_return_type_accumulation_loops() {
let matcher = JavaScriptPatternMatcher;
let sum_pattern = ExtractablePattern::AccumulationLoop {
iterator_binding: "items".to_string(),
accumulator: "sum".to_string(),
operation: AccumulationOp::Sum,
filter: None,
transform: None,
start_line: 1,
end_line: 10,
};
assert_eq!(matcher.infer_return_type(&sum_pattern), "number");
let collection_pattern = ExtractablePattern::AccumulationLoop {
iterator_binding: "items".to_string(),
accumulator: "result".to_string(),
operation: AccumulationOp::Collection,
filter: None,
transform: None,
start_line: 1,
end_line: 10,
};
assert_eq!(matcher.infer_return_type(&collection_pattern), "Array");
}
#[test]
fn test_infer_return_type_default_cases() {
let matcher = JavaScriptPatternMatcher;
let pattern = ExtractablePattern::SimilarBranches {
condition_var: "test".to_string(),
common_operations: vec![],
branch_specific: vec![],
start_line: 1,
end_line: 10,
};
assert_eq!(matcher.infer_return_type(&pattern), "void");
}
}