tree-sitter-cfml
August 17, 2026 · View on GitHub
Tree-sitter grammars for ColdFusion Markup Language (CFML).
There are three grammars: two for CFML in .cfc/.cfm and .cfs files, and one for SQL inside <cfquery> (embedded dialect).
| Grammar | Scope | File types | Description |
|---|---|---|---|
cfml | source.cfml | .cfc, .cfm | ColdFusion components and template files - CFScript, tag-based components, and HTML with embedded CF tags |
cfscript | source.cfscript | .cfs | Pure CFScript files |
cfquery | source.cfquery | (embedded) | SQL inside <cfquery> bodies (including QueryExecute-style usage), with #hash# interpolation and CF tags in the body |
Playground
Browser demo: cfmleditor.github.io/tree-sitter-cfml
Installation
Node.js
npm install @cfmleditor/tree-sitter-cfml
const {
cfml,
cfscript,
cfquery,
} = require("@cfmleditor/tree-sitter-cfml");
const Parser = require("tree-sitter");
const parser = new Parser();
parser.setLanguage(cfml.language);
const tree = parser.parse("<cfif condition>#value#</cfif>");
console.log(tree.rootNode.toString());
Rust
[dependencies]
tree-sitter = "0.25"
tree-sitter-cfml = "0.26.33"
The tree-sitter crate should be 0.25+ so the ABI matches the generated parsers (see LANGUAGE_VERSION in cf*/src/parser.c).
use tree_sitter_cfml::LANGUAGE_CFML;
let mut parser = tree_sitter::Parser::new();
parser.set_language(&LANGUAGE_CFML.into())
.expect("Error loading CFML grammar");
// LANGUAGE_CFSCRIPT, LANGUAGE_CFQUERY load the same way.
Python
pip install tree-sitter-cfml
import tree_sitter_cfml as ts_cfml
from tree_sitter import Language, Parser
# cfml for .cfc and .cfm files
parser = Parser(Language(ts_cfml.language_cfml()))
tree = parser.parse(b'<cfif x GT 0>#x#</cfif>')
# cfscript for .cfs pure script files
parser = Parser(Language(ts_cfml.language_cfscript()))
# cfquery SQL dialect (embedded)
parser = Parser(Language(ts_cfml.language_cfquery()))
Go
import (
tree_sitter_cfml "github.com/cfmleditor/tree-sitter-cfml/bindings/go"
sitter "github.com/tree-sitter/go-tree-sitter"
)
// cfml for .cfc and .cfm files
parser := sitter.NewParser()
parser.SetLanguage(sitter.NewLanguage(tree_sitter_cfml.LanguageCfml()))
// cfscript for .cfs pure script files
parser.SetLanguage(sitter.NewLanguage(tree_sitter_cfml.LanguageCfscript()))
// cfquery SQL dialect (embedded)
parser.SetLanguage(sitter.NewLanguage(tree_sitter_cfml.LanguageCfquery()))
Java
Needs JDK 23+ — the binding is built on the Foreign Function & Memory API, through jtreesitter.
<dependency>
<groupId>io.github.cfmleditor</groupId>
<artifactId>tree-sitter-cfml</artifactId>
<version>0.26.31</version>
</dependency>
import io.github.cfmleditor.jtreesitter.cfml.TreeSitterCfml;
import io.github.cfmleditor.jtreesitter.cfscript.TreeSitterCfscript;
import io.github.cfmleditor.jtreesitter.cfquery.TreeSitterCfquery;
import io.github.treesitter.jtreesitter.Language;
import io.github.treesitter.jtreesitter.Parser;
// cfml for .cfc and .cfm files
try (var parser = new Parser(new Language(TreeSitterCfml.language()))) {
var tree = parser.parse("<cfif x GT 0>#x#</cfif>").orElseThrow();
}
// cfscript for .cfs pure script files
var cfscript = new Language(TreeSitterCfscript.language());
// cfquery SQL dialect (embedded)
var cfquery = new Language(TreeSitterCfquery.language());
Unlike the other bindings, this one does not compile the C for you. It loads
four shared libraries at runtime — libtree-sitter plus one per grammar — so
they have to be somewhere the loader looks (LD_LIBRARY_PATH,
java.library.path, or a system library directory). Either install them:
make && sudo make install # libtree-sitter-{cfml,cfscript,cfquery}
or, from a checkout, build all four (including the tree-sitter runtime, pinned
by package-lock.json) into build/native/:
npm install && npm run build:native
java --enable-native-access=ALL-UNNAMED -Djava.library.path=build/native …
Run the binding's own tests with mvn test — it points java.library.path at
both locations.
The artifact is not on Maven Central yet; see the note on publish-maven in
.github/workflows/release.yml.
Development
Each dialect has grammar.js, generated C under src/, corpus tests under test/corpus/, and queries under queries/. Shared scanner code is under common/. The multi-grammar CLI and playground config is tree-sitter.json. Upstream docs: Creating parsers, CLI.
Setup
git clone https://github.com/cfmleditor/tree-sitter-cfml.git
cd tree-sitter-cfml
Use Node >=18 and <24 (package.json engines). Optional: .nvmrc with nvm / fnm (nvm use).
npm install
That installs dependencies, builds the Node native addon (node-gyp-build), and runs postinstall, which downloads the tree-sitter CLI binary into node_modules/tree-sitter-cli/ when needed. Repo npm scripts do not require a global tree-sitter on PATH.
npm test # all three grammars
npm run lint # ESLint
npm run build # regenerate parsers + rebuild native addon (after grammar edits)
Windows
Put GCC from MinGW-w64 on your PATH (gcc / g++). This repo uses GCC for npm test (tree-sitter compile), the Node native binding, Python extension builds, and Go CGO — not MSVC.
Standalone toolchain (recommended): install WinLibs with winget, then add the extracted mingw64\bin directory (contains gcc.exe) to your user PATH, open a new terminal, and verify:
gcc --version
Example package (UCRT, POSIX threads):
winget install BrechtSanders.WinLibs.POSIX.UCRT
The installer path varies by machine; locate mingw64\bin under the WinLibs folder (or under %LOCALAPPDATA%\Microsoft\WinGet\Packages\ after install) and add that bin to PATH.
Alternatively: MSYS2 with pacman -S mingw-w64-x86_64-gcc, then prepend msys64\mingw64\bin to PATH (or develop from an MSYS2 MinGW64 shell).
If node-gyp still picks Visual Studio instead of MinGW, set CC / CXX to your MinGW gcc / g++ for npm install / npm rebuild, or keep MinGW’s bin before MSVC entries on PATH.
macOS
Install the Xcode command-line tools:
xcode-select --install
Homebrew
brew install gcc
Linux
Install a C/C++ toolchain (for example build-essential on Debian/Ubuntu, gcc / clang plus development headers on other distributions).
CI
CI (.github/workflows/ci.yml): npm install, npm test, npm run lint on Ubuntu, macOS, and Windows. It does not run npm run build; generated cf*/src/ files are committed.
Tree-sitter CLI
Scripts use scripts/tree-sitter-cli.cjs (node node_modules/tree-sitter-cli/cli.js). A global tree-sitter-cli install is optional. If the binary is missing after install:
node scripts/ensure-tree-sitter-cli-binary.js
From a dialect directory (after npm install at repo root):
cd cfml
node ../node_modules/tree-sitter-cli/cli.js test
node ../node_modules/tree-sitter-cli/cli.js generate
node ../node_modules/tree-sitter-cli/cli.js parse path/to/file.cfc
Dependency versions
Pinned in package.json / tree-sitter.json; approximate roles:
| Role | Package | Version |
|---|---|---|
| Native binding (peer / dev) | tree-sitter | 0.25.0 |
| Parser CLI | tree-sitter-cli | 0.26.8 |
| Native addon | node-addon-api | ^8.3.0 |
| Native addon | node-gyp-build | ^4.8.4 |
| Prebuild | prebuildify | ^6.0.1 |
| Runtime | Node.js | >=18 <24 |
Java binding (pom.xml) | jtreesitter | 0.26.1 |
| Java binding | JDK | >=23 |
CFML engines
Corpus and behavior are checked mainly against Lucee. Overlapping Adobe ColdFusion syntax should still parse in a reasonable way. Avoid Adobe-only or Lucee-only assumptions in examples or grammar design where portable CFML is enough.
Building
After changing common/define-grammar.js or a grammar.js:
npm run build
On Unix, make generate at the repo root works if tree-sitter is on your PATH; otherwise use npm run build.
tree-sitter generate may warn about “unnecessary conflicts” (expressions vs _property_name, cfscript declaration / primary_expression, cfquery hash rules, etc.). Those come from common/define-grammar.js. If npm run build and npm test succeed, the warnings can be ignored.
Testing and helpers
See Setup for npm test, npm run lint, and npm run build.
npm run testbindings # Node binding smoke test
npm run probe # real-world construct probes (test/probes/)
npm run build:native && mvn test # Java binding smoke test (JDK 23+)
One grammar only: run test via the CLI from that dialect’s directory (above), or npm test for all three.
Real-world corpus: npm run corpus:fetch shallow-clones ~25 public CFML projects into a gitignored corpus/, npm run scan corpus reports every ERROR/MISSING node, and npm run corpus:report clusters those into distinct failure sites. See CORPUS.md for the current results and the known gaps they turned up.
Parse a file: from the dialect folder (e.g. cfml for .cfc), use the parse subcommand with the same node ../node_modules/.../cli.js pattern.
Playground / WebAssembly (WASM)
npm start— playground at repo root (tree-sitter.json). Runnpm run prestartfirst if WASM is stale.npm run prestart—tree-sitter build --wasmnpm run playground— playground in each ofcfml/,cfscript/,cfquery/npm run docswasm— writesdocs/tree-sitter-{cfml,cfscript,cfquery}.wasmfordocs/(e.g. GitHub Pages)
Releasing
npm run release -- 0.26.18
npm run release -- 0.26.18 --user=ghedwards # optional: switch gh auth before push
The release script (scripts/release.js) will:
- Validate version format and ensure it's greater than current
- Ensure the working tree is clean and local branch is not behind remote
- Verify tag
v<version>doesn't already exist - Verify
CHANGELOG.mdhas a## [<version>]or## [Unreleased]entry with notes - Update the version in
package.json,Cargo.toml,pyproject.toml,tree-sitter.jsonandpom.xml - Run
npm run build(regenerate parsers) - Run
npm run lint(ESLint) - Run
npm test(all three grammars) - Run
npm run docswasm(rebuild playground WASM) - Run
npm run install(rebuild native addon) - Commit all changes and create a
v<version>tag (prompted) - Push commit and tag (prompted)
Once the tag is pushed, the GitHub Release workflow (.github/workflows/release.yml) will automatically publish to npm, PyPI, crates.io, and create a GitHub Release with the changelog notes. Maven Central is wired up but disabled — see publish-maven in that workflow.
Grammar structure
Shared rules: common/define-grammar.js. External scanner: common/scanner.h (implicit end tags, CF tag names, hash expressions, raw text).
common/
define-grammar.js
scanner.h
tag.h
cfml/ # .cfc, .cfm
grammar.js
src/ # generated
queries/
cfscript/ # .cfs
grammar.js
src/
queries/
cfquery/ # embedded SQL
grammar.js
src/
queries/
Queries
| Grammar | Highlights | Indents | Injections | Tags |
|---|---|---|---|---|
cfml | yes | yes | yes | yes |
cfscript | yes | no | no | yes |
cfquery | yes | no | no | yes |
Contributing
See CONTRIBUTING.md.
Security
See SECURITY.md.
Agent and AI assistant guidance
See AGENTS.md.
