1use crate::analysis::Workspace;
2use crate::config::{EngineMode, Policy};
3use crate::emit::ReportEmitter;
4use crate::report::{AdapterRun, Report};
5use crate::rules::{self, RuleBackend, RuleContext};
6use crate::semantic::SemanticBackendStatus;
7
8pub struct Runner;
9
10impl Runner {
11 pub fn run(ws: &Workspace, policy: &Policy) -> Result<Report, RunError> {
12 let semantic_status = SemanticBackendStatus::probe();
13 Self::run_with_semantic_status(ws, policy, semantic_status)
14 }
15
16 pub fn run_with_semantic_status(
17 ws: &Workspace,
18 policy: &Policy,
19 semantic_status: SemanticBackendStatus,
20 ) -> Result<Report, RunError> {
21 let mut report = Report {
22 rule_catalog: rules::rule_catalog_entries(),
23 ..Report::default()
24 };
25
26 if policy.engine.semantic == EngineMode::Require && !semantic_status.is_available() {
27 return Err(RunError::SemanticBackendRequired(
28 semantic_status
29 .reason
30 .clone()
31 .unwrap_or_else(|| "semantic backend unavailable".to_string()),
32 ));
33 }
34
35 let mut emitter = ReportEmitter::new();
36 let ctx = RuleContext { policy };
37 for rule in rules::enabled_rules(policy) {
38 let info = rule.info();
39 if info.backend == RuleBackend::Semantic && !semantic_status.is_available() {
40 report.summary.skipped_rules.push(info.id.to_string());
41 continue;
42 }
43 if !report.summary.engine_used.contains(&info.backend) {
44 report.summary.engine_used.push(info.backend);
45 }
46 rule.run(ws, &ctx, &mut emitter);
47 }
48
49 report.findings = emitter.findings;
50 report.metrics.per_file = emitter.metrics;
51 report.summary.semantic_backend_available = semantic_status.is_available();
52 report.summary.semantic_backend_reason = semantic_status.reason;
53 report.summary.adapter_runs.push(AdapterRun {
54 name: "clippy".to_string(),
55 enabled: policy.adapters.clippy.enabled,
56 toolchain: None,
57 status: None,
58 });
59 Ok(report)
60 }
61}
62
63#[derive(Debug, thiserror::Error)]
64pub enum RunError {
65 #[error("{0}")]
66 SemanticBackendRequired(String),
67}