摘要:結合上面三個函數,我們可以得到的基本使用方法獲得語法樹獲得選擇器查找節點如果語法樹和選擇器可能被多次使用,則建議使用變量將它們分別保存下來,避免重復解析導致的資源浪費和時間開銷的生成和遍歷還是比較花時間的。
前言
最近在給公司的 web 框架做一個 vscode 的輔助插件,其中有個對需要路由一些文件進行解析,實現配置文件和對應文件的關聯信息顯示和跳轉的功能。既然是對文件進行解析,很自然就會想到使用 ast 的方式來做,加上需要對 TypeScript 也進行支持,我便選擇了使用 TypeScript 自帶的 ast 工具來進行解析。
在一開始我通過 ts 的forEachChild方法遍歷和對比節點的kind屬性來確定是否是我需要處理的節點,但是之后發現這個方式有幾個缺點:
當需要查找滿足條件的子級的 ast 節點時,需要做多次比較
對滿足某一條件的多個不同類型的節點需要比較多次,編寫滿足條件麻煩
對分布在同一文件中的多個同名標識符,不能統一提取和處理
為了解決這些,我找到并引入了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(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語法樹,實際上就是調用了ts的createSourceFile方法。
parse:
function parse(selector: string, options?: TSQueryOptions): TSQuerySelectorNode;
parse方法接收一個規則字符串,這個字符串會被解析成tsquery的選擇器對象并返回,再被用于下面的match方法中。
match:
function match(ast: Node | TSQueryNode , selector: TSQuerySelectorNode, options?: TSQueryOptions): Array >;
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(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);選擇器規則
通用選擇器
和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 上進行在線的測試。
參考內容:
Easier TypeScript tooling with TSQuery
在文章最后打個招聘廣告:
有贊招聘前端工程師,實習、校招、社招都可,具體要求可以參考https://job.youzan.com/,同時您也可以將簡歷投遞到我的內推郵箱:zhangshikai@youzan.com
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/97820.html
摘要:我們更多要去做的是去修改和改變生成的這個抽象語法樹。我們已經知道會遍歷節點組成的抽象語法樹,每一個節點都會有自己對應的比如變量節點等。 你有可能會聽到過這個詞 webpack工程師 ,這個看似像是一個專業很強的職位其實很多時候是一些前端對現在前端工作方式對一些吐槽,對于一個之前沒有接觸過webpack,nodejs,babel 之類的工具的人來說,看到大量的配置文件后很多人都會看懵 s...
摘要:比如上面的例子文件文件我們利用做了語法解析檢測,代碼如下報錯哪里類重復了不存在查看該屬性是否存在于父類中原理能就是對解析出來的繼續做分析,但是前人栽樹后人乘涼,這樣的完整工具已經有大神幫我們做好了。 原文:我的個人博客 https://mengkang.net/1356.html 工作了兩三年,技術停滯不前,迷茫沒有方向,不如看下我的直播 PHP 進階之路 (金三銀四跳槽必考,一般人...
摘要:針對語法樹節點的查詢操作通常伴隨著和這兩種方法見下一篇文章。注意上述代碼打印出的和中的并不完全一致。如函數,在中的為,但其實際的為。這個大家一定要注意哦,因為在我們后面的實際代碼中也有用到。 ??在上一篇文章中,我們介紹了AST的Create。在這篇文章中,我們接著來介紹AST的Retrieve。??針對語法樹節點的查詢(Retrieve)操作通常伴隨著Update和Remove(這兩...
摘要:抽象語法樹,是一個非常基礎而重要的知識點,但國內的文檔卻幾乎一片空白。事實上,在世界中,你可以認為抽象語法樹是最底層。通過抽象語法樹解析,我們可以像童年時拆解玩具一樣,透視這臺機器的運轉,并且重新按著你的意愿來組裝。 抽象語法樹(AST),是一個非常基礎而重要的知識點,但國內的文檔卻幾乎一片空白。本文將帶大家從底層了解AST,并且通過發布一個小型前端工具,來帶大家了解AST的強大功能 ...
閱讀 3475·2021-10-13 09:39
閱讀 1458·2021-10-08 10:05
閱讀 2260·2021-09-26 09:56
閱讀 2275·2021-09-03 10:28
閱讀 2673·2019-08-29 18:37
閱讀 2032·2019-08-29 17:07
閱讀 600·2019-08-29 16:23
閱讀 2191·2019-08-29 11:24