archidoc_rust/
pattern_heuristic.rs1use std::path::Path;
13
14use syn::{Item, Visibility};
15
16use crate::walker;
17
18pub fn check_observer(source: &str) -> bool {
23 let indicators = [
25 "mpsc::Sender",
26 "mpsc::Receiver",
27 "mpsc::channel",
28 "crossbeam_channel",
29 "broadcast::Sender",
30 "watch::Sender",
31 "watch::Receiver",
32 "Box<dyn Fn",
33 "Box<dyn FnMut",
34 "Box<dyn FnOnce",
35 "Arc<dyn Fn",
36 "impl Fn(",
37 "impl FnMut(",
38 "impl FnOnce(",
39 "-> Receiver",
40 "-> Sender",
41 ];
42
43 for indicator in &indicators {
44 if source.contains(indicator) {
45 return true;
46 }
47 }
48
49 if let Ok(file) = syn::parse_file(source) {
51 for item in &file.items {
52 if let Item::Trait(trait_item) = item {
53 for method in &trait_item.items {
54 if let syn::TraitItem::Fn(m) = method {
55 let name = m.sig.ident.to_string();
56 if matches!(
57 name.as_str(),
58 "subscribe"
59 | "unsubscribe"
60 | "notify"
61 | "on_event"
62 | "on_update"
63 | "on_change"
64 | "emit"
65 | "publish"
66 | "add_listener"
67 | "remove_listener"
68 ) {
69 return true;
70 }
71 }
72 }
73 }
74 }
75 }
76
77 false
78}
79
80pub fn check_strategy(source: &str) -> bool {
85 if let Ok(file) = syn::parse_file(source) {
86 for item in &file.items {
87 if let Item::Trait(_) = item {
88 return true;
89 }
90 }
91 }
92 false
93}
94
95pub fn check_facade(source: &str) -> bool {
100 if let Ok(file) = syn::parse_file(source) {
101 let mut pub_use_count = 0;
102 let mut pub_mod_count = 0;
103
104 for item in &file.items {
105 match item {
106 Item::Use(use_item) => {
107 if matches!(use_item.vis, Visibility::Public(_)) {
108 pub_use_count += 1;
109 }
110 }
111 Item::Mod(mod_item) => {
112 if matches!(mod_item.vis, Visibility::Public(_)) {
113 pub_mod_count += 1;
114 }
115 }
116 _ => {}
117 }
118 }
119
120 pub_use_count >= 1 || pub_mod_count >= 2
122 } else {
123 false
124 }
125}
126
127pub fn check_builder(source: &str) -> bool {
131 if let Ok(file) = syn::parse_file(source) {
132 for item in &file.items {
133 if let Item::Impl(impl_item) = item {
134 let mut has_self_return = 0;
135 let mut has_build = false;
136
137 for method in &impl_item.items {
138 if let syn::ImplItem::Fn(m) = method {
139 let name = m.sig.ident.to_string();
140 if name == "build" {
141 has_build = true;
142 }
143 if let syn::ReturnType::Type(_, ty) = &m.sig.output {
145 let ty_str = quote::quote!(#ty).to_string();
146 if ty_str.contains("Self") {
147 has_self_return += 1;
148 }
149 }
150 }
151 }
152
153 if has_build || has_self_return >= 2 {
155 return true;
156 }
157 }
158 }
159 }
160
161 let indicators = ["fn build(self)", "fn build(&self)", "fn build(&mut self)"];
163 indicators.iter().any(|i| source.contains(i))
164}
165
166pub fn check_factory(source: &str) -> bool {
170 let indicators = [
171 "-> Box<dyn",
172 "-> Arc<dyn",
173 "-> Rc<dyn",
174 "fn create(",
175 "fn create_",
176 "fn make(",
177 "fn make_",
178 ];
179
180 for indicator in &indicators {
181 if source.contains(indicator) {
182 return true;
183 }
184 }
185
186 if let Ok(file) = syn::parse_file(source) {
187 for item in &file.items {
188 if let Item::Fn(func) = item {
189 if let syn::ReturnType::Type(_, ty) = &func.sig.output {
190 let ty_str = quote::quote!(#ty).to_string();
191 if ty_str.contains("Box < dyn") || ty_str.contains("impl ") {
192 return true;
193 }
194 }
195 }
196 }
197 }
198
199 false
200}
201
202pub fn check_adapter(source: &str) -> bool {
206 if let Ok(file) = syn::parse_file(source) {
207 let mut has_wrapper_struct = false;
208 let mut has_trait_impl = false;
209
210 for item in &file.items {
211 match item {
212 Item::Struct(s) => {
213 if let syn::Fields::Named(fields) = &s.fields {
215 if (1..=2).contains(&fields.named.len()) {
216 has_wrapper_struct = true;
217 }
218 }
219 }
220 Item::Impl(impl_item) => {
221 if impl_item.trait_.is_some() {
222 has_trait_impl = true;
223 }
224 }
225 _ => {}
226 }
227 }
228
229 return has_wrapper_struct && has_trait_impl;
230 }
231
232 false
233}
234
235pub fn check_decorator(source: &str) -> bool {
239 let indicators = [
240 "Box<dyn",
241 "Arc<dyn",
242 ];
243
244 let has_trait_object_field = indicators.iter().any(|i| source.contains(i));
245
246 if has_trait_object_field {
247 if let Ok(file) = syn::parse_file(source) {
248 let mut has_struct_with_dyn = false;
249 let mut has_trait_impl = false;
250
251 for item in &file.items {
252 match item {
253 Item::Struct(s) => {
254 if let syn::Fields::Named(fields) = &s.fields {
255 for field in &fields.named {
256 let ty_str = quote::quote!(#field).to_string();
257 if ty_str.contains("Box < dyn") || ty_str.contains("Arc < dyn") {
258 has_struct_with_dyn = true;
259 }
260 }
261 }
262 }
263 Item::Impl(impl_item) => {
264 if impl_item.trait_.is_some() {
265 has_trait_impl = true;
266 }
267 }
268 _ => {}
269 }
270 }
271
272 return has_struct_with_dyn && has_trait_impl;
273 }
274 }
275
276 false
277}
278
279pub fn check_singleton(source: &str) -> bool {
283 let indicators = [
284 "lazy_static!",
285 "once_cell::sync::Lazy",
286 "OnceLock",
287 "OnceCell",
288 "static ref ",
289 "fn instance()",
290 "fn get_instance()",
291 ];
292
293 indicators.iter().any(|i| source.contains(i))
294}
295
296pub fn check_command(source: &str) -> bool {
300 if let Ok(file) = syn::parse_file(source) {
301 for item in &file.items {
302 if let Item::Trait(trait_item) = item {
303 for method in &trait_item.items {
304 if let syn::TraitItem::Fn(m) = method {
305 let name = m.sig.ident.to_string();
306 if matches!(
307 name.as_str(),
308 "execute" | "exec" | "run" | "invoke" | "perform" | "undo" | "redo"
309 ) {
310 return true;
311 }
312 }
313 }
314 }
315 }
316 }
317
318 false
319}
320
321pub fn check_pattern(pattern: &str, source: &str) -> bool {
323 match pattern {
324 "Observer" => check_observer(source),
325 "Strategy" => check_strategy(source),
326 "Facade" => check_facade(source),
327 "Builder" => check_builder(source),
328 "Factory" => check_factory(source),
329 "Adapter" => check_adapter(source),
330 "Decorator" => check_decorator(source),
331 "Singleton" => check_singleton(source),
332 "Command" => check_command(source),
333 _ => false,
334 }
335}
336
337pub fn check_module_pattern(pattern: &str, source_dir: &Path) -> bool {
343 walker::read_rs_sources(source_dir)
344 .iter()
345 .any(|(_, source)| check_pattern(pattern, source))
346}
347
348#[cfg(test)]
349mod tests {
350 use super::*;
351
352 #[test]
353 fn strategy_detects_trait() {
354 let source = r#"
355 pub trait Calculator {
356 fn calculate(&self, prices: &[f64]) -> f64;
357 }
358 "#;
359 assert!(check_strategy(source));
360 }
361
362 #[test]
363 fn strategy_rejects_no_trait() {
364 let source = r#"
365 pub struct SimpleCalc;
366 impl SimpleCalc {
367 pub fn calculate(&self, prices: &[f64]) -> f64 {
368 prices.iter().sum()
369 }
370 }
371 "#;
372 assert!(!check_strategy(source));
373 }
374
375 #[test]
376 fn facade_detects_pub_use() {
377 let source = r#"
378 pub use crate::calc::Calculator;
379 pub use crate::store::DataStore;
380 "#;
381 assert!(check_facade(source));
382 }
383
384 #[test]
385 fn facade_detects_pub_mod() {
386 let source = r#"
387 pub mod calc;
388 pub mod store;
389 "#;
390 assert!(check_facade(source));
391 }
392
393 #[test]
394 fn facade_rejects_private_mods() {
395 let source = r#"
396 mod calc;
397 mod store;
398 "#;
399 assert!(!check_facade(source));
400 }
401
402 #[test]
403 fn observer_detects_channel() {
404 let source = r#"
405 use std::sync::mpsc::Sender;
406 use std::sync::mpsc::Receiver;
407 pub fn create_bus() -> (mpsc::Sender<Event>, mpsc::Receiver<Event>) {
408 std::sync::mpsc::channel()
409 }
410 "#;
411 assert!(check_observer(source));
412 }
413
414 #[test]
415 fn observer_detects_callback_trait() {
416 let source = r#"
417 pub trait EventBus {
418 fn subscribe(&mut self, handler: Box<dyn Fn(Event)>);
419 fn notify(&self, event: Event);
420 }
421 "#;
422 assert!(check_observer(source));
423 }
424
425 #[test]
426 fn observer_rejects_plain_struct() {
427 let source = r#"
428 pub struct Logger {
429 path: String,
430 }
431 impl Logger {
432 pub fn log(&self, msg: &str) {
433 println!("{}", msg);
434 }
435 }
436 "#;
437 assert!(!check_observer(source));
438 }
439
440 #[test]
441 fn check_pattern_dispatches_correctly() {
442 let strategy_src = "pub trait Algo { fn run(&self); }";
443 assert!(check_pattern("Strategy", strategy_src));
444 assert!(!check_pattern("Observer", strategy_src));
445 assert!(!check_pattern("UnknownPattern", strategy_src));
446 }
447
448}