An OCaml library and CLI tool for parsing source files using tree-sitter and pattern matching with concrete syntax.
- Parse source files to S-expressions using tree-sitter grammars
- Pattern matching with concrete syntax and metavariables
- Semantic patch transforms (find-and-replace at the AST level)
- Support for TypeScript, Kotlin, and other languages (extensible)
- OCaml 5.2+
- opam
- tree-sitter library (
libtree-sitter) - see below - npm (to fetch grammar sources)
macOS:
brew install tree-sitterUbuntu/Debian:
sudo apt install libtree-sitter-devArch Linux:
sudo pacman -S tree-sitter# Install OCaml dependencies (add --with-test to include test/benchmark deps)
opam install . --deps-only --with-test
# Build grammar libraries (TypeScript, Kotlin)
cd grammars && ./build-grammars.sh && cd ..
# Build the project
dune build
# Run tests
dune test
# Format code (requires ocamlformat: opam install ocamlformat)
dune fmtFormatting is managed via ocamlformat.
Run dune fmt to reformat all OCaml and dune files before committing.
Note: dune fmt emits "Stray '@'" warnings for @@ delimiters appearing
in doc comments. These are harmless and can be ignored.
# Parse and print parsed tree
diffract example.ts
# Parse with explicit language
diffract --language kotlin example.kt
# Match a pattern against a single file
diffract --match pattern.txt source.ts
# Scan a directory for pattern matches
diffract --match pattern.txt --include '*.ts' src/
# Scan with custom directory exclusions
diffract --match pattern.txt --include '*.ts' -e vendor -e dist src/
# Apply a semantic patch (preview diff)
diffract --apply --match patch.txt source.ts
# Apply a semantic patch in place
diffract --apply --in-place --match patch.txt source.ts
# Apply across a directory
diffract --apply --match patch.txt --include '*.ts' src/
# List available languages
diffract --list-languagesPatterns can include -/+ prefixed lines to describe code transformations.
For example, to rename console.log to logger.info:
patch.txt:
@@
match: strict
metavar $MSG: single
@@
- console.log($MSG)
+ logger.info($MSG)
$ diffract --apply --match patch.txt source.ts
--- a/source.ts
+++ b/source.ts
@@ -1,3 +1,3 @@
function greet(name: string) {
- console.log(name);
+ logger.info(name);
}Lines prefixed with - are matched and removed; lines with + are inserted.
Unprefixed (or space-prefixed) lines are context that appears in both match and replace.
Metavariables carry values from the match side to the replace side.
See Transform documentation for partial-mode and field-mode transforms.
When the target is a directory, use --include to specify which files to scan:
| Option | Description |
|---|---|
--include GLOB / -i |
Glob pattern for files (e.g., *.ts, *.py). Required for directories. |
--exclude DIR / -e |
Directory names to skip (repeatable). Defaults: node_modules, .git, _build, target, __pycache__, .hg, .svn |
Supported glob patterns:
*.ts- files ending with.tsprefix*- files starting withprefix*suffix- files ending withsuffix
Example output:
src/api/auth.ts:15: console.log("login")
$msg = "login"
src/utils/logger.ts:8: console.log("initialized")
$msg = "initialized"
Found 2 match(es) in 2 file(s) (scanned 47 files)
The matching pipeline is split into focused modules:
match_parsehandles@@preambles, metavars, ellipsis expansion, and spatch line classification.match_engineperforms the structural matching (strict,field,partial) and sequence metavars (except inpartialmode).match_searchdrives traversal, nested pattern contexts, indexing, and formatting.match_transformcomputes edits from match results and applies them to source text.matchexposes the public API surface.
GPL-3.0-or-later