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}