Placeholder values
The examples below usemylang for the registry key, grammar name, and
generated parser destination. Replace it with your language’s name and
replace .ext with its file extension (for example, ruby and .rb).
Run commands from the Semgrep repository root.
Semgrep repository overview
The repository includes:test-grammar builds and tests the wrapper and OCaml parser. regen-grammar
copies generated files into languages/. Commit these files with the language
integration. There is no separate parser repository or publishing step.
Summary
1
Register the upstream grammar.
2
Create the wrapper, generator setup, and tests under
libs/ocaml-tree-sitter-semgrep/lang/.3
Extend
semgrep-mylang/grammar.js with pattern syntax.4
Test and generate the OCaml parser.
5
Integrate the parser in Semgrep.
6
Add parsing and rule matching tests.
7
Run
make core, make test, and then open a pull request
against develop.Before adding a language
Grammar development requires Node.js, Cargo, clang/clang++, and Git LFS onPATH. Grammar targets build the generator and provision the selected
Tree-sitter CLI automatically.
1. Register the upstream grammar
Add an entry tolibs/ocaml-tree-sitter-semgrep/lang/upstream-grammars.json:
mylang) identifies the grammar and its semgrep-mylang
wrapper. Each entry has the following fields:
tree_sitter selects the CLI independently of the shared runtime version in
libs/ocaml-tree-sitter-semgrep/core/tree-sitter-version. Destinations in regen must be unique across
the registry. clone changes only the fetched directory name, not the wrapper
name or regeneration destinations.
The fetch command recursively fetches dependencies at their own pinned
commits before the requested grammar. Every dependency must exist in the
registry. Cycles are invalid. For example, TypeScript declares:
c, also run make test-grammar-cpp and
make regen-grammar-cpp.
libs/ocaml-tree-sitter-semgrep/lang/semgrep-grammars/src/tree-sitter-mylang/. Do not commit this
clone.
A key can produce several destinations: typescript has
"regen": ["typescript", "tsx"]. The examples below assume one root
grammar.js and one destination. Use the TypeScript or PHP wrappers as
references for other layouts.
2. Set up the wrapper and generator
From the Semgrep repository root, create the directories and shared build links:libs/ocaml-tree-sitter-semgrep/lang/mylang/test/ok/, for example,
hello-world.ext. These files test the generated OCaml parser separately
from the wrapper’s Tree-sitter corpus.
The following example shows Ruby’s generator setup, omitting build outputs:
The fyi.list file
Create libs/ocaml-tree-sitter-semgrep/lang/mylang/fyi.list to list the source and license files that
explain where the generated parser came from. These are reference copies for
reviewing grammar changes, not build inputs.
Include:
- The upstream license file and grammar definition (
grammar.jsor its equivalent). - The Semgrep wrapper’s
grammar.js. - Additional source files used by those grammars, such as imported JavaScript modules, scanner sources, and scanner headers.
- Grammar sources and licenses from dependencies, if the grammar extends another language.
libs/ocaml-tree-sitter-semgrep/lang/, not to the directory
containing fyi.list. Blank lines and full-line # comments are allowed.
List individual files, not directories or globs. For a simple grammar:
libs/ocaml-tree-sitter-semgrep/lang/semgrep-grammars/src/tree-sitter-mylang/LICENSE. Use the actual
license filename and clone directory name (including any registry clone
override). Every listed file must exist when regenerating.
If the upstream grammar has a scanner, also list its source, for example:
libs/ocaml-tree-sitter-semgrep/lang/cpp/fyi.list and
libs/ocaml-tree-sitter-semgrep/lang/typescript/fyi.list.
Do not list generated outputs such as parser.c or OCaml parser files.
make regen-grammar-mylang copies the listed files into
languages/mylang/tree-sitter/semgrep-mylang/fyi/, preserving their relative
paths, and writes Git provenance to fyi/versions automatically.
3. Extend the grammar with Semgrep syntax
Createlibs/ocaml-tree-sitter-semgrep/lang/semgrep-grammars/src/semgrep-mylang/grammar.js:
expression rule, an ellipsis alternative can look like:
test/corpus/.
Use libs/ocaml-tree-sitter-semgrep/lang/semgrep-grammars/src/semgrep-fga/ as an example.
The shared prep script imports the upstream scanner and corpus. Use a
custom script for additional headers or a different upstream layout.
4. Test and generate the parser
make regen-grammar-mylang writes
the following layout:
5. Integrate the parser in Semgrep
Connect the generatedmylang parser to Semgrep. Use languages/fga/ as
an integration example.
1
Add the language name and file extensions to
cli/src/semgrep/semgrep_interfaces/generate.py, then regenerate the interfaces:2
In
src/parsing/Check_pattern.ml, add the language to
lang_has_no_dollar_ids. Return true if dollar identifiers are invalid
in the language, or false otherwise.3
In
src/printing/Pretty_print_AST.ml, add the language to the
appropriate functions:print_boolif_stmtwhile_stmtdo_whilefor_stmtdef_stmtreturnbreakcontinueliteral
4
Create
languages/mylang/generic/Parse_mylang_tree_sitter.ml using the
generated languages/mylang/tree-sitter/semgrep-mylang/lib/Boilerplate.ml
as a starting point. Add a corresponding .mli.Boilerplate.ml provides map_* functions that traverse the CST and
construct Tree_sitter_run.Raw_tree values. Adapt them to construct
AST_generic nodes instead.Follow languages/fga/generic/Parse_fga_tree_sitter.ml to define parse
and parse_pattern, using Parse_tree_sitter_helpers for parser wrapping
and token locations. Keep this handwritten converter separate from
Boilerplate.ml. Regeneration overwrites Boilerplate.ml.Include something similar to the following:5
Add the OCaml library definitions, using
languages/fga/generic/dune as an example, and link the converter from
src/parsing/dune. The generated parser already lives under
languages/mylang/tree-sitter/semgrep-mylang/. Do not add a submodule.6
Route target and pattern parsing to the converter in
src/parsing/Parse_target.ml and src/parsing/Parse_pattern.ml.7
Add a case to
dump_tree_sitter_cst in
src/parsing/tests/Test_parsing.ml, following Ruby’s use of
Tree_sitter_ruby.Parse.file and Tree_sitter_ruby.Boilerplate.dump_tree.
Add tests/parsing/mylang/hello-world.ext, then build and inspect its CST:6. Add parsing and rule matching tests
Add source files undertests/parsing/mylang/ to exercise supported syntax,
starting with hello-world.ext. For example, see
tests/parsing/fga/hello-world.fga.
Add matching tests under tests/patterns/mylang/. Each test pairs a source
file such as dots_args.ext with a pattern file named dots_args.sgrep.
Mark expected matches in the source with ERROR: comments, using the
language’s comment syntax. See tests/patterns/fga/dots_args.fga and
tests/patterns/fga/dots_args.sgrep for an example.
Cover literal patterns, metavariables, and ellipses, including code that should
not match. These tests check Semgrep’s CST-to-AST conversion and matching,
beyond the wrapper corpus and standalone parser tests.
Parsing statistics
projects.txt and extensions.txt are optional inputs for parsing benchmarks.
For example, after building Ruby’s grammar:
stat target downloads projects from projects.txt and reports parsing
success for file extensions listed in extensions.txt.
Troubleshooting
- C/C++ compilation errors: check that
prepimports the scanner and required headers. - Missing JavaScript modules: check
require()paths and registrydepends_onentries. - Syntax errors: inspect the corpus output for
ERRORnodes and reduce the failing input. - OCaml generation or re-parsing errors: preserve a minimal reproducer and investigate the generator.
Licenses and attribution
Preserve attribution for the original authors. Keep license notices visible, and make it easy to find the original projects:- Preserve the
LICENSEfiles. List them inlibs/ocaml-tree-sitter-semgrep/lang/mylang/fyi.list. - For sample input in
test/, use public-domain files under the Unlicense or write your own. You can filter projects by license and programming language with GitHub Search.