tldr_cli/commands/bugbot/l2/
mod.rs1pub mod composition;
19pub mod context;
20pub mod daemon_client;
21pub mod dedup;
22pub mod engines;
23pub mod findings;
24pub mod ir;
25pub mod types;
26
27pub use context::L2Context;
28pub use types::*;
29
30pub trait L2Engine: Send + Sync {
40 fn name(&self) -> &'static str;
42
43 fn finding_types(&self) -> &[&'static str];
45
46 fn languages(&self) -> &[tldr_core::Language] {
49 &[]
50 }
51
52 fn analyze(&self, ctx: &context::L2Context) -> types::L2AnalyzerOutput;
54}
55
56pub fn l2_engine_registry() -> Vec<Box<dyn L2Engine>> {
62 vec![Box::new(
63 engines::tldr_differential::TldrDifferentialEngine::new(),
64 )]
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 #[test]
73 fn test_l2_engine_registry_contains_engines() {
74 let engines = l2_engine_registry();
75 assert_eq!(
76 engines.len(),
77 1,
78 "Registry should contain exactly 1 engine (TldrDifferentialEngine)"
79 );
80 assert!(
81 engines.iter().any(|e| e.name() == "TldrDifferentialEngine"),
82 "Registry must contain TldrDifferentialEngine"
83 );
84 }
85
86 #[test]
88 fn test_tldr_engine_registered() {
89 let engines = l2_engine_registry();
90 let engine = engines
91 .iter()
92 .find(|e| e.name() == "TldrDifferentialEngine");
93 assert!(
94 engine.is_some(),
95 "TldrDifferentialEngine must be in registry"
96 );
97 let engine = engine.unwrap();
98 assert_eq!(engine.finding_types().len(), 11);
99 }
100
101 #[test]
104 fn test_l2_engine_trait_object_safe() {
105 struct MockEngine;
106
107 impl L2Engine for MockEngine {
108 fn name(&self) -> &'static str {
109 "MockEngine"
110 }
111
112 fn finding_types(&self) -> &[&'static str] {
113 &["test-finding"]
114 }
115
116 fn analyze(&self, _ctx: &context::L2Context) -> types::L2AnalyzerOutput {
117 types::L2AnalyzerOutput {
118 findings: vec![],
119 status: types::AnalyzerStatus::Complete,
120 duration_ms: 0,
121 functions_analyzed: 0,
122 functions_skipped: 0,
123 }
124 }
125 }
126
127 let engine: Box<dyn L2Engine> = Box::new(MockEngine);
128
129 assert_eq!(engine.name(), "MockEngine");
130 assert_eq!(engine.finding_types(), &["test-finding"]);
131 assert!(engine.languages().is_empty());
132
133 let ctx = context::L2Context::new(
135 std::path::PathBuf::from("/tmp/test"),
136 tldr_core::Language::Rust,
137 vec![],
138 context::FunctionDiff {
139 changed: vec![],
140 inserted: vec![],
141 deleted: vec![],
142 },
143 std::collections::HashMap::new(),
144 std::collections::HashMap::new(),
145 std::collections::HashMap::new(),
146 );
147 let output = engine.analyze(&ctx);
148 assert!(output.findings.is_empty());
149 assert_eq!(output.status, types::AnalyzerStatus::Complete);
150 assert_eq!(output.duration_ms, 0);
151 assert_eq!(output.functions_analyzed, 0);
152 assert_eq!(output.functions_skipped, 0);
153 }
154}