1pub mod git;
17pub mod naming;
18pub mod path;
19pub mod visibility;
20
21use std::path::Path;
22
23use crate::ast::SymbolKind;
24use crate::git::GitRepository;
25
26use super::{Suggestion, SuggestionSource};
27
28pub struct HeuristicsEngine {
31 naming: naming::NamingHeuristics,
33
34 path: path::PathHeuristics,
36
37 #[allow(dead_code)]
39 visibility: visibility::VisibilityHeuristics,
40
41 git: git::GitHeuristics,
43
44 generate_summaries: bool,
46
47 use_git_heuristics: bool,
49}
50
51impl HeuristicsEngine {
52 pub fn new() -> Self {
54 Self {
55 naming: naming::NamingHeuristics::new(),
56 path: path::PathHeuristics::new(),
57 visibility: visibility::VisibilityHeuristics::new(),
58 git: git::GitHeuristics::new(),
59 generate_summaries: true,
60 use_git_heuristics: true,
61 }
62 }
63
64 pub fn with_summary_generation(mut self, enabled: bool) -> Self {
66 self.generate_summaries = enabled;
67 self
68 }
69
70 pub fn with_git_heuristics(mut self, enabled: bool) -> Self {
72 self.use_git_heuristics = enabled;
73 self
74 }
75
76 pub fn suggest(
84 &self,
85 target: &str,
86 line: usize,
87 symbol_kind: Option<SymbolKind>,
88 file_path: &str,
89 ) -> Vec<Suggestion> {
90 self.suggest_full(target, line, symbol_kind, file_path, None, false)
91 }
92
93 pub fn suggest_full(
97 &self,
98 target: &str,
99 line: usize,
100 symbol_kind: Option<SymbolKind>,
101 file_path: &str,
102 visibility: Option<crate::ast::Visibility>,
103 is_exported: bool,
104 ) -> Vec<Suggestion> {
105 let mut suggestions = Vec::new();
106
107 let naming_suggestions = self.naming.suggest(target, line);
109 suggestions.extend(naming_suggestions);
110
111 let path_suggestions = self.path.suggest(file_path, target, line);
113 suggestions.extend(path_suggestions);
114
115 if let Some(vis) = visibility {
117 let visibility_suggestions = self.visibility.suggest(target, line, vis, is_exported);
118 suggestions.extend(visibility_suggestions);
119 }
120
121 if self.generate_summaries {
123 if let Some(summary) = self.naming.generate_summary(target, symbol_kind) {
124 suggestions.push(
125 Suggestion::summary(target, line, summary, SuggestionSource::Heuristic)
126 .with_confidence(0.6),
127 );
128 }
129 }
130
131 suggestions
132 }
133
134 pub fn suggest_with_git(
141 &self,
142 target: &str,
143 line: usize,
144 symbol_kind: Option<SymbolKind>,
145 file_path: &str,
146 repo: Option<&GitRepository>,
147 ) -> Vec<Suggestion> {
148 self.suggest_with_git_full(target, line, symbol_kind, file_path, repo, None, false)
149 }
150
151 #[allow(clippy::too_many_arguments)]
159 pub fn suggest_with_git_full(
160 &self,
161 target: &str,
162 line: usize,
163 symbol_kind: Option<SymbolKind>,
164 file_path: &str,
165 repo: Option<&GitRepository>,
166 visibility: Option<crate::ast::Visibility>,
167 is_exported: bool,
168 ) -> Vec<Suggestion> {
169 let mut suggestions = self.suggest_full(
170 target,
171 line,
172 symbol_kind,
173 file_path,
174 visibility,
175 is_exported,
176 );
177
178 if self.use_git_heuristics {
180 if let Some(repo) = repo {
181 let path = Path::new(file_path);
182 let git_suggestions = self.git.suggest_for_file(repo, path, target, line);
183 suggestions.extend(git_suggestions);
184 }
185 }
186
187 suggestions
188 }
189}
190
191impl Default for HeuristicsEngine {
192 fn default() -> Self {
193 Self::new()
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200 use crate::annotate::AnnotationType;
201
202 #[test]
203 fn test_heuristics_engine_creation() {
204 let engine = HeuristicsEngine::new();
205 assert!(engine.generate_summaries);
206 }
207
208 #[test]
209 fn test_suggest_security_pattern() {
210 let engine = HeuristicsEngine::new();
211 let suggestions = engine.suggest(
212 "authenticateUser",
213 10,
214 Some(SymbolKind::Function),
215 "src/auth/service.ts",
216 );
217
218 let has_security = suggestions.iter().any(|s| {
220 s.annotation_type == AnnotationType::Domain && s.value.contains("security")
221 || s.annotation_type == AnnotationType::Lock
222 });
223
224 assert!(has_security || !suggestions.is_empty());
225 }
226
227 #[test]
228 fn test_suggest_from_path() {
229 let engine = HeuristicsEngine::new();
230 let suggestions = engine.suggest(
231 "processPayment",
232 10,
233 Some(SymbolKind::Function),
234 "src/billing/payments.ts",
235 );
236
237 let has_billing = suggestions
239 .iter()
240 .any(|s| s.annotation_type == AnnotationType::Domain && s.value == "billing");
241
242 assert!(has_billing);
243 }
244
245 #[test]
246 fn test_suggest_with_visibility_private() {
247 use crate::ast::Visibility;
248
249 let engine = HeuristicsEngine::new();
250 let suggestions = engine.suggest_full(
251 "internalHelper",
252 10,
253 Some(SymbolKind::Function),
254 "src/utils.ts",
255 Some(Visibility::Private),
256 false,
257 );
258
259 let has_restricted = suggestions
261 .iter()
262 .any(|s| s.annotation_type == AnnotationType::Lock && s.value == "restricted");
263
264 assert!(has_restricted, "Private symbols should get restricted lock");
265 }
266
267 #[test]
268 fn test_suggest_with_visibility_public_exported() {
269 use crate::ast::Visibility;
270
271 let engine = HeuristicsEngine::new();
272 let suggestions = engine.suggest_full(
273 "PublicAPI",
274 10,
275 Some(SymbolKind::Function),
276 "src/api.ts",
277 Some(Visibility::Public),
278 true, );
280
281 let has_normal = suggestions
283 .iter()
284 .any(|s| s.annotation_type == AnnotationType::Lock && s.value == "normal");
285
286 assert!(has_normal, "Public exported symbols should get normal lock");
287 }
288
289 #[test]
290 fn test_suggest_full_combines_all_sources() {
291 use crate::ast::Visibility;
292
293 let engine = HeuristicsEngine::new();
294 let suggestions = engine.suggest_full(
295 "authenticateUser",
296 10,
297 Some(SymbolKind::Function),
298 "src/auth/login.ts",
299 Some(Visibility::Internal),
300 false,
301 );
302
303 assert!(!suggestions.is_empty(), "Should have combined suggestions");
305
306 let has_naming = suggestions
308 .iter()
309 .any(|s| s.annotation_type == AnnotationType::Domain);
310 assert!(has_naming, "Should have naming/path-based suggestions");
311
312 let has_visibility = suggestions.iter().any(|s| {
314 s.annotation_type == AnnotationType::Lock || s.annotation_type == AnnotationType::AiHint
315 });
316 assert!(has_visibility, "Should have visibility-based suggestions");
317 }
318
319 #[test]
320 fn test_git_heuristics_disabled() {
321 let engine = HeuristicsEngine::new().with_git_heuristics(false);
322 assert!(!engine.use_git_heuristics);
323 }
324}