tsquery——一個方便的ast查詢工具

前言

最近在給公司的 web 框架作一個 vscode 的輔助插件,其中有個對須要路由一些文件進行解析,實現配置文件和對應文件的關聯信息顯示和跳轉的功能。既然是對文件進行解析,很天然就會想到使用 ast 的方式來作,加上須要對 TypeScript 也進行支持,我便選擇了使用 TypeScript 自帶的 ast 工具來進行解析。css

在一開始我經過 ts 的forEachChild方法遍歷和對比節點的kind屬性來肯定是不是我須要處理的節點,可是以後發現這個方式有幾個缺點:前端

  1. 當須要查找知足條件的子級的 ast 節點時,須要作屢次比較
  2. 對知足某一條件的多個不一樣類型的節點須要比較屢次,編寫知足條件麻煩
  3. 對分佈在同一文件中的多個同名標識符,不能統一提取和處理

爲了解決這些,我找到並引入了tsquery這個庫,它是 TypeScript 版的esquery,可以讓咱們使用 css 選擇器的方式來快速查詢知足指定條件的 TypeScript ast 節點(也支持 JavaScript)。node

比較 demo

在介紹tsquery的使用方式以前,咱們先來看一個對比。git

對下面這段簡單的代碼:github

class Animal {
  constructor(public name: string) { }
  move(distanceInMeters: number = 0) {
    console.log(`${this.name} moved ${distanceInMeters}m.`);
  }
}

若咱們要查找到Animal這個類的構造函數的全部參數並打印它們的名稱,在使用 tsquery 以前,咱們會編寫這樣一段代碼:web

import { ClassDeclaration, createSourceFile, Node, ScriptTarget, ConstructorDeclaration, SyntaxKind } from 'TypeScript';
import { code } from './code';

const sourceFile = createSourceFile('fileName', code, ScriptTarget.Latest, true);
sourceFile.forEachChild(findClass);

function findClass(node: Node): void {
  if (node.kind === SyntaxKind.ClassDeclaration) {
    const { name } = node as ClassDeclaration;
    if (name && name.text === 'Animal') {
      node.forEachChild(findConstructor);
      return;
    }
  }
  node.forEachChild(findClass);
}

function findConstructor(node: Node): void {
  if (node.kind === SyntaxKind.Constructor) {
    printParameters(node as ConstructorDeclaration);
  }
}

function printParameters(node: ConstructorDeclaration) {
  node.parameters.forEach(parameter => {
    console.log(parameter.name.getText());
  })
}

而在咱們引入了tsquery以後,只須要下面這麼幾行簡單的代碼:正則表達式

import { tsquery } from '@phenomnomnominal/tsquery';
import * as ts from 'TypeScript';
import { code } from './code';

const parameters = tsquery.query<ts.ParameterDeclaration>(code, 'ClassDeclaration[name.name="Animal"] > Constructor > Parameter');
parameters.forEach(param => console.log(param.name.getText()));

怎麼樣,是否是對比強烈,讓你火燒眉毛得想把tsquery用到本身的項目中?express

使用方式

那麼接下來,我就來介紹一下如何去使用tsquery:api

API

tsquery對象提供了下面幾個方法:數組

  • ast:
function ast(source: string, fileName?: string): SourceFile;

ast方法的功能如同其名,就是接收源代碼,返回一個解析後的ast語法樹,實際上就是調用了ts的createSourceFile方法。

  • parse:
function parse(selector: string, options?: TSQueryOptions): TSQuerySelectorNode;

parse方法接收一個規則字符串,這個字符串會被解析成tsquery的選擇器對象並返回,再被用於下面的match方法中。

  • match:
function match<T extends Node = Node>(ast: Node | TSQueryNode<T>, selector: TSQuerySelectorNode, options?: TSQueryOptions): Array<TSQueryNode<T>>;

match方法接收一個ast對象和一個parse解析後獲得的選擇器對象,返回從ast中搜索獲得的全部知足選擇器條件的節點的數組。

結合上面三個函數,咱們能夠獲得tsquery的基本使用方法:

const ast = tsquery.ast(code);  // 得到ast語法樹
const selector = tsquery.parse(selectorStr);  // 得到選擇器
const result = tsquery.match(ast, selector);  // 查找節點

若是語法樹和選擇器可能被屢次使用,則建議使用變量將它們分別保存下來,避免重複解析致使的資源浪費和時間開銷(ast的生成和遍歷仍是比較花時間的)。

若是語法樹和選擇器不會被重複使用,那麼可使用更簡單的方法 query

  • query:
function query<T extends Node = Node>(ast: string | Node | TSQueryNode<T>, selector: string, options?: TSQueryOptions): Array<TSQueryNode<T>>;

query封裝了ast、parse和match三個方法,能夠更方便地完成一次查詢,同時tsquery自身也是一個query方法。

const result = tsquery.query(code, selectorStr);
// const result = tsquery(code, selectorStr);

選擇器規則

  • 通用選擇器

和css中的同樣,*表示選擇全部的節點。

  • AST節點類型選擇器

你能夠直接使用一個ast節點的類型來看成查詢的選擇器,例如:類聲明: ClassDeclaration,變量聲明:VariableDeclaration等,就跟你使用css選擇器選擇某種HTML元素同樣。

  • 屬性選擇器

tsquery支持使用css中屬性選擇器的方式來搜索知足屬性條件的節點,你能夠僅僅只聲明一個屬性的名稱(例如:[text]),也能夠指定屬性的值所知足的條件(例如:[text="foo"]),其中操做符能夠是=、'!='、'>'、'<'、'<='、'>=',值也能夠是字符串、數字、正則表達式中的任意一種。
tsquery支持多級的屬性選擇,因此你也可使用.來組合屬性(例如:[members.length<3])。

  • 常見的後代、兄弟節點選擇器等

後代節點選擇器:node otherNode
子節點選擇器:node > otherNode
同級節點選擇器:node ~ otherNode
相鄰節點選擇器:node + otherNode
羣組選擇器:node, otherNode

  • 各類特殊的選擇器

not選擇器::not(ClassDeclaration) 用來選擇全部不是類聲明的節點
has選擇器:IfStatement:has([left.text="foo"]) 用來選擇含有符合[left.text="foo"]屬性選擇器的子節點的if語句
第n個節點的選擇器:包含 :first-child:last-child:nth-child(n):nth-last-child(n) 這幾種選擇器,其中須要注意的是,tsquery並不支持an+b這種類型的序號匹配
類型選擇器:區分於AST節點類型選擇器,這個選擇器是用來選擇某種共通類型的(好比全部聲明、全部表達式等),目前支持的有:statement, :expression, :declaration, :function, 和 :pattern

以上全部的選擇器均可以混合使用

總結

tsquery 是一個很是方便和值得使用的 ast 輔助工具,它使用極爲簡單的 api 和學習成本較低的選擇器規則,提供了對抽象和複雜的 AST 語法樹較強的查詢能力,能夠在咱們對 AST 進行處理時節省大量的編寫成本。

若是你對 tsquery 的選擇器規則抱有疑問,能夠在 TSQuery Playground 上進行在線的測試。

參考內容:

在文章最後打個招聘廣告:

有贊招聘前端工程師,實習、校招、社招均可,具體要求能夠參考https://job.youzan.com/,同時您也能夠將簡歷投遞到個人內推郵箱:zhangshikai@youzan.com

相關文章
相關標籤/搜索