FuncSpace 結果の走査

FuncSpace は、ライブラリが analyze から返すツリーです。トップレベルのノードはファイル全体を表し、その spaces フィールドにはネストされた関数 / クラス / impl / トレイト / 名前空間のスペースが入ります。各ノードは同じ CodeMetrics ペイロードを持つため、どのメトリクスも任意の粒度で参照できます。

FuncSpace の構造

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

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

SpaceKind は列挙型です。これに対してマッチすることで、関心のあるもの(Function のみ、あるいは「メソッドを持つものすべて」など)に絞り込めます。

再帰的な走査

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

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}");
    }
}

反復的な走査

深いツリーには明示的なスタックを使ってください。Rust は末尾呼び出し最適化を行わず、病的な生成コードは際限なくネストし得ます。

#![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 は各メトリクスをそれぞれ独立した Stats 構造体として公開します。各構造体は、整数値のサマリーアクセサに加えて、スペースごとの派生値のアクセサを提供します。いくつかのパターンを示します。

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

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

    println!("cognitive (this space):     {}", m.cognitive.cognitive_sum());
    println!("cyclomatic (this space):    {}", m.cyclomatic.cyclomatic_sum());
    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 にあります。

走査順序に依存しない

ライブラリは 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
}
}