Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

FuncSpace 結果の走査

FuncSpace is the tree the library hands back from analyze. The top-level node represents the whole file; its spaces field holds nested function, class, impl, trait and namespace spaces. Each node carries the same CodeMetrics payload, so any metric is available at any level.

FuncSpace の構造

最もよく使うフィールドは次のとおりです。

フィールド型説明
nameOption<String>呼び出し側が指定した識別子(トップレベル)またはシンボル名(ネスト時)
kindSpaceKindUnit, Function, Class, Impl, …
start_lineusize開始行(1 始まり)
end_lineusize終了行(1 始まり)
spacesVec<FuncSpace>ネストされたスペース
metricsCodeMetricsスペースごとのすべてのメトリクス値
suppressedSuppressionScopeソース内の抑制マーカー

SpaceKind is an enum; match on it to filter what you care about (Function only, or “anything that owns methods”).

start_line..=end_line は両端を含み、ファイルの終端を越えることはありません。また、ネストしたスペースの範囲は常に親の範囲の内側にあるため、これでソースをスライスしても安全です。

再帰的な走査

再帰はツリーの形をそのまま反映します。ここでは、認知的複雑度がしきい値を超えるすべての関数スペースを収集します。

use big_code_analysis::{
    analyze, FuncSpace, MetricsOptions, SpaceKind, Source, LANG,
};

fn hotspots(space: &FuncSpace, threshold: u64, out: &mut Vec<String>) {
    if space.kind == SpaceKind::Function
        && space.metrics.cognitive.cognitive_sum() > threshold
        && let Some(name) = &space.name
    {
        out.push(format!(
            "{name} (lines {}–{})",
            space.start_line, space.end_line,
        ));
    }
    for child in &space.spaces {
        hotspots(child, threshold, out);
    }
}

fn main() {
    let source = b"\
fn easy() { let _ = 1; }
fn hard(x: i32) -> i32 {
    if x > 0 { if x > 10 { 1 } else { 2 } } else { 3 }
}
";
    let space = analyze(
        Source::new(LANG::Rust, source).with_name(Some("snippet.rs".to_owned())),
        MetricsOptions::default(),
    )
    .expect("parses");

    let mut hits = Vec::new();
    hotspots(&space, 2, &mut hits);
    for hit in hits {
        println!("{hit}");
    }
}

反復的な走査

For deep trees, prefer an explicit stack. Rust does not guarantee tail-call optimisation, and pathological generated code can be arbitrarily nested:

#![allow(unused)]
fn main() {
use big_code_analysis::FuncSpace;

fn total_functions(root: &FuncSpace) -> usize {
    let mut stack = vec![root];
    let mut count = 0;
    while let Some(space) = stack.pop() {
        if space.kind == big_code_analysis::SpaceKind::Function {
            count += 1;
        }
        stack.extend(space.spaces.iter());
    }
    count
}
}

メトリクスごとの値の読み取り

CodeMetrics exposes each metric as its own Stats struct. Each struct offers integer-valued summary accessors plus per-space derived ones. A few patterns:

#![allow(unused)]
fn main() {
use big_code_analysis::FuncSpace;

fn summary(space: &FuncSpace) {
    let m = &space.metrics;

    println!("cognitive (this space):     {}", m.cognitive.cognitive());
    println!("cyclomatic (this space):    {}", m.cyclomatic.cyclomatic());
    println!("# functions in this space:  {}", m.nom.functions_sum());
    println!("source lines (sloc):        {}", m.loc.sloc());
    println!("physical lines (ploc):      {}", m.loc.ploc());
    println!("ABC branches:               {}", m.abc.branches());
}
}

*_sum アクセサは子スペースをまたいで集計します。m.loc.sloc() のような素のアクセサは、この ノード自体に帰属する値です。フィールドとメソッドの全一覧はメトリクスごとの rustdoc にあります。

Do not rely on traversal order

ライブラリは AST(抽象構文木)をソース順に走査しますが、契約上保証されるのは、各スペースがツリーに一度だけ現れることだけです。バージョン間で安定した順序が必要な場合は、走査後に start_line でソートしてください。

#![allow(unused)]
fn main() {
use big_code_analysis::FuncSpace;

fn flatten(space: &FuncSpace, out: &mut Vec<(usize, String)>) {
    if let Some(name) = &space.name {
        out.push((space.start_line, name.clone()));
    }
    for child in &space.spaces {
        flatten(child, out);
    }
}

fn sorted(space: &FuncSpace) -> Vec<(usize, String)> {
    let mut v = Vec::new();
    flatten(space, &mut v);
    v.sort_by_key(|&(line, _)| line);
    v
}
}