NodeQuery defines an AST node query language, which is a css like syntax for matching nodes,
it supports other ast parser if it implements NodeQuery.Adapter
.
- NodeQuery
- Table of Contents
- Installation
- Usage
- Node Query Language
- nql matches node type
- nql matches attribute
- nql matches nested attribute
- nql matches evaluated value
- nql matches nested selector
- nql matches property
- nql matches operators
- nql matches array node attribute
- nql matches * in attribute key
- nql matches multiple selectors
- nql matches goto scope
- nql matches pseudo selector
- nql matches multiple expressions
- Node Rules
- Write Adapter
- Contributing Guide
Install NodeQuery using npm:
npm install --save @xinminlabs/node-query
Or yarn:
yarn add @xinminlabs/node-query
It provides two apis: queryNodes
and matchNode
const nodeQuery = new NodeQuery<Node>(nqlOrRules: string | object) // Initialize NodeQuery
nodeQuery.queryNodes(node: Node, includingSelf = true): Node[] // Get the matching nodes.
nodeQuery.matchNode(node: Node): boolean // Check if the node matches nql or rules.
Here is an example for typescript ast node.
import ts, { Node } from 'typescript';
import NodeQuery from '@xinminlabs/node-query';
const source = `
interface User {
name: string;
id: number;
}
class UserAccount {
name: string;
id: number;
constructor(name: string, id: number) {
this.name = name;
this.id = id;
}
}
const user: User = new UserAccount("Murphy", 1);
`
const node = ts.createSourceFile('code.ts', source, ts.ScriptTarget.Latest, true)
// It will get the two nodes of property declaration in the class declaration.
new NodeQuery<Node>('.ClassDeclaration .PropertyDeclaration').queryNodes(node)
new NodeQuery<Node>({ nodeType: "PropertyDeclaration" }).queryNodes(node)
.ClassDeclaration
It matches ClassDeclaration node
.NewExpression[expression=UserAccount]
It matches NewExpression node whose expression value is UserAccount
.NewExpression[arguments.0="Murphy"][arguments.1=1]
It matches NewExpression node whose first argument is "Murphy" and second argument is 1
.NewExpression[expression.escapedText=UserAccount]
It matches NewExpression node whose escapedText of expression is UserAccount
.PropertyAssignment[name="{{initializer}}"]
It matches PropertyAssignement node whose node value of name matches node value of intiailizer
.VariableDeclaration[initializer=.NewExpression[expression=UserAccount]]
It matches VariableDelclaration node whose initializer is a NewExpression node whose expression is UserAccount
.NewExpression[arguments.length=2]
It matches NewExpression node whose arguments length is 2
.NewExpression[expression=UserAccount]
Value of expression is equal to UserAccount
.NewExpression[expression^=User]
Value of expression starts with User
.NewExpression[expression$=Account]
Value of expression ends with Account
.NewExpression[expression*=Acc]
Value of expression contains Account
.NewExpression[arguments.length!=0]
Length of arguments is not equal to 0
.NewExpression[arguments.length>=2]
Length of arguments is greater than or equal to 2
.NewExpression[arguments.length>1]
Length of arguments is greater than 1
.NewExpression[arguments.length<=2]
Length of arguments is less than or equal to 2
.NewExpression[arguments.length<3]
Length of arguments is less than 3
.ClassDeclaration[name IN (User Account UserAccount)]
Value of name matches any of User, Account and UserAccount
.ClassDeclaration[name NOT IN (User Account)]
Value of name does not match all of User and Account
.ClassDeclaration[name=~/^User/]
Value of name starts with User
.ClassDeclaration[name!=~/^User/]
Value of name does not start with User
.ClassDeclaration[name IN (/User/ /Account/)]
Value of name matches any of /User/ and /Account/
.NewExpression[arguments=("Murphy" 1)]
It matches NewExpressioin node whose arguments are ["Murphy", 1]
.Constructor[parameters.*.name IN (name id)]
It matches Constructor whose parameters' names are all in [name id]
.ClassDeclaration .Constructor
It matches Constructor node whose ancestor matches the ClassDeclaration node
.ClassDeclaration > .PropertyDeclaration
It matches PropertyDeclaration node whose parent matches the ClassDeclartion node
.PropertyDeclaration[name=name] + .PropertyDeclaration
It matches PropertyDeclaration node only if it immediately follows the PropertyDeclaration whose name is name
.PropertyDeclaration[name=name] ~ .PropertyDeclaration
It matches PropertyDeclaration node only if it follows the PropertyDeclaration whose name is name
.ClassDeclaration members .PropertyDeclaration
It matches PropertyDeclaration node who is in the members of ClassDeclaration node
.ClassDeclaration:has(.Constructor)
It matches ClassDeclaration node if it has a Constructor node
.ClassDeclaration:not_has(.Constructor)
It matches ClassDeclaration node if it does not have a Constructor node
.JSXOpeningElement[name=Fragment], .JSXClosingElement[name=Fragment]
It matches JSXOpeningElement node whose name is Fragment or JSXClosingElement node whose name is Fragment
{ nodeType: "ClassDeclaration" }
It matches ClassDeclaration node
{ nodeType: "NewExpression", expression: "UserAccount" }
It matches NewExpression node whose expression value is UserAccount
{ nodeType: "NewExpression", arguments: { 0: "Murphy", 1: 1 } }
It matches NewExpression node whose first argument is "Murphy" and second argument is 1
{ nodeType: "NewExpression", expression: { escapedText: "UserAccount" } }
It matches NewExpression node whose escapedText of expression is UserAccount
{ nodeType: "PropertyAssignment", name: "{{initializer}}" }
It matches PropertyAssignement node whose node value of name matches node value of intiailizer
{ nodeType: "VariableDeclaration", initializer: { nodeType: "NewExpression", expression: "UserAccount" } }
It matches VariableDelclaration node whose initializer is a NewExpression node whose expression is UserAccount
{ nodeType: "NewExpression", arguments: { length: 2 } }
It matches NewExpression node whose arguments length is 2
{ nodeType: "NewExpression", expression: "UserAccount" }
Value of expression is equal to UserAccount
{ nodeType: "NewExpression", arguments: { length: { not: 0 } } }
Length of arguments is not equal to 0
{ nodeType: "NewExpression", arguments: { length: { gte: 2 } } }
Length of arguments is greater than or equal to 2
{ nodeType: "NewExpression", arguments: { length: { gt: 1 } } }
Length of arguments is greater than 1
{ nodeType: "NewExpression", arguments: { length: { lte: 2 } } }
Length of arguments is less than or equal to 2
{ nodeType: "NewExpression", arguments: { length: { lt: 3 } } }
Length of arguments is less than 3
{ nodeType: "ClassDeclaration", name: { in: [User Account UserAccount] } }
Value of name matches any of User, Account and UserAccount
{ nodeType: "ClassDeclaration", name: { notIn: [User Account] } }
Value of name does not match all of User and Account
{ nodeType: "ClassDeclaration", name: /^User/ }
Value of name starts with User
{ nodeType: "ClassDeclaration", name: { not: /^User/ } }
Value of name does not start with User
{ nodeType: "ClassDeclaration", name: { in: [/User/, /Account/] } }
Value of name matches any of /User/ and /Account/
{ nodeType: "NewExpression", arguments: ["Murphy", 1] }
It matches NewExpressioin node whose arguments are ["Murphy", 1]
Different parsers, like typescript, espree, will generate different AST nodes, to make NodeQuery work for them all, we define an Adapter interface, if you implement the Adapter interface, you can set it as NodeQuery's adapter.
NodeQuery.configure({ adapter: new EspreeAdapter() })
Here are some examples:
-
Fork and clone the repo.
-
Run
npm install
to install dependencies. -
Run
npm run generate
ornpm run watch:generate
to generatesrc/parser.js
. -
Run
npm run test
ornpm run watch:test
to run tests. -
Make some changes and make tests all passed.
-
Push the changes to the repo.