1use std::path::{Path, PathBuf};
4
5use fallow_output::{
6 CheckOutput, DupesOutput, FeatureFlagFinding, FeatureFlagsOutput as FeatureFlagsOutputContract,
7 GroupByMode, HealthGroup, HealthGrouping, HealthJsonOutputInput, HealthOutputInput,
8 HealthReport, RootEnvelopeMode, health_meta,
9};
10use fallow_types::output::NextStep;
11use fallow_types::output_dead_code::{
12 BoundaryCallViolationFinding, BoundaryCoverageViolationFinding, BoundaryViolationFinding,
13 CircularDependencyFinding,
14};
15use fallow_types::results::AnalysisResults;
16use fallow_types::workspace::WorkspaceDiagnostic;
17use rustc_hash::FxHashSet;
18
19use crate::{AuditAttribution, AuditSummary, AuditVerdict};
20use crate::{CloneFamilyFinding, CloneGroupFinding, DupesReportPayload, DuplicationGroup};
21
22pub const HEALTH_SCHEMA_VERSION: u32 = 7;
23
24pub type DeadCodeOutput = CheckOutput;
26
27pub type CircularDependenciesOutput = CheckOutput;
29
30pub type BoundaryViolationsOutput = CheckOutput;
32
33pub type DuplicationOutput = DupesOutput<DupesReportPayload, DuplicationGroup>;
35
36pub type FeatureFlagsOutput = FeatureFlagsOutputContract;
38
39pub type TraceExportOutput = fallow_types::trace::ExportTrace;
41
42pub type TraceClassMemberOutput = fallow_types::trace::ClassMemberTrace;
46
47#[derive(Debug, serde::Serialize)]
53#[serde(untagged)]
54pub enum TraceExportTargetOutput {
55 Export(TraceExportOutput),
57 Member(TraceClassMemberOutput),
59}
60
61pub type TraceFileOutput = fallow_types::trace::FileTrace;
63
64pub type TraceDependencyOutput = fallow_types::trace::DependencyTrace;
66
67pub type TraceCloneOutput = fallow_types::trace::CloneTrace;
69
70pub struct HealthJsonReportInput<'a> {
72 pub report: HealthReport,
73 pub root: &'a Path,
74 pub elapsed: std::time::Duration,
75 pub explain: bool,
76 pub grouped_by: Option<GroupByMode>,
77 pub groups: Option<Vec<HealthGroup>>,
78 pub workspace_diagnostics: Vec<WorkspaceDiagnostic>,
79 pub next_steps: Vec<NextStep>,
80 pub envelope_mode: RootEnvelopeMode,
81 pub telemetry_analysis_run_id: Option<&'a str>,
82}
83
84#[derive(Debug, Clone)]
86pub struct CombinedProgrammaticOutput {
87 pub dead_code: Option<DeadCodeProgrammaticOutput>,
88 pub duplication: Option<DuplicationProgrammaticOutput>,
89 pub health: Option<HealthProgrammaticOutput>,
90 pub root: PathBuf,
91 pub elapsed: std::time::Duration,
92 pub explain: bool,
93 pub next_steps: Vec<NextStep>,
94 pub envelope_mode: RootEnvelopeMode,
95 pub telemetry_analysis_run_id: Option<String>,
96}
97
98#[derive(Debug, Clone)]
104pub struct DeadCodeProgrammaticOutput {
105 pub output: DeadCodeOutput,
106 pub root: PathBuf,
107 pub config_fixable: bool,
108 pub envelope_mode: RootEnvelopeMode,
109 pub telemetry_analysis_run_id: Option<String>,
110}
111
112impl DeadCodeProgrammaticOutput {
113 #[must_use]
115 pub fn results(&self) -> &AnalysisResults {
116 &self.output.results
117 }
118
119 #[must_use]
121 pub fn root(&self) -> &Path {
122 &self.root
123 }
124}
125
126#[derive(Debug, Clone)]
132pub struct CircularDependenciesProgrammaticOutput {
133 pub output: CircularDependenciesOutput,
134 pub root: PathBuf,
135 pub envelope_mode: RootEnvelopeMode,
136 pub telemetry_analysis_run_id: Option<String>,
137}
138
139impl CircularDependenciesProgrammaticOutput {
140 #[must_use]
142 pub fn results(&self) -> &AnalysisResults {
143 &self.output.results
144 }
145
146 #[must_use]
148 pub fn circular_dependencies(&self) -> &[CircularDependencyFinding] {
149 &self.output.results.circular_dependencies
150 }
151}
152
153impl From<DeadCodeProgrammaticOutput> for CircularDependenciesProgrammaticOutput {
154 fn from(value: DeadCodeProgrammaticOutput) -> Self {
155 Self {
156 output: value.output,
157 root: value.root,
158 envelope_mode: value.envelope_mode,
159 telemetry_analysis_run_id: value.telemetry_analysis_run_id,
160 }
161 }
162}
163
164#[derive(Debug, Clone)]
169pub struct BoundaryViolationsProgrammaticOutput {
170 pub output: BoundaryViolationsOutput,
171 pub root: PathBuf,
172 pub envelope_mode: RootEnvelopeMode,
173 pub telemetry_analysis_run_id: Option<String>,
174}
175
176impl BoundaryViolationsProgrammaticOutput {
177 #[must_use]
179 pub fn results(&self) -> &AnalysisResults {
180 &self.output.results
181 }
182
183 #[must_use]
185 pub fn boundary_violations(&self) -> &[BoundaryViolationFinding] {
186 &self.output.results.boundary_violations
187 }
188
189 #[must_use]
191 pub fn boundary_coverage_violations(&self) -> &[BoundaryCoverageViolationFinding] {
192 &self.output.results.boundary_coverage_violations
193 }
194
195 #[must_use]
197 pub fn boundary_call_violations(&self) -> &[BoundaryCallViolationFinding] {
198 &self.output.results.boundary_call_violations
199 }
200}
201
202impl From<DeadCodeProgrammaticOutput> for BoundaryViolationsProgrammaticOutput {
203 fn from(value: DeadCodeProgrammaticOutput) -> Self {
204 Self {
205 output: value.output,
206 root: value.root,
207 envelope_mode: value.envelope_mode,
208 telemetry_analysis_run_id: value.telemetry_analysis_run_id,
209 }
210 }
211}
212
213#[derive(Debug, Clone)]
215pub struct DuplicationProgrammaticOutput {
216 pub output: DuplicationOutput,
217 pub root: PathBuf,
218 pub threshold: f64,
219 pub envelope_mode: RootEnvelopeMode,
220 pub telemetry_analysis_run_id: Option<String>,
221}
222
223impl DuplicationProgrammaticOutput {
224 #[must_use]
226 pub const fn report(&self) -> &DupesReportPayload {
227 &self.output.report
228 }
229
230 #[must_use]
232 pub fn clone_groups(&self) -> &[CloneGroupFinding] {
233 &self.output.report.clone_groups
234 }
235
236 #[must_use]
238 pub fn clone_families(&self) -> &[CloneFamilyFinding] {
239 &self.output.report.clone_families
240 }
241
242 #[must_use]
244 pub fn groups(&self) -> Option<&[DuplicationGroup]> {
245 self.output.groups.as_deref()
246 }
247}
248
249#[derive(Debug, Clone)]
251pub struct FeatureFlagsProgrammaticOutput {
252 pub output: FeatureFlagsOutput,
253 pub envelope_mode: RootEnvelopeMode,
254 pub telemetry_analysis_run_id: Option<String>,
255}
256
257impl FeatureFlagsProgrammaticOutput {
258 #[must_use]
260 pub fn feature_flags(&self) -> &[FeatureFlagFinding] {
261 &self.output.feature_flags
262 }
263
264 #[must_use]
266 pub const fn total_flags(&self) -> usize {
267 self.output.total_flags
268 }
269}
270
271#[derive(Debug)]
274pub struct TraceExportProgrammaticOutput {
275 pub output: TraceExportTargetOutput,
276}
277
278impl TraceExportProgrammaticOutput {
279 #[must_use]
281 pub const fn trace(&self) -> &TraceExportTargetOutput {
282 &self.output
283 }
284
285 #[must_use]
287 pub const fn as_export(&self) -> Option<&TraceExportOutput> {
288 match &self.output {
289 TraceExportTargetOutput::Export(export) => Some(export),
290 TraceExportTargetOutput::Member(_) => None,
291 }
292 }
293
294 #[must_use]
297 pub const fn as_member(&self) -> Option<&TraceClassMemberOutput> {
298 match &self.output {
299 TraceExportTargetOutput::Member(member) => Some(member),
300 TraceExportTargetOutput::Export(_) => None,
301 }
302 }
303}
304
305#[derive(Debug)]
307pub struct TraceFileProgrammaticOutput {
308 pub output: TraceFileOutput,
309}
310
311impl TraceFileProgrammaticOutput {
312 #[must_use]
314 pub const fn trace(&self) -> &TraceFileOutput {
315 &self.output
316 }
317}
318
319#[derive(Debug)]
321pub struct TraceDependencyProgrammaticOutput {
322 pub output: TraceDependencyOutput,
323}
324
325impl TraceDependencyProgrammaticOutput {
326 #[must_use]
328 pub const fn trace(&self) -> &TraceDependencyOutput {
329 &self.output
330 }
331}
332
333#[derive(Debug)]
335pub struct TraceCloneProgrammaticOutput {
336 pub output: TraceCloneOutput,
337}
338
339impl TraceCloneProgrammaticOutput {
340 #[must_use]
342 pub const fn trace(&self) -> &TraceCloneOutput {
343 &self.output
344 }
345}
346
347#[derive(Debug, Clone)]
349pub struct HealthProgrammaticOutput {
350 pub report: HealthReport,
351 pub grouping: Option<HealthGrouping>,
352 pub root: PathBuf,
353 pub elapsed: std::time::Duration,
354 pub explain: bool,
355 pub workspace_diagnostics: Vec<WorkspaceDiagnostic>,
356 pub next_steps: Vec<NextStep>,
357 pub envelope_mode: RootEnvelopeMode,
358 pub telemetry_analysis_run_id: Option<String>,
359}
360
361#[derive(Debug, Clone)]
363pub struct AuditProgrammaticOutput {
364 pub verdict: AuditVerdict,
365 pub summary: AuditSummary,
366 pub attribution: AuditAttribution,
367 pub changed_files_count: usize,
368 pub base_ref: String,
369 pub base_description: Option<String>,
370 pub head_sha: Option<String>,
371 pub elapsed: std::time::Duration,
372 pub base_snapshot_skipped: Option<bool>,
373 pub base_snapshot: Option<AuditProgrammaticKeySnapshot>,
374 pub dead_code: Option<DeadCodeProgrammaticOutput>,
375 pub duplication: Option<DuplicationProgrammaticOutput>,
376 pub complexity: Option<HealthProgrammaticOutput>,
377 pub next_steps: Vec<NextStep>,
378 pub envelope_mode: RootEnvelopeMode,
379 pub telemetry_analysis_run_id: Option<String>,
380}
381
382#[derive(Debug, Clone, Default)]
384pub struct AuditProgrammaticKeySnapshot {
385 pub dead_code: FxHashSet<String>,
386 pub health: FxHashSet<String>,
387 pub dupes: FxHashSet<String>,
388}
389
390#[derive(Debug, Clone)]
392pub struct DecisionSurfaceProgrammaticOutput {
393 pub surface: fallow_output::DecisionSurface,
394 pub elapsed: std::time::Duration,
395 pub envelope_mode: RootEnvelopeMode,
396 pub telemetry_analysis_run_id: Option<String>,
397}
398
399pub fn serialize_health_report_json(
405 input: HealthJsonReportInput<'_>,
406) -> Result<serde_json::Value, serde_json::Error> {
407 let root_prefix = format!("{}/", input.root.display());
408 fallow_output::serialize_health_json_output(HealthJsonOutputInput {
409 output: HealthOutputInput {
410 schema_version: HEALTH_SCHEMA_VERSION,
411 version: env!("CARGO_PKG_VERSION").to_string(),
412 elapsed: input.elapsed,
413 report: input.report,
414 grouped_by: input.grouped_by,
415 groups: input.groups,
416 meta: input.explain.then(health_meta),
417 workspace_diagnostics: input.workspace_diagnostics,
418 next_steps: input.next_steps,
419 },
420 root_prefix: Some(&root_prefix),
421 envelope_mode: input.envelope_mode,
422 analysis_run_id: input.telemetry_analysis_run_id,
423 })
424}