@chevrotain/regexp-to-ast
`@chevrotain/regexp-to-ast` parses a JavaScript-regex-like literal string into the compact syntax tree Chevrotain uses to analyze lexer token patterns. Its tree represents alternatives, characters, character sets and ranges, capturing or noncapturing groups, numeric backreferences, assertions, lookarounds, quantifiers, source offsets, and the `g`, `i`, `m`, `u`, and `y` flags. It also exports a recursive base visitor with one hook per node kind. It does not execute regexes, build a Chevrotain grammar, or cover every current ECMAScript regular-expression feature.
Install this directly only when compatibility with Chevrotain's own regex AST is the point. For general JavaScript regex tooling, its undocumented subset and missing modern syntax make `@eslint-community/regexpp` or `regexp-tree` the safer choice.
Use it if
- You extend Chevrotain's lexer analysis and need the same AST shapes and character-set expansion that Chevrotain expects internally
- You analyze a controlled regex subset limited to numbered groups, classic character classes, lookarounds, quantifiers, and `gimuy` flags
- You want a small dependency-free AST plus a subclassable visitor and are comfortable treating the TypeScript declaration and tests as the API reference
- You need complete current JavaScript regex syntax: 13.2.0 has no AST fields for `d`, `s`, or `v` flags, named groups, named backreferences, Unicode property escapes, or Unicode set operations
- You need CommonJS: the package is `type: module` and its exports map offers only an import target, with no `require` condition
- You want a documented standalone public API: the subpackage has no README or dedicated documentation page, while the repository front page documents the full Chevrotain parser toolkit
- You need to parse a RegExp object or its bare `.source`: `pattern()` expects delimiters and optional flags in one string, such as `'/[a-z]+/i'`
- You are merely defining Chevrotain tokens: install `chevrotain` and pass RegExp patterns to its Lexer rather than depending directly on this internal analysis layer
Setup reality
There are no runtime dependencies, peers, native builds, or configuration files, and TypeScript declarations ship in the package. Importing is the first trap: version 13.2.0 is ESM-only and exposes an `import` export but no CommonJS target, so `require('@chevrotain/regexp-to-ast')` is unsupported. The parser accepts a string formatted like a regex literal, including opening and closing slashes; passing `/abc/` as a RegExp object or passing only `'abc'` fails. The output uses numeric UTF-16 character values and `{from, to}` ranges, with `loc.begin` and `loc.end` offsets into the input string. Infinity represents an unbounded quantifier, which needs special handling before JSON serialization because JSON turns Infinity into null. The supported flag model is only global, ignoreCase, multiLine, unicode, and sticky. Source inspection shows no parser paths for dotAll, hasIndices, Unicode Sets, named captures, named backreferences, Unicode property escapes, or braced Unicode code points, so validate the accepted dialect before using this with arbitrary user regexes. Error messages are synchronous parser errors rather than a structured diagnostic list. The base visitor automatically calls `visitChildren()` after a specialized hook; calling `super.visitCharacter()` is harmless but does not control traversal. Override `visitChildren()` if you must prune or extend traversal. Finally, pin the version when consuming AST shapes directly because this package is released as part of the larger Chevrotain monorepo and has no standalone compatibility guide.
Patterns
Parse a slash-delimited patternparse-regex-literal
import { RegExpParser } from '@chevrotain/regexp-to-ast';
const parser = new RegExpParser();
const ast = parser.pattern('/^[a-z]+$/i');
console.log(ast.type, ast.flags.ignoreCase);Pass a string containing both slashes and flags. A RegExp object or bare source string is not accepted by pattern().
Read top-level alternativesinspect-alternatives
const ast = parser.pattern('/cat|dog|fox/');
const alternatives = ast.value.value;
console.log(alternatives.length); // 3The tree is Pattern -> Disjunction -> Alternative[]. Nested groups and lookarounds contain their own Disjunction nodes.
Map an AST node back to source textread-source-locations
const input = '/ab+c/';
const ast = parser.pattern(input);
const first = ast.value.value[0].value[0];
console.log(input.slice(first.loc.begin, first.loc.end)); // aLocations are zero-based offsets into the complete slash-delimited input, not offsets into RegExp.source alone.
Collect literal character code unitsvisit-every-character
import { BaseRegExpVisitor } from '@chevrotain/regexp-to-ast';
import type { Character } from '@chevrotain/regexp-to-ast';
class CharacterCollector extends BaseRegExpVisitor {
values: number[] = [];
visitCharacter(node: Character) {
this.values.push(node.value);
}
}
const visitor = new CharacterCollector();
visitor.visit(parser.pattern('/abc/'));
console.log(visitor.values.map(String.fromCharCode));Character values are numeric UTF-16 units. Specialized visitor hooks are followed automatically by recursive child traversal.
Read character-class rangesinspect-character-ranges
import type { Set } from '@chevrotain/regexp-to-ast';
class SetReporter extends BaseRegExpVisitor {
visitSet(node: Set) {
console.log({ complement: node.complement, entries: node.value });
}
}
new SetReporter().visit(parser.pattern('/[^a-z0-9]/'));Range entries use `{ from, to }`; individual members are numbers. Predefined classes such as `\d` are expanded into set entries.
Find greedy and lazy quantifiersinspect-quantifiers
import type { Quantifier } from '@chevrotain/regexp-to-ast';
class QuantifierReporter extends BaseRegExpVisitor {
visitQuantifier(node: Quantifier) {
console.log(node.atLeast, node.atMost, node.greedy);
}
}
new QuantifierReporter().visit(parser.pattern('/a+?b{2,}/'));Unbounded maximums are Infinity. Convert that explicitly before JSON.stringify, which otherwise serializes Infinity as null.
Detect lookahead and lookbehind assertionsfind-lookarounds
import type { Assertion } from '@chevrotain/regexp-to-ast';
class LookaroundCounter extends BaseRegExpVisitor {
count = 0;
visitLookahead(_node: Assertion) { this.count += 1; }
visitNegativeLookahead(_node: Assertion) { this.count += 1; }
visitLookbehind(_node: Assertion) { this.count += 1; }
visitNegativeLookbehind(_node: Assertion) { this.count += 1; }
}
const visitor = new LookaroundCounter();
visitor.visit(parser.pattern('/(?<=a)b(?!c)/'));
console.log(visitor.count); // 2Lookaround nodes contain a Disjunction in `value`. Named capture syntax is not supported by this parser.
List numbered capturing groupsinspect-capturing-groups
import type { Group } from '@chevrotain/regexp-to-ast';
class GroupCollector extends BaseRegExpVisitor {
groups: number[] = [];
visitGroup(node: Group) {
if (node.capturing && node.idx !== undefined) this.groups.push(node.idx);
}
}
const visitor = new GroupCollector();
visitor.visit(parser.pattern('/(a)(?:b)(c)/'));
console.log(visitor.groups); // [1, 2]Only numbered captures are represented. Noncapturing groups set capturing false and omit idx.
Reject syntax outside the supported flag setvalidate-supported-flags
function parseSupported(input: string) {
if (!/^\/(?:\\.|[^/])*\/[gimuy]*$/.test(input)) {
throw new Error('Expected a simple /pattern/gimuy literal');
}
return parser.pattern(input);
}The guard is intentionally conservative and does not parse every valid slash occurrence. The package itself rejects `d`, `s`, and `v` flags.
Turn a parser exception into a diagnostichandle-parse-error
try {
const ast = parser.pattern(candidate);
analyze(ast);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.error({ candidate, message });
}Parsing stops at the first synchronous error. There is no structured error code, recovery mode, or list of diagnostics.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| @eslint-community/regexpp | npm | Choose it for a parser maintained around current ECMAScript regular-expression syntax and validator behavior |
| regexp-tree | npm | Choose it when you need parsing plus traversal, transforms, optimization, and regeneration of regular expressions |
| regexpp | npm | Choose it when an existing ESLint-era integration already depends on its ESTree-like regex AST contract |
| chevrotain | npm | Choose the parent toolkit when the actual goal is defining lexer tokens and parsing a language rather than inspecting regex syntax |