big_code_analysis/function.rs
1// Per-language metric and AST modules deliberately consume the macro-
2// generated tree-sitter token enums via `use crate::*` and `use Foo::*`
3// inside match expressions — explicit imports would list dozens of
4// variants per arm and obscure the per-language token sets that are the
5// point of these files. Allowed at the module level rather than per
6// function so the per-language impl blocks stay readable.
7#![allow(
8 clippy::enum_glob_use,
9 clippy::needless_pass_by_value,
10 clippy::wildcard_imports
11)]
12
13use std::path::Path;
14
15use termcolor::{Color, StandardStream, WriteColor};
16
17use crate::output::ColorMode;
18use crate::traits::{ParserTrait, Search};
19
20use crate::checker::Checker;
21use crate::getter::Getter;
22
23use crate::tools::{color, intense_color};
24
25/// Function span data.
26#[derive(Debug)]
27pub struct FunctionSpan {
28 /// The function name, or `None` when the name could not be
29 /// resolved from the AST. Mirrors the `Option<String>` name
30 /// convention used by [`crate::FuncSpace`] and [`crate::ops::Ops`].
31 pub name: Option<String>,
32 /// The first line of a function
33 pub start_line: usize,
34 /// The last line of a function
35 pub end_line: usize,
36}
37
38impl FunctionSpan {
39 /// Project this span into its [`crate::wire::FunctionSpan`] form —
40 /// the plain, `Deserialize`-capable record that defines the serialized
41 /// shape.
42 #[must_use]
43 pub fn to_wire(&self) -> crate::wire::FunctionSpan {
44 crate::wire::FunctionSpan::from(self)
45 }
46}
47
48/// Detects the span of each function in a code. Crate-internal walk
49/// core reached through the [`crate::Ast::functions`] seam.
50///
51/// Returns a vector containing the [`FunctionSpan`] of each function
52///
53/// [`FunctionSpan`]: struct.FunctionSpan.html
54pub(crate) fn function<T: ParserTrait>(parser: &T) -> Vec<FunctionSpan> {
55 let root = parser.root();
56 let code = parser.code();
57 let mut spans = Vec::new();
58 root.act_on_node(&mut |n| {
59 if T::Checker::is_func(n) {
60 let start_line = n.start_row() + 1;
61 let end_line = n.end_row() + 1;
62 spans.push(FunctionSpan {
63 name: T::Getter::get_func_name(n, code).map(str::to_string),
64 start_line,
65 end_line,
66 });
67 }
68 });
69
70 spans
71}
72
73fn dump_span(span: FunctionSpan, stdout: &mut dyn WriteColor, last: bool) -> std::io::Result<()> {
74 // Build the six (color, intense, segment) entries once, then write them
75 // in one loop. The original 25-line body called color() / intense_color()
76 // and write!()/writeln!() in alternation, scattering 13 `?` exits across
77 // the function — over the per-fn nexits cap. Collapsing into a table
78 // keeps the rendered byte sequence identical (verified by the
79 // `dump_span_ansi_layout_*` tests).
80 //
81 // The `Seg` enum lets the dynamic chunks (span name, start/end
82 // line numbers) reach the writer via `write!` without intermediate
83 // heap allocations, matching the streaming form of the
84 // pre-refactor code.
85 let prefix = if last { " `- " } else { " |- " };
86 let (label_color, label) = match &span.name {
87 Some(name) => (Color::Magenta, Seg::NameColon(name)),
88 None => (Color::Red, Seg::Text("error: ")),
89 };
90 // Only the label is intense; the other five entries use `color()`.
91 let segments: [(Color, bool, Seg<'_>); 6] = [
92 (Color::Blue, false, Seg::Text(prefix)),
93 (label_color, true, label),
94 (Color::Green, false, Seg::Text("from line ")),
95 (Color::White, false, Seg::Int(span.start_line)),
96 (Color::Green, false, Seg::Text(" to line ")),
97 (Color::White, false, Seg::IntDot(span.end_line)),
98 ];
99 for (col, intense, seg) in segments {
100 write_seg(stdout, col, intense, seg)?;
101 }
102 Ok(())
103}
104
105/// One segment of `dump_span`'s rendered output. The dynamic chunks
106/// (span name, line numbers) flow straight through `write!` to the
107/// writer — no intermediate `String` allocation.
108#[derive(Clone, Copy)]
109enum Seg<'a> {
110 /// Static text fragment (prefixes, " to line ", etc.).
111 Text(&'a str),
112 /// `start_line` rendered as a plain integer.
113 Int(usize),
114 /// `end_line` rendered as `<n>.\n` — the trailing punctuation and
115 /// newline that close the line.
116 IntDot(usize),
117 /// `<name>: ` — the span name followed by the colon-space label
118 /// separator. Used when the span is not an error.
119 NameColon(&'a str),
120}
121
122fn write_seg(
123 stdout: &mut dyn WriteColor,
124 col: Color,
125 intense: bool,
126 seg: Seg<'_>,
127) -> std::io::Result<()> {
128 if intense {
129 intense_color(stdout, col)?;
130 } else {
131 color(stdout, col)?;
132 }
133 match seg {
134 Seg::Text(s) => stdout.write_all(s.as_bytes()),
135 Seg::Int(n) => write!(stdout, "{n}"),
136 Seg::IntDot(n) => writeln!(stdout, "{n}."),
137 Seg::NameColon(s) => write!(stdout, "{s}: "),
138 }
139}
140
141// Trait-object writer so production passes a locked `StandardStream`
142// (colored stdout) and tests capture rendered bytes via `termcolor::NoColor`
143// over a `Vec<u8>` — matches the dispatch shape of `dump_span` and the
144// `color` / `intense_color` helpers in `tools.rs`.
145fn dump_spans(
146 spans: Vec<FunctionSpan>,
147 path: &Path,
148 stdout: &mut dyn WriteColor,
149) -> std::io::Result<()> {
150 if spans.is_empty() {
151 return Ok(());
152 }
153 intense_color(stdout, Color::Yellow)?;
154 writeln!(stdout, "In file {}", path.display())?;
155 // Consume `spans` by value: cloning to use `split_last` would
156 // allocate each `FunctionSpan`'s `name: String` unnecessarily.
157 let last_idx = spans.len() - 1;
158 for (i, span) in spans.into_iter().enumerate() {
159 dump_span(span, stdout, i == last_idx)?;
160 }
161 color(stdout, Color::White)
162}
163
164/// Render `spans` for `path` to colored stdout (nothing when `spans`
165/// is empty). Callers obtain `spans` from [`crate::Ast::functions`]
166/// and render them without naming the parser surface. Mirrors the
167/// self-contained-stdout shape of [`crate::dump_root`] /
168/// [`crate::dump_ops`].
169///
170/// # Errors
171///
172/// Propagates any [`std::io::Error`] from writing to stdout.
173pub fn dump_function_spans(spans: Vec<FunctionSpan>, path: &Path) -> std::io::Result<()> {
174 dump_function_spans_with_color(spans, path, ColorMode::Always)
175}
176
177/// Like [`dump_function_spans`], but the caller selects the
178/// [`ColorMode`].
179///
180/// `bca` resolves a `--color` flag, the `NO_COLOR` convention, and
181/// stdout tty detection into a mode and passes it here so piped output
182/// is escape-free by default. The bare [`dump_function_spans`] keeps the
183/// historical always-colored behavior for backward compatibility.
184///
185/// # Errors
186///
187/// Propagates any [`std::io::Error`] from writing to stdout.
188pub fn dump_function_spans_with_color(
189 spans: Vec<FunctionSpan>,
190 path: &Path,
191 color_mode: ColorMode,
192) -> std::io::Result<()> {
193 // Skip the stdout lock entirely when there are no spans (the common
194 // case for config / data files in a whole-repo run). `dump_spans`
195 // self-guards too, so direct callers with an empty Vec are safe.
196 if spans.is_empty() {
197 return Ok(());
198 }
199 let stdout = StandardStream::stdout(color_mode.to_color_choice());
200 let mut stdout = stdout.lock();
201 dump_spans(spans, path, &mut stdout)
202}
203
204#[cfg(test)]
205#[path = "function_tests.rs"]
206mod tests;