Skip to main content

add-grammar

Step-by-step guide for adding a new tree-sitter language grammar to diffsitter. Use when adding support for a new programming language.

跳到安装

来源信息

仓库
afnanenayet/diffsitter
最近来源活动
2026年3月26日 20:56
检测到的 SKILL.md 语言
英语
星标
2,401
分支
53

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
add-grammar
description
Step-by-step guide for adding a new tree-sitter language grammar to diffsitter. Use when adding support for a new programming language.
allowed-tools
Read, Grep, Glob, Bash, Edit, Write
user-invocable
true
argument-hint
[language] Language name (e.g., 'yaml', 'lua')
# Adding a New Tree-sitter Grammar to diffsitter Follow this checklist to add support for a new language `$ARGUMENTS`. If the user did not provide a language name, ask for one before proceeding. ## Prerequisites Read these files first: - `build.rs` -- `grammars()` function and `GrammarCompileInfo` struct - `src/parse.rs` -- `FILE_EXTS` map and language resolution ## Step-by-Step Checklist ### Step 1: Add the grammar as a git submodule ```sh git submodule add https://github.com/tree-sitter/tree-sitter-$ARGUMENTS grammars/tree-sitter-$ARGUMENTS git submodule update --init --recursive ``` Verify the submodule has a `src/` directory containing at minimum `parser.c`. Check for a `scanner.c` or `scanner.cc` file -- this determines whether you need C or C++ compilation. ### Step 2: Strip Rust bindings from the grammar repo Tree-sitter grammar repos typically include Rust bindings that would interfere with diffsitter's custom build process. Remove them: ```sh cd grammars/tree-sitter-$ARGUMENTS rm -rf bindings/rust Cargo.toml build.rs cd ../.. ``` This is critical -- if `Cargo.toml` or `build.rs` remain in the grammar directory, Cargo may try to compile the grammar as a separate crate and conflict with diffsitter's build script. ### Step 3: Add `GrammarCompileInfo` to `build.rs` In `build.rs`, add a new entry to the `grammars()` function's vector. The struct has these fields: ```rust GrammarCompileInfo { /// The language name -- must match what you use in FILE_EXTS and the /// tree_sitter_$ARGUMENTS() constructor function name display_name: "$ARGUMENTS", /// Path to the grammar root (contains src/ directory) path: PathBuf::from("grammars/tree-sitter-$ARGUMENTS"), /// C source files in src/ to compile c_sources: vec!["parser.c"], // always include parser.c /// C++ source files in src/ to compile (empty if no C++ scanner) cpp_sources: vec![], ..Default::default() } ``` **Determining sources:** - `parser.c` is always present and always compiled as C. - If `src/scanner.c` exists, add `"scanner.c"` to `c_sources`. - If `src/scanner.cc` exists, add `"scanner.cc"` to `cpp_sources`. Do NOT put `.cc` files in `c_sources`. - Some grammars have no scanner at all (e.g., `json`, `go`, `java`, `c`). **Examples from the codebase:** C-only (parser + C scanner): ```rust GrammarCompileInfo { display_name: "rust", path: PathBuf::from("grammars/tree-sitter-rust"), c_sources: vec!["parser.c", "scanner.c"], ..Default::default() } ``` C + C++ scanner: ```rust GrammarCompileInfo { display_name: "ruby", path: PathBuf::from("grammars/tree-sitter-ruby"), c_sources: vec!["parser.c"], cpp_sources: vec!["scanner.cc"], ..GrammarCompileInfo::default() } ``` Parser only (no scanner): ```rust GrammarCompileInfo { display_name: "json", path: PathBuf::from("grammars/tree-sitter-json"), c_sources: vec!["parser.c"], ..Default::default() } ``` ### Step 4: Add file extension mappings to `src/parse.rs` Add entries to the `FILE_EXTS` static `phf_map!` in `src/parse.rs`. The key is the file extension (without dot), the value is the `display_name` from Step 3: ```rust static FILE_EXTS: phf::Map<&'static str, &'static str> = phf_map! { // ... existing entries ... "$EXT" => "$ARGUMENTS", }; ``` Add all common extensions for the language. For example, C++ has `"cc"`, `"cpp"`, `"hpp"`, and `"tpp"`. ### Step 5: Build and verify ```sh cargo build ``` The build script will: 1. Compile the grammar's C/C++ sources via the `cc` crate 2. Generate an `unsafe extern "C" { pub fn tree_sitter_$ARGUMENTS() -> Language; }` declaration 3. Add the language to the generated `LANGUAGES` phf_map If the build fails, check: - Are the source file paths correct? The build script prepends `{path}/src/` to each filename. - Does the grammar's `src/` directory exist? Run `ls grammars/tree-sitter-$ARGUMENTS/src/`. - Are git submodules initialized? Run `git submodule update --init --recursive`. ### Step 6: Add test data (optional but recommended) If you want to add integration or snapshot tests, add test input files under `resources/` and write tests that parse them. Run the full test suite: ```sh cargo test --all ``` The `static_load_parsers` test in `src/parse.rs` will automatically test that the new grammar can be loaded by tree-sitter, since it iterates over all entries in the `LANGUAGES` map. ## Common Pitfalls ### Monorepo grammars Some tree-sitter grammars contain multiple languages in one repository. In this case, the `path` field must point to the subdirectory containing the `src/` folder, not the repo root. Examples from the codebase: - **TypeScript**: The `tree-sitter-typescript` repo has `typescript/` and `tsx/` subdirectories, each with their own `src/`: ```rust GrammarCompileInfo { display_name: "typescript", path: PathBuf::from("grammars/tree-sitter-typescript/typescript"), // ... } GrammarCompileInfo { display_name: "tsx", path: PathBuf::from("grammars/tree-sitter-typescript/tsx"), // ... } ``` - **OCaml**: `tree-sitter-ocaml` has `grammars/ocaml/` subdirectory: ```rust path: PathBuf::from("grammars/tree-sitter-ocaml/grammars/ocaml"), ``` - **PHP**: `tree-sitter-php` has a `php/` subdirectory: ```rust path: PathBuf::from("grammars/tree-sitter-php/php"), ``` - **Markdown**: `tree-sitter-markdown` has a `tree-sitter-markdown/` subdirectory: ```rust path: PathBuf::from("grammars/tree-sitter-markdown/tree-sitter-markdown"), ``` ### C++ scanner pitfalls If the grammar has a C++ scanner (`scanner.cc`), it must go in `cpp_sources`, not `c_sources`. The build script compiles C and C++ sources with different compilers (`cc` vs `c++`) and links them into separate static libraries. ### Non-standard include paths If the grammar's headers are not in `src/` (the default include path), use the `include_paths` field: ```rust GrammarCompileInfo { display_name: "some_lang", path: PathBuf::from("grammars/tree-sitter-some-lang"), c_sources: vec!["parser.c"], include_paths: Some(vec![ PathBuf::from("grammars/tree-sitter-some-lang/include"), ]), ..Default::default() } ``` ### display_name must match the constructor function The `display_name` is used to generate the FFI symbol name `tree_sitter_{display_name}`. This must match the actual symbol exported by the compiled grammar. For languages with hyphens, use underscores in the display name (e.g., `c_sharp` for `tree-sitter-c-sharp`). ### Grammar submodule coordination Grammar submodule updates are coordinated via the [diffsitter-grammars](https://github.com/afnanenayet/diffsitter-grammars) repository using nvchecker. For ongoing maintenance, consider adding the grammar there too. ### ABI compatibility The grammar must have a compatible tree-sitter ABI version. The `ts_language_abi_checked` function in `src/parse.rs` verifies this at runtime, checking that the grammar's ABI version falls within `MIN_COMPATIBLE_LANGUAGE_VERSION..=LANGUAGE_VERSION`. If you see an `AbiOutOfRange` error, the grammar may need to be rebuilt with a compatible tree-sitter version.
在 GitHub 查看