tsquery——一个方便的ast查询工具

2018-09-16 admin

前言

最近在给公司的 web 框架做一个 vscode 的辅助插件,其中有个对需要路由一些文件进行解析,实现配置文件和对应文件的关联信息显示和跳转的功能。既然是对文件进行解析,很自然就会想到使用 ast 的方式来做,加上需要对 TypeScript 也进行支持,我便选择了使用 TypeScript 自带的 ast 工具来进行解析。

在一开始我通过 ts 的forEachChild方法遍历和对比节点的kind属性来确定是否是我需要处理的节点,但是之后发现这个方式有几个缺点:

  1. 当需要查找满足条件的子级的 ast 节点时,需要做多次比较
  2. 对满足某一条件的多个不同类型的节点需要比较多次,编写满足条件麻烦
  3. 对分布在同一文件中的多个同名标识符,不能统一提取和处理

为了解决这些,我找到并引入了tsquery这个库,它是 TypeScript 版的esquery,能够让我们使用 css 选择器的方式来快速查询满足指定条件的 TypeScript ast 节点(也支持 JavaScript)。

比较 demo

在介绍tsquery的使用方式之前,我们先来看一个对比。

对下面这段简单的代码:

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

若我们要查找到Animal这个类的构造函数的所有参数并打印它们的名称,在使用 tsquery 之前,我们会编写这样一段代码:

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用到自己的项目中?

使用方式

那么接下来,我就来介绍一下如何去使用tsquery:

API

tsquery对象提供了下面几个方法:

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

ast方法的功能如同其名,就是接收源代码,返回一个解析后的ast语法树,实际上就是调用了ts的createSourceFile方法。

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

parse方法接收一个规则字符串,这个字符串会被解析成tsquery的选择器对象并返回,再被用于下面的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

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节点的类型来当作查询的选择器,例如:类声明: ClassDeclaration,变量声明:VariableDeclaration等,就跟你使用css选择器选择某种HTML元素一样。

tsquery支持使用css中属性选择器的方式来搜索满足属性条件的节点,你可以仅仅只声明一个属性的名称(例如:[text]),也可以指定属性的值所满足的条件(例如:[text="foo"]),其中操作符可以是=、’!=’、’>’、’<’、’<=’、’>=’,值也可以是字符串、数字、正则表达式中的任意一种。 tsquery支持多级的属性选择,所以你也可以使用.来组合属性(例如:[members.length<3])。

后代节点选择器: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

原文链接:https://segmentfault.com/a/1190000016412549

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-40370.html

文章标题:tsquery——一个方便的ast查询工具

相关文章
15个提高编程技巧的JavaScript工具
JavaScript鑴氭湰搴撴槸涓€涓�棰勫厛鐢↗avaScript璇�瑷€鍐欏ソ鐨勫簱锛屽畠鏂逛究浜嗘垜浠�寮€鍙戝熀浜嶫avaScript鐨勫簲鐢ㄧ▼搴忥紝鐗瑰埆閫傚悎AJAX鍜屽叾浠栦竴浜涗互Web涓轰腑蹇冪殑鎶€鏈�銆侸avaScr...
2015-11-12
五个值得尝试的前端开发工具
在过去的几年时间里,出现了许多全新的网页应用程序,不过,由于应用程序的功能越来越丰富,也导致了前端开发的复杂度大幅增加。 现在也有不少前端开发工具,比如Backbone和EmberJS框架都能提供稳定的App开发解决方案。同时,Javasc...
2015-12-23
12个Web设计师必备的Bootstrap工具
作为一位设计师,会经常追寻新鲜有趣的设计工具,这些工具会提高工作的效率,使得工作更有效, 最重要的是使工作变得更方便。非常肯定的说,随着日益增长的工具和应用的数量,设计和开发变得越来越简单了。其中最普遍使用的最终框架 之一是 Bootstr...
2015-12-24
利用n 升级工具升级Node.js版本及在mac环境下的坑
一、利用n 升级Node.js 最近在用NPM安装一个nodejs工具时发现,我的nodejs的版本有些旧了。这不是大问题,只要升级就可以了,当然,重新从nodejs.org最新版本是一种方法,但我想应该有更简单的方法,那就是使用 n 这个...
2017-03-17
v-charts | 饿了么团队开源的基于 Vue 和 ECharts 的图表工具
在使用echarts生成图表时,经常需要做繁琐的数据类型转化、修改复杂的配置项,v-charts的出现正是为了解决这个 痛点。基于Vue2.0和echarts封装的v-charts图表组件,只需要统一提供一种对前后端都友好的数据格式 设置简...
2018-05-24
使用JavaScript制作一个简单的计数器的方法
设计思想 该方法的关键是Cookie技术和动态图像特性的综合运用。使用Cookie,可以在用户端的硬盘上记录用户的数据,下次访问此站点时,即可读取用户端硬盘的Cookie,直接得知来访者的身份和访问次数等有关信息。JavaScript中通过...
2017-03-27
推荐15款最好的 Twitter Bootstrap 开发工具
銆€銆€Twitter Bootstrap 鑷�浠�2011骞存渶鍒濆彂甯冨埌缃戜笂鍚庯紝杩呴€熸垚涓� Web 棰嗗煙鏈€娴佽�岀殑鍝嶅簲寮忓墠绔�寮€鍙戞�嗘灦涔嬩竴锛屾槸缃戦〉璁捐�$殑浼樼�€瀹炶返銆俆witter Bootstra...
2015-12-23
JavaScript数组对象实现增加一个返回随机元素的方法
本文实例讲述了JavaScript数组对象实现增加一个返回随机元素的方法。分享给大家供大家参考。具体如下: 核心特性: 概率随机、顺序随机、随机冒泡 本方法 来自个人手写 JavaScript 的实践,只涉及 JavaScript 1.5(...
2017-03-27
JavaScript使用pop方法移除数组最后一个元素用法实例
本文实例讲述了JavaScript使用pop方法移除数组最后一个元素的用法。分享给大家供大家参考。具体如下: 下面的代码演示了JS数组的pop方法,可以用来移除数组的最后一个元素,实际上就是把数组当成堆栈使用 &lt;!DOCTYPE ht...
2017-03-22
10 个免费的 HTML 视频转换工具
现在,人们都喜欢看视频目前,大多数的人都是看视频在线或移动。因此,视频必须有正确的格式,可以通过手机或支持系统。因此,人们需要一些应用程序的工具,将有助于他们现有的文件格式转换成当前的格式如MP4,WebM,和OGG。另外,如果你想改变音频...
2015-12-23
回到顶部