use crate::complexity::entropy_core::EntropyScore;
use crate::core::PurityLevel;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum FunctionKind {
Declaration,
Expression,
Arrow,
Method,
ClassMethod,
Constructor,
Getter,
Setter,
Generator,
Async,
AsyncGenerator,
}
impl FunctionKind {
pub fn is_async(&self) -> bool {
matches!(self, FunctionKind::Async | FunctionKind::AsyncGenerator)
}
pub fn is_generator(&self) -> bool {
matches!(self, FunctionKind::Generator | FunctionKind::AsyncGenerator)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AsyncPattern {
AsyncAwait {
await_count: u32,
nested_await_depth: u32,
},
PromiseChain {
chain_length: u32,
has_catch: bool,
has_finally: bool,
},
PromiseAll { promise_count: u32, method: String },
CallbackNesting { depth: u32, callback_count: u32 },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum JsDebtPattern {
AnyTypeUsage { count: u32, locations: Vec<usize> },
TypeAssertion { count: u32, locations: Vec<usize> },
NonNullAssertion { count: u32, locations: Vec<usize> },
CallbackHell { depth: u32, line: usize },
UnhandledPromise { line: usize },
LongPromiseChain { length: u32, line: usize },
ConsoleUsage { count: u32, locations: Vec<usize> },
TodoComment { message: String, line: usize },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsFunctionMetrics {
pub name: String,
pub file: PathBuf,
pub line: usize,
pub kind: FunctionKind,
pub cyclomatic: u32,
pub cognitive: u32,
pub nesting: u32,
pub length: usize,
pub is_test: bool,
pub is_async: bool,
pub async_patterns: Vec<AsyncPattern>,
pub parameter_count: u32,
pub is_exported: bool,
pub ts_patterns: Option<TypeScriptPatternResult>,
pub entropy_score: Option<EntropyScore>,
pub purity_level: Option<PurityLevel>,
pub purity_confidence: Option<f32>,
pub impurity_reasons: Vec<String>,
pub functional_chains: Vec<FunctionalChain>,
pub nested_functions: NestedFunctionSummary,
}
impl JsFunctionMetrics {
pub fn new(name: String, file: PathBuf, line: usize, kind: FunctionKind) -> Self {
Self {
name,
file,
line,
kind,
cyclomatic: 1, cognitive: 0,
nesting: 0,
length: 0,
is_test: false,
is_async: false,
async_patterns: Vec::new(),
parameter_count: 0,
is_exported: false,
ts_patterns: None,
entropy_score: None,
purity_level: None,
purity_confidence: None,
impurity_reasons: Vec::new(),
functional_chains: Vec::new(),
nested_functions: NestedFunctionSummary::default(),
}
}
pub fn is_complex(&self, threshold: u32) -> bool {
self.cyclomatic > threshold || self.cognitive > threshold
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct NestedFunctionSummary {
pub count: u32,
pub cyclomatic: u32,
pub cognitive: u32,
pub max_nesting: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TypeScriptPatternResult {
pub any_count: u32,
pub assertion_count: u32,
pub non_null_assertion_count: u32,
pub has_strict_null: bool,
pub generic_count: u32,
}
impl TypeScriptPatternResult {
pub fn new() -> Self {
Self {
any_count: 0,
assertion_count: 0,
non_null_assertion_count: 0,
has_strict_null: true,
generic_count: 0,
}
}
}
impl Default for TypeScriptPatternResult {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct FunctionContext {
pub name: String,
pub file: PathBuf,
pub line: usize,
pub kind: FunctionKind,
pub in_class: bool,
pub class_name: Option<String>,
pub is_exported: bool,
pub in_test_context: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FunctionalChain {
pub methods: Vec<String>,
pub length: u32,
pub line: usize,
pub appears_pure: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsDependency {
pub specifier: String,
pub kind: JsDependencyKind,
pub names: Vec<String>,
pub line: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum JsDependencyKind {
Import,
Require,
DynamicImport,
Export,
ReExport,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_function_kind_is_async() {
assert!(FunctionKind::Async.is_async());
assert!(FunctionKind::AsyncGenerator.is_async());
assert!(!FunctionKind::Declaration.is_async());
assert!(!FunctionKind::Arrow.is_async());
}
#[test]
fn test_function_kind_is_generator() {
assert!(FunctionKind::Generator.is_generator());
assert!(FunctionKind::AsyncGenerator.is_generator());
assert!(!FunctionKind::Async.is_generator());
assert!(!FunctionKind::Declaration.is_generator());
}
#[test]
fn test_js_function_metrics_new() {
let metrics = JsFunctionMetrics::new(
"test".to_string(),
PathBuf::from("test.js"),
1,
FunctionKind::Declaration,
);
assert_eq!(metrics.name, "test");
assert_eq!(metrics.cyclomatic, 1);
assert_eq!(metrics.cognitive, 0);
}
#[test]
fn test_js_function_metrics_is_complex() {
let mut metrics = JsFunctionMetrics::new(
"test".to_string(),
PathBuf::from("test.js"),
1,
FunctionKind::Declaration,
);
assert!(!metrics.is_complex(10));
metrics.cyclomatic = 15;
assert!(metrics.is_complex(10));
}
#[test]
fn test_typescript_pattern_result_default() {
let result = TypeScriptPatternResult::default();
assert_eq!(result.any_count, 0);
assert_eq!(result.assertion_count, 0);
assert!(result.has_strict_null);
}
}