@xinminlabs/node-query
TypeScript icon, indicating that this package has built-in type declarations

1.19.1 • Public • Published

npm version CI

NodeQuery

NodeQuery defines a NQL (node query language) and node rules to query AST nodes.

Table of Contents

Installation

Install NodeQuery using npm:

npm install --save @xinminlabs/node-query

Or yarn:

yarn add @xinminlabs/node-query

Usage

It provides two apis: queryNodes and matchNode

const nodeQuery = new NodeQuery<Node>(nqlOrRules: string | object, { adapter: "typescript" | "espree" | "gonzales-pe" }) // 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', { adapter: 'typescript' }).queryNodes(node)
new NodeQuery<Node>({ nodeType: "PropertyDeclaration" }, { adapter: 'typescript' }).queryNodes(node)

Node Query Language

nql matches node type

.ClassDeclaration

It matches ClassDeclaration node

nql matches attribute

.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

nql matches nested attribute

.NewExpression[expression.escapedText=UserAccount]

It matches NewExpression node whose escapedText of expression is UserAccount

nql matches evaluated value

.PropertyAssignment[name="{{initializer}}"]

It matches PropertyAssignement node whose node value of name matches node value of intiailizer

nql matches nested selector

.VariableDeclaration[initializer=.NewExpression[expression=UserAccount]]

It matches VariableDelclaration node whose initializer is a NewExpression node whose expression is UserAccount

nql matches property

.NewExpression[arguments.length=2]

It matches NewExpression node whose arguments length is 2

nql matches operators

.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

.NewExpression[arguments INCLUDES "Murphy"]

It matches NewExpressioin node one of whose arguments is "Murphy"

.NewExpression[arguments NOT INCLUDES "Murphy"]

It matches NewExpressioin node none of whose arguments is "Murphy"

.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/

nql matches array node attribute

.NewExpression[arguments=("Murphy" 1)]

It matches NewExpressioin node whose arguments are ["Murphy", 1]

nql matches * in attribute key

.Constructor[parameters.*.name IN (name id)]

It matches Constructor whose parameters' names are all in [name id]

nql matches multiple selectors

Descendant combinator

.ClassDeclaration .Constructor

It matches Constructor node whose ancestor matches the ClassDeclaration node

Child combinator

.ClassDeclaration > .PropertyDeclaration

It matches PropertyDeclaration node whose parent matches the ClassDeclartion node

Adjacent sibling combinator

.PropertyDeclaration[name=name] + .PropertyDeclaration

It matches PropertyDeclaration node only if it immediately follows the PropertyDeclaration whose name is name

General sibling combinator

.PropertyDeclaration[name=name] ~ .PropertyDeclaration

It matches PropertyDeclaration node only if it follows the PropertyDeclaration whose name is name

nql matches goto scope

.ClassDeclaration members .PropertyDeclaration

It matches PropertyDeclaration node who is in the members of ClassDeclaration node

nql matches :has and :not_has pseudo selector

.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

nql matches :first-child and :last-child selector

.MethodDefinition:first-child

It matches the first MethodDefinition node

.MethodDefinition:last-child

It matches the last MethodDefinition node

nql matches multiple expressions

.JSXOpeningElement[name=Fragment], .JSXClosingElement[name=Fragment]

It matches JSXOpeningElement node whose name is Fragment or JSXClosingElement node whose name is Fragment

Node Rules

rules matches node type

{ nodeType: "ClassDeclaration" }

It matches ClassDeclaration node

rules matches attribute

{ 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

rules matches nested attribute

{ nodeType: "NewExpression", expression: { escapedText: "UserAccount" } }

It matches NewExpression node whose escapedText of expression is UserAccount

rules matches evaluated value

{ nodeType: "PropertyAssignment", name: "{{initializer}}" }

It matches PropertyAssignement node whose node value of name matches node value of intiailizer

rules matches nested selector

{ nodeType: "VariableDeclaration", initializer: { nodeType: "NewExpression", expression: "UserAccount" } }

It matches VariableDelclaration node whose initializer is a NewExpression node whose expression is UserAccount

rules matches property

{ nodeType: "NewExpression", arguments: { length: 2 } }

It matches NewExpression node whose arguments length is 2

rules matches operators

{ 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: "NewExpression", arguments: { includes: "Murphy" } }

It matches NewExpressioin node one of whose arguments is "Murphy"

{ nodeType: "NewExpression", arguments: { notIncludes: "Murphy" } }

It matches NewExpressioin node none of whose arguments is "Murphy"

{ 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/

rules matches array node attribute

{ nodeType: "NewExpression", arguments: ["Murphy", 1] }

It matches NewExpressioin node whose arguments are ["Murphy", 1]

Write Adapter

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.

It provides 3 adapters

  1. TypescriptAdapter
  2. EspreeAdapter
  3. GonzalesPeAdapter

Contributing Guide

  1. Fork and clone the repo.

  2. Run npm install to install dependencies.

  3. Run npm run generate or npm run watch:generate to generate src/parser.js.

  4. Run npm run test or npm run watch:test to run tests.

  5. Make some changes and make tests all passed.

  6. Push the changes to the repo.

Package Sidebar

Install

npm i @xinminlabs/node-query

Weekly Downloads

25

Version

1.19.1

License

ISC

Unpacked Size

99.1 kB

Total Files

58

Last publish

Collaborators

  • flyerhzm