How to Configure Custom Language Parsers via `languages.toml` in code-review-graph
Place a languages.toml file inside the .code-review-graph/ directory at your repository root to register tree-sitter grammars for unsupported languages, enabling full code graph analysis without modifying the core tool.
The code-review-graph repository analyzer ships with built-in support for popular languages, but you can extend it to parse any grammar available in the tree-sitter ecosystem. By creating a configuration-driven parser definition, you teach the tool to recognize custom file extensions and extract semantic nodes (functions, classes, imports) using generic tree-sitter walkers, making impact radius and community detection features available for languages like Erlang, BibTeX, or LaTeX.
Where to Place the Configuration File
The loader expects the configuration at a specific relative path from your repository root:
.code-review-graph/languages.toml
As defined in code_review_graph/custom_languages.py at lines 47-48, the constant CONFIG_RELATIVE_PATH = Path(".code-review-graph") / "languages.toml" determines the lookup location. If this file exists, the tool automatically detects it during the build process when you run:
uv run code-review-graph build
TOML Schema for Custom Languages
Each custom language is defined in a [languages.<name>] table. The schema requires specific fields that map tree-sitter node types to the tool's internal representation.
Required Fields
- grammar: The tree-sitter grammar name (validated against
tslp.get_language) - extensions: Array of file extensions (e.g.,
[".erl", ".hrl"]) - function_node_types: Array of AST node types representing callable units
- class_node_types: Array of AST node types representing type definitions or modules
- import_node_types: Array of AST node types representing dependency declarations
- call_node_types: Array of AST node types representing invocation sites
Optional Fields
- name_field: String or list of strings (max 8 candidates) specifying which child field contains the identifier name
- comment: Descriptive string for documentation purposes
After validation in _validate_entry (lines 211-254 of custom_languages.py), the loader instantiates a frozen CustomLanguage dataclass (lines 75-86) to store these values immutably.
Validation Rules and Safety Constraints
The loader implements a safety-first approach that prevents malformed configurations from crashing the build.
- Pattern Enforcement: Language names must match
_NAME_REand extensions must match_EXTENSION_RE - Grammar Existence: The specified grammar must be resolvable via the tree-sitter language pack
- Extension Conflicts: Custom entries cannot override built-in extensions or language identifiers—built-ins always win
- Hard Caps: Maximum of 20 custom languages per repository and 8 candidate fields in
name_fieldper language - Graceful Degradation: Invalid entries are skipped with logged warnings; the build continues with valid entries only
The implementation caches the file's mtime and size (lines 97-102) to avoid re-parsing on every build, clearing only on explicit clear_cache() calls.
Integration with the Parser Pipeline
Once validated, custom languages integrate seamlessly with the existing analysis infrastructure:
- TOML Parsing: The file is read using
tomllib(ortomlion Python < 3.11) viatomllib.loadsat lines 155-170 - Registration:
load_custom_languages()returns a mapping of extensions toCustomLanguageobjects - Language Detection:
CodeParser.detect_language(inparser.py) consults this mapping when encountering registered extensions - Node Extraction: The generic tree-sitter walker uses your specified
node_typeslists to identify definitions, whilename_fielddetermines how to extract identifiers from AST nodes
According to parser.py at lines 1132-1138, the parser receives the custom language mapping and treats these entries identically to built-ins for downstream features like search, wiki generation, and impact analysis.
Complete Configuration Examples
Minimal Erlang Configuration
[languages.erlang]
extensions = [".erl"]
grammar = "erlang"
function_node_types = ["function_clause"]
class_node_types = ["record_decl"]
import_node_types = ["import_attribute"]
call_node_types = ["call"]
comment = "Erlang via the bundled tree-sitter-erlang grammar"
BibTeX with Non-Standard Name Locations
For languages where identifiers appear in child fields rather than node text, use name_field to specify extraction candidates:
[languages.bibtex]
extensions = [".bib"]
grammar = "bibtex"
class_node_types = ["entry"]
name_field = ["key"]
An entry like @article{smith2020,…} now generates a Class node named smith2020.
LaTeX with Multiple Name Field Candidates
[languages.latex]
extensions = [".tex"]
grammar = "latex"
class_node_types = ["section", "chapter", "subsection"]
function_node_types = ["new_command_definition"]
name_field = ["name", "text", "declaration"]
Validating Your Configuration
Run this Python snippet to verify your languages.toml loads correctly before building:
from pathlib import Path
from code_review_graph.custom_languages import load_custom_languages
from code_review_graph.parser import EXTENSION_TO_LANGUAGE, CodeParser
repo = Path("/path/to/your/repo")
langs = load_custom_languages(
repo,
builtin_extensions=EXTENSION_TO_LANGUAGE,
builtin_languages=CodeParser()._builtin_language_names(),
)
print("Loaded custom languages:", list(langs))
Summary
- Create
.code-review-graph/languages.tomlat your repository root to register custom tree-sitter grammars - Define each language with required node type mappings and optional
name_fieldextraction rules - The loader enforces strict validation (max 20 languages, no built-in overrides) with graceful error handling
- Custom languages integrate into
CodeParserand work with all analysis features immediately after configuration - Cache invalidation occurs automatically when the configuration file changes size or modification time
Frequently Asked Questions
What happens if my tree-sitter grammar is not installed?
The validator calls tslp.get_language during _validate_entry. If the grammar is unavailable, the entry is skipped with a warning and the build continues without that language. Install the missing grammar via pip install tree-sitter-<language> or the tree-sitter language pack.
Can I override built-in language definitions like Python or JavaScript?
No. The validation logic explicitly prevents custom entries from claiming extensions or language names already reserved by built-ins. This ensures core language parsers remain stable and prevents ambiguous extension mappings.
How do I debug why my custom language isn't loading?
Check the build logs for warnings emitted during TOML parsing (lines 155-170) or validation (lines 211-254). Common issues include: regex mismatches in language names (must satisfy _NAME_RE), extension collisions, or missing required node type arrays. Use the Python validation snippet in the examples section to test programmatically.
Where does the actual AST parsing occur for custom languages?
The CodeParser class in code_review_graph/parser.py handles the tree-sitter parsing. At lines 1132-1138, it passes your custom language configuration to the generic walker, which uses your defined node_types to extract graph nodes. No additional code changes are required—the pipeline treats custom and built-in languages identically once registered.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →