Skip to main content

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;