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
30
31pub trait L2Engine: Send + Sync {
41 fn name(&self) -> &'static str;
43
44 fn finding_types(&self) -> &[&'static str];
46
47 fn languages(&self) -> &[tldr_core::Language] {
50 &[]
51 }
52
53 fn analyze(&self, ctx: &context::L2Context) -> types::L2AnalyzerOutput;
55}
56
57pub fn l2_engine_registry() -> Vec<Box<dyn L2Engine>> {
63 vec![
64 Box::new(engines::tldr_differential::TldrDifferentialEngine::new()),
65 ]
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71
72 #[test]
74 fn test_l2_engine_registry_contains_engines() {
75 let engines = l2_engine_registry();
76 assert_eq!(engines.len(), 1, "Registry should contain exactly 1 engine (TldrDifferentialEngine)");
77 assert!(
78 engines.iter().any(|e| e.name() == "TldrDifferentialEngine"),
79 "Registry must contain TldrDifferentialEngine"
80 );
81 }
82
83 #[test]
85 fn test_tldr_engine_registered() {
86 let engines = l2_engine_registry();
87 let engine = engines.iter().find(|e| e.name() == "TldrDifferentialEngine");
88 assert!(engine.is_some(), "TldrDifferentialEngine must be in registry");
89 let engine = engine.unwrap();
90 assert_eq!(engine.finding_types().len(), 11);
91 }
92
93 #[test]
96 fn test_l2_engine_trait_object_safe() {
97 struct MockEngine;
98
99 impl L2Engine for MockEngine {
100 fn name(&self) -> &'static str {
101 "MockEngine"
102 }
103
104 fn finding_types(&self) -> &[&'static str] {
105 &["test-finding"]
106 }
107
108 fn analyze(&self, _ctx: &context::L2Context) -> types::L2AnalyzerOutput {
109 types::L2AnalyzerOutput {
110 findings: vec![],
111 status: types::AnalyzerStatus::Complete,
112 duration_ms: 0,
113 functions_analyzed: 0,
114 functions_skipped: 0,
115 }
116 }
117 }
118
119 let engine: Box<dyn L2Engine> = Box::new(MockEngine);
120
121 assert_eq!(engine.name(), "MockEngine");
122 assert_eq!(engine.finding_types(), &["test-finding"]);
123 assert!(engine.languages().is_empty());
124
125 let ctx = context::L2Context::new(
127 std::path::PathBuf::from("/tmp/test"),
128 tldr_core::Language::Rust,
129 vec![],
130 context::FunctionDiff {
131 changed: vec![],
132 inserted: vec![],
133 deleted: vec![],
134 },
135 std::collections::HashMap::new(),
136 std::collections::HashMap::new(),
137 std::collections::HashMap::new(),
138 );
139 let output = engine.analyze(&ctx);
140 assert!(output.findings.is_empty());
141 assert_eq!(output.status, types::AnalyzerStatus::Complete);
142 assert_eq!(output.duration_ms, 0);
143 assert_eq!(output.functions_analyzed, 0);
144 assert_eq!(output.functions_skipped, 0);
145 }
146}