メモリ上のソースの分析

big-code-analysis はソースがディスク上にあることを要求しません。推奨エントリポイント analyze は、言語、ソースバイト列、* 省略可能な_ 呼び出し側指定の表示名を持つ Source を受け取ります。C/C++ プリプロセッサの参照でパスが必要になる場合(Source::preproc_path)を除き、ファイルシステムのパスは関与しません。

これは次の用途に有用です:

  • 生成コードを書き出す前にスコアリングする。
  • 前処理済み「または」*バンドル済み*のソース(テンプレート展開後など)をスコアリングする。
  • すでにバッファをメモリ上に保持している言語サーバーエディタプラグインから分析ツールを駆動する。
  • 標準入力パイプラインや、ファイルシステムに触れるべきでない単体テスト。

バッファからの読み取り

#![allow(unused)]
fn main() {
use big_code_analysis::{analyze, MetricsOptions, Source, LANG};

fn analyze_buffer(source: &[u8]) -> Option<u64> {
    // `Source::name` はトップレベルの `FuncSpace` に埋め込まれる
    // 表示識別子です。下流の消費者(ログ、JSON 出力)にとって
    // 意味のあるものを選んでください。付ける価値のあるものが
    // なければ `None` を渡します。
    let space = analyze(
        Source::new(LANG::Python, source).with_name(Some("<stdin>".to_owned())),
        MetricsOptions::default(),
    )
    .ok()?;

    Some(space.metrics.cognitive.cognitive_sum())
}
}

Source::new はソースバイト列を借用し、所有権は呼び出し側に残ります。下流のパイプラインが同じバイト列上で検出結果をハイライトする必要がある場合、analyze が返った後も元のバッファをそのまま使い続けられます。

標準入力からの読み取り

use std::io::{self, Read};

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

fn main() -> io::Result<()> {
    let mut source = Vec::new();
    io::stdin().read_to_end(&mut source)?;

    let space = match analyze(
        Source::new(LANG::Javascript, &source)
            .with_name(Some("<stdin>".to_owned())),
        MetricsOptions::default(),
    ) {
        Ok(space) => space,
        Err(err) => {
            eprintln!("parse failed: {err}");
            std::process::exit(1);
        }
    };

    println!("{}", space.metrics.cyclomatic.cyclomatic_sum());
    Ok(())
}

内容から言語を選択する

言語が事前にわからない場合は、guess_languageanalyze を組み合わせてください。guess_language はパスの拡張子、Emacs モード行、シバンの順に調べます:

#![allow(unused)]
fn main() {
use std::path::PathBuf;

use big_code_analysis::{analyze, guess_language, MetricsOptions, Source};

fn analyze_unknown(path: PathBuf, source: Vec<u8>) -> Option<()> {
    let (lang, _name) = guess_language(&source, &path);
    let lang = lang?;
    // `.ok()?` は `MetricsError` を `None` に畳み込み、このヘルパーの
    // `Option` の戻り値の形を保ちます。バリアントを保持するより
    // 詳細なマッピングは `error-handling.md` を参照してください。
    let _space = analyze(
        Source::new(lang, &source)
            .with_name(path.to_str().map(str::to_owned)),
        MetricsOptions::default(),
    )
    .ok()?;
    Some(())
}
}

guess_language は認識できない拡張子に対して (None, _) を返します。これはハードエラーではなく「スキップ」として扱ってください。

注意点

  • 名前の同一性は重要です。 トップレベルの FuncSpace::nameSource::name に指定した文字列そのものです。同じ名前を共有する 2 つの分析は、それをキーにする下流の消費者からは区別できません。異なるバッファには異なるラベルを使用してください。
  • Source::nameOption<String> です。 None を渡すとトップレベルの FuncSpace::nameNone のままになります。意味のある識別性を持たないアドホックなスニペットには便利です。安定した識別子を 必要とする 下流の消費者は、None を明示的にチェックすべきです。
  • ファイルシステムへのフォールバックはありません。 CLI と異なり、ライブラリは隣接ファイルを読んだり、#include を辿ったり、.gitignore を解釈したりしません。分析したいバイト列を正確に与えてください。

一度のパースを複数のパスで再利用する

analyze はワンショットのエントリポイントです。バイト列を入れると FuncSpace が返ります。一度のパースからメトリクス以外のもの(演算子とオペランド、AST ダンプ、関数スパンの一覧)が必要な場合は、Ast::parse で一度だけパースし、返されたハンドルに対してパスごとのメソッド(metricsopsdumpfunctions など)を呼び出してください。一度のパースでメトリクスを何度も実行するを参照してください。