一度のパースで複数回メトリクスを実行
The one-shot entry point analyze re-parses its Source on every call. For pipelines that score a file several times (different metric subsets, an interleaved custom tree-sitter walk, or a metric re-run after a configuration change) that re-parse is wasted work.
The Ast type, added in 1.1.0, exposes the seam: parse the source once, then call Ast::metrics as many times as you need against the held parse. Ast::from_path is the file-backed counterpart: it reads, language-detects and parses one file, returning a FromPathError naming the reason when it cannot.
これを使うべきとき
次のいずれかに当てはまる場合は Ast を使用してください:
- 選択的なメトリクス実行。 同じファイルに対して、レポート用にあるメトリクスの集合を計算し、次に CI のしきい値ゲート用に別の集合を計算する。
- Custom tree-sitter walks. You already drive a
tree_sitter::Treefor queries, highlighting or symbol extraction and want to fold the metric walker into the same parse. - Cached analysis. A language-server-like service that holds parsed files in memory re-runs metrics on demand when configuration changes, without going back to bytes.
If you only ever compute every metric once per file, stick with analyze. It delegates to Ast internally, so the shapes line up, but the one-shot API stays simpler.
呼び出しをまたいだ選択的メトリクス
#![allow(unused)]
fn main() {
use big_code_analysis::{Ast, LANG, Metric, MetricsOptions, Source};
let source = b"fn f(x: i32) -> i32 { if x > 0 { 1 } else { -1 } }";
// 1 回のパースで、2 つのメトリクスサブセットを計算します。
let ast = Ast::parse(Source::new(LANG::Rust, source))
.expect("rust feature enabled");
let loc = ast
.metrics(MetricsOptions::default().with_only(&[Metric::Loc]))
.expect("walker succeeds");
let cyclomatic = ast
.metrics(MetricsOptions::default().with_only(&[Metric::Cyclomatic]))
.expect("walker succeeds");
println!("ploc = {}", loc.metrics.loc.ploc());
println!("ccn = {}", cyclomatic.metrics.cyclomatic.cyclomatic_sum());
}
各 metrics 呼び出しはツリーを 1 回走査します。analyze を 2 回呼ぶ場合と比べた節約分はパースの省略によるものです。パースは、ごく大きなソースファイルを除くあらゆるケースで実行時間の大半を占めます。
同じパース上でのカスタム tree-sitter 走査とメトリクス
Ast::as_tree_sitter は基盤となる tree_sitter::Tree を借用します。返される参照は Ast のライフタイムの間有効で、そこから得られるノードは Ast::source に対して解決されます(マクロ展開時に source が何を返すかについては、下記の C++ プリプロセッサに関する注記 を参照してください)。
For realistic AST work (counting node kinds, finding constructs by name, detecting parse errors, building a symbol table) see Walking the AST directly. The example below is a minimal smoke test; the dedicated chapter shows the full pattern (reusable depth-first walker, field-name lookup, error detection).
#![allow(unused)]
fn main() {
use big_code_analysis::{Ast, LANG, MetricsOptions, Source};
let ast = Ast::parse(Source::new(LANG::Rust, b"fn f() {}"))
.expect("rust feature enabled");
// 独自の目的でツリーを走査します…
let root = ast.as_tree_sitter().root_node();
assert_eq!(root.kind(), "source_file");
// …そして同じパースに対してメトリクスウォーカーを実行します。
let space = ast
.metrics(MetricsOptions::default())
.expect("walker succeeds");
println!("name = {:?}", space.name);
}
同じパース上のオペレーターとオペランド
Ast::ops returns the per-space operator and operand Ops tree, the data behind the Halstead metrics. The top-level Ops::name is the Source::name you supplied, carried explicitly (including the None case) rather than a lossy-path string, so Ops::name_was_lossy is never set on this path.
#![allow(unused)]
fn main() {
use big_code_analysis::{Ast, LANG, Source};
let ops = Ast::parse(
Source::new(LANG::Rust, b"fn f() { let x = 1 + 2; }")
.with_name(Some("snippet.rs".to_owned())),
)
.expect("rust feature enabled")
.ops()
.expect("walker succeeds");
assert_eq!(ops.name.as_deref(), Some("snippet.rs"));
assert!(ops.operators.iter().any(|op| op == "+"));
}
呼び出し側で構築したツリーの取り込み
If you already build the tree_sitter::Tree yourself (for example, because your editor or language server has its own parser pool), Ast::from_tree_sitter is the tree-adoption seam. It carries an explicit name: Option<String> end to end instead of deriving one from a path via lossy UTF-8 conversion.
#![allow(unused)]
fn main() {
use big_code_analysis::{Ast, LANG, MetricsOptions, tree_sitter};
let source = b"fn f() {}".to_vec();
let mut parser = tree_sitter::Parser::new();
parser
.set_language(
&LANG::Rust
.tree_sitter_language()
.expect("rust feature enabled"),
)
.expect("rust grammar compatible");
let tree = parser
.parse(&source, None)
.expect("parser has a language set");
let ast = Ast::from_tree_sitter(LANG::Rust, tree, source, None)
.expect("rust feature enabled");
let _ = ast.metrics(MetricsOptions::default()).expect("walker succeeds");
}
The tree must have been produced from code with the grammar returned by LANG::tree_sitter_language for lang. A mismatch is not unsafe, but it is a precondition violation. The walk slices code by node byte ranges, so a tree built from longer source than code indexes out of bounds and panics. A tree from a different grammar that stays in bounds yields nonsensical results instead, because the metric walker matches on tree-sitter kind_id values from the language’s enum.
C++ プリプロセッサ
When Ast::parse is called on a Source carrying preprocessor inputs (Source::with_preproc_path plus Source::with_preproc) and the language is a C-family one (LANG::C, LANG::Cpp or LANG::Mozcpp), the macro pre-pass runs before tree-sitter does. Ast::source then returns the expanded bytes the parser saw, not the original input.
Ast::from_tree_sitter is unaffected: it adopts whatever tree the caller built. Ast::source reflects whatever expansion, if any, the caller applied before building the tree.
並行性
Ast is Send + Sync. Running Ast::metrics from several threads against the same &Ast is safe, because the walker only reads from the held tree_sitter::Tree. Benchmarking parallel metric runs is a separate follow-up.
対象外
tree_sitter::InputEditによる増分再パース。 解析パイプライン全体で安定したAstをキャッシュすることはスコープ内ですが、保持しているツリーを編集することはスコープ外です。- デフォルトで並列な API。
Ast::metricsはメトリクス集合をまたいで内部的に並列化しません。サブセットごとに 1 スレッドを使いたい呼び出し側は、自由にそうできます。