語言伺服器索引格式 (LSIF)
2019 年 2 月 19 日,作者:Dirk Bäumer
無需檢出(checkout)即可實現豐富的程式碼導航
作為一名開發人員,您會花費大量時間閱讀和審查程式碼,而不僅僅是編寫新的原始碼。例如,您可能希望瀏覽像 GitHub 這樣的倉庫中的現有程式碼庫,或者您可能想要審查同事的拉取請求(Pull Request)。
通常,您需要檢出(check out)一個分支或克隆(clone)一個倉庫,將原始碼拉取到本地機器上,開啟您首選的開發工具,然後才能開始閱讀和導航程式碼。如果能不先克隆倉庫就能做到這一點,那豈不是很酷?想象一下,無需下載原始碼即可獲得智慧程式碼功能,例如懸停資訊、轉到定義(Go to Definition)和查詢所有引用(Find All References)。部落格文章《豐富程式碼導航體驗初探》展示了拉取請求審查的這種場景。
語言伺服器索引格式(LSIF,發音類似於“else if”)的目標是支援在開發工具或 Web UI 中實現豐富的程式碼導航,而無需本地原始碼副本。該格式在精神上類似於語言伺服器協議(LSP),LSP 簡化了將豐富的程式碼編輯功能整合到開發工具中的過程。
為什麼不直接使用現有的 LSP 語言伺服器?LSP 提供了豐富的程式碼編寫功能,例如自動完成、按鍵格式化和豐富的程式碼導航。為了高效地提供這些功能,語言伺服器要求所有原始碼檔案在本地磁碟上可用。LSP 語言伺服器可能還會將部分或全部檔案讀入記憶體,並計算抽象語法樹來支援這些功能。語言伺服器索引格式的目標是增強 LSP 協議,以在沒有這些要求的情況下支援豐富的程式碼導航功能。LSIF 定義了一種標準格式,供語言伺服器或其他程式設計工具發出它們對程式碼工作區的瞭解。然後,這些持久化的資訊可用於回答同一工作區的 LSP 請求,而無需執行語言伺服器。
語言伺服器索引格式
LSIF 建立在 LSP 之上,並使用與 LSP 中定義的資料型別相同的資料型別。從高層次來看,LSIF 模擬了語言伺服器請求返回的資料。與 LSP 一樣,LSIF 不包含任何程式符號資訊,LSIF 也不定義任何符號語義(例如,什麼構成符號的定義,或者一個方法是否重寫了另一個方法)。因此,LSIF 不定義符號資料庫,這與 LSP 的方法一致。
使用現有的 LSP 資料型別作為 LSIF 的基礎還有另一個優勢,即 LSIF 可以輕鬆整合到已經理解 LSP 的工具或伺服器中。
讓我們看一個例子。我們從一個名為 sample.ts 的簡單 Typescript 檔案開始,其內容如下:
function bar(): void {}
在 Visual Studio Code 中將滑鼠懸停在 bar() 上會顯示以下懸停資訊:

此懸停資訊使用 Hover 型別在 LSP 中表示:
export interface Hover {
/**
* The hover's content
*/
contents: MarkupContent | MarkedString | MarkedString[];
/**
* An optional range
*/
range?: Range;
}
在上面的示例中,具體值是:
{
contents: [{ language: 'typescript', value: 'function bar(): void' }];
}
客戶端工具將透過傳送針對文件 file:///Users/username/sample.ts 中位置 {line: 0, character: 10} 的 textDocument/hover 請求,從語言伺服器檢索懸停內容。
LSIF 定義了一種格式,語言伺服器或獨立工具會發出該格式來描述元組 ['textDocument/hover', 'file:///Users/username/sample.ts', {line: 0, character: 10}] 解析為上述懸停資訊。然後可以將資料獲取並持久化到資料庫中。
LSP 請求基於位置,但結果通常僅針對範圍變化,而不是針對單個位置變化。在上面的懸停示例中,對於識別符號 bar 的所有位置,懸停值都是相同的。這意味著當用戶將滑鼠懸停在 bar 中的 b 上或 bar 中的 r 上時,返回的懸停值相同。為了使發出的資料更緊湊,LSIF 使用範圍而不是位置。對於此示例,LSIF 工具會發出元組 ['textDocument/hover', 'file:///Users/username/sample.ts', { start: { line: 0, character: 9 }, end: { line: 0, character: 12 }],其中包含範圍資訊。
LSIF 使用圖來發出此資訊。在圖中,LSP 請求使用邊表示。文件、範圍或請求結果(例如,懸停)使用頂點表示。這種格式具有以下優點:
- 對於給定的程式碼範圍,可以有不同的結果。對於給定的識別符號範圍,使用者可能對懸停值、定義的a位置或查詢所有引用感興趣。因此,LSIF 將這些結果與範圍連結起來。
- 透過新增新的邊或頂點種類,可以輕鬆擴充套件格式以包含其他請求型別或結果。
- 一旦資料可用,就可以立即發出。這使得流式傳輸成為可能,而不是必須在記憶體中儲存大量資料。例如,隨著解析的進行,應該為每個檔案發出資料。
對於懸停示例,發出的 LSIF 圖資料如下所示:
// a vertex representing the document
{ id: 1, type: "vertex", label: "document", uri: "file:///Users/username/sample.ts", languageId: "typescript" }
// a vertex representing the range for the identifier bar
{ id: 4, type: "vertex", label: "range", start: { line: 0, character: 9}, end: { line: 0, character: 12 } }
// an edge saying that the document with id 1 contains the range with id 4
{ id: 5, type: "edge", label: "contains", outV: 1, inV: 4}
// a vertex representing the actual hover result
{ id: 6, type: "vertex", label: "hoverResult",
result: {
contents: [
{ language: "typescript", value: "function bar(): void" }
]
}
}
// an edge linking the hover result to the range.
{ id: 7, type: "edge", label: "textDocument/hover", outV: 4, inV: 6 }
相應的圖看起來像這樣:

LSP 還支援僅將文件作為引數的請求(它們不基於位置)。對程式碼理解有用的示例請求是獲取所有文件符號的列表或計算所有摺疊範圍。這些請求在 LSIF 中以 [request, document] -> result 的形式建模。
讓我們看另一個例子:
function bar(): void {
console.log('Hello World!');
}
包含上述函式 bar 的文件的摺疊範圍結果如下所示:
// a vertex representing the document
{ id: 1, type: "vertex", label: "document", uri: "file:///Users/username/sample.ts", languageId: "typescript" }
// a vertex representing the folding result
{ id: 2, type: "vertex", label: "foldingRangeResult", result: [ { startLine: 0, startCharacter: 20, endLine: 2, endCharacter: 1 } ] }
// an edge connecting the folding result to the document.
{ id: 3, type: "edge", label: "textDocument/foldingRange", outV: 1, inV: 2 }

這些只是 LSIF 支援的 LSP 請求的兩個示例。當前版本的 LSIF 規範還支援文件符號、文件連結、轉到定義、轉到宣告、轉到型別定義、查詢所有引用和轉到實現。
我們需要您的反饋!
我們在 LSIF 規範上取得了良好的初步進展,我們希望向社群開放討論,以便您瞭解我們正在進行的工作。如需反饋,請在問題 Language Server Index Format 中發表評論。
如何開始
要開始使用 LSIF,您可以檢視以下資源:
- LSIF 規範 - 該文件還描述了一些為保持發出資料緊湊而進行的其他最佳化。
- TypeScript 的 LSIF 索引 - 一個為 TypeScript 生成 LSIF 的工具。README 提供了使用該工具的說明。
- Visual Studio Code LSIF 擴充套件 - 一個用於 VS Code 的擴充套件,它使用 LSIF JSON 轉儲提供語言理解功能。如果您實現了一個新的 LSIF 生成器,您可以使用此擴充套件來使用任意原始碼對其進行驗證。