Skip to main content

fallow_output/
analysis_sarif.rs

1//! Shared SARIF output assembly for health and duplication reports.
2
3use std::path::Path;
4
5use crate::{
6    CoverageIntelligenceRecommendation, CoverageIntelligenceReport, CoverageIntelligenceVerdict,
7    ExceededThreshold, FindingSeverity, HealthReport, RuntimeCoverageReport,
8    RuntimeCoverageVerdict, SarifDocumentInput, SarifSourceSnippetCache as SourceSnippetCache,
9    StylingFindingSeverity, build_sarif_document,
10    build_sarif_result_with_snippet as sarif_result_with_snippet, normalize_uri,
11};
12use fallow_types::duplicates::{CloneGroup, DuplicationReport};
13use fallow_types::output_dead_code::EffectiveSeverity;
14
15type SarifRuleBuilder<'a> = dyn Fn(&str, &str, &str) -> serde_json::Value + 'a;
16
17/// Build SARIF output from duplication analysis results.
18#[must_use]
19pub fn build_duplication_sarif(
20    report: &DuplicationReport,
21    root: &Path,
22    rule_builder: &SarifRuleBuilder<'_>,
23) -> serde_json::Value {
24    build_duplication_sarif_with_group(report, root, rule_builder, |_| None)
25}
26
27/// Build grouped SARIF output from duplication analysis results.
28#[must_use]
29pub fn build_grouped_duplication_sarif(
30    report: &DuplicationReport,
31    root: &Path,
32    rule_builder: &SarifRuleBuilder<'_>,
33    group_for_clone: impl Fn(&CloneGroup) -> String,
34) -> serde_json::Value {
35    build_duplication_sarif_with_group(report, root, rule_builder, |group| {
36        Some(group_for_clone(group))
37    })
38}
39
40#[expect(
41    clippy::cast_possible_truncation,
42    reason = "line and column values are bounded by source size"
43)]
44fn build_duplication_sarif_with_group(
45    report: &DuplicationReport,
46    root: &Path,
47    rule_builder: &SarifRuleBuilder<'_>,
48    group_for_clone: impl Fn(&CloneGroup) -> Option<String>,
49) -> serde_json::Value {
50    let mut sarif_results = Vec::new();
51    let mut snippets = SourceSnippetCache::with_root(root);
52
53    for (i, group) in report.clone_groups.iter().enumerate() {
54        let group_value = group_for_clone(group);
55        for instance in &group.instances {
56            let uri = relative_uri(&instance.file, root);
57            let source_snippet = snippets.line(&instance.file, instance.start_line as u32);
58            let mut result = sarif_result_with_snippet(
59                "fallow/code-duplication",
60                "warning",
61                &format!(
62                    "Code clone group {} ({} lines, {} instances)",
63                    i + 1,
64                    group.line_count,
65                    group.instances.len()
66                ),
67                &uri,
68                Some((instance.start_line as u32, (instance.start_col + 1) as u32)),
69                source_snippet.as_deref(),
70            );
71            if let Some(group) = &group_value {
72                set_sarif_result_property(&mut result, "group", group.clone());
73            }
74            sarif_results.push(result);
75        }
76    }
77
78    let rules = vec![rule_builder(
79        "fallow/code-duplication",
80        "Duplicated code block",
81        "warning",
82    )];
83    sarif_document(&sarif_results, &rules)
84}
85
86/// Build SARIF output from a health report.
87#[must_use]
88pub fn build_health_sarif(
89    report: &HealthReport,
90    root: &Path,
91    rule_builder: &SarifRuleBuilder<'_>,
92) -> serde_json::Value {
93    let mut sarif_results = Vec::new();
94    let mut snippets = SourceSnippetCache::with_root(root);
95
96    append_health_sarif_results(report, root, &mut sarif_results, &mut snippets);
97    let health_rules = health_sarif_rules(rule_builder, report);
98    sarif_document(&sarif_results, &health_rules)
99}
100
101/// Add a SARIF result property by resolving each result URI through a caller.
102pub fn annotate_sarif_results(
103    sarif: &mut serde_json::Value,
104    property: &str,
105    mut value_for_uri: impl FnMut(&str) -> String,
106) {
107    if let Some(runs) = sarif
108        .get_mut("runs")
109        .and_then(serde_json::Value::as_array_mut)
110    {
111        for run in runs {
112            if let Some(results) = run
113                .get_mut("results")
114                .and_then(serde_json::Value::as_array_mut)
115            {
116                for result in results {
117                    let uri = result
118                        .pointer("/locations/0/physicalLocation/artifactLocation/uri")
119                        .and_then(serde_json::Value::as_str)
120                        .unwrap_or("");
121                    let value = value_for_uri(uri);
122                    set_sarif_result_property(result, property, value);
123                }
124            }
125        }
126    }
127}
128
129fn set_sarif_result_property(result: &mut serde_json::Value, key: &str, value: String) {
130    let Some(result) = result.as_object_mut() else {
131        return;
132    };
133    let props = result
134        .entry("properties")
135        .or_insert_with(|| serde_json::json!({}));
136    let Some(props) = props.as_object_mut() else {
137        return;
138    };
139    props.insert(key.to_string(), serde_json::Value::String(value));
140}
141
142fn append_health_sarif_results(
143    report: &HealthReport,
144    root: &Path,
145    sarif_results: &mut Vec<serde_json::Value>,
146    snippets: &mut SourceSnippetCache,
147) {
148    append_complexity_sarif_results(sarif_results, report, root, snippets);
149
150    if let Some(ref production) = report.runtime_coverage {
151        append_runtime_coverage_sarif_results(sarif_results, production, root, snippets);
152    }
153    if let Some(ref intelligence) = report.coverage_intelligence {
154        append_coverage_intelligence_sarif_results(sarif_results, intelligence, root, snippets);
155    }
156
157    append_refactoring_target_sarif_results(sarif_results, report, root);
158    append_coverage_gap_sarif_results(sarif_results, report, root, snippets);
159    append_styling_sarif_results(sarif_results, report, root);
160}
161
162/// SARIF results for the styling-domain findings (css-token-drift, ...). Uses the
163/// finding's kebab `code` as the SARIF rule id via the shared issue_meta contract,
164/// so every styling family surfaces uniformly. Advisory (`warning` level).
165fn append_styling_sarif_results(
166    sarif_results: &mut Vec<serde_json::Value>,
167    report: &HealthReport,
168    root: &Path,
169) {
170    for finding in &report.styling_findings {
171        let uri = relative_uri(std::path::Path::new(&finding.path), root);
172        let message = format!(
173            "[{}] {}: `{}`",
174            finding.code, finding.sub_kind, finding.value
175        );
176        sarif_results.push(sarif_result(
177            &format!("fallow/{}", finding.code),
178            styling_sarif_level(finding.effective_severity),
179            &message,
180            &uri,
181            Some((finding.line, 1)),
182        ));
183    }
184}
185
186fn health_styling_sarif_rules(
187    rule_builder: &SarifRuleBuilder<'_>,
188    report: &HealthReport,
189) -> Vec<serde_json::Value> {
190    vec![
191        rule_builder(
192            "fallow/css-token-drift",
193            "CSS / CSS-in-JS design-token drift (a hardcoded value where a token exists)",
194            styling_rule_default_level(report, "css-token-drift"),
195        ),
196        rule_builder(
197            "fallow/css-duplicate-block",
198            "CSS / CSS-in-JS duplicate declaration block",
199            styling_rule_default_level(report, "css-duplicate-block"),
200        ),
201        rule_builder(
202            "fallow/css-selector-complexity",
203            "CSS selector complexity, deep nesting, or important density",
204            styling_rule_default_level(report, "css-selector-complexity"),
205        ),
206        rule_builder(
207            "fallow/css-dead-surface",
208            "CSS / CSS-in-JS dead styling surface",
209            styling_rule_default_level(report, "css-dead-surface"),
210        ),
211        rule_builder(
212            "fallow/css-broken-reference",
213            "CSS / CSS-in-JS reference resolves to no known styling definition",
214            styling_rule_default_level(report, "css-broken-reference"),
215        ),
216    ]
217}
218
219fn health_sarif_rules(
220    rule_builder: &SarifRuleBuilder<'_>,
221    report: &HealthReport,
222) -> Vec<serde_json::Value> {
223    let mut rules = health_complexity_sarif_rules(rule_builder);
224    rules.extend(health_runtime_sarif_rules(rule_builder));
225    rules.extend(health_coverage_intelligence_sarif_rules(rule_builder));
226    rules.extend(health_styling_sarif_rules(rule_builder, report));
227    rules
228}
229
230fn styling_rule_default_level(report: &HealthReport, code: &str) -> &'static str {
231    if report.styling_findings.iter().any(|finding| {
232        finding.code == code && finding.effective_severity == StylingFindingSeverity::Error
233    }) {
234        "error"
235    } else {
236        "warning"
237    }
238}
239
240const fn styling_sarif_level(severity: StylingFindingSeverity) -> &'static str {
241    match severity {
242        StylingFindingSeverity::Error => "error",
243        StylingFindingSeverity::Warn => "warning",
244    }
245}
246
247fn health_complexity_sarif_rules(rule_builder: &SarifRuleBuilder<'_>) -> Vec<serde_json::Value> {
248    vec![
249        rule_builder(
250            "fallow/high-cyclomatic-complexity",
251            "Function has high cyclomatic complexity",
252            "note",
253        ),
254        rule_builder(
255            "fallow/high-cognitive-complexity",
256            "Function has high cognitive complexity",
257            "note",
258        ),
259        rule_builder(
260            "fallow/high-complexity",
261            "Function exceeds both complexity thresholds",
262            "note",
263        ),
264        rule_builder(
265            "fallow/high-crap-score",
266            "Function has a high CRAP score (high complexity combined with low coverage)",
267            "warning",
268        ),
269        rule_builder(
270            "fallow/refactoring-target",
271            "File identified as a high-priority refactoring candidate",
272            "warning",
273        ),
274    ]
275}
276
277fn health_runtime_sarif_rules(rule_builder: &SarifRuleBuilder<'_>) -> Vec<serde_json::Value> {
278    vec![
279        rule_builder(
280            "fallow/untested-file",
281            "Runtime-reachable file has no test dependency path",
282            "warning",
283        ),
284        rule_builder(
285            "fallow/untested-export",
286            "Runtime-reachable export has no test dependency path",
287            "warning",
288        ),
289        rule_builder(
290            "fallow/runtime-safe-to-delete",
291            "Function is statically unused and was never invoked in production",
292            "warning",
293        ),
294        rule_builder(
295            "fallow/runtime-review-required",
296            "Function is statically used but was never invoked in production",
297            "warning",
298        ),
299        rule_builder(
300            "fallow/runtime-low-traffic",
301            "Function was invoked below the low-traffic threshold relative to total trace count",
302            "note",
303        ),
304        rule_builder(
305            "fallow/runtime-coverage-unavailable",
306            "Runtime coverage could not be resolved for this function",
307            "note",
308        ),
309        rule_builder(
310            "fallow/runtime-coverage",
311            "Runtime coverage finding",
312            "note",
313        ),
314    ]
315}
316
317fn health_coverage_intelligence_sarif_rules(
318    rule_builder: &SarifRuleBuilder<'_>,
319) -> Vec<serde_json::Value> {
320    vec![
321        rule_builder(
322            "fallow/coverage-intelligence-risky-change",
323            "Changed hot path combines high CRAP and low test coverage",
324            "warning",
325        ),
326        rule_builder(
327            "fallow/coverage-intelligence-delete",
328            "Static and runtime evidence indicate code can be deleted",
329            "warning",
330        ),
331        rule_builder(
332            "fallow/coverage-intelligence-review",
333            "Cold reachable uncovered code needs owner review",
334            "warning",
335        ),
336        rule_builder(
337            "fallow/coverage-intelligence-refactor",
338            "Hot covered code has high CRAP and should be refactored carefully",
339            "warning",
340        ),
341    ]
342}
343
344fn append_complexity_sarif_results(
345    sarif_results: &mut Vec<serde_json::Value>,
346    report: &HealthReport,
347    root: &Path,
348    snippets: &mut SourceSnippetCache,
349) {
350    for finding in &report.findings {
351        let uri = relative_uri(&finding.path, root);
352        let (rule_id, message) = health_complexity_sarif_message(finding, report);
353        let level = complexity_sarif_level(finding);
354        let source_snippet = snippets.line(&finding.path, finding.line);
355        sarif_results.push(sarif_result_with_snippet(
356            rule_id,
357            level,
358            &message,
359            &uri,
360            Some((finding.line, finding.col + 1)),
361            source_snippet.as_deref(),
362        ));
363    }
364}
365
366/// The SARIF level of a complexity finding.
367///
368/// The gate severity from the `complexity-*` rules sets the level. The band
369/// stays in the message. A finding without a gate severity (a saved report
370/// from an older version) keeps the band mapping.
371const fn complexity_sarif_level(finding: &crate::ComplexityViolation) -> &'static str {
372    match finding.effective_severity {
373        Some(EffectiveSeverity::Error) => "error",
374        Some(EffectiveSeverity::Warn) => "warning",
375        None => match finding.severity {
376            FindingSeverity::Critical => "error",
377            FindingSeverity::High => "warning",
378            FindingSeverity::Moderate => "note",
379        },
380    }
381}
382
383/// Build the SARIF rule id and message for a complexity finding.
384///
385/// Thresholds come from the finding's resolved ceilings, so an override-affected
386/// finding is never described against a ceiling it was not evaluated with
387/// (issue #2163).
388fn health_complexity_sarif_message(
389    finding: &crate::ComplexityViolation,
390    report: &HealthReport,
391) -> (&'static str, String) {
392    let thresholds = finding.resolved_thresholds(&report.summary);
393    match finding.exceeded {
394        ExceededThreshold::Cyclomatic => (
395            "fallow/high-cyclomatic-complexity",
396            format!(
397                "'{}' has cyclomatic complexity {} (threshold: {})",
398                finding.name, finding.cyclomatic, thresholds.max_cyclomatic,
399            ),
400        ),
401        ExceededThreshold::Cognitive => (
402            "fallow/high-cognitive-complexity",
403            format!(
404                "'{}' has cognitive complexity {} (threshold: {})",
405                finding.name, finding.cognitive, thresholds.max_cognitive,
406            ),
407        ),
408        ExceededThreshold::Both => (
409            "fallow/high-complexity",
410            format!(
411                "'{}' has cyclomatic complexity {} (threshold: {}) and cognitive complexity {} (threshold: {})",
412                finding.name,
413                finding.cyclomatic,
414                thresholds.max_cyclomatic,
415                finding.cognitive,
416                thresholds.max_cognitive,
417            ),
418        ),
419        ExceededThreshold::Crap
420        | ExceededThreshold::CyclomaticCrap
421        | ExceededThreshold::CognitiveCrap
422        | ExceededThreshold::All => {
423            let crap = finding.crap.unwrap_or(0.0);
424            let coverage = finding
425                .coverage_pct
426                .map(|pct| format!(", coverage {pct:.0}%"))
427                .unwrap_or_default();
428            (
429                "fallow/high-crap-score",
430                format!(
431                    "'{}' has CRAP score {:.1} (threshold: {:.1}, cyclomatic {}{})",
432                    finding.name, crap, thresholds.max_crap, finding.cyclomatic, coverage,
433                ),
434            )
435        }
436    }
437}
438
439fn append_refactoring_target_sarif_results(
440    sarif_results: &mut Vec<serde_json::Value>,
441    report: &HealthReport,
442    root: &Path,
443) {
444    for target in &report.targets {
445        let uri = relative_uri(&target.path, root);
446        let message = format!(
447            "[{}] {} (priority: {:.1}, efficiency: {:.1}, effort: {}, confidence: {})",
448            target.category.label(),
449            target.recommendation,
450            target.priority,
451            target.efficiency,
452            target.effort.label(),
453            target.confidence.label(),
454        );
455        sarif_results.push(sarif_result(
456            "fallow/refactoring-target",
457            "warning",
458            &message,
459            &uri,
460            None,
461        ));
462    }
463}
464
465fn append_coverage_gap_sarif_results(
466    sarif_results: &mut Vec<serde_json::Value>,
467    report: &HealthReport,
468    root: &Path,
469    snippets: &mut SourceSnippetCache,
470) {
471    let Some(ref gaps) = report.coverage_gaps else {
472        return;
473    };
474    for item in &gaps.files {
475        let uri = relative_uri(&item.file.path, root);
476        let message = format!(
477            "File is runtime-reachable but has no test dependency path ({} value export{})",
478            item.file.value_export_count,
479            if item.file.value_export_count == 1 {
480                ""
481            } else {
482                "s"
483            },
484        );
485        sarif_results.push(sarif_result(
486            "fallow/untested-file",
487            "warning",
488            &message,
489            &uri,
490            None,
491        ));
492    }
493
494    for item in &gaps.exports {
495        let uri = relative_uri(&item.export.path, root);
496        let message = format!(
497            "Export '{}' is runtime-reachable but never referenced by test-reachable modules",
498            item.export.export_name
499        );
500        let source_snippet = snippets.line(&item.export.path, item.export.line);
501        sarif_results.push(sarif_result_with_snippet(
502            "fallow/untested-export",
503            "warning",
504            &message,
505            &uri,
506            Some((item.export.line, item.export.col + 1)),
507            source_snippet.as_deref(),
508        ));
509    }
510}
511
512fn append_runtime_coverage_sarif_results(
513    sarif_results: &mut Vec<serde_json::Value>,
514    production: &RuntimeCoverageReport,
515    root: &Path,
516    snippets: &mut SourceSnippetCache,
517) {
518    for finding in &production.findings {
519        let uri = relative_uri(&finding.path, root);
520        let rule_id = match finding.verdict {
521            RuntimeCoverageVerdict::SafeToDelete => "fallow/runtime-safe-to-delete",
522            RuntimeCoverageVerdict::ReviewRequired => "fallow/runtime-review-required",
523            RuntimeCoverageVerdict::LowTraffic => "fallow/runtime-low-traffic",
524            RuntimeCoverageVerdict::CoverageUnavailable => "fallow/runtime-coverage-unavailable",
525            RuntimeCoverageVerdict::Active | RuntimeCoverageVerdict::Unknown => {
526                "fallow/runtime-coverage"
527            }
528        };
529        let level = match finding.verdict {
530            RuntimeCoverageVerdict::SafeToDelete | RuntimeCoverageVerdict::ReviewRequired => {
531                "warning"
532            }
533            _ => "note",
534        };
535        let invocations_hint = finding.invocations.map_or_else(
536            || "untracked".to_owned(),
537            |hits| format!("{hits} invocations"),
538        );
539        let message = format!(
540            "'{}' runtime coverage verdict: {} ({})",
541            finding.function,
542            finding.verdict.human_label(),
543            invocations_hint,
544        );
545        let source_snippet = snippets.line(&finding.path, finding.line);
546        sarif_results.push(sarif_result_with_snippet(
547            rule_id,
548            level,
549            &message,
550            &uri,
551            Some((finding.line, 1)),
552            source_snippet.as_deref(),
553        ));
554    }
555}
556
557fn append_coverage_intelligence_sarif_results(
558    sarif_results: &mut Vec<serde_json::Value>,
559    intelligence: &CoverageIntelligenceReport,
560    root: &Path,
561    snippets: &mut SourceSnippetCache,
562) {
563    for finding in &intelligence.findings {
564        let rule_id = coverage_intelligence_rule_id(finding.recommendation);
565        let level = match finding.verdict {
566            CoverageIntelligenceVerdict::Clean | CoverageIntelligenceVerdict::Unknown => continue,
567            _ => "warning",
568        };
569        let uri = relative_uri(&finding.path, root);
570        let identity = finding.identity.as_deref().unwrap_or("code");
571        let signals = finding
572            .signals
573            .iter()
574            .map(ToString::to_string)
575            .collect::<Vec<_>>()
576            .join(", ");
577        let message = format!(
578            "'{}' coverage intelligence verdict: {} ({}, signals: {})",
579            identity, finding.verdict, finding.recommendation, signals,
580        );
581        let source_snippet = snippets.line(&finding.path, finding.line);
582        let mut result = sarif_result_with_snippet(
583            rule_id,
584            level,
585            &message,
586            &uri,
587            Some((finding.line, 1)),
588            source_snippet.as_deref(),
589        );
590        result["properties"] = serde_json::json!({
591            "coverage_intelligence_id": &finding.id,
592            "verdict": finding.verdict,
593            "recommendation": finding.recommendation,
594            "confidence": finding.confidence,
595            "signals": &finding.signals,
596            "related_ids": &finding.related_ids,
597        });
598        sarif_results.push(result);
599    }
600}
601
602fn coverage_intelligence_rule_id(
603    recommendation: CoverageIntelligenceRecommendation,
604) -> &'static str {
605    match recommendation {
606        CoverageIntelligenceRecommendation::AddTestOrSplitBeforeMerge => {
607            "fallow/coverage-intelligence-risky-change"
608        }
609        CoverageIntelligenceRecommendation::DeleteAfterConfirmingOwner => {
610            "fallow/coverage-intelligence-delete"
611        }
612        CoverageIntelligenceRecommendation::ReviewBeforeChanging => {
613            "fallow/coverage-intelligence-review"
614        }
615        CoverageIntelligenceRecommendation::RefactorCarefullyKeepBehavior => {
616            "fallow/coverage-intelligence-refactor"
617        }
618    }
619}
620
621fn sarif_document(
622    sarif_results: &[serde_json::Value],
623    sarif_rules: &[serde_json::Value],
624) -> serde_json::Value {
625    build_sarif_document(SarifDocumentInput {
626        results: sarif_results,
627        rules: sarif_rules,
628        tool_version: env!("CARGO_PKG_VERSION"),
629    })
630}
631
632fn sarif_result(
633    rule_id: &str,
634    level: &str,
635    message: &str,
636    uri: &str,
637    region: Option<(u32, u32)>,
638) -> serde_json::Value {
639    sarif_result_with_snippet(rule_id, level, message, uri, region, None)
640}
641
642fn relative_uri(path: &Path, root: &Path) -> String {
643    normalize_uri(
644        &path
645            .strip_prefix(root)
646            .unwrap_or(path)
647            .display()
648            .to_string(),
649    )
650}
651
652#[cfg(test)]
653mod tests {
654    use std::path::PathBuf;
655
656    use crate::{SarifRuleInput, build_sarif_rule};
657    use fallow_types::duplicates::{CloneGroup, CloneInstance, DuplicationStats};
658
659    use super::*;
660
661    fn rule(id: &str, short_description: &str, level: &str) -> serde_json::Value {
662        build_sarif_rule(SarifRuleInput {
663            id,
664            short_description,
665            level,
666            full_description: None,
667            help_uri: None,
668        })
669    }
670
671    #[test]
672    fn grouped_duplication_sarif_attaches_group_property() {
673        let root = PathBuf::from("/repo");
674        let report = DuplicationReport {
675            clone_groups: vec![CloneGroup {
676                instances: vec![CloneInstance {
677                    file: root.join("src/a.ts"),
678                    start_line: 2,
679                    end_line: 5,
680                    start_col: 0,
681                    end_col: 1,
682                    fragment: "copy();".to_string(),
683                }],
684                token_count: 10,
685                line_count: 4,
686                similarity: None,
687            }],
688            clone_families: Vec::new(),
689            mirrored_directories: Vec::new(),
690            stats: DuplicationStats::default(),
691        };
692
693        let sarif = build_grouped_duplication_sarif(&report, &root, &rule, |_| "src".to_string());
694
695        assert_eq!(sarif["runs"][0]["results"][0]["properties"]["group"], "src");
696        assert_eq!(
697            sarif["runs"][0]["results"][0]["locations"][0]["physicalLocation"]["artifactLocation"]
698                ["uri"],
699            "src/a.ts"
700        );
701    }
702
703    fn sarif_message_violation(
704        exceeded: ExceededThreshold,
705        effective: Option<crate::HealthEffectiveThresholds>,
706    ) -> crate::ComplexityViolation {
707        crate::ComplexityViolation {
708            path: PathBuf::from("src/Widget.svelte"),
709            name: "<template>".to_string(),
710            line: 5,
711            col: 0,
712            cyclomatic: 25,
713            cognitive: 21,
714            line_count: 13,
715            param_count: 0,
716            react_hook_count: 0,
717            react_jsx_max_depth: 0,
718            react_prop_count: 0,
719            react_hook_profile: None,
720            exceeded,
721            severity: crate::FindingSeverity::High,
722            effective_severity: None,
723            crap: Some(90.0),
724            coverage_pct: None,
725            coverage_tier: None,
726            coverage_source: None,
727            inherited_from: None,
728            component_rollup: None,
729            contributions: Vec::new(),
730            effective_thresholds: effective,
731            threshold_source: effective.map(|_| crate::ThresholdSource::Override),
732        }
733    }
734
735    #[test]
736    fn sarif_message_uses_the_override_ceiling_not_the_global_one() {
737        let report = HealthReport::default();
738        let overridden = crate::HealthEffectiveThresholds {
739            max_cyclomatic: 500,
740            max_cognitive: 500,
741            max_crap: 500.0,
742            max_unit_size: 60,
743        };
744
745        let (global_rule, global_message) = health_complexity_sarif_message(
746            &sarif_message_violation(ExceededThreshold::Cyclomatic, None),
747            &report,
748        );
749        assert_eq!(global_rule, "fallow/high-cyclomatic-complexity");
750        assert!(
751            global_message.contains(&format!(
752                "threshold: {}",
753                report.summary.max_cyclomatic_threshold
754            )),
755            "{global_message}"
756        );
757
758        let (_, override_message) = health_complexity_sarif_message(
759            &sarif_message_violation(ExceededThreshold::Cyclomatic, Some(overridden)),
760            &report,
761        );
762        assert!(
763            override_message.contains("threshold: 500"),
764            "an override-affected finding must not be described against the global ceiling: {override_message}"
765        );
766
767        let (crap_rule, crap_message) = health_complexity_sarif_message(
768            &sarif_message_violation(ExceededThreshold::Crap, Some(overridden)),
769            &report,
770        );
771        assert_eq!(crap_rule, "fallow/high-crap-score");
772        assert!(crap_message.contains("threshold: 500.0"), "{crap_message}");
773    }
774}