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

新しい言語のサポート

How to add a language to big-code-analysis, for contributors who have a tree-sitter grammar crate for it. The work has three parts:

  1. Generate the kind enum from the grammar.
  2. Wire the grammar into the workspace.
  3. Implement the traits and tests for every supported metric.

文法の生成

As a prerequisite, a tree-sitter grammar crate for the language must exist that matches the tree-sitter runtime version used in this project.

The kind enums are generated by a crate in this repository called enums. The following steps register the grammar crate there and generate the enum file this project matches on when it evaluates metrics.

  1. 言語固有の tree-sitter クレートを enums クレートに追加します。このとき、依存関係がルートの big-code-analysis の Cargo.toml で使用されているのと同じバージョンに =X.Y.Z でピン留めされていることを確認してください。たとえば Rust サポートの場合、/enums/Cargo.toml には次の行があります: tree-sitter-rust = "=0.24.2"。
  2. Append the language to the mk_langs! list in /enums/src/languages.rs. Keeping with Rust as the example, the line is Rust,: one variant name per line, which becomes the name of the Rust enum that will be generated. Add a // -> <crate> comment when the backing crate is not the obvious tree_sitter_<name>.
  3. Add a case to the match in the mk_get_language macro rule in /enums/src/macros.rs, which binds each variant to its grammar crate. The current convention uses the LANGUAGE constant exposed by modern grammar crates: for Rust that line is Lang::Rust => tree_sitter_rust::LANGUAGE.into().
  4. Run the /recreate-grammars.sh script, which runs the enums crate to generate the enum for the new language.

This produces a new enum file for the language in /big-code-analysis-ast/src/languages/. See /big-code-analysis-ast/src/languages/language_rust.rs as an example of the generated enum.

新しい文法を big-code-analysis に追加する

  1. Add the language specific tree-sitter crate to the big-code-analysis workspace, with the same =X.Y.Z pin as the enums crate uses. Since #1376 this is three edits across two manifests. In the root Cargo.toml [workspace.dependencies], add the exact grammar pin (for Rust, tree-sitter-rust = "=0.24.2"). In /big-code-analysis-ast/Cargo.toml, add the optional workspace dependency (tree-sitter-rust = { workspace = true, optional = true }) and its feature (rust = ["dep:tree-sitter-rust"]), then include that feature in all-languages. In the root [features], add the forwarding feature (rust = ["big-code-analysis-ast/rust"]) and include it in all-languages.
  2. Add the new language module to /big-code-analysis-ast/src/languages/mod.rs:
#![allow(unused)]
fn main() {
pub mod language_rust;
pub use language_rust::*;
}
  1. Add a definition of the language to the arguments of the mk_langs! macro in /big-code-analysis-ast/src/langs.rs.
#![allow(unused)]
fn main() {
// 1) このバリアントの文法を有効にする Cargo フィーチャ名
// 2) enum の名前
// 3) 言語の説明
// 4) 表示名
// 5) 実装対象となる空の構造体名
// 6) パーサー名
// 7) Language を取得するために呼び出す tree-sitter 関数
// 8) ファイル拡張子
// 9) emacs モード
// 10) ピン留めされた文法クレートのバージョン(ワークスペースの Cargo.toml にある
//     `=X.Y.Z` ピンを反映。両者の一致はドリフトテストで検証されます)
(
    "rust",
    Rust,
    "The `Rust` language",
    "rust",
    RustCode,
    RustParser,
    tree_sitter_rust,
    [rs],
    ["rust"],
    "0.24.2"
)
}

トレイトとテストの実装

文法の配線は最初のステップにすぎません。新しい <Lang>Code 型は、AST の基盤処理と、ワークスペースが定義するすべてのメトリクストレイトも実装しなければなりません。

  • Checker in a new per-language file under /big-code-analysis-ast/src/checker/ (checker/<lang>.rs, registered with a mod <lang>; line in checker.rs): comment, function, closure, call, string-literal, and else-if predicates over the grammar’s kind_ids, most of which forward to the language’s role table (below).
  • Getter in a new per-language file under /big-code-analysis-ast/src/getter/ (getter/<lang>.rs, registered in getter.rs): get_space_kind plus the Halstead operator/operand classification table.
  • Alterator in /big-code-analysis-ast/src/alterator.rs: an alterate body that flattens the kinds the language’s StringLit row lists to one verbatim leaf, so the AST dump agrees with find string. Every grammar carries one; only the Preproc and Ccomment helper variants, whose dump is not a user surface, keep the bare default.
  • A role table in /big-code-analysis-ast/src/roles/ (roles/<lang>.rs, one role_table! per language), declaring for every role either its rows, absent "why", or pending Rnn. make role-census, make role-coverage and make role-bypass (all in make pre-commit) fail on a language with no table, a kind id that is neither claimed by a row, excluded with a reason, nor baselined, or a claimed kind restated as classification in a dispatch file without a // role-bypass: allow(<Role>) exemption.
  • All thirteen metric traits: Abc, Cognitive, Cyclomatic, Exit (the nexits metric), Halstead, Loc, Mi, NArgs, Nom, Npa, Npm, Tokens, Wmc. Register twelve of them via the implement_metric_trait! macro invocation in each module under /src/metrics/ to start with default (no-op) bodies, then replace with real impls for the metrics that have meaningful semantics for the language. Halstead is the exception: every grammar is listed in impl_halstead_forwarding! in src/metrics/halstead.rs, which routes the metric through the language’s Getter, so there is no per-language Halstead body to write.

エイリアス化された文法バリアントの監査

Tree-sitter grammars frequently emit several distinct kind_ids that map to the same node.kind() string (Identifier / Identifier2 / Identifier3 in Go, InvocationExpression / InvocationExpression2 in C#, QuotedContent through QuotedContent20 in Elixir). Every match node.kind_id() arm that touches an aliasable rule must either list every numbered variant or compare on the string node.kind() instead. Missing an alias silently drops nodes from the metric. See the add-lang skill for the mechanical audit procedure, and .claude/rules/grammar-dispatch.md plus lessons 2, 19 and 34 in docs/development/lessons_learned.md for the failure modes.

テスト

Add per-language tests under each src/metrics/*.rs test module; aim for parity with the existing Rust coverage across the metric files. Every insta assert_json_snapshot! call MUST be anchored: either with an inline expected block, a positive assert_eq! on the headline integer accessor above it, or an explanatory // expected: comment. make snapshot-anchors (run as part of make pre-commit) enforces this against .snapshot-anchor-baseline.txt. Every test that names the new parser also needs #[cfg(feature = "<lang>")], since the *Parser alias compiles in every build and panics in one without the grammar; make check-test-lang-gates (also in make pre-commit) derives the required gate, and ./utils/check-test-lang-gates.py --fix writes it (the Makefile target forwards no arguments).

エンドツーエンドのワークフロー

For an opinionated, end-to-end recipe (including the alias audit, test layout, snapshot anchoring, and code-quality post-passes) see the project’s add-lang Claude Code skill. It is the canonical workflow used by recent language additions (Elixir, PHP, C#, Bash, Go).