Skip to main content

code_analyze_core/
formatter_defuse.rs

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