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 type_aware: Option<fallow_types::envelope::TypeAwareMeta>,
77 pub grouped_by: Option<GroupByMode>,
78 pub groups: Option<Vec<HealthGroup>>,
79 pub workspace_diagnostics: Vec<WorkspaceDiagnostic>,
80 pub next_steps: Vec<NextStep>,
81 pub envelope_mode: RootEnvelopeMode,
82 pub telemetry_analysis_run_id: Option<&'a str>,
83}
84
85#[derive(Debug, Clone)]
87pub struct CombinedProgrammaticOutput {
88 pub dead_code: Option<DeadCodeProgrammaticOutput>,
89 pub duplication: Option<DuplicationProgrammaticOutput>,
90 pub health: Option<HealthProgrammaticOutput>,
91 pub root: PathBuf,
92 pub elapsed: std::time::Duration,
93 pub explain: bool,
94 pub next_steps: Vec<NextStep>,
95 pub envelope_mode: RootEnvelopeMode,
96 pub telemetry_analysis_run_id: Option<String>,
97}
98
99#[derive(Debug, Clone)]
105pub struct DeadCodeProgrammaticOutput {
106 pub output: DeadCodeOutput,
107 pub root: PathBuf,
108 pub config_fixable: bool,
109 pub envelope_mode: RootEnvelopeMode,
110 pub telemetry_analysis_run_id: Option<String>,
111}
112
113impl DeadCodeProgrammaticOutput {
114 #[must_use]
116 pub fn results(&self) -> &AnalysisResults {
117 &self.output.results
118 }
119
120 #[must_use]
122 pub fn root(&self) -> &Path {
123 &self.root
124 }
125}
126
127#[derive(Debug, Clone)]
133pub struct CircularDependenciesProgrammaticOutput {
134 pub output: CircularDependenciesOutput,
135 pub root: PathBuf,
136 pub envelope_mode: RootEnvelopeMode,
137 pub telemetry_analysis_run_id: Option<String>,
138}
139
140impl CircularDependenciesProgrammaticOutput {
141 #[must_use]
143 pub fn results(&self) -> &AnalysisResults {
144 &self.output.results
145 }
146
147 #[must_use]
149 pub fn circular_dependencies(&self) -> &[CircularDependencyFinding] {
150 &self.output.results.circular_dependencies
151 }
152}
153
154impl From<DeadCodeProgrammaticOutput> for CircularDependenciesProgrammaticOutput {
155 fn from(value: DeadCodeProgrammaticOutput) -> Self {
156 Self {
157 output: value.output,
158 root: value.root,
159 envelope_mode: value.envelope_mode,
160 telemetry_analysis_run_id: value.telemetry_analysis_run_id,
161 }
162 }
163}
164
165#[derive(Debug, Clone)]
170pub struct BoundaryViolationsProgrammaticOutput {
171 pub output: BoundaryViolationsOutput,
172 pub root: PathBuf,
173 pub envelope_mode: RootEnvelopeMode,
174 pub telemetry_analysis_run_id: Option<String>,
175}
176
177impl BoundaryViolationsProgrammaticOutput {
178 #[must_use]
180 pub fn results(&self) -> &AnalysisResults {
181 &self.output.results
182 }
183
184 #[must_use]
186 pub fn boundary_violations(&self) -> &[BoundaryViolationFinding] {
187 &self.output.results.boundary_violations
188 }
189
190 #[must_use]
192 pub fn boundary_coverage_violations(&self) -> &[BoundaryCoverageViolationFinding] {
193 &self.output.results.boundary_coverage_violations
194 }
195
196 #[must_use]
198 pub fn boundary_call_violations(&self) -> &[BoundaryCallViolationFinding] {
199 &self.output.results.boundary_call_violations
200 }
201}
202
203impl From<DeadCodeProgrammaticOutput> for BoundaryViolationsProgrammaticOutput {
204 fn from(value: DeadCodeProgrammaticOutput) -> Self {
205 Self {
206 output: value.output,
207 root: value.root,
208 envelope_mode: value.envelope_mode,
209 telemetry_analysis_run_id: value.telemetry_analysis_run_id,
210 }
211 }
212}
213
214#[derive(Debug, Clone)]
216pub struct DuplicationProgrammaticOutput {
217 pub output: DuplicationOutput,
218 pub root: PathBuf,
219 pub threshold: f64,
220 pub envelope_mode: RootEnvelopeMode,
221 pub telemetry_analysis_run_id: Option<String>,
222}
223
224impl DuplicationProgrammaticOutput {
225 #[must_use]
227 pub const fn report(&self) -> &DupesReportPayload {
228 &self.output.report
229 }
230
231 #[must_use]
233 pub fn clone_groups(&self) -> &[CloneGroupFinding] {
234 &self.output.report.clone_groups
235 }
236
237 #[must_use]
239 pub fn clone_families(&self) -> &[CloneFamilyFinding] {
240 &self.output.report.clone_families
241 }
242
243 #[must_use]
245 pub fn groups(&self) -> Option<&[DuplicationGroup]> {
246 self.output.groups.as_deref()
247 }
248}
249
250#[derive(Debug, Clone)]
252pub struct FeatureFlagsProgrammaticOutput {
253 pub output: FeatureFlagsOutput,
254 pub envelope_mode: RootEnvelopeMode,
255 pub telemetry_analysis_run_id: Option<String>,
256}
257
258impl FeatureFlagsProgrammaticOutput {
259 #[must_use]
261 pub fn feature_flags(&self) -> &[FeatureFlagFinding] {
262 &self.output.feature_flags
263 }
264
265 #[must_use]
267 pub const fn total_flags(&self) -> usize {
268 self.output.total_flags
269 }
270}
271
272#[derive(Debug)]
275pub struct TraceExportProgrammaticOutput {
276 pub output: TraceExportTargetOutput,
277}
278
279impl TraceExportProgrammaticOutput {
280 #[must_use]
282 pub const fn trace(&self) -> &TraceExportTargetOutput {
283 &self.output
284 }
285
286 #[must_use]
288 pub const fn as_export(&self) -> Option<&TraceExportOutput> {
289 match &self.output {
290 TraceExportTargetOutput::Export(export) => Some(export),
291 TraceExportTargetOutput::Member(_) => None,
292 }
293 }
294
295 #[must_use]
298 pub const fn as_member(&self) -> Option<&TraceClassMemberOutput> {
299 match &self.output {
300 TraceExportTargetOutput::Member(member) => Some(member),
301 TraceExportTargetOutput::Export(_) => None,
302 }
303 }
304}
305
306#[derive(Debug)]
308pub struct TraceFileProgrammaticOutput {
309 pub output: TraceFileOutput,
310}
311
312impl TraceFileProgrammaticOutput {
313 #[must_use]
315 pub const fn trace(&self) -> &TraceFileOutput {
316 &self.output
317 }
318}
319
320#[derive(Debug)]
322pub struct TraceDependencyProgrammaticOutput {
323 pub output: TraceDependencyOutput,
324}
325
326impl TraceDependencyProgrammaticOutput {
327 #[must_use]
329 pub const fn trace(&self) -> &TraceDependencyOutput {
330 &self.output
331 }
332}
333
334#[derive(Debug)]
336pub struct TraceCloneProgrammaticOutput {
337 pub output: TraceCloneOutput,
338}
339
340impl TraceCloneProgrammaticOutput {
341 #[must_use]
343 pub const fn trace(&self) -> &TraceCloneOutput {
344 &self.output
345 }
346}
347
348#[derive(Debug, Clone)]
350pub struct HealthProgrammaticOutput {
351 pub report: HealthReport,
352 pub grouping: Option<HealthGrouping>,
353 pub root: PathBuf,
354 pub elapsed: std::time::Duration,
355 pub explain: bool,
356 pub workspace_diagnostics: Vec<WorkspaceDiagnostic>,
357 pub next_steps: Vec<NextStep>,
358 pub envelope_mode: RootEnvelopeMode,
359 pub telemetry_analysis_run_id: Option<String>,
360}
361
362#[derive(Debug, Clone)]
364pub struct AuditProgrammaticOutput {
365 pub verdict: AuditVerdict,
366 pub summary: AuditSummary,
367 pub attribution: AuditAttribution,
368 pub changed_files_count: usize,
369 pub base_ref: String,
370 pub base_description: Option<String>,
371 pub head_sha: Option<String>,
372 pub elapsed: std::time::Duration,
373 pub base_snapshot_skipped: Option<bool>,
374 pub base_snapshot: Option<AuditProgrammaticKeySnapshot>,
375 pub dead_code: Option<DeadCodeProgrammaticOutput>,
376 pub duplication: Option<DuplicationProgrammaticOutput>,
377 pub complexity: Option<HealthProgrammaticOutput>,
378 pub next_steps: Vec<NextStep>,
379 pub envelope_mode: RootEnvelopeMode,
380 pub telemetry_analysis_run_id: Option<String>,
381}
382
383#[derive(Debug, Clone, Default)]
385pub struct AuditProgrammaticKeySnapshot {
386 pub dead_code: FxHashSet<String>,
387 pub health: FxHashSet<String>,
388 pub dupes: FxHashSet<String>,
389}
390
391#[derive(Debug, Clone)]
393pub struct DecisionSurfaceProgrammaticOutput {
394 pub surface: fallow_output::DecisionSurface,
395 pub elapsed: std::time::Duration,
396 pub envelope_mode: RootEnvelopeMode,
397 pub telemetry_analysis_run_id: Option<String>,
398}
399
400pub fn serialize_health_report_json(
406 input: HealthJsonReportInput<'_>,
407) -> Result<serde_json::Value, serde_json::Error> {
408 let root_prefix = format!("{}/", input.root.display());
409 let meta = match (input.explain, input.type_aware) {
410 (false, None) => None,
411 (true, None) => Some(health_meta()),
412 (false, Some(type_aware)) => Some(fallow_types::envelope::Meta {
413 type_aware: Some(type_aware),
414 ..fallow_types::envelope::Meta::default()
415 }),
416 (true, Some(type_aware)) => {
417 let mut meta = health_meta();
418 meta.type_aware = Some(type_aware);
419 Some(meta)
420 }
421 };
422 fallow_output::serialize_health_json_output(HealthJsonOutputInput {
423 output: HealthOutputInput {
424 schema_version: HEALTH_SCHEMA_VERSION,
425 version: env!("CARGO_PKG_VERSION").to_string(),
426 elapsed: input.elapsed,
427 report: input.report,
428 grouped_by: input.grouped_by,
429 groups: input.groups,
430 meta,
431 workspace_diagnostics: input.workspace_diagnostics,
432 next_steps: input.next_steps,
433 },
434 root_prefix: Some(&root_prefix),
435 envelope_mode: input.envelope_mode,
436 analysis_run_id: input.telemetry_analysis_run_id,
437 })
438}