use crate::types::Evidence;
use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone, Default)]
pub struct PatternSignals {
pub soft_delete: SoftDeleteSignals,
pub error_handling: ErrorHandlingSignals,
pub naming: NamingSignals,
pub resource_management: ResourceManagementSignals,
pub validation: ValidationSignals,
pub test_idioms: TestIdiomSignals,
pub import_patterns: ImportPatternSignals,
pub type_coverage: TypeCoverageSignals,
pub api_conventions: ApiConventionSignals,
pub async_patterns: AsyncPatternSignals,
pub extensions: HashMap<String, Vec<Evidence>>,
}
impl PatternSignals {
pub fn merge(&mut self, other: &PatternSignals) {
self.soft_delete.merge(&other.soft_delete);
self.error_handling.merge(&other.error_handling);
self.naming.merge(&other.naming);
self.resource_management.merge(&other.resource_management);
self.validation.merge(&other.validation);
self.test_idioms.merge(&other.test_idioms);
self.import_patterns.merge(&other.import_patterns);
self.type_coverage.merge(&other.type_coverage);
self.api_conventions.merge(&other.api_conventions);
self.async_patterns.merge(&other.async_patterns);
for (key, values) in &other.extensions {
self.extensions
.entry(key.clone())
.or_default()
.extend(values.clone());
}
}
}
#[derive(Debug, Clone, Default)]
pub struct SoftDeleteSignals {
pub is_deleted_fields: Vec<Evidence>,
pub deleted_at_fields: Vec<Evidence>,
pub delete_query_filters: Vec<Evidence>,
pub paranoid_annotations: Vec<Evidence>,
}
impl SoftDeleteSignals {
pub fn merge(&mut self, other: &SoftDeleteSignals) {
self.is_deleted_fields
.extend(other.is_deleted_fields.clone());
self.deleted_at_fields
.extend(other.deleted_at_fields.clone());
self.delete_query_filters
.extend(other.delete_query_filters.clone());
self.paranoid_annotations
.extend(other.paranoid_annotations.clone());
}
pub fn has_signals(&self) -> bool {
!self.is_deleted_fields.is_empty()
|| !self.deleted_at_fields.is_empty()
|| !self.delete_query_filters.is_empty()
|| !self.paranoid_annotations.is_empty()
}
pub fn calculate_confidence(&self) -> f64 {
let mut confidence: f64 = 0.0;
if !self.is_deleted_fields.is_empty() {
confidence += 0.4;
}
if !self.deleted_at_fields.is_empty() {
confidence += 0.4;
}
if !self.delete_query_filters.is_empty() {
confidence += 0.2;
}
if !self.paranoid_annotations.is_empty() {
confidence += 0.3;
}
confidence.min(1.0)
}
}
#[derive(Debug, Clone, Default)]
pub struct ErrorHandlingSignals {
pub try_except_blocks: Vec<Evidence>,
pub custom_exceptions: Vec<(String, Evidence)>,
pub result_types: Vec<Evidence>,
pub question_mark_ops: Vec<Evidence>,
pub err_nil_checks: Vec<Evidence>,
pub try_catch_blocks: Vec<Evidence>,
pub error_enums: Vec<(String, Evidence)>,
}
impl ErrorHandlingSignals {
pub fn merge(&mut self, other: &ErrorHandlingSignals) {
self.try_except_blocks
.extend(other.try_except_blocks.clone());
self.custom_exceptions
.extend(other.custom_exceptions.clone());
self.result_types.extend(other.result_types.clone());
self.question_mark_ops
.extend(other.question_mark_ops.clone());
self.err_nil_checks.extend(other.err_nil_checks.clone());
self.try_catch_blocks.extend(other.try_catch_blocks.clone());
self.error_enums.extend(other.error_enums.clone());
}
pub fn has_signals(&self) -> bool {
!self.try_except_blocks.is_empty()
|| !self.custom_exceptions.is_empty()
|| !self.result_types.is_empty()
|| !self.question_mark_ops.is_empty()
|| !self.err_nil_checks.is_empty()
|| !self.try_catch_blocks.is_empty()
|| !self.error_enums.is_empty()
}
pub fn calculate_confidence(&self) -> f64 {
let mut confidence: f64 = 0.0;
if !self.try_except_blocks.is_empty() || !self.try_catch_blocks.is_empty() {
confidence += 0.3;
}
if !self.custom_exceptions.is_empty() || !self.error_enums.is_empty() {
confidence += 0.4;
}
if !self.result_types.is_empty() {
confidence += 0.4;
}
if !self.question_mark_ops.is_empty() {
confidence += 0.3;
}
if !self.err_nil_checks.is_empty() {
confidence += 0.4;
}
confidence.min(1.0)
}
}
#[derive(Debug, Clone, Default)]
pub struct NamingSignals {
pub function_names: Vec<(String, NamingCase, String)>, pub class_names: Vec<(String, NamingCase, String)>,
pub constant_names: Vec<(String, NamingCase, String)>,
pub private_prefixes: HashMap<String, usize>, }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NamingCase {
SnakeCase,
CamelCase,
PascalCase,
UpperSnakeCase,
Unknown,
}
impl NamingSignals {
pub fn merge(&mut self, other: &NamingSignals) {
self.function_names.extend(other.function_names.clone());
self.class_names.extend(other.class_names.clone());
self.constant_names.extend(other.constant_names.clone());
for (prefix, count) in &other.private_prefixes {
*self.private_prefixes.entry(prefix.clone()).or_insert(0) += count;
}
}
pub fn has_signals(&self) -> bool {
!self.function_names.is_empty()
|| !self.class_names.is_empty()
|| !self.constant_names.is_empty()
}
}
pub fn detect_naming_case(name: &str) -> NamingCase {
if name.is_empty() {
return NamingCase::Unknown;
}
if name.starts_with("__") && name.ends_with("__") {
return NamingCase::Unknown;
}
if name.len() == 1 {
return NamingCase::Unknown;
}
if name
.chars()
.all(|c| c.is_ascii_uppercase() || c == '_' || c.is_ascii_digit())
{
return NamingCase::UpperSnakeCase;
}
if name
.chars()
.all(|c| c.is_ascii_lowercase() || c == '_' || c.is_ascii_digit())
{
return NamingCase::SnakeCase;
}
let check_name = name.trim_start_matches('_');
if !check_name.is_empty() {
let first = check_name.chars().next().unwrap();
if first.is_ascii_uppercase() && !check_name.contains('_') {
return NamingCase::PascalCase;
}
if first.is_ascii_lowercase()
&& !check_name.contains('_')
&& check_name.chars().any(|c| c.is_ascii_uppercase())
{
return NamingCase::CamelCase;
}
}
NamingCase::Unknown
}
#[derive(Debug, Clone, Default)]
pub struct ResourceManagementSignals {
pub context_managers: Vec<Evidence>,
pub enter_exit_methods: Vec<Evidence>,
pub defer_statements: Vec<Evidence>,
pub drop_impls: Vec<Evidence>,
pub try_finally_blocks: Vec<Evidence>,
pub close_calls: Vec<Evidence>,
}
impl ResourceManagementSignals {
pub fn merge(&mut self, other: &ResourceManagementSignals) {
self.context_managers.extend(other.context_managers.clone());
self.enter_exit_methods
.extend(other.enter_exit_methods.clone());
self.defer_statements.extend(other.defer_statements.clone());
self.drop_impls.extend(other.drop_impls.clone());
self.try_finally_blocks
.extend(other.try_finally_blocks.clone());
self.close_calls.extend(other.close_calls.clone());
}
pub fn has_signals(&self) -> bool {
!self.context_managers.is_empty()
|| !self.enter_exit_methods.is_empty()
|| !self.defer_statements.is_empty()
|| !self.drop_impls.is_empty()
|| !self.try_finally_blocks.is_empty()
|| !self.close_calls.is_empty()
}
pub fn calculate_confidence(&self) -> f64 {
let mut confidence: f64 = 0.0;
if !self.context_managers.is_empty() {
confidence += 0.4;
}
if !self.enter_exit_methods.is_empty() {
confidence += 0.3;
}
if !self.defer_statements.is_empty() {
confidence += 0.4;
}
if !self.drop_impls.is_empty() {
confidence += 0.4;
}
if !self.try_finally_blocks.is_empty() {
confidence += 0.3;
}
if !self.close_calls.is_empty() {
confidence += 0.2;
}
confidence.min(1.0)
}
}
#[derive(Debug, Clone, Default)]
pub struct ValidationSignals {
pub pydantic_models: Vec<Evidence>,
pub zod_schemas: Vec<Evidence>,
pub guard_clauses: Vec<Evidence>,
pub assert_statements: Vec<Evidence>,
pub type_checks: Vec<Evidence>,
pub other_validators: Vec<(String, Evidence)>,
}
impl ValidationSignals {
pub fn merge(&mut self, other: &ValidationSignals) {
self.pydantic_models.extend(other.pydantic_models.clone());
self.zod_schemas.extend(other.zod_schemas.clone());
self.guard_clauses.extend(other.guard_clauses.clone());
self.assert_statements
.extend(other.assert_statements.clone());
self.type_checks.extend(other.type_checks.clone());
self.other_validators.extend(other.other_validators.clone());
}
pub fn has_signals(&self) -> bool {
!self.pydantic_models.is_empty()
|| !self.zod_schemas.is_empty()
|| !self.guard_clauses.is_empty()
|| !self.assert_statements.is_empty()
|| !self.type_checks.is_empty()
|| !self.other_validators.is_empty()
}
pub fn calculate_confidence(&self) -> f64 {
let mut confidence: f64 = 0.0;
if !self.pydantic_models.is_empty() {
confidence += 0.5;
}
if !self.zod_schemas.is_empty() {
confidence += 0.5;
}
if !self.guard_clauses.is_empty() {
confidence += 0.3;
}
if !self.assert_statements.is_empty() {
confidence += 0.2;
}
if !self.type_checks.is_empty() {
confidence += 0.2;
}
if !self.other_validators.is_empty() {
confidence += 0.3;
}
confidence.min(1.0)
}
}
#[derive(Debug, Clone, Default)]
pub struct TestIdiomSignals {
pub pytest_fixtures: Vec<Evidence>,
pub mock_patches: Vec<Evidence>,
pub jest_blocks: Vec<Evidence>,
pub go_table_tests: Vec<Evidence>,
pub aaa_patterns: Vec<Evidence>,
pub test_function_count: usize,
pub detected_framework: Option<String>,
}
impl TestIdiomSignals {
pub fn merge(&mut self, other: &TestIdiomSignals) {
self.pytest_fixtures.extend(other.pytest_fixtures.clone());
self.mock_patches.extend(other.mock_patches.clone());
self.jest_blocks.extend(other.jest_blocks.clone());
self.go_table_tests.extend(other.go_table_tests.clone());
self.aaa_patterns.extend(other.aaa_patterns.clone());
self.test_function_count += other.test_function_count;
if self.detected_framework.is_none() {
self.detected_framework = other.detected_framework.clone();
}
}
pub fn has_signals(&self) -> bool {
!self.pytest_fixtures.is_empty()
|| !self.mock_patches.is_empty()
|| !self.jest_blocks.is_empty()
|| !self.go_table_tests.is_empty()
|| !self.aaa_patterns.is_empty()
|| self.test_function_count > 0
}
pub fn calculate_confidence(&self) -> f64 {
let mut confidence: f64 = 0.0;
if !self.pytest_fixtures.is_empty() {
confidence += 0.4;
}
if !self.mock_patches.is_empty() {
confidence += 0.3;
}
if !self.jest_blocks.is_empty() {
confidence += 0.4;
}
if !self.go_table_tests.is_empty() {
confidence += 0.4;
}
if !self.aaa_patterns.is_empty() {
confidence += 0.3;
}
confidence.min(1.0)
}
}
#[derive(Debug, Clone, Default)]
pub struct ImportPatternSignals {
pub absolute_imports: Vec<(String, String)>, pub relative_imports: Vec<(String, String)>,
pub star_imports: Vec<Evidence>,
pub aliases: HashMap<String, String>, pub groupings: Vec<ImportGrouping>,
}
#[derive(Debug, Clone)]
pub struct ImportGrouping {
pub file: String,
pub stdlib_imports: Vec<String>,
pub third_party_imports: Vec<String>,
pub local_imports: Vec<String>,
}
impl ImportPatternSignals {
pub fn merge(&mut self, other: &ImportPatternSignals) {
self.absolute_imports.extend(other.absolute_imports.clone());
self.relative_imports.extend(other.relative_imports.clone());
self.star_imports.extend(other.star_imports.clone());
self.aliases.extend(other.aliases.clone());
self.groupings.extend(other.groupings.clone());
}
pub fn has_signals(&self) -> bool {
!self.absolute_imports.is_empty()
|| !self.relative_imports.is_empty()
|| !self.star_imports.is_empty()
}
}
#[derive(Debug, Clone, Default)]
pub struct TypeCoverageSignals {
pub typed_params: usize,
pub untyped_params: usize,
pub typed_returns: usize,
pub untyped_returns: usize,
pub typed_variables: usize,
pub untyped_variables: usize,
pub generic_usage: Vec<Evidence>,
pub generic_patterns: HashSet<String>,
}
impl TypeCoverageSignals {
pub fn merge(&mut self, other: &TypeCoverageSignals) {
self.typed_params += other.typed_params;
self.untyped_params += other.untyped_params;
self.typed_returns += other.typed_returns;
self.untyped_returns += other.untyped_returns;
self.typed_variables += other.typed_variables;
self.untyped_variables += other.untyped_variables;
self.generic_usage.extend(other.generic_usage.clone());
self.generic_patterns.extend(other.generic_patterns.clone());
}
pub fn has_signals(&self) -> bool {
self.typed_params > 0
|| self.typed_returns > 0
|| self.typed_variables > 0
|| self.untyped_params > 0
|| self.untyped_returns > 0
}
pub fn calculate_function_coverage(&self) -> f64 {
let total =
self.typed_params + self.untyped_params + self.typed_returns + self.untyped_returns;
if total == 0 {
return 0.0;
}
(self.typed_params + self.typed_returns) as f64 / total as f64
}
pub fn calculate_variable_coverage(&self) -> f64 {
let total = self.typed_variables + self.untyped_variables;
if total == 0 {
return 0.0;
}
self.typed_variables as f64 / total as f64
}
pub fn calculate_overall_coverage(&self) -> f64 {
let total_typed = self.typed_params + self.typed_returns + self.typed_variables;
let total_untyped = self.untyped_params + self.untyped_returns + self.untyped_variables;
let total = total_typed + total_untyped;
if total == 0 {
return 0.0;
}
total_typed as f64 / total as f64
}
}
#[derive(Debug, Clone, Default)]
pub struct ApiConventionSignals {
pub fastapi_decorators: Vec<Evidence>,
pub flask_decorators: Vec<Evidence>,
pub express_routes: Vec<Evidence>,
pub restful_patterns: Vec<Evidence>,
pub orm_models: Vec<(String, Evidence)>, pub graphql_defs: Vec<Evidence>,
}
impl ApiConventionSignals {
pub fn merge(&mut self, other: &ApiConventionSignals) {
self.fastapi_decorators
.extend(other.fastapi_decorators.clone());
self.flask_decorators.extend(other.flask_decorators.clone());
self.express_routes.extend(other.express_routes.clone());
self.restful_patterns.extend(other.restful_patterns.clone());
self.orm_models.extend(other.orm_models.clone());
self.graphql_defs.extend(other.graphql_defs.clone());
}
pub fn has_signals(&self) -> bool {
!self.fastapi_decorators.is_empty()
|| !self.flask_decorators.is_empty()
|| !self.express_routes.is_empty()
|| !self.restful_patterns.is_empty()
|| !self.orm_models.is_empty()
|| !self.graphql_defs.is_empty()
}
pub fn calculate_confidence(&self) -> f64 {
let mut confidence: f64 = 0.0;
if !self.fastapi_decorators.is_empty() {
confidence += 0.5;
}
if !self.flask_decorators.is_empty() {
confidence += 0.5;
}
if !self.express_routes.is_empty() {
confidence += 0.5;
}
if !self.restful_patterns.is_empty() {
confidence += 0.3;
}
if !self.orm_models.is_empty() {
confidence += 0.4;
}
if !self.graphql_defs.is_empty() {
confidence += 0.4;
}
confidence.min(1.0)
}
pub fn detect_framework(&self) -> Option<String> {
if !self.fastapi_decorators.is_empty() {
return Some("fastapi".to_string());
}
if !self.flask_decorators.is_empty() {
return Some("flask".to_string());
}
if !self.express_routes.is_empty() {
return Some("express".to_string());
}
None
}
pub fn detect_orm(&self) -> Option<String> {
self.orm_models.first().map(|(orm, _)| orm.clone())
}
}
#[derive(Debug, Clone, Default)]
pub struct AsyncPatternSignals {
pub async_await: Vec<Evidence>,
pub goroutines: Vec<Evidence>,
pub tokio_usage: Vec<Evidence>,
pub sync_primitives: Vec<(String, Evidence)>,
pub thread_spawns: Vec<Evidence>,
}
impl AsyncPatternSignals {
pub fn merge(&mut self, other: &AsyncPatternSignals) {
self.async_await.extend(other.async_await.clone());
self.goroutines.extend(other.goroutines.clone());
self.tokio_usage.extend(other.tokio_usage.clone());
self.sync_primitives.extend(other.sync_primitives.clone());
self.thread_spawns.extend(other.thread_spawns.clone());
}
pub fn has_signals(&self) -> bool {
!self.async_await.is_empty()
|| !self.goroutines.is_empty()
|| !self.tokio_usage.is_empty()
|| !self.sync_primitives.is_empty()
|| !self.thread_spawns.is_empty()
}
pub fn calculate_confidence(&self) -> f64 {
let mut confidence: f64 = 0.0;
if !self.async_await.is_empty() {
confidence += 0.4;
}
if !self.goroutines.is_empty() {
confidence += 0.5;
}
if !self.tokio_usage.is_empty() {
confidence += 0.5;
}
if !self.sync_primitives.is_empty() {
confidence += 0.3;
}
if !self.thread_spawns.is_empty() {
confidence += 0.3;
}
confidence.min(1.0)
}
}