🚀 在 VS Code 中取得

Language Server Index Format (LSIF)

2019 年 2 月 19 日,作者:Dirk Bäumer

無需簽出的豐富程式碼導覽

身為開發人員,您花費大量時間閱讀和檢閱程式碼,而非撰寫新的原始碼。例如,您可能想要瀏覽 GitHub 等儲存庫中的現有程式碼庫,或者您可能想要檢閱同事的提取請求。

一般來說,您會簽出分支或複製儲存庫,將原始碼提取到本機電腦上,開啟您偏好的開發工具,然後才能閱讀和瀏覽程式碼。如果您不必先複製儲存庫就能做到這一點,豈不是很棒嗎?想像一下,無需下載原始碼即可獲得智慧程式碼功能,例如滑鼠停留資訊、「前往定義」和「尋找所有參考」。部落格文章搶先看豐富的程式碼導覽體驗說明了提取請求檢閱的這種情境。

Language Server Index Format (LSIF,發音為 "else if") 的目標是支援開發工具或 Web UI 中的豐富程式碼導覽,而無需原始碼的本機副本。此格式在精神上與Language Server Protocol (LSP) 類似,後者簡化了將豐富程式碼編輯功能整合到開發工具中的過程。

為什麼不直接使用現有的 LSP 語言伺服器?LSP 提供豐富的程式碼撰寫功能,例如自動完成、輸入時格式化和豐富的程式碼導覽。為了有效率地提供這些功能,語言伺服器需要所有原始碼檔案在本機磁碟上可用。LSP 語言伺服器也可能會將部分或全部檔案讀取到記憶體中,並計算抽象語法樹狀結構,以支援這些功能。Language Server Index Format 的目標是擴充 LSP 協定,以支援豐富的程式碼導覽功能,而無需這些需求。LSIF 定義了一種標準格式,供語言伺服器或其他程式設計工具發出關於程式碼工作區的知識。此持續存在的資訊稍後可用於回答同一工作區的 LSP 請求,而無需執行語言伺服器。

Language Server Index Format

LSIF 以 LSP 為基礎建構,並且使用與 LSP 中定義的相同資料類型。在高階層次,LSIF 模擬從語言伺服器請求傳回的資料。與 LSP 相同,LSIF 不包含任何程式符號資訊,LSIF 也不定義任何符號語意 (例如,什麼構成符號的定義,或方法是否覆寫另一個方法)。因此,LSIF 不定義符號資料庫,這與 LSP 方法一致。

使用現有的 LSP 資料類型作為 LSIF 的基礎還有另一個優點,因為 LSIF 可以輕鬆整合到已經理解 LSP 的工具或伺服器中。

讓我們來看一個範例。我們先從一個名為 sample.ts 的簡單 Typescript 檔案開始,內容如下

function bar(): void {}

將滑鼠停留在 bar() 上會在 Visual Studio Code 中顯示以下滑鼠停留資訊


Hover over Bar


此滑鼠停留資訊在 LSP 中使用 Hover 類型表示

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 中的 bbar 中的 r 上時,會傳回相同的滑鼠停留值。為了使發出的資料更精簡,LSIF 使用範圍而非位置。對於此範例,LSIF 工具發出元組 ['textDocument/hover', 'file:///Users/username/sample.ts', { start: { line: 0, character: 9 }, end: { line: 0, character: 12 }],其中包含範圍資訊。

LSIF 使用圖形來發出此資訊。在圖形中,LSP 請求使用邊緣表示。文件、範圍或請求結果 (例如,滑鼠停留) 使用頂點表示。此格式具有以下優點

  • 對於給定的程式碼範圍,可能會有不同的結果。對於給定的識別碼範圍,使用者對滑鼠停留值、定義的位置或「尋找所有參考」感興趣。因此,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 }

對應的圖形如下所示

LSIF graph for a hover

LSP 也支援僅採用文件作為參數的請求 (它們不是以位置為基礎的)。對程式碼理解有用的範例請求是針對所有文件符號的清單或計算所有摺疊範圍。這些請求在 LSIF 中以 [請求, 文件] -> 結果的形式建模。

讓我們看看另一個範例

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 graph for a folding range result

這些只是 LSIF 支援的 LSP 請求的兩個範例。LSIF 規格的目前版本也支援文件符號、文件連結、「前往定義」、「前往宣告」、「前往類型定義」、「尋找所有參考」和「前往實作」。

我們需要您的意見反應!

我們在 LSIF 規格方面取得了良好的初步進展,我們希望向社群公開對話,以便您可以了解我們正在做什麼。如需意見反應,請在問題 Language Server Index Format 上發表評論。

如何開始使用

若要開始使用 LSIF,您可以查看以下資源

  • LSIF 規格 - 文件也描述了一些已完成的其他最佳化,以保持發出的資料精簡。
  • TypeScript 的 LSIF 索引 - 一種為 TypeScript 產生 LSIF 的工具。README 提供使用該工具的指示。
  • 適用於 LSIF 的 Visual Studio Code 擴充功能 - 適用於 VS Code 的擴充功能,可使用 LSIF JSON 傾印提供語言理解功能。如果您實作新的 LSIF 產生器,您可以使用此擴充功能來使用任意原始碼驗證它。