TextMate Grammar Test

April 25, 2026 ยท View on GitHub

CI npm version npm updated

Write unit and snapshot tests for TextMate grammars, validated against the VS Code TextMate engine.

๐Ÿ“ฆ Installation

npm install --save-dev textmate-grammar-test

๐Ÿ”„ Migrating from vscode-tmgrammar-test

Looking for up-to-date dependencies, cleaner and fully refactored codebase, plus additional bug fixes and features?

Migration is straightforward and should only take a few minutes:

  • Install the new package: npm i -D textmate-grammar-test
  • Replace occurrences of
    • vscode-tmgrammar-test -> textmate-grammar-test
    • vscode-tmgrammar-snap -> textmate-grammar-snap
  • Snapshot testing: run once with --updateSnapshot to apply minor .snap format changes
  • Newer versions include a few minor breaking changes. For migration notes see:

๐Ÿš€ Usage

Note

Spaces are recommended indentation for test files.

This package provides the commands textmate-grammar-test and textmate-grammar-snap.

Add a package.json script like:

"scripts": {
  "test:grammar": "npx textmate-grammar-test syntax/tests/**/*.foo"
}

To see all available command line options, run:

npx textmate-grammar-test --help
# or
npx textmate-grammar-snap --help

๐Ÿ“ธ Snapshot Testing

Snapshot tests are a simple and fast way to test your grammar.

  1. Create small source files for your test cases
  2. Run npx textmate-grammar-snap "tests/**/*.foo"
  3. Review the generated .snap files
  4. Commit them to your version control

After grammar changes, update snapshots and review the diffs:

npx textmate-grammar-snap --updateSnapshot "tests/**/*.foo"

๐Ÿงฉ Unit Testing Syntax

File Header

Every test file must start with a header line in the format <comment token> SYNTAX TEST v1 "<scopeName>" "Optional description".

For example:

// SYNTAX TEST v1 "source.ts" "Example header for a TypeScript grammar test"

Require specific scopes

Assert that a token has a specific scope using ^:

let count: number = 1
//  ^^^^^ variable.other.readwrite.ts
//         ^^^^^^ support.type.primitive.ts

You can also assert multiple scopes on the same token. Scopes must be ordered from most general to most specific:

let count: number = 1
//         ^^^^^^ meta.type.annotation.ts meta.var-single-variable.expr.ts meta.var.expr.ts

Or you assert the same scopes for multiple tokens in one line:

let x = y + z
//  ^   ^   ^ variable.other.readwrite.ts

// Also works with negative assertions:
let x = y + z
//  ^   ^   ^ variable.other.readwrite.ts ! keyword.operator.arithmetic.ts

Prevent specific scopes

To ensure a token does not receive an unexpected scope, use ! (surrounded by spaces):

    / not a comment
//  ^ ! comment.line.double-slash.ts

Positive and negative assertions can be combined. The ! is only needed to separate the groups:

    / not a comment
//  ^ source.ts ! comment.line.double-slash.ts storage.type.ts

Test the first token of a line

To target a token at the start of a line, use <-. The number of - characters defines the token length.

If an offset is needed, use ~:

let x = "a"
// <--- storage.type.ts

// With offset:
x = "b"
// <~~- keyword.operator.assignment.ts

Language configuration via package.json

Needed information about the grammars is read from the package.json contribution points contributes.grammars and contributes.languages.

If it's not in your project root, provide the path with the --config option.

You can also pass the path to a custom json file imitating the structure.

๐Ÿ“œ License

This repo is licensed under the MIT License.