Skip to main content

ryo_suggest/spec/
mod.rs

1//! Spec-related suggestions
2//!
3//! This module provides suggestions for domain specification (Spec) types.
4//!
5//! # Available Rules
6//!
7//! - [`MissingSpecForDomainType`] - Detects domain types without Spec TypeAlias (RS001)
8//! - [`OrphanSpec`] - Detects unused Spec TypeAliases (RS002)
9//! - [`InvalidSpecRelation`] - Detects SpecRelation targets that don't exist (RS003)
10//! - [`SpecGroupInconsistency`] - Detects mixed Groups in same module (RS004)
11//! - [`SpecRelationCycle`] - Detects circular dependencies in SpecRelations (RS005)
12//! - [`MissingRelation`] - Suggests Relations based on struct field types (RS006)
13//! - [`SpecRelationToField`] - Suggests struct fields based on Spec relations (RS007)
14//! - [`BidirectionalRelation`] - Ensures consistency of bidirectional Spec relations (RS008)
15//!
16//! # Common Utilities
17//!
18//! - [`SpecSuggest`] - Trait for Spec-specific suggestions with common helpers
19//! - [`is_framework_type`] - Check if a type is a common framework type
20
21use ryo_analysis::SymbolPath;
22
23use crate::{OpportunityContext, OpportunityId, SuggestLocation, SuggestOpportunity};
24
25mod bidirectional_relation;
26mod generator;
27mod group_inconsistency;
28mod invalid_spec_relation;
29mod missing_relation;
30mod missing_spec;
31mod orphan_spec;
32mod parameterized;
33mod relation_cycle;
34mod relation_to_field;
35
36pub use bidirectional_relation::BidirectionalRelation;
37pub use generator::{DomainSpecGenerator, GeneratorOptions, SpecGenerator, SpecGeneratorRegistry};
38pub use group_inconsistency::SpecGroupInconsistency;
39pub use invalid_spec_relation::InvalidSpecRelation;
40pub use missing_relation::MissingRelation;
41pub use missing_spec::MissingSpecForDomainType;
42pub use orphan_spec::OrphanSpec;
43pub use parameterized::{ApiPatternSuggest, DomainStructSuggest};
44pub use relation_cycle::SpecRelationCycle;
45pub use relation_to_field::SpecRelationToField;
46
47// =============================================================================
48// Common Utilities for Spec Suggestions
49// =============================================================================
50
51/// Trait for Spec-specific suggestions with common helpers.
52///
53/// Provides default implementations for common Spec TypeAlias operations.
54/// This trait can be implemented alongside `LintSuggest` for rules that
55/// are both lint rules and spec-related.
56pub trait SpecSuggest {
57    /// Get the suffix pattern for identifying Spec TypeAliases (default: "Spec")
58    fn spec_suffix(&self) -> &str {
59        "Spec"
60    }
61
62    /// Check if a TypeAlias name looks like a Spec
63    fn is_spec_alias(&self, name: &str) -> bool {
64        let suffix = self.spec_suffix();
65        name.ends_with(suffix) && name.len() > suffix.len()
66    }
67
68    /// Extract the base type name from a Spec alias name
69    /// e.g., "TaskSpec" -> "Task"
70    fn extract_base_type<'a>(&self, alias_name: &'a str) -> Option<&'a str> {
71        let suffix = self.spec_suffix();
72        if alias_name.ends_with(suffix) && alias_name.len() > suffix.len() {
73            Some(&alias_name[..alias_name.len() - suffix.len()])
74        } else {
75            None
76        }
77    }
78
79    /// Get the module path for a symbol (parent of the symbol)
80    fn get_module_path(&self, path: &SymbolPath) -> Option<SymbolPath> {
81        path.parent()
82    }
83}
84
85/// Spec-specific context fields for a spec opportunity.
86pub struct SpecDetails {
87    /// The type alias name involved in the spec
88    pub alias_name: Option<String>,
89    /// The base type being checked
90    pub base_type: Option<String>,
91    /// The group this spec belongs to
92    pub group: Option<String>,
93    /// Related types involved
94    pub related_types: Vec<String>,
95    /// Fix suggestion text
96    pub suggestion: Option<String>,
97}
98
99/// Create a Spec opportunity with standardized context
100///
101/// This is a free function to allow rules implementing both `LintSuggest`
102/// and `SpecSuggest` to create spec-specific opportunities.
103pub fn create_spec_opportunity(
104    code: &str,
105    id: OpportunityId,
106    targets: Vec<ryo_analysis::SymbolId>,
107    location: SuggestLocation,
108    message: impl Into<String>,
109    confidence: f32,
110    details: SpecDetails,
111) -> SuggestOpportunity {
112    SuggestOpportunity::new(
113        id,
114        targets,
115        location,
116        message,
117        confidence,
118        OpportunityContext::Spec {
119            code: code.to_string(),
120            alias_name: details.alias_name,
121            base_type: details.base_type,
122            group: details.group,
123            related_types: details.related_types,
124            suggestion: details.suggestion,
125        },
126    )
127}
128
129/// Check if a type name is a common framework type that should be skipped
130pub fn is_framework_type(name: &str) -> bool {
131    matches!(
132        name,
133        "Spec"
134            | "DomainGroup"
135            | "Group"
136            | "Relations"
137            | "DependsOn"
138            | "RelatedTo"
139            | "PartOf"
140            | "String"
141            | "Vec"
142            | "Option"
143            | "Result"
144            | "Box"
145            | "Arc"
146            | "Rc"
147            | "HashMap"
148            | "HashSet"
149            | "BTreeMap"
150            | "BTreeSet"
151            | "u8"
152            | "u16"
153            | "u32"
154            | "u64"
155            | "u128"
156            | "usize"
157            | "i8"
158            | "i16"
159            | "i32"
160            | "i64"
161            | "i128"
162            | "isize"
163            | "f32"
164            | "f64"
165            | "bool"
166            | "char"
167            | "str"
168    )
169}