TSQuery
TSQuery is a port of the ESQuery API for TypeScript! TSQuery allows you to query a TypeScript AST for patterns of syntax using a CSS style selector system.
Demos:
ESQuery demo - note that the demo requires JavaScript code, not TypeScript TSQuery demo by Uri Shaked
Installation
npm install @phenomnomnominal/tsquery --save-dev
Examples
Say we want to select all instances of an identifier with name "Animal", e.g. the identifier in the class
declaration, and the identifier in the extends
declaration.
We would do something like the following:
import { ast, query } from '@phenomnomnominal/tsquery';
const typescript = `
class Animal {
constructor(public name: string) { }
move(distanceInMeters: number = 0) {
console.log(\`\${this.name} moved \${distanceInMeters}m.\`);
}
}
class Snake extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 5) {
console.log("Slithering...");
super.move(distanceInMeters);
}
}
`;
const ast = ast(typescript);
const nodes = query(ast, 'Identifier[name="Animal"]');
console.log(nodes.length); // 2
Selectors
The following selectors are supported:
- AST node type:
ForStatement
(see common node types) -
wildcard:
*
-
attribute existence:
[attr]
-
attribute value:
[attr="foo"]
or[attr=123]
- attribute regex:
[attr=/foo.*/]
- attribute conditions:
[attr!="foo"]
,[attr>2]
,[attr<3]
,[attr>=2]
, or[attr<=3]
- nested attribute:
[attr.level2="foo"]
- field:
FunctionDeclaration > Identifier.id
-
First or last child:
:first-child
or:last-child
-
nth-child (no ax+b support):
:nth-child(2)
-
nth-last-child (no ax+b support):
:nth-last-child(1)
-
descendant:
ancestor descendant
-
child:
parent > child
-
following sibling:
node ~ sibling
-
adjacent sibling:
node + adjacent
-
negation:
:not(ForStatement)
-
matches-any:
:matches([attr] > :first-child, :last-child)
-
has:
IfStatement:has([name="foo"])
- class of AST node:
:statement
,:expression
,:declaration
,:function
, or:pattern
Common AST node types
-
Identifier
- any identifier (name of a function, class, variable, etc) -
IfStatement
,ForStatement
,WhileStatement
,DoStatement
- control flow -
FunctionDeclaration
,ClassDeclaration
,ArrowFunction
- declarations -
VariableStatement
- var, const, let. -
ImportDeclaration
- anyimport
statement -
StringLiteral
- any string -
TrueKeyword
,FalseKeyword
,NullKeyword
,AnyKeyword
- various keywords -
CallExpression
- function call -
NumericLiteral
- any numeric constant -
NoSubstitutionTemplateLiteral
,TemplateExpression
- template strings and expressions
API:
ast
:
Parse a string of code into an Abstract Syntax Tree which can then be queried with TSQuery Selectors.
import { ast } from '@phenomnomnominal/tsquery';
const sourceFile = ast('const x = 1;');
includes
:
Check for Nodes
within a given string
of code or AST Node
matching a Selector
.
import { includes } from '@phenomnomnominal/tsquery';
const hasIdentifier = includes('const x = 1;', 'Identifier');
map
:
Transform AST Nodes
within a given Node
matching a Selector
. Can be used to do Node
-based replacement or removal of parts of the input AST.
import { factory } from 'typescript';
import { map } from '@phenomnomnominal/tsquery';
const tree = ast('const x = 1;')
const updatedTree = map(tree, 'Identifier', () => factory.createIdentifier('y'));
match
:
Find AST Nodes
within a given AST Node
matching a Selector
.
import { ast, match } from '@phenomnomnominal/tsquery';
const tree = ast('const x = 1;')
const [xNode] = match(tree, 'Identifier');
parse
:
Parse a string
into an ESQuery Selector
.
import { parse } from '@phenomnomnominal/tsquery';
const selector = parse(':matches([attr] > :first-child, :last-child)');
print
:
Print a given Node
or SourceFile
to a string, using the default TypeScript printer.
import { print } from '@phenomnomnominal/tsquery';
import { factory } from 'typescript';
// create synthetic node:
const node = factory.createArrowFunction(
// ...
);
const code = print(node);
project
:
Get all the SourceFiles
included in a the TypeScript project described by a given config file.
import { project } from '@phenomnomnominal/tsquery';
const files = project('./tsconfig.json');
files
:
Get all the file paths included ina the TypeScript project described by a given config file.
import { files } from '@phenomnomnominal/tsquery';
const filePaths = files('./tsconfig.json');
match
:
Find AST Nodes
within a given string
of code or AST Node
matching a Selector
.
import {query } from '@phenomnomnominal/tsquery';
const [xNode] = query('const x = 1;', 'Identifier');
replace
:
Transform AST Nodes
within a given Node
matching a Selector
. Can be used to do string-based replacement or removal of parts of the input AST. The updated code will be printed with the TypeScript Printer
, so you may need to run your own formatter on any output code.
import { replace } from '@phenomnomnominal/tsquery';
const updatedCode = replace('const x = 1;', 'Identifier', () => 'y'));