ryo-suggest 0.1.0

[experimental] Pattern-based suggestion engine for RYO
Documentation
//! Spec-related suggestions
//!
//! This module provides suggestions for domain specification (Spec) types.
//!
//! # Available Rules
//!
//! - [`MissingSpecForDomainType`] - Detects domain types without Spec TypeAlias (RS001)
//! - [`OrphanSpec`] - Detects unused Spec TypeAliases (RS002)
//! - [`InvalidSpecRelation`] - Detects SpecRelation targets that don't exist (RS003)
//! - [`SpecGroupInconsistency`] - Detects mixed Groups in same module (RS004)
//! - [`SpecRelationCycle`] - Detects circular dependencies in SpecRelations (RS005)
//! - [`MissingRelation`] - Suggests Relations based on struct field types (RS006)
//! - [`SpecRelationToField`] - Suggests struct fields based on Spec relations (RS007)
//! - [`BidirectionalRelation`] - Ensures consistency of bidirectional Spec relations (RS008)
//!
//! # Common Utilities
//!
//! - [`SpecSuggest`] - Trait for Spec-specific suggestions with common helpers
//! - [`is_framework_type`] - Check if a type is a common framework type

use ryo_analysis::SymbolPath;

use crate::{OpportunityContext, OpportunityId, SuggestLocation, SuggestOpportunity};

mod bidirectional_relation;
mod generator;
mod group_inconsistency;
mod invalid_spec_relation;
mod missing_relation;
mod missing_spec;
mod orphan_spec;
mod parameterized;
mod relation_cycle;
mod relation_to_field;

pub use bidirectional_relation::BidirectionalRelation;
pub use generator::{DomainSpecGenerator, GeneratorOptions, SpecGenerator, SpecGeneratorRegistry};
pub use group_inconsistency::SpecGroupInconsistency;
pub use invalid_spec_relation::InvalidSpecRelation;
pub use missing_relation::MissingRelation;
pub use missing_spec::MissingSpecForDomainType;
pub use orphan_spec::OrphanSpec;
pub use parameterized::{ApiPatternSuggest, DomainStructSuggest};
pub use relation_cycle::SpecRelationCycle;
pub use relation_to_field::SpecRelationToField;

// =============================================================================
// Common Utilities for Spec Suggestions
// =============================================================================

/// Trait for Spec-specific suggestions with common helpers.
///
/// Provides default implementations for common Spec TypeAlias operations.
/// This trait can be implemented alongside `LintSuggest` for rules that
/// are both lint rules and spec-related.
pub trait SpecSuggest {
    /// Get the suffix pattern for identifying Spec TypeAliases (default: "Spec")
    fn spec_suffix(&self) -> &str {
        "Spec"
    }

    /// Check if a TypeAlias name looks like a Spec
    fn is_spec_alias(&self, name: &str) -> bool {
        let suffix = self.spec_suffix();
        name.ends_with(suffix) && name.len() > suffix.len()
    }

    /// Extract the base type name from a Spec alias name
    /// e.g., "TaskSpec" -> "Task"
    fn extract_base_type<'a>(&self, alias_name: &'a str) -> Option<&'a str> {
        let suffix = self.spec_suffix();
        if alias_name.ends_with(suffix) && alias_name.len() > suffix.len() {
            Some(&alias_name[..alias_name.len() - suffix.len()])
        } else {
            None
        }
    }

    /// Get the module path for a symbol (parent of the symbol)
    fn get_module_path(&self, path: &SymbolPath) -> Option<SymbolPath> {
        path.parent()
    }
}

/// Spec-specific context fields for a spec opportunity.
pub struct SpecDetails {
    /// The type alias name involved in the spec
    pub alias_name: Option<String>,
    /// The base type being checked
    pub base_type: Option<String>,
    /// The group this spec belongs to
    pub group: Option<String>,
    /// Related types involved
    pub related_types: Vec<String>,
    /// Fix suggestion text
    pub suggestion: Option<String>,
}

/// Create a Spec opportunity with standardized context
///
/// This is a free function to allow rules implementing both `LintSuggest`
/// and `SpecSuggest` to create spec-specific opportunities.
pub fn create_spec_opportunity(
    code: &str,
    id: OpportunityId,
    targets: Vec<ryo_analysis::SymbolId>,
    location: SuggestLocation,
    message: impl Into<String>,
    confidence: f32,
    details: SpecDetails,
) -> SuggestOpportunity {
    SuggestOpportunity::new(
        id,
        targets,
        location,
        message,
        confidence,
        OpportunityContext::Spec {
            code: code.to_string(),
            alias_name: details.alias_name,
            base_type: details.base_type,
            group: details.group,
            related_types: details.related_types,
            suggestion: details.suggestion,
        },
    )
}

/// Check if a type name is a common framework type that should be skipped
pub fn is_framework_type(name: &str) -> bool {
    matches!(
        name,
        "Spec"
            | "DomainGroup"
            | "Group"
            | "Relations"
            | "DependsOn"
            | "RelatedTo"
            | "PartOf"
            | "String"
            | "Vec"
            | "Option"
            | "Result"
            | "Box"
            | "Arc"
            | "Rc"
            | "HashMap"
            | "HashSet"
            | "BTreeMap"
            | "BTreeSet"
            | "u8"
            | "u16"
            | "u32"
            | "u64"
            | "u128"
            | "usize"
            | "i8"
            | "i16"
            | "i32"
            | "i64"
            | "i128"
            | "isize"
            | "f32"
            | "f64"
            | "bool"
            | "char"
            | "str"
    )
}