Skip to main content

aptu_coder_core/
formatter_defuse.rs

1// SPDX-FileCopyrightText: 2026 aptu-coder contributors
2// SPDX-License-Identifier: Apache-2.0
3//! Def-use pagination formatting.
4
5use std::fmt::Write;
6use std::path::Path;
7
8use crate::formatter::{snippet_one_line, strip_base_path};
9
10/// Format a page of def-use sites for pagination.
11/// Renders a DEF-USE SITES section with WRITES and READS sub-sections.
12#[must_use]
13pub fn format_focused_paginated_defuse(
14    paginated_sites: &[crate::types::DefUseSite],
15    total: usize,
16    symbol: &str,
17    offset: usize,
18    base_path: Option<&Path>,
19    _verbose: bool,
20) -> String {
21    let mut output = String::new();
22
23    let page_size = paginated_sites.len();
24    let (start, end) = if page_size == 0 {
25        (0, 0)
26    } else {
27        (offset + 1, offset + page_size)
28    };
29
30    let _ = writeln!(
31        output,
32        "DEF-USE SITES  {symbol}  ({start}-{end} of {total})"
33    );
34
35    // Render writes (Write and WriteRead)
36    let write_sites: Vec<_> = paginated_sites
37        .iter()
38        .filter(|s| {
39            matches!(
40                s.kind,
41                crate::types::DefUseKind::Write | crate::types::DefUseKind::WriteRead
42            )
43        })
44        .collect();
45
46    if !write_sites.is_empty() {
47        output.push_str("  WRITES\n");
48        for site in write_sites {
49            let file_display = strip_base_path(Path::new(&site.file), base_path);
50            let scope_str = site
51                .enclosing_scope
52                .as_ref()
53                .map(|s| format!("{}()", s))
54                .unwrap_or_default();
55            let snippet = snippet_one_line(&site.snippet);
56            let wr_label = if site.kind == crate::types::DefUseKind::WriteRead {
57                " [write_read]"
58            } else {
59                ""
60            };
61            let _ = writeln!(
62                output,
63                "    {file_display}:{}  {scope_str}  {snippet}{wr_label}",
64                site.line
65            );
66        }
67    }
68
69    // Render reads
70    let read_sites: Vec<_> = paginated_sites
71        .iter()
72        .filter(|s| matches!(s.kind, crate::types::DefUseKind::Read))
73        .collect();
74
75    if !read_sites.is_empty() {
76        output.push_str("  READS\n");
77        for site in read_sites {
78            let file_display = strip_base_path(Path::new(&site.file), base_path);
79            let scope_str = site
80                .enclosing_scope
81                .as_ref()
82                .map(|s| format!("{}()", s))
83                .unwrap_or_default();
84            let snippet = snippet_one_line(&site.snippet);
85            let _ = writeln!(
86                output,
87                "    {file_display}:{}  {scope_str}  {snippet}",
88                site.line
89            );
90        }
91    }
92
93    output
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99    use crate::types::{DefUseKind, DefUseSite};
100
101    fn site(kind: DefUseKind, line: usize, scope: Option<&str>, file: &str) -> DefUseSite {
102        DefUseSite {
103            kind,
104            symbol: "x".to_string(),
105            file: file.to_string(),
106            line,
107            column: 0,
108            snippet: "prev\nlet x = 1;\nnext".to_string(),
109            enclosing_scope: scope.map(String::from),
110        }
111    }
112
113    #[test]
114    fn test_format_paginated_defuse_writes_and_reads() {
115        // Arrange
116        let sites = vec![
117            site(DefUseKind::Write, 10, Some("init"), "src/main.rs"),
118            site(DefUseKind::WriteRead, 20, None, "src/lib.rs"),
119            site(DefUseKind::Read, 30, Some("run"), "src/main.rs"),
120        ];
121        let base = Path::new("/project");
122
123        // Act
124        let output = format_focused_paginated_defuse(&sites, 3, "x", 0, Some(base), false);
125
126        // Assert
127        assert!(output.contains("DEF-USE SITES  x  (1-3 of 3)"));
128        assert!(output.contains("WRITES"));
129        assert!(output.contains("src/main.rs:10  init()"));
130        assert!(output.contains("[write_read]"));
131        assert!(output.contains("READS"));
132        assert!(output.contains("src/main.rs:30  run()"));
133    }
134
135    #[test]
136    fn test_format_paginated_defuse_no_base_path() {
137        // Arrange: single read, no base path, no enclosing scope
138        let sites = vec![site(DefUseKind::Read, 5, None, "/abs/path/file.rs")];
139
140        // Act
141        let output = format_focused_paginated_defuse(&sites, 1, "x", 0, None, false);
142
143        // Assert
144        assert!(output.contains("/abs/path/file.rs:5"));
145        assert!(!output.contains("WRITES"));
146        assert!(output.contains("READS"));
147    }
148}