核心TypeScript編譯器
語(yǔ)法分析器(Parser): 以一系列原文件開(kāi)始, 根據(jù)語(yǔ)言的語(yǔ)法, 生成抽象語(yǔ)法樹(shù)(AST)
聯(lián)合器(Binder): 使用一個(gè)Symbol
將針對(duì)相同結(jié)構(gòu)的聲明聯(lián)合在一起(例如:同一個(gè)接口或模塊的不同聲明,或擁有相同名字的函數(shù)和模塊)。這能幫助類型系統(tǒng)推導(dǎo)出這些具名的聲明。
類型解析器與檢查器(Type resolver / Checker): 解析每種類型的構(gòu)造,檢查讀寫(xiě)語(yǔ)義并生成適當(dāng)?shù)脑\斷信息。
生成器(Emitter): 從一系列輸入文件(.ts和.d.ts)生成輸出,它們可以是以下形式之一:JavaScript(.js),聲明(.d.ts),或者是source maps(.js.map)。
import
語(yǔ)句和/// <reference path=... />
標(biāo)簽間接引用的其它文件。沿著引用圖走下來(lái)你會(huì)發(fā)現(xiàn)它是一個(gè)有序的源文件列表,它們組成了整個(gè)程序。
當(dāng)解析導(dǎo)出(import)的時(shí)候,會(huì)優(yōu)先選擇“.ts”文件而不是“.d.ts”文件,以確保處理的是最新的文件。 編譯器會(huì)進(jìn)行與Nodejs相似的流程來(lái)解析導(dǎo)入,沿著目錄鏈查找與將要導(dǎo)入相匹配的帶.ts或.d.ts擴(kuò)展名的文件。 導(dǎo)入失敗不會(huì)報(bào)error,因?yàn)榭赡芤呀?jīng)聲明了外部模塊。
獨(dú)立編譯器(tsc): 批處理編譯命令行界面。主要處理針對(duì)不同支持的引擎讀寫(xiě)文件(比如:Node.js)。
語(yǔ)言服務(wù)支持一系列典型的編輯器操作比如語(yǔ)句自動(dòng)補(bǔ)全,函數(shù)簽名提示,代碼格式化和突出高亮,著色等。
基本的重構(gòu)功能比如重命名,調(diào)試接口輔助功能比如驗(yàn)證斷點(diǎn),還有TypeScript特有的功能比如支持增量編譯(在命令行上使用--watch
)。
語(yǔ)言服務(wù)是被設(shè)計(jì)用來(lái)有效的處理在一個(gè)長(zhǎng)期存在的編譯上下文中文件隨著時(shí)間改變的情況;在這樣的情況下,語(yǔ)言服務(wù)提供了與其它編譯器接口不同的角度來(lái)處理程序和源文件。
請(qǐng)參考 [[Using the Language Service API]] 以了解更多詳細(xì)內(nèi)容。
Node: 抽象語(yǔ)法樹(shù)(AST)的基本組成塊。通常Node
表示語(yǔ)言語(yǔ)法里的非終結(jié)符;一些終結(jié)符保存在語(yǔ)法樹(shù)里比如標(biāo)識(shí)符和字面量。
SourceFile: 給定源文件的AST。SourceFile
本身是一個(gè)Node
;它提供了額外的接口用來(lái)訪問(wèn)文件的源碼,文件里的引用,文件里的標(biāo)識(shí)符列表和文件里的某個(gè)位置與它對(duì)應(yīng)的行號(hào)與列號(hào)的映射。
Program: SourceFile
的集合和一系列編譯選項(xiàng)代表一個(gè)編譯單元。Program
是類型系統(tǒng)和生成代碼的主入口。
Symbol: 具名的聲明。Symbols
是做為聯(lián)合的結(jié)果而創(chuàng)建。Symbols
連接了樹(shù)里的聲明節(jié)點(diǎn)和其它對(duì)同一個(gè)實(shí)體的聲明。Symbols
是語(yǔ)義系統(tǒng)的基本構(gòu)建塊。
Type: Type
是語(yǔ)義系統(tǒng)的其它部分。Type
可能被命名(比如,類和接口),或匿名(比如,對(duì)象類型)。
Signature
類型:調(diào)用簽名(call),構(gòu)造簽名(construct)和索引簽名(index)。整個(gè)過(guò)程從預(yù)處理開(kāi)始。
預(yù)處理器會(huì)算出哪些文件參與編譯,它會(huì)去查找如下引用(/// <reference path=... />
標(biāo)簽和import
語(yǔ)句)。
語(yǔ)法分析器(Parser)生成抽象語(yǔ)法樹(shù)(AST)Node
.
這些僅為用戶輸出的抽象表現(xiàn),以樹(shù)的形式。
一個(gè)SourceFile
對(duì)象表示一個(gè)給定文件的AST并且?guī)в幸恍╊~外的信息如文件名及源文件內(nèi)容。
然后,聯(lián)合器(Binder)處理AST節(jié)點(diǎn),結(jié)合并生成Symbols
。
一個(gè)Symbol
會(huì)對(duì)應(yīng)到一個(gè)命名實(shí)體。
這里有個(gè)一微妙的差別,幾個(gè)聲明節(jié)點(diǎn)可能會(huì)是名字相同的實(shí)體。
也就是說(shuō),有時(shí)候不同的Node
具有相同的Symbol
,并且每個(gè)Symbol
保持跟蹤它的聲明節(jié)點(diǎn)。
比如,一個(gè)名字相同的class
和namespace
可以合并,并且擁有相同的Symbol
。
聯(lián)合器也會(huì)處理作用域,以確保每個(gè)Symbol
都在正確的封閉作用域里創(chuàng)建。
生成SourceFile
(還帶有它的Symbols
們)是通過(guò)調(diào)用createSourceFile
API。
到目前為止,Symbol
代表的命名實(shí)體可以在單個(gè)文件里看到,但是有些聲明可以從多文件合并,因此下一步就是構(gòu)建一個(gè)全局的包含所有文件的視圖,也就是創(chuàng)建一個(gè)Program
。
一個(gè)Program
是SourceFile
的集合并帶有一系列CompilerOptions
。
通過(guò)調(diào)用createProgram
API來(lái)創(chuàng)建Program
。
通過(guò)一個(gè)Program
實(shí)例創(chuàng)建TypeChecker
。
TypeChecker
是TypeScript類型系統(tǒng)的核心。
它負(fù)責(zé)計(jì)算出不同文件里的Symbols
之間的關(guān)系,將Type
賦值給Symbol
,并生成任何語(yǔ)義Diagnostic
(比如:error)。
TypeChecker
首先要做的是合并不同的SourceFile
里的Symbol
到一個(gè)單獨(dú)的視圖,創(chuàng)建單一的Symbol
表,合并所有普通的Symbol
(比如:不同文件里的namespace
)。
在原始狀態(tài)初始化完成后,TypeChecker
就可以解決關(guān)于這個(gè)程序的任何問(wèn)題了。
這些“問(wèn)題”可以是:
Node
的Symbol
是什么?Symbol
的Type
是什么?Symbol
是可見(jiàn)的?Signature
都有哪些?TypeChecker
計(jì)算所有東西都是“懶惰的”;為了回答一個(gè)問(wèn)題它僅“解決”必要的信息。
TypeChecker
僅會(huì)檢測(cè)和這個(gè)問(wèn)題有關(guān)的Node
,Symbol
或Type
,不會(huì)檢測(cè)額外的實(shí)體。
對(duì)于一個(gè)Program
同樣會(huì)生成一個(gè)Emitter
。
Emitter
負(fù)責(zé)生成給定SourceFile
的輸出;它包括:.js
,.jsx
,.d.ts
和.js.map
。
令牌本身就具有我們稱為一個(gè)“完整開(kāi)始”和一個(gè)“令牌開(kāi)始”。“令牌開(kāi)始”是指更自然的版本,它表示在文件中令牌開(kāi)始的位置?!巴暾_(kāi)始”是指從上一個(gè)有意義的令牌之后掃描器開(kāi)始掃描的起始位置。當(dāng)關(guān)心瑣事時(shí),我們往往更關(guān)心完整開(kāi)始。
函數(shù) | 描述 |
---|---|
ts.Node.getStart |
取得某節(jié)點(diǎn)的第一個(gè)令牌起始位置。 |
ts.Node.getFullStart |
取得某節(jié)點(diǎn)擁有的第一個(gè)令牌的完整開(kāi)始。 |
語(yǔ)法的瑣碎內(nèi)容代表源碼里那些對(duì)理解代碼無(wú)關(guān)緊要的內(nèi)容,比如空白,注釋甚至一些沖突的標(biāo)記。
因?yàn)楝嵥閮?nèi)容不是語(yǔ)言正常語(yǔ)法的一部分(不包括ECMAScript API規(guī)范)并且可能在任意2個(gè)令牌中的任意位置出現(xiàn),它們不會(huì)包含在語(yǔ)法樹(shù)里。但是,因?yàn)樗鼈儗?duì)于像重構(gòu)和維護(hù)高保真源碼很重要,所以需要的時(shí)候還是能夠通過(guò)我們的APIs訪問(wèn)。
因?yàn)?code>EndOfFileToken后面可以沒(méi)有任何內(nèi)容(令牌和瑣碎內(nèi)容),所有瑣碎內(nèi)容自然地在非瑣碎內(nèi)容之前,而且存在于那個(gè)令牌的“完整開(kāi)始”和“令牌開(kāi)始”之間。
雖然這個(gè)一個(gè)方便的標(biāo)記法來(lái)說(shuō)明一個(gè)注釋“屬于”一個(gè)Node
。比如,在下面的例子里,可以明顯看出genie
函數(shù)擁有兩個(gè)注釋:
var x = 10; // This is x.
/**
* Postcondition: Grants all three wishes.
*/
function genie([wish1, wish2, wish3]: [Wish, Wish, Wish]) {
while (true) {
}
} // End function
這是盡管事實(shí)上,函數(shù)聲明的完整開(kāi)始是在var x = 10;
后。
對(duì)于大多數(shù)普通用戶,注釋是“有趣的”瑣碎內(nèi)容。屬于一個(gè)節(jié)點(diǎn)的注釋內(nèi)容可以通過(guò)下面的函數(shù)來(lái)獲?。?/p>
函數(shù) | 描述 |
---|---|
ts.getLeadingCommentRanges |
提供源文件和一個(gè)指定位置,返回指定位置后的第一個(gè)換行與令牌之間的注釋的范圍(與ts.Node.getFullStart 配合會(huì)更有用)。 |
ts.getTrailingCommentRanges |
提供源文件和一個(gè)指定位置,返回到指定位置后第一個(gè)換行為止的注釋的范圍(與ts.Node.getEnd 配合會(huì)更有用)。 |
做為例子,假設(shè)有下面一部分源代碼:
debugger;/*hello*/
//bye
/*hi*/ function
function
關(guān)鍵字的完整開(kāi)始是從/*hello*/
注釋,但是getLeadingCommentRanges
僅會(huì)返回后面2個(gè)注釋:
d e b u g g e r ; / * h e l l o * / _ _ _ _ _ [CR] [NL] _ _ _ _ / / b y e [CR] [NL] _ _ / * h i * / _ _ _ _ f u n c t i o n
↑ ↑ ↑ ↑ ↑
完整開(kāi)始 查找 第一個(gè)注釋 第二個(gè)注釋 令牌開(kāi)始
開(kāi)始注釋
適當(dāng)?shù)兀?code>debugger語(yǔ)句后調(diào)用getTrailingCommentRanges
可以提取出/*hello*/
注釋。
如果你關(guān)心令牌流的更多信息,createScanner
也有一個(gè)skipTrivia
標(biāo)記,你可以設(shè)置成false
,然后使用setText
/setTextPos
來(lái)掃描文件里的不同位置。