tree-sitter-typescript: two grammars for one language family
TypeScript grammar for tree-sitter
At a glance
- What is it?
- tree-sitter-typescript is the tree-sitter grammar for TypeScript and TSX, and it ships two separate parsers because TSX and TypeScript are different dialects with different rules for angle brackets, with bindings for Node, Rust, Python, Swift, Go and C++ all built from the same grammar sources. It suits anyone building an editor, a linter or a code intelligence tool over TypeScript, and the detail to plan around is that the version in the package manifests lags the grammar features the language now has.
- Who is it for?
- Adopt tree-sitter-typescript if you are building tooling over TypeScript source and want an incremental parser rather than a regex, since that is what the grammar gives you.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 36 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What a tree-sitter grammar is, in one sentence
tree-sitter-typescript holds the TypeScript and TSX grammars for tree-sitter, and the package description says exactly that: TypeScript and TSX grammars for tree-sitter.
A grammar in the tree-sitter world is a description of a language's syntax written in a JavaScript DSL, which the tree-sitter CLI compiles into a C parser. The compiled parser is then usable from any of the languages tree-sitter has bindings for, and the point of the whole design is incremental parsing: the parser keeps a tree of the source it has seen and can update just the part that changed when you edit a file, rather than reparsing the whole file from scratch.
That property is what makes tree-sitter popular in editors and in code intelligence tools. A syntax highlighter that reparses a 5000 line file on every keystroke is unusable, and a parser that can say the only thing that changed is this function signature is fast enough to run on every keypress.
The two dialects are the first thing to understand about this particular grammar. Because TSX and TypeScript are actually two different dialects, this module defines two grammars. The reason is the classic ambiguity in TypeScript syntax: the angle bracket that opens a generic type parameter is indistinguishable from a less-than sign, and in TSX that bracket instead begins a JSX element. So a file containing `<div>hello</div>` is not valid TypeScript but is valid TSX, and the two need different rules.
Requiring them is explicit:
require("tree-sitter-typescript").typescript; // TypeScript grammar
require("tree-sitter-typescript").tsx; // TSX grammarTwo exports from one module, and the caller has to know which one it wants. There is also a useful note for the awkward cases: for Javascript files with flow type annotations you can use the `tsx` parser. Flow annotations look close enough to TypeScript's that the TSX grammar handles them, which is a pragmatic shortcut rather than a principled one.
The reference for the grammar itself is the TypeScript Language Spec, linked at a specific archived commit of the Microsoft TypeScript repository, so the grammar is pinned to a particular version of the specification rather than to the moving main branch.
One grammar, six language bindings
The interesting engineering in this repository is that a single pair of grammar sources is compiled and then packaged for six different language ecosystems.
The grammar sources live in `typescript/grammar.js` and `tsx/grammar.js`, with the shared parts in the `common/` directory, and the npm package's files list includes both grammar files alongside the generated `typescript/src/**` and `tsx/src/**` trees and the `common/**` directory.
The `common/` directory is worth pausing on, because it explains how two dialects share one codebase. Rather than duplicating the whole grammar, the shared rules live in common and each dialect pulls in what it needs, with the differences concentrated where they actually are. The lint script in the npm manifest only lints `common/define-grammar.js`, which suggests the shared definition file is the place where the real work is and where the linting effort is concentrated.
The generated C is what everything else consumes. The Python binding in `setup.py` compiles five source files into one extension: the Python binding's own `binding.c`, plus `typescript/src/parser.c`, `typescript/src/scanner.c`, `tsx/src/parser.c` and `tsx/src/scanner.c`. The scanner files are the hand-written parts of a tree-sitter grammar, the code that handles the things a context-free grammar cannot express, such as indentation or, here, the ambiguity between a type argument and a comparison.
The Python build is careful about compatibility. It defines `Py_LIMITED_API` as `0x03090000`, matching the requires-python of 3.9 or newer, and the wheel tag logic rewrites any CPython tag to `cp39` with `abi3`, so one wheel works across Python versions rather than one wheel per version. The `py_limited_api=True` flag on the extension is what enables that. On Windows the compiler flags differ, using `/std:c11` and `/utf-8` instead of `-std=c11` and `-fvisibility=hidden`.
The Rust side is much shorter. The Cargo manifest declares a single dependency, `tree-sitter-language` at 0.1, with `cc` as a build dependency to compile the C, and the library path points at `bindings/rust/lib.rs`. The Cargo package is marked with `autoexamples = false`, so the examples do not get built as part of the crate, and the include list names the grammar files, the generated sources, the queries and the `tree-sitter.json`.
The Node side is the one most people use. The npm manifest's main and types both point at `bindings/node`, the install script is `node-gyp-build`, which picks a prebuilt binary if one is present and falls back to compiling, and the `files` list includes `prebuilds/**`. The peer dependency on `tree-sitter` at `^0.21.0` is marked optional in `peerDependenciesMeta`, which is how the package can be installed without the runtime binding present.
The packaging surface, and what queries are for
The npm manifest's `files` list is worth reading as a description of what the package actually delivers, because it is unusually explicit.
Alongside the binding files and the prebuilds, it ships `queries/*`, the `tree-sitter.json` file, the two `grammar.js` files, the generated sources for both dialects, the `common/**` directory and `*.wasm`. The queries and the WebAssembly build are the two entries that are not what you would expect from a parser package.
The queries directory is the syntax-highlighting and structural-query layer. A tree-sitter grammar tells you the shape of the code; queries are the patterns that pick out the parts you care about, such as the definition of a function or the use of an import. Shipping them with the grammar means an editor can highlight TypeScript correctly without the user having to install a separate query pack, which is the difference between a component that works when installed and one that needs a second download.
The WebAssembly build is the other entry, and it serves a different purpose. A grammar compiled to WebAssembly can be loaded in a browser, which is what lets a web-based editor or a web-based code intelligence tool parse code without a server. The presence of `*.wasm` in the files list means the package can be published for that use directly.
The `tree-sitter.json` file is the modern manifest for a tree-sitter grammar, replacing the older combination of a package.json per grammar and a metadata file. Its inclusion in both the npm files list and the Cargo include list shows it is treated as part of the grammar proper rather than as build tooling.
There is also a `queries/` directory at the repository root and a `test/` directory, and the test script in the npm manifest runs `node --test bindings/node/*_test.js`. That is the Node built-in test runner rather than a third-party framework, and the glob shows that the tests live with the binding rather than in a separate tree.
The Python side mirrors this. The pyproject declares an optional dependency group named `core` with `tree-sitter~=0.23`, so the runtime binding is an opt-in extra rather than a hard requirement, and the classifiers describe the package as intended for developers, MIT licensed, in the compilers and text processing topics, and typed.
The repository as a multi-language project
The top level of the repository is a good illustration of what a tree-sitter grammar repository actually contains, because it looks like several projects at once.
There is `bindings/` holding the per-language binding code, `common/` for the shared grammar rules, `typescript/` and `tsx/` for the two dialect directories, each containing its `grammar.js` and the generated `src/` tree. There is `queries/` for the query files, `test/` for the test suite and `examples/`, with a `parser.ts` example that shows how to drive the parser.
Then there are the build files for each ecosystem, side by side. `package.json` and `package-lock.json` for Node, `pyproject.toml` and `setup.py` for Python, `Cargo.toml` and `Cargo.lock` for Rust, `Package.swift` and `Package.resolved` for Swift, `go.mod` and `go.sum` for Go, `CMakeLists.txt` for C++, and `binding.gyp` for the node-gyp path. `tree-sitter.json` is the grammar manifest and `Makefile` is the top-level task runner.
The style configuration follows the same spread. There is an `.editorconfig`, a `.gitattributes`, an `eslint.config.mjs` for the JavaScript sources and the grammar definitions, and a `.clang-format` equivalent is not present but the C sources are generated rather than hand-written, which is why they do not need one.
The `.github/` directory holds the CI workflow whose badge sits at the top of the README, and the badges themselves tell you which ecosystems are supported in practice. There are badges for crates.io, npm, PyPI, Discord and Matrix, and the Matrix room is the tree-sitter chat, so the grammar and the wider project share a community channel.
The maintainer field in the npm manifest names Amaan Qureshi, and the original author is Max Brunsfeld, who is also the author of the tree-sitter project itself. The Rust and Python manifests credit both, which matches the pattern of a grammar written by the language's tooling lead and then maintained by the community.
The releases are v0.23.2 and v0.23.1, both from 2024-11-11, and before that v0.20.4 from 2024-01-31. The last push was on 2026-09-17, so the repository is still being worked on, but the published package versions have not moved since late 2024 and the manifests still say 0.23.2.
What the two dialects mean for a tool that consumes the grammar
It is worth spelling out what a consumer of the grammar actually gets, because the choice of parser is not a detail you can defer.
A tree-sitter parse produces a concrete syntax tree, and that tree is what every downstream feature is built on. Highlighting, folding, a symbol index, a rename, a type-aware lookup, a lint rule: all of them are queries over the tree, and the tree's node names and structure are determined by the grammar.
So the grammar is not just a parser, it is a schema. When the tree-sitter project changes a node type, or renames a field, or restructures a rule, every consumer that queries for that node type has to change too. This is why grammars in the tree-sitter ecosystem are versioned and why the node type names are treated as a public interface.
The two-dialect arrangement doubles that surface, and it does so in a way that catches people out. A file with JSX in it will not parse under the TypeScript grammar, because the angle bracket that opens the element is a syntax error there. The error will be reported at the position of the offending bracket, which is a reasonable error message for a syntax mistake and a completely misleading one for a dialect mistake.
The practical guidance that follows is simple: pick the grammar from the file, not from the project. A React codebase with `.tsx` files needs the TSX parser; a Node backend with `.ts` files and no JSX needs the TypeScript one; a repository with both needs both, and the choice has to be made per file. The npm package exposing the two as separate named exports rather than trying to detect the dialect is the honest design, since detection from the file extension alone is a heuristic and a source file can contain JSX without a `.tsx` extension if it was generated.
There is also a performance angle to the choice, since the TSX grammar is the more permissive of the two and has to handle more ambiguity. For a tool that parses millions of files, the grammar that fits the code is the faster one, and the difference between a grammar tuned for the dialect you actually have and one that has to cope with both is the kind of thing that shows up in a profile.
The other thing to keep in mind is the version question the manifests raise. The package versions in the npm, Python and Rust files are all 0.23.2, matching the November 2024 releases, while the repository itself was pushed to in September 2026. The grammar for a language that moves every few months cannot be pinned indefinitely, so the practical question for anyone depending on this is which grammar version their tools expect, and whether the published artefact has caught up with the language features they need.
Editorial conclusion
Adopt tree-sitter-typescript if you are building tooling over TypeScript source and want an incremental parser rather than a regex, since that is what the grammar gives you. Do not assume the published package version tracks the language, since the manifests are at 0.23.2 while the grammar has moved on since the 2024 releases, and check first whether your files are TypeScript or TSX, because picking the wrong one of the two grammars produces parse errors on JSX rather than on ordinary code.
Frequently asked questions
What is tree-sitter-typescript?
It is the TypeScript and TSX grammars for tree-sitter, a parser generator. Because TSX and TypeScript are two different dialects, the module defines two grammars, exposed as the typescript and tsx exports of the package, and it ships bindings for Node, Rust, Python, Swift, Go and C++.
How do I require the right grammar in tree-sitter-typescript?
Use `require("tree-sitter-typescript").typescript` for the TypeScript grammar and `require("tree-sitter-typescript").tsx` for the TSX grammar. For JavaScript files carrying flow type annotations you can also use the tsx parser, since flow annotations are close enough in shape to TypeScript's.
Why are there two grammars for TypeScript in tree-sitter-typescript?
Because TSX and TypeScript are different dialects, most visibly in how the angle bracket is read. In TypeScript it opens a generic type parameter, and in TSX it begins a JSX element, so the two need different rules and cannot be one grammar. The shared parts live in the common directory and each dialect pulls in what it needs.
Which languages can I use the tree-sitter-typescript grammar from?
Node, Rust, Python, Swift, Go and C++. The Node binding uses node-gyp-build with prebuilds, the Python binding compiles the generated parser with a limited API build so one wheel covers Python 3.9 and later, and the Rust crate depends on tree-sitter-language with cc as a build dependency to compile the C sources.
What does the tree-sitter-typescript npm package ship besides the parser?
The files list includes the queries, the tree-sitter.json grammar manifest, both grammar.js sources, the generated sources for each dialect, the shared common directory and any WebAssembly builds. Shipping the queries means an editor can highlight TypeScript without a separate query pack, and the wasm builds let a browser load the grammar.
Is tree-sitter-typescript still being developed?
The repository is not archived and the last push was on 2026-09-17, with a CI badge on a GitHub Actions workflow. The published releases are v0.23.2 and v0.23.1 from 2024-11-11, with v0.20.4 from 2024-01-31 before them, and the npm, Python and Rust manifests all still declare version 0.23.2.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/tree-sitter-tree-sitter-typescript)Was this useful?