big-code-analysis 2.0.0

Tool to compute and export code metrics
Documentation
// Per-language metric and AST modules deliberately consume the macro-
// generated tree-sitter token enums via `use crate::*` and `use Foo::*`
// inside match expressions — explicit imports would list dozens of
// variants per arm and obscure the per-language token sets that are the
// point of these files. Allowed at the module level rather than per
// function so the per-language impl blocks stay readable.
#![allow(
    clippy::enum_glob_use,
    clippy::needless_pass_by_value,
    clippy::wildcard_imports
)]

use std::path::Path;

use termcolor::{Color, StandardStream, WriteColor};

use crate::output::ColorMode;
use crate::traits::{ParserTrait, Search};

use crate::checker::Checker;
use crate::getter::Getter;

use crate::tools::{color, intense_color};

/// Function span data.
#[derive(Debug)]
pub struct FunctionSpan {
    /// The function name, or `None` when the name could not be
    /// resolved from the AST. Mirrors the `Option<String>` name
    /// convention used by [`crate::FuncSpace`] and [`crate::ops::Ops`].
    pub name: Option<String>,
    /// The first line of a function
    pub start_line: usize,
    /// The last line of a function
    pub end_line: usize,
}

impl FunctionSpan {
    /// Project this span into its [`crate::wire::FunctionSpan`] form —
    /// the plain, `Deserialize`-capable record that defines the serialized
    /// shape.
    #[must_use]
    pub fn to_wire(&self) -> crate::wire::FunctionSpan {
        crate::wire::FunctionSpan::from(self)
    }
}

/// Detects the span of each function in a code. Crate-internal walk
/// core reached through the [`crate::Ast::functions`] seam.
///
/// Returns a vector containing the [`FunctionSpan`] of each function
///
/// [`FunctionSpan`]: struct.FunctionSpan.html
pub(crate) fn function<T: ParserTrait>(parser: &T) -> Vec<FunctionSpan> {
    let root = parser.root();
    let code = parser.code();
    let mut spans = Vec::new();
    root.act_on_node(&mut |n| {
        if T::Checker::is_func(n) {
            let start_line = n.start_row() + 1;
            let end_line = n.end_row() + 1;
            spans.push(FunctionSpan {
                name: T::Getter::get_func_name(n, code).map(str::to_string),
                start_line,
                end_line,
            });
        }
    });

    spans
}

fn dump_span(span: FunctionSpan, stdout: &mut dyn WriteColor, last: bool) -> std::io::Result<()> {
    // Build the six (color, intense, segment) entries once, then write them
    // in one loop. The original 25-line body called color() / intense_color()
    // and write!()/writeln!() in alternation, scattering 13 `?` exits across
    // the function — over the per-fn nexits cap. Collapsing into a table
    // keeps the rendered byte sequence identical (verified by the
    // `dump_span_ansi_layout_*` tests).
    //
    // The `Seg` enum lets the dynamic chunks (span name, start/end
    // line numbers) reach the writer via `write!` without intermediate
    // heap allocations, matching the streaming form of the
    // pre-refactor code.
    let prefix = if last { "   `- " } else { "   |- " };
    let (label_color, label) = match &span.name {
        Some(name) => (Color::Magenta, Seg::NameColon(name)),
        None => (Color::Red, Seg::Text("error: ")),
    };
    // Only the label is intense; the other five entries use `color()`.
    let segments: [(Color, bool, Seg<'_>); 6] = [
        (Color::Blue, false, Seg::Text(prefix)),
        (label_color, true, label),
        (Color::Green, false, Seg::Text("from line ")),
        (Color::White, false, Seg::Int(span.start_line)),
        (Color::Green, false, Seg::Text(" to line ")),
        (Color::White, false, Seg::IntDot(span.end_line)),
    ];
    for (col, intense, seg) in segments {
        write_seg(stdout, col, intense, seg)?;
    }
    Ok(())
}

/// One segment of `dump_span`'s rendered output. The dynamic chunks
/// (span name, line numbers) flow straight through `write!` to the
/// writer — no intermediate `String` allocation.
#[derive(Clone, Copy)]
enum Seg<'a> {
    /// Static text fragment (prefixes, " to line ", etc.).
    Text(&'a str),
    /// `start_line` rendered as a plain integer.
    Int(usize),
    /// `end_line` rendered as `<n>.\n` — the trailing punctuation and
    /// newline that close the line.
    IntDot(usize),
    /// `<name>: ` — the span name followed by the colon-space label
    /// separator. Used when the span is not an error.
    NameColon(&'a str),
}

fn write_seg(
    stdout: &mut dyn WriteColor,
    col: Color,
    intense: bool,
    seg: Seg<'_>,
) -> std::io::Result<()> {
    if intense {
        intense_color(stdout, col)?;
    } else {
        color(stdout, col)?;
    }
    match seg {
        Seg::Text(s) => stdout.write_all(s.as_bytes()),
        Seg::Int(n) => write!(stdout, "{n}"),
        Seg::IntDot(n) => writeln!(stdout, "{n}."),
        Seg::NameColon(s) => write!(stdout, "{s}: "),
    }
}

// Trait-object writer so production passes a locked `StandardStream`
// (colored stdout) and tests capture rendered bytes via `termcolor::NoColor`
// over a `Vec<u8>` — matches the dispatch shape of `dump_span` and the
// `color` / `intense_color` helpers in `tools.rs`.
fn dump_spans(
    spans: Vec<FunctionSpan>,
    path: &Path,
    stdout: &mut dyn WriteColor,
) -> std::io::Result<()> {
    if spans.is_empty() {
        return Ok(());
    }
    intense_color(stdout, Color::Yellow)?;
    writeln!(stdout, "In file {}", path.display())?;
    // Consume `spans` by value: cloning to use `split_last` would
    // allocate each `FunctionSpan`'s `name: String` unnecessarily.
    let last_idx = spans.len() - 1;
    for (i, span) in spans.into_iter().enumerate() {
        dump_span(span, stdout, i == last_idx)?;
    }
    color(stdout, Color::White)
}

/// Render `spans` for `path` to colored stdout (nothing when `spans`
/// is empty). Callers obtain `spans` from [`crate::Ast::functions`]
/// and render them without naming the parser surface. Mirrors the
/// self-contained-stdout shape of [`crate::dump_root`] /
/// [`crate::dump_ops`].
///
/// # Errors
///
/// Propagates any [`std::io::Error`] from writing to stdout.
pub fn dump_function_spans(spans: Vec<FunctionSpan>, path: &Path) -> std::io::Result<()> {
    dump_function_spans_with_color(spans, path, ColorMode::Always)
}

/// Like [`dump_function_spans`], but the caller selects the
/// [`ColorMode`].
///
/// `bca` resolves a `--color` flag, the `NO_COLOR` convention, and
/// stdout tty detection into a mode and passes it here so piped output
/// is escape-free by default. The bare [`dump_function_spans`] keeps the
/// historical always-colored behavior for backward compatibility.
///
/// # Errors
///
/// Propagates any [`std::io::Error`] from writing to stdout.
pub fn dump_function_spans_with_color(
    spans: Vec<FunctionSpan>,
    path: &Path,
    color_mode: ColorMode,
) -> std::io::Result<()> {
    // Skip the stdout lock entirely when there are no spans (the common
    // case for config / data files in a whole-repo run). `dump_spans`
    // self-guards too, so direct callers with an empty Vec are safe.
    if spans.is_empty() {
        return Ok(());
    }
    let stdout = StandardStream::stdout(color_mode.to_color_choice());
    let mut stdout = stdout.lock();
    dump_spans(spans, path, &mut stdout)
}

#[cfg(test)]
#[path = "function_tests.rs"]
mod tests;