Skip to main content

devscout_rs/render/
coverage.rs

1use crate::query;
2
3use super::blocks::{compact_block, ref_kind_block_if_any};
4use super::markers::{compact_marker, heuristic_suffix, test_via_suffix};
5use super::refs::bus_hop_line;
6
7/// Default `devscout tests` output.
8///
9/// The zero case is a first-class answer, not
10/// an empty table: "nothing tests this" is exactly what the caller asked, and it
11/// is one line rather than a header over a void. A symbol reached ONLY over a
12/// possible-route bus hop -- no precise or heuristic row at all -- still gets
13/// the header and its `bus-hop` block; only a symbol with neither prints the
14/// zero-case line.
15///
16/// Heuristic file lines carry the SAME word-suffix the refs/impact renderers
17/// use, and the summary counts stay precise-only, so a file listed under a
18/// covered count is a file a test really references.
19pub fn render_tests_text(model: &query::TestsModel) -> String {
20    let mut out: Vec<String> = vec![format!("tests for {}", model.symbol)];
21    if model.rows.is_empty() && model.bus.total == 0 {
22        out.push("no test references found".to_string());
23        return out.join("\n");
24    }
25    out.push(format!(
26        "covered by {} test file(s), {} reference(s)",
27        model.test_file_count, model.ref_count
28    ));
29    out.push(String::new());
30    for r in &model.rows {
31        out.push(format!(
32            "{}{}{}",
33            r.file,
34            heuristic_suffix(r.heuristic, r.tier),
35            test_via_suffix(r.via)
36        ));
37        let lines = r
38            .lines
39            .iter()
40            .map(|l| l.to_string())
41            .collect::<Vec<_>>()
42            .join(", ");
43        if r.test_defs.is_empty() {
44            // A `Project`-vouched row can carry no attributed def at all --
45            // still one line, so the file's referencing lines are never
46            // silently dropped.
47            out.push(format!("  lines: {lines}"));
48        } else {
49            for def_id in &r.test_defs {
50                out.push(format!("  {def_id}  lines: {lines}"));
51            }
52        }
53    }
54    // A test file that PUBLISHES to this handler over a bus hop: a possible
55    // route, kept out of `test_file_count`/`ref_count` by construction (see
56    // `TestsModel::bus`'s own doc comment), rendered through the exact same
57    // row formatter `refs`' own `bus-hop` block uses so the two never state
58    // the disclosure in different words.
59    ref_kind_block_if_any(&mut out, "bus-hop", &model.bus, bus_hop_line);
60    out.join("\n")
61}
62
63/// `--compact` `devscout tests` output.
64///
65/// The same model with the def ids and the
66/// per-file indentation dropped: one header line carrying every count, then one
67/// line per file. The `x`/`h` line marker is the compact renderer's existing
68/// convention for a guess.
69pub fn render_tests_compact(model: &query::TestsModel) -> String {
70    let heur = if model.heuristic_file_count != 0 {
71        format!(" heuristic={}", model.heuristic_file_count)
72    } else {
73        String::new()
74    };
75    let mut out: Vec<String> = vec![format!(
76        "tests {} files={} refs={}{heur}",
77        model.symbol, model.test_file_count, model.ref_count
78    )];
79    for r in &model.rows {
80        let lines = r
81            .lines
82            .iter()
83            .map(|l| format!("{l}{}", compact_marker(r.heuristic, r.tier)))
84            .collect::<Vec<_>>()
85            .join(",");
86        out.push(format!("{} {lines}", r.file));
87    }
88    // Same marker-plus-path-to-full-row shape `refs`' own compact `bus-hop`
89    // block established, reused verbatim rather than respelled here.
90    let file_of_bus: fn(&query::BusHopRow) -> &str = |r| r.file.as_str();
91    let line_bus = |r: &query::BusHopRow| format!("{}?", r.line);
92    compact_block(
93        &mut out,
94        "bus-hop (? = possible route, rerun without --compact for the full row)",
95        Some(&model.bus),
96        file_of_bus,
97        line_bus,
98    );
99    out.join("\n")
100}