@rushstack/tree-pattern
This is a simple, fast pattern matcher for JavaScript tree structures. It was designed for ESLint rules and
transforms that match parse trees such as produced by Esprima. However, it can be used
with any JSON-like data structure.
Usage
Suppose we are fixing up obsolete Promise
calls, and we need to match an input like this:
Promise.fulfilled(123);
The parsed subtree looks like this:
{
"type": "Program",
"body": [
{
"type": "ExpressionStatement",
"expression": {
"type": "CallExpression",
"callee": {
"type": "MemberExpression",
"object": {
"type": "Identifier",
"name": "Promise"
},
"property": {
"type": "Identifier",
"name": "fulfilled"
},
"computed": false,
"optional": false
},
"arguments": [
{
"type": "Literal",
"value": 123,
"raw": "123"
}
],
"optional": false
}
}
],
"sourceType": "module"
}
Throwing away the details that we don't care about, we can specify a pattern expression with the parts
that need to be present:
const pattern1: TreePattern = new TreePattern({
type: 'CallExpression',
callee: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'Promise'
},
property: {
type: 'Identifier',
name: 'fulfilled'
},
computed: false
}
});
Then when our visitor encounters an ExpressionStatement
, we can match the expressionNode
like this:
if (pattern1.match(expressionNode)) {
console.log('Success!');
}
Capturing matched subtrees
Suppose we want to generalize this to match any API such as Promise.thing(123);
or Promise.otherThing(123);
.
We can use a "tag" to extract the matching identifier:
const pattern2: TreePattern = new TreePattern({
type: 'CallExpression',
callee: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'Promise'
},
property: TreePattern.tag('promiseMethod', {
type: 'Identifier'
}),
computed: false
}
});
On a successful match, the tagged promiseMethod
subtree can be retrieved like this:
interface IMyCaptures {
promiseMethod?: { name?: string };
}
const captures: IMyCaptures = {};
if (pattern2.match(node, captures)) {
console.log('Matched ' + captures?.promiseMethod?.name);
}
Alternative subtrees
The oneOf
API enables you to write patterns that match alternative subtrees.
const pattern3: TreePattern = new TreePattern({
animal: TreePattern.oneOf([
{ kind: 'dog', bark: 'loud' },
{ kind: 'cat', meow: 'quiet' }
])
});
if (pattern3.match({ animal: { kind: 'dog', bark: 'loud' } })) {
console.log('I can match dog.');
}
if (pattern3.match({ animal: { kind: 'cat', meow: 'quiet' } })) {
console.log('I can match cat, too.');
}
For example, maybe we want to match Promise['fulfilled'](123);
as well as Promise.fulfilled(123);
.
If the structure of the expressions is similar enough, TreePattern.oneOf
avoids having to create two
separate patterns.
Links
@rushstack/tree-pattern
is part of the Rush Stack family of projects.