1#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used))]
2
3use std::path::Path;
4
5mod audit;
6mod baseline;
7mod bounded_read;
8mod cargo_health;
9mod configuration;
10mod delta;
11mod execution;
12mod git;
13mod git_scope;
14mod internal_error;
15#[cfg(test)]
16mod permutations;
17mod policy;
18pub mod presentation;
19pub mod render;
20mod repo_hygiene;
21mod report;
22mod scan_target;
23pub mod score_block;
24mod source_kernel;
25mod source_text;
26mod structure;
27#[cfg(test)]
28mod test_clock;
29#[cfg(test)]
30#[path = "test_scratch.rs"]
31mod test_scratch;
32pub mod terminal_text;
33mod workspace_path;
34
35pub use audit::{
36 Audit, AuditCategory, AuditCategoryName, AuditScore, SCORE_MODEL, ScoreDimensions, ScoreLabel,
37 SeverityCounts, ShareError,
38};
39pub use delta::{DeltaMatch, DeltaReport, DeltaSummary};
40pub use git_scope::{ExecutionScope, ScopeMode, ScopeReport};
41pub use policy::{
42 BlockingLevel, BlockingLevelSource, CatalogEntry, CategoryOverride, RuleLevel, RuleLevelSource,
43 RuleOverride, RuleTier, catalog,
44};
45pub use structure::FILE_LINES as OVERSIZED_UNIT_FILE_LINES;
52pub use report::{
53 Diagnostic, DiagnosticSource, DiagnosticSpan, GateReport, GateStatus, InspectReport,
54 InspectRequest, PackageReport, PolicyBlockingReport, PolicyReport, PolicyRuleReport,
55 ProjectReport, RelatedLocation, ReportError, SCHEMA_VERSION, ScanReport, Severity, Status,
56 Summary, ToolchainReport,
57};
58
59pub fn inspect(request: InspectRequest) -> InspectReport {
60 match InspectionSession::prepare(request) {
61 Ok(session) => session.inspect(),
62 Err(report) => *report,
63 }
64}
65
66#[derive(Debug)]
67pub struct InspectionSession {
68 prepared: execution::PreparedInspection,
69 plan: policy::PolicyPlan,
70 scope: git_scope::ValidatedScope,
71}
72
73impl InspectionSession {
74 pub fn prepare(request: InspectRequest) -> Result<Self, Box<InspectReport>> {
75 let policy = request.policy().clone();
76 Self::prepare_with(request, &policy)
77 }
78
79 fn prepare_with(
80 request: InspectRequest,
81 policy: &policy::PolicyInput,
82 ) -> Result<Self, Box<InspectReport>> {
83 let validated = match policy.validate() {
88 Ok(validated) => validated,
89 Err(error) => {
90 return Err(Box::new(report::policy_failure(
91 error,
92 policy.failure_blocking(),
93 )));
94 }
95 };
96 let scope = match request.scope().validate() {
100 Ok(scope) => scope,
101 Err(error) => {
102 return Err(Box::new(report::scope_failure(
103 error,
104 policy.failure_blocking(),
105 )));
106 }
107 };
108 let prepared = match execution::prepare(&request.path) {
109 Ok(prepared) => prepared,
110 Err(result) => {
111 return Err(Box::new(report::preparation_failure(
112 *result,
113 policy.failure_blocking(),
114 )));
115 }
116 };
117 let plan =
118 policy::PolicyPlan::compile_with_configuration(&validated, &prepared.configuration);
119 Ok(Self {
120 prepared,
121 plan,
122 scope,
123 })
124 }
125
126 pub fn workspace_root(&self) -> &Path {
127 self.prepared.workspace_root()
128 }
129
130 pub fn inspect(self) -> InspectReport {
131 inspect_prepared(self)
132 }
133}
134
135#[cfg(test)]
136fn inspect_with(request: InspectRequest, policy: &policy::PolicyInput) -> InspectReport {
137 match InspectionSession::prepare_with(request, policy) {
138 Ok(session) => session.inspect(),
139 Err(report) => *report,
140 }
141}
142
143fn inspect_prepared(session: InspectionSession) -> InspectReport {
144 let InspectionSession {
145 prepared,
146 plan,
147 scope: requested,
148 } = session;
149 let scope = match git_scope::resolve(&requested, prepared.workspace_root()) {
150 Ok(scope) => scope,
151 Err(error) => {
152 return report::preparation_failure(prepared.fail(error), plan.blocking());
153 }
154 };
155 if let git_scope::ResolvedScope::Baseline { comparison_base } = scope.kind() {
156 let snapshot = match baseline::materialize(prepared.workspace_root(), comparison_base) {
157 Ok(snapshot) => snapshot,
158 Err(error) => {
159 return report::from_execution_scoped(prepared.fail(error), &plan, scope);
160 }
161 };
162 let execution =
163 execution::execute_baseline(prepared, snapshot.workspace(), snapshot.target(), &plan);
164 let report = report::from_baseline_execution(execution, &plan, scope);
165 return match snapshot.cleanup() {
166 Ok(()) => report,
167 Err(error) => report::baseline_report_failure(report, error),
168 };
169 }
170 report::from_execution_scoped(execution::execute(prepared, &plan), &plan, scope)
171}
172
173#[cfg(test)]
174mod tests {
175 use std::path::Path;
176
177 use super::*;
178 use crate::policy::{PolicyInput, RuleLevel};
179
180 #[test]
181 fn invalid_policy_returns_before_execution_or_path_discovery() {
182 let request = InspectRequest::new("/path/that/must/not/be/inspected");
183 let policy = PolicyInput::default().with_rule("unknown::rule", RuleLevel::Warn);
184
185 let report = inspect_with(request, &policy);
186
187 assert_eq!(report.status, Status::Failed);
188 assert!(!report.complete);
189 assert!(report.project.is_none());
190 assert!(report.scan.command.is_none());
191 assert!(report.diagnostics.is_empty());
192 assert_eq!(report.errors.len(), 1);
193 assert_eq!(report.errors[0].stage, "policy");
194 assert_eq!(report.errors[0].code, "unknown-rule");
195 }
196
197 #[test]
198 fn clippy_off_prunes_execution_and_error_preserves_warning_identity() {
199 let fixture =
200 Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/kernel-contract/todo");
201 let baseline = inspect_with(InspectRequest::new(&fixture), &PolicyInput::default());
202 assert_eq!(baseline.status, Status::Complete);
203 let baseline_ids: Vec<_> = baseline
204 .diagnostics
205 .iter()
206 .filter(|diagnostic| diagnostic.code.as_deref() == Some("clippy::todo"))
207 .map(|diagnostic| diagnostic.id.clone())
208 .collect();
209 assert!(!baseline_ids.is_empty());
210
211 let off_policy = PolicyInput::default().with_rule("clippy::todo", RuleLevel::Off);
212 let off = inspect_with(InspectRequest::new(&fixture), &off_policy);
213 assert_eq!(off.status, Status::Complete);
214 assert!(
215 off.diagnostics
216 .iter()
217 .all(|diagnostic| diagnostic.code.as_deref() != Some("clippy::todo"))
218 );
219 let off_command = off
220 .scan
221 .command
222 .expect("complete scan should expose its command");
223 assert!(
224 !off_command
225 .iter()
226 .any(|argument| argument == "clippy::todo")
227 );
228
229 let error_policy = PolicyInput::default().with_rule("clippy::todo", RuleLevel::Error);
230 let error = inspect_with(InspectRequest::new(&fixture), &error_policy);
231 assert_eq!(error.status, Status::Complete);
232 let error_ids: Vec<_> = error
233 .diagnostics
234 .iter()
235 .filter(|diagnostic| diagnostic.code.as_deref() == Some("clippy::todo"))
236 .map(|diagnostic| {
237 assert_eq!(diagnostic.base_severity, Severity::Warning);
238 assert_eq!(diagnostic.severity, Severity::Error);
239 diagnostic.id.clone()
240 })
241 .collect();
242 assert_eq!(error_ids, baseline_ids);
243 assert_eq!(error.gate.status, GateStatus::Failed);
244 assert_eq!(error.gate.blocking_diagnostics, Some(error_ids.len()));
245 let error_command = error
246 .scan
247 .command
248 .expect("complete scan should expose its command");
249 assert!(
250 error_command
251 .windows(2)
252 .any(|pair| pair == ["-W", "clippy::todo"])
253 );
254 assert!(!error_command.iter().any(|argument| argument == "-D"));
255 }
256}