LSPから派生したLanguage Server Index Formatとは何か

# 注意書き

本記事で扱うLanguage Server Index Format (LSIF)は、バージョン0.4.0が策定中と、まだまだ発展途上のプロジェクトです。今後大きな変更が加えられたり、派生元であるLSPに吸収合併されるかもしれないことにご留意下さい。そんな中でLSIFをご紹介しようと思ったのは、執筆時点で日本語の情報がなかったことと、LSIFを含めたLSPの理念をより多くの開発者に広め、快適な開発環境を皆の力で作り上げる一端を担う事ができたら、との思いからです。LSP / LSIFに興味をお持ちの皆様、ご意見・ご感想等ありましたらぜひコメントして下さい！

# LSIFが提案された経緯

## LSP

Language Server Protocol (LSP)とは、Microsoftが中心となって策定を進めている、プログラミング言語やエディタ / IDEに依存しない開発支援ツールのためのプロトコルです。これまで言語とエディタの組み合わせごとにツールの作成が必要であったところ、LSPの登場により、その言語向けのLanguage Serverと呼ばれるプログラムとLSPに準拠したエディタ拡張機能をそれぞれ一度書くだけで良くなりました。これによって、マイナーな言語やエディタでも一定のコストで多くの人に開発環境を提供できるようになりました。

詳しくは[公式ドキュメント(英語)](https://microsoft.github.io/language-server-protocol/)か、[日本語での解説記事(少し古いです)](https://qiita.com/atsushieno/items/ce31df9bd88e98eec5c4)を参照して下さい。

## LSPのその先と問題点

LSPの登場によりプログラムを書く際の障壁が小さくなったのは確かです。しかし、プログラミングにおいては書く時間よりも読む時間のほうが長い、とよく言われるように、プログラムを読む際にかかる労力の削減もまた重要になります。そして、プログラムは必ずしもエディタを用いて読まれるわけではありません。例えばGitHubで興味深いプロジェクトを見つけてソースコードの一部を読むとき、あるいは同僚からのプルリクエストのレビューをするときなど、ブラウザ上でコードリーディングをする機会は多々ある思います。このとき、「定義にジャンプ」や「全ての参照を検索」などの機能を使うためにはコードを手元のマシンにcloneしてエディタを開かなくてはなりません[^1]。

このように、コードを読む際に必要になる機能を言語や環境によらずに提供することを目的として提案されたのがLSIFです。

ここで、わざわざ新しいものを作らなくてもブラウザやGitHubのサーバ上でLanguage Serverを動かせば良いではないか、と思うかもしれません。しかし、そのためには依存する全てのライブラリのソースコードがLanguage Serverからアクセス可能である必要があります。なぜならコード補完に代表される高度な編集支援機能を提供するには現在開いているファイル以外の情報も必要になるからです。また、そのためにそれらのソースコードからASTを構築し、メモリ上に保持するなどの不必要な負荷もかかります。そうして、読むことに限定した機能を提供するのに必要な情報のみを予め構築しておくことで、Language Serverを動かすことなくコードリーディング支援機能を提供できるようにする、という目的の達成に向けてLSIFが提案された、というわけです。

# LSIF

Language Server Index Format、LSIFとは、Formatと書いてあるとおりに、特定の情報を保存する際の取り決めを指します。よって、その恩恵を受けるためにはLSIFに基づくデータを構築するジェネレータや、そのデータを理解し実際にユーザの支援を行うツールや拡張機能、Web UIが必要になります。これらについては後ほど話します。

本題のどのような情報を保存しておくべきか、という点ですが、これはLSPが定義する機能のうち、コードリーディングに役立つものを対象に、Language Serverへリクエストを投げた場合のレスポンスを全てまとめたもの、になります。

例えばファイルaのx行目にある変数 `foo` を対象に `Goto Definition` のリクエストを行ったら "ファイルbのy行目に定義がある" といったレスポンスが得られるとします。このときLSIFのジェネレータは、ソースコード中の `Goto Definition` の対象である全ての部分（変数や関数呼び出し等）と、それに対応するレスポンスの一覧を情報として保存します。また、 `Document Symbols` のようなドキュメント全体を対象としたリクエストも含め、ジェネレータが対応する全ての種類のリクエストに対するレスポンスがLSIFの定義する様式に則って保存されます。

実際には、愚直に全ての考えられるリクエストに対するレスポンスを保存すると膨大な量になってしまうため、リクエストのパラメータとなるファイル上での位置やファイル名、レスポンスの内容はグラフのノードとして表現され、リクエストの種類やファイルと位置の関係性はそれらをつなぐエッジとして表現されます。これにより重複したレスポンス内容などをひとまとめに扱うことができます。

![](https://code.visualstudio.com/assets/blogs/2019/02/19/hoverResult.png)
(画像は[こちら](https://code.visualstudio.com/blogs/2019/02/19/lsif)より引用)

# LSIF対応ツール

LSIFそのものが定義中ということもあり、対応ツールの多くは試験的なものが多いです。公式が開発している[TypeScript向けのジェネレータ](https://github.com/Microsoft/lsif-typescript)や[Visual Studio Code用拡張機能](https://github.com/Microsoft/vscode-lsif-extension)の他、現在利用可能なツールはSourceGraphがオープンソースで運営している[LSIF.dev](https://lsif.dev)上で確認できます。

将来的にはGitHubを中心としたコード共有サイトやQiitaなどの質問サイトでもLSIFの恩恵を受けられるようになるのではないでしょうか。

# 最後に

現在、LSIFプロジェクトはユーザからのフィードバックを募集しています。今の所、LSIFの実質的なリファレンス実装であるlsif-nodeリポジトリの[issueに投稿](https://github.com/microsoft/lsif-node/labels/discussion)するのが良いかと思われます。

LSP / LSIFを盛り上げてより良い開発環境を作り上げて行きましょう！！

[^1]: GitHubは現在一部の言語で[ナビゲーション機能を提供](https://code.visualstudio.com/blogs/2018/12/04/rich-navigation)していますが、LSIFはこれを超えて全ての言語で同様の機能を提供することを目指しています。