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