摘要:結(jié)合上面三個(gè)函數(shù),我們可以得到的基本使用方法獲得語法樹獲得選擇器查找節(jié)點(diǎn)如果語法樹和選擇器可能被多次使用,則建議使用變量將它們分別保存下來,避免重復(fù)解析導(dǎo)致的資源浪費(fèi)和時(shí)間開銷的生成和遍歷還是比較花時(shí)間的。
前言
最近在給公司的 web 框架做一個(gè) vscode 的輔助插件,其中有個(gè)對(duì)需要路由一些文件進(jìn)行解析,實(shí)現(xiàn)配置文件和對(duì)應(yīng)文件的關(guān)聯(lián)信息顯示和跳轉(zhuǎn)的功能。既然是對(duì)文件進(jìn)行解析,很自然就會(huì)想到使用 ast 的方式來做,加上需要對(duì) TypeScript 也進(jìn)行支持,我便選擇了使用 TypeScript 自帶的 ast 工具來進(jìn)行解析。
在一開始我通過 ts 的forEachChild方法遍歷和對(duì)比節(jié)點(diǎn)的kind屬性來確定是否是我需要處理的節(jié)點(diǎn),但是之后發(fā)現(xiàn)這個(gè)方式有幾個(gè)缺點(diǎn):
當(dāng)需要查找滿足條件的子級(jí)的 ast 節(jié)點(diǎn)時(shí),需要做多次比較
對(duì)滿足某一條件的多個(gè)不同類型的節(jié)點(diǎn)需要比較多次,編寫滿足條件麻煩
對(duì)分布在同一文件中的多個(gè)同名標(biāo)識(shí)符,不能統(tǒng)一提取和處理
為了解決這些,我找到并引入了tsquery這個(gè)庫,它是 TypeScript 版的esquery,能夠讓我們使用 css 選擇器的方式來快速查詢滿足指定條件的 TypeScript ast 節(jié)點(diǎn)(也支持 JavaScript)。
比較 demo在介紹tsquery的使用方式之前,我們先來看一個(gè)對(duì)比。
對(duì)下面這段簡(jiǎn)單的代碼:
class Animal { constructor(public name: string) { } move(distanceInMeters: number = 0) { console.log(`${this.name} moved ${distanceInMeters}m.`); } }
若我們要查找到Animal這個(gè)類的構(gòu)造函數(shù)的所有參數(shù)并打印它們的名稱,在使用 tsquery 之前,我們會(huì)編寫這樣一段代碼:
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之后,只需要下面這么幾行簡(jiǎn)單的代碼:
import { tsquery } from "@phenomnomnominal/tsquery"; import * as ts from "TypeScript"; import { code } from "./code"; const parameters = tsquery.query(code, "ClassDeclaration[name.name="Animal"] > Constructor > Parameter"); parameters.forEach(param => console.log(param.name.getText()));
怎么樣,是不是對(duì)比強(qiáng)烈,讓你迫不及待得想把tsquery用到自己的項(xiàng)目中?
使用方式那么接下來,我就來介紹一下如何去使用tsquery:
APItsquery對(duì)象提供了下面幾個(gè)方法:
ast:
function ast(source: string, fileName?: string): SourceFile;
ast方法的功能如同其名,就是接收源代碼,返回一個(gè)解析后的ast語法樹,實(shí)際上就是調(diào)用了ts的createSourceFile方法。
parse:
function parse(selector: string, options?: TSQueryOptions): TSQuerySelectorNode;
parse方法接收一個(gè)規(guī)則字符串,這個(gè)字符串會(huì)被解析成tsquery的選擇器對(duì)象并返回,再被用于下面的match方法中。
match:
function match(ast: Node | TSQueryNode , selector: TSQuerySelectorNode, options?: TSQueryOptions): Array >;
match方法接收一個(gè)ast對(duì)象和一個(gè)parse解析后得到的選擇器對(duì)象,返回從ast中搜索得到的所有滿足選擇器條件的節(jié)點(diǎn)的數(shù)組。
結(jié)合上面三個(gè)函數(shù),我們可以得到tsquery的基本使用方法:
const ast = tsquery.ast(code); // 獲得ast語法樹 const selector = tsquery.parse(selectorStr); // 獲得選擇器 const result = tsquery.match(ast, selector); // 查找節(jié)點(diǎn)
如果語法樹和選擇器可能被多次使用,則建議使用變量將它們分別保存下來,避免重復(fù)解析導(dǎo)致的資源浪費(fèi)和時(shí)間開銷(ast的生成和遍歷還是比較花時(shí)間的)。
如果語法樹和選擇器不會(huì)被重復(fù)使用,那么可以使用更簡(jiǎn)單的方法 query。
query:
function query(ast: string | Node | TSQueryNode , selector: string, options?: TSQueryOptions): Array >;
query封裝了ast、parse和match三個(gè)方法,可以更方便地完成一次查詢,同時(shí)tsquery自身也是一個(gè)query方法。
const result = tsquery.query(code, selectorStr); // const result = tsquery(code, selectorStr);選擇器規(guī)則
通用選擇器
和css中的一樣,*表示選擇所有的節(jié)點(diǎn)。
AST節(jié)點(diǎn)類型選擇器
你可以直接使用一個(gè)ast節(jié)點(diǎn)的類型來當(dāng)作查詢的選擇器,例如:類聲明: ClassDeclaration,變量聲明:VariableDeclaration等,就跟你使用css選擇器選擇某種HTML元素一樣。
屬性選擇器
tsquery支持使用css中屬性選擇器的方式來搜索滿足屬性條件的節(jié)點(diǎn),你可以僅僅只聲明一個(gè)屬性的名稱(例如:[text]),也可以指定屬性的值所滿足的條件(例如:[text="foo"]),其中操作符可以是=、"!="、">"、"<"、"<="、">=",值也可以是字符串、數(shù)字、正則表達(dá)式中的任意一種。
tsquery支持多級(jí)的屬性選擇,所以你也可以使用.來組合屬性(例如:[members.length<3])。
常見的后代、兄弟節(jié)點(diǎn)選擇器等
后代節(jié)點(diǎn)選擇器:node otherNode
子節(jié)點(diǎn)選擇器:node > otherNode
同級(jí)節(jié)點(diǎn)選擇器:node ~ otherNode
相鄰節(jié)點(diǎn)選擇器:node + otherNode
群組選擇器:node, otherNode
各種特殊的選擇器
not選擇器::not(ClassDeclaration) 用來選擇所有不是類聲明的節(jié)點(diǎn)
has選擇器:IfStatement:has([left.text="foo"]) 用來選擇含有符合[left.text="foo"]屬性選擇器的子節(jié)點(diǎn)的if語句
第n個(gè)節(jié)點(diǎn)的選擇器:包含 :first-child、:last-child、:nth-child(n)、:nth-last-child(n) 這幾種選擇器,其中需要注意的是,tsquery并不支持an+b這種類型的序號(hào)匹配
類型選擇器:區(qū)分于AST節(jié)點(diǎn)類型選擇器,這個(gè)選擇器是用來選擇某種共通類型的(比如所有聲明、所有表達(dá)式等),目前支持的有:statement, :expression, :declaration, :function, 和 :pattern
以上所有的選擇器都可以混合使用
總結(jié)tsquery 是一個(gè)非常方便和值得使用的 ast 輔助工具,它使用極為簡(jiǎn)單的 api 和學(xué)習(xí)成本較低的選擇器規(guī)則,提供了對(duì)抽象和復(fù)雜的 AST 語法樹較強(qiáng)的查詢能力,可以在我們對(duì) AST 進(jìn)行處理時(shí)節(jié)省大量的編寫成本。
如果你對(duì) tsquery 的選擇器規(guī)則抱有疑問,可以在 TSQuery Playground 上進(jìn)行在線的測(cè)試。
參考內(nèi)容:
Easier TypeScript tooling with TSQuery
在文章最后打個(gè)招聘廣告:
有贊招聘前端工程師,實(shí)習(xí)、校招、社招都可,具體要求可以參考https://job.youzan.com/,同時(shí)您也可以將簡(jiǎn)歷投遞到我的內(nèi)推郵箱:[email protected]
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/97820.html
摘要:我們更多要去做的是去修改和改變生成的這個(gè)抽象語法樹。我們已經(jīng)知道會(huì)遍歷節(jié)點(diǎn)組成的抽象語法樹,每一個(gè)節(jié)點(diǎn)都會(huì)有自己對(duì)應(yīng)的比如變量節(jié)點(diǎn)等。 你有可能會(huì)聽到過這個(gè)詞 webpack工程師 ,這個(gè)看似像是一個(gè)專業(yè)很強(qiáng)的職位其實(shí)很多時(shí)候是一些前端對(duì)現(xiàn)在前端工作方式對(duì)一些吐槽,對(duì)于一個(gè)之前沒有接觸過webpack,nodejs,babel 之類的工具的人來說,看到大量的配置文件后很多人都會(huì)看懵 s...
摘要:比如上面的例子文件文件我們利用做了語法解析檢測(cè),代碼如下報(bào)錯(cuò)哪里類重復(fù)了不存在查看該屬性是否存在于父類中原理能就是對(duì)解析出來的繼續(xù)做分析,但是前人栽樹后人乘涼,這樣的完整工具已經(jīng)有大神幫我們做好了。 原文:我的個(gè)人博客 https://mengkang.net/1356.html 工作了兩三年,技術(shù)停滯不前,迷茫沒有方向,不如看下我的直播 PHP 進(jìn)階之路 (金三銀四跳槽必考,一般人...
摘要:通過給用戶提供的一系列交互接口,接收到用戶的指令,使用自己的,結(jié)合元數(shù)據(jù),將這些指令翻譯成,提交到中執(zhí)行,最后,將執(zhí)行返回的結(jié)果輸出到用戶交互接口。什么是Hivehive簡(jiǎn)介hive是由Facebook開源用于解決海量結(jié)構(gòu)化日志的數(shù)據(jù)統(tǒng)計(jì)工具,是基于Hadoop的一個(gè)數(shù)據(jù)倉庫工具,可以將結(jié)構(gòu)化的數(shù)據(jù)文件映射為一張表,并提供類 SQL查詢功能。Hive本質(zhì)將HQL轉(zhuǎn)化成MapReduce程序。...
摘要:針對(duì)語法樹節(jié)點(diǎn)的查詢操作通常伴隨著和這兩種方法見下一篇文章。注意上述代碼打印出的和中的并不完全一致。如函數(shù),在中的為,但其實(shí)際的為。這個(gè)大家一定要注意哦,因?yàn)樵谖覀兒竺娴膶?shí)際代碼中也有用到。 ??在上一篇文章中,我們介紹了AST的Create。在這篇文章中,我們接著來介紹AST的Retrieve。??針對(duì)語法樹節(jié)點(diǎn)的查詢(Retrieve)操作通常伴隨著Update和Remove(這兩...
摘要:抽象語法樹,是一個(gè)非常基礎(chǔ)而重要的知識(shí)點(diǎn),但國(guó)內(nèi)的文檔卻幾乎一片空白。事實(shí)上,在世界中,你可以認(rèn)為抽象語法樹是最底層。通過抽象語法樹解析,我們可以像童年時(shí)拆解玩具一樣,透視這臺(tái)機(jī)器的運(yùn)轉(zhuǎn),并且重新按著你的意愿來組裝。 抽象語法樹(AST),是一個(gè)非?;A(chǔ)而重要的知識(shí)點(diǎn),但國(guó)內(nèi)的文檔卻幾乎一片空白。本文將帶大家從底層了解AST,并且通過發(fā)布一個(gè)小型前端工具,來帶大家了解AST的強(qiáng)大功能 ...
閱讀 3487·2021-10-13 09:39
閱讀 1468·2021-10-08 10:05
閱讀 2273·2021-09-26 09:56
閱讀 2289·2021-09-03 10:28
閱讀 2688·2019-08-29 18:37
閱讀 2047·2019-08-29 17:07
閱讀 609·2019-08-29 16:23
閱讀 2200·2019-08-29 11:24