Ruby LSPのコードナビゲーションで強化された主な機能: 2024年上半期(翻訳)
2024年上半期のRuby LSPは、インデクサーが強化されたことで特にIDEでのコードナビゲーション方面が大幅に強化されました。
本記事では、Ruby LSPで行われたコードナビゲーション関連の主な機能強化について詳しく解説します。また、近いうちにリリースされる実験的機能についても触れます。
原注
Ruby LSPサーバー(ruby-lsp
gem)は、さまざまなエディタと統合可能ですが、本記事で扱う機能は、すべてVS CodeとRuby LSP
拡張をmacOS上で動かしたものです。
そのため、VS Code以外のエディタでは一部の機能が同じように動作しない可能性や、開発環境によってはキーボードショートカットが異なる可能性もあります。
🔗 1: Ruby LSPで行うコードナビゲーション
本記事ではエディタのコードナビゲーション機能に重点を置いているので、これらの機能を詳しく見ていくことにしましょう。
本記事におけるコードナビゲーションは、「ホバー(マウスオーバーとも)」「定義に移動」「コード補完」「メソッドシグネチャのヘルプ」というエディタの4大機能を指します。これらの機能に言語サーバー(language server)を組み合わせると、開発者の生産性が大幅に向上します。
🔗 ホバーによるドキュメント表示
ホバー(hover)機能は、エディタ上で定数やメソッドにカーソルをかざすとコメントやドキュメントをツールチップに表示する機能です。
VS Codeの場合、コマンド(⌘
)キーを押しながらホバーすると、その対象で可能な定義元を探索するdefinition
リクエストも送信されます。
また、対象の定義元が1つしか見つからない場合(例: クラスが複数の場所で再オープンされていない場合)は、対象のソースコードをツールチップに表示します。
🔗 定義に移動
定義に移動(Go-to-definition)機能は、対象の定数やメソッドの定義に移動します。移動先が、そのプロジェクト内や依存関係内にあるかどうかにかかわらず移動できます。
この機能は、以下のいずれかの方法でトリガーできます。
- 対象を右クリックして[定義に移動](Go to Definition)を選択する
- 対象にカーソルをかざして
F12
キーを押す - 対象をコマンド(
⌘
)キーを押しながらクリックする
定義が1個の場合:
定義元に直接ジャンプします。
定義が複数ある場合:
すべての定義元リストが表示され、その横にソースコードのプレビューウィンドウが表示されます。
🔗 コード補完
コード補完は、ユーザーが入力中のテキストが特定のインデックス済みコンポーネントと一致するときに補完候補を表示する機能です。これにより、メソッド名や定数名を丸ごと手入力することが減り、コーディングをスピードアップできます。また、どんな定数やメソッドが利用可能なのかを探すのにも使えます。
🔗 メソッドシグネチャのヘルプ
メソッドシグネチャのヘルプは、メソッドのパラメータに関するヒントを表示するためのもので、多くの場合メソッドを入力した直後に表示されます。この機能は、メソッドにわたすことが期待される引数を理解してコードの精度を向上させるのに非常に役立ちます。
🔗 2: コードナビゲーションで強化された点
それでは、コードナビゲーションで行われた改良点を詳しく見ていくことにしましょう。
🔗 シングルトンメソッド
シングルトンメソッドのあらゆるコードナビゲーション機能が大幅に改善されました。
🔗 ローカル変数
Ruby LSPでローカル変数のコード補完がサポートされるようになりました。この改善によって、コード内における変数のスコープや利用法を把握しやすくなって間違いが減り、コードの読みやすさも向上します。
🔗 継承やミックスインのサポート
Ruby LSPのインデクサーがプログラムの継承階層を推測できるようになり、メソッドや変数や定数の定義を先祖リスト経由で探索可能になりました。スーパークラスやミックスイン(pretend
、include
、extend
)も探索できます。
Rubyのautoload
機能では、最終的にどのファイルが必要になるかを正確に決定することが難しいため(継承チェインのビルド方法に影響する可能性がある)、この機能で得られるのはあくまで推測値です。
この改良によって、これまで実現が難しかった以下の機能強化が新たに可能になりました。
- シングルトンメソッド
シングルトンメソッドで可能なすべてのコードナビゲーションが、継承されたシングルトンメソッドでも使えるようになりました。 -
super
「ホバー」機能や「定義に移動」機能がsuper
キーワードでもサポートされ、メソッドの呼び出しチェインを追いかけやすくなりました。 -
スーパークラスやミックスインで定義されたメソッド
スーパークラスやミックスインを経由して定義されたメソッドでも、すべてのコードナビゲーションがサポートされるようになり、継承構造を把握しやすくなりました。 -
インスタンス変数やクラスインスタンス変数
「ホバー」機能や「定義に移動」機能や「コード補完」機能が、インスタンス変数やクラスインスタンス変数(継承された変数も含む)でもサポートされるようになりました。
これらの新機能のいくつかは以下の動画で紹介されています。
制限事項:
- デフォルトのRuby LSPは、コードベースや依存関係の全体をインデックス化しますが、アプリケーション内でその中のどのファイルが最終的に必要とされるかについては認識しません。クラスやモジュールやメソッド定義が「ホバー」機能や「コード補完」機能に表示されていても、それらがアプリケーションの実行時には使われない可能性もあります。
- ナビゲーション機能は大幅に改善されつつありますが、「ランタイムでのミックスイン操作」や「
instance_eval
、module_eval
、class_eval
で追加されたミックスイン」などではいくつかの制限事項がまだ残っています。Railsのconcernsなどについては、アドオン経由でサポートする計画があります。
🔗 Rubyコアのクラス/モジュール/メソッド
String
やEnumerable
などのRubyのコアクラスやコアモジュールについても、Ruby LSPでさまざまなコードナビゲーション機能が提供されるようになりました。
- ホバーでドキュメントを表示
- 定義に移動で対応するRBS宣言にジャンプ
- コード補完で候補を表示
- メソッドシグネチャのヘルプでメソッド呼び出しのシグネチャ情報を表示
制限事項:
現時点のRuby LSPは、String
やArray
のようなクラス定数リテラルのドキュメントは表示できますが、"str"
や[1, 2, 3]
といったリテラルについてはまだドキュメントを表示できません。
私たちはこれらの機能を有効にするための型推論器を構築中で、リテラルのサポートも間もなく提供できる予定です。ご期待ください!
🔗 3: Railsアドオン用のコードナビゲーション改良
Railsアドオンのコードナビゲーション機能についても大幅な改善が行われました。
以下の表は、現在のRailsアドオンで利用可能なナビゲーション機能と、接続先コンポーネントをまとめたものです。
ナビゲーション機能 | 移動元 | 移動先 | 説明 |
---|---|---|---|
定義に移動 | モデル | モデル | 関連付け名から他のモデルに移動する |
CodeLens | コントローラ | ビュー | アクションメソッドから対応するビューにジャンプする |
定義に移動 | コントローラ | ルーティング | ルーティングヘルパーから関連するルーティングに移動する |
CodeLens | コントローラ | ルーティング | アクションメソッドから関連するルーティングにジャンプする |
定義に移動 | コントローラ | モデル | 定数名から関連するモデルに移動する |
定義に移動 | ビュー | モデル | 定数名から関連するモデルに移動する |
定義に移動 | ビュー | ルーティング | ルーティングヘルパーから関連するルーティングに移動する |
原注
CodeLensは、VS Codeエディタのコード上に表示されるクリック可能な項目です。これはコードナビゲーション固有の機能ではありませんが、Railsアドオンではコントローラからのジャンプをトリガーするのに使われます。
それでは個別の改善点を見ていくことにしましょう。
🔗 Active Recordモデルのコールバックや関連付けでの「定義に移動」機能
Active Recordコールバックの定義に移動したり、互いに関連付けられているモデル同士を行き来したりできるようになります。
🔗 ルーティングやビューへの移動
Railsアドオンでは、「コントローラ」「ルーティング定義」「ビュー」の間を移動する新しい方法が2種類提供されるようになりました。
- 定義に移動: ルーティングヘルパーの定義に移動する場合
- CodeLens: ルーティングの定義やビューファイルに移動する場合
🔗 .erb
ファイル内のコードナビゲーション
Ruby LSPに.erb
ファイルのコードナビゲーション機能も追加され、.erb
ファイル内ですべてのコードナビゲーションが使えるようになりました。
(技術的には、この機能強化はRails専用というわけではありませんが、ビューファイルですべてのコードナビゲーション機能が使えるようになるので、Railsの開発エクスペリエンスが大きく向上します)
🔗 4: 実験的機能
Ruby LSPチームは、開発エクスペリエンスを向上させるために常に新機能の開発に取り組んでいます。現在開発が進められている実験的機能をいくつか紹介します。
「先祖階層リクエスト」機能
継承の先祖階層リクエスト機能は、Rubyコード内の継承階層をより深く把握できるようにするためのものです。この機能を用いることで、クラスやモジュールの継承関係をトラッキングして以下のような作業をやりやすくできます。
- クラスやモジュールの継承関係をビジュアル表示する
- 継承チェインを素早く行き来する
🔗 実験的である理由
この機能は、language serverプロトコルのType Hierarchy Supertypes LSP requestによってサポートされています。この機能の実装中にRubyに適用しようとしたとき、いくつかの曖昧な点が発生しました。
- このリストに含めるのは「クラスのみ(純粋な継承チェイン)」にすべきか「モジュールも含める(現在の振る舞い)」べきか。
- シングルトンクラスは継承チェイン内でどのようにトリガー・表示すべきか。
- クラスやモジュールの再オープンが複数回行われると、継承リストにも複数回表示されてしまう。このリストは実際のアプリケーションでは非常に長くなる可能性がある。
私たちはLSPメンテナー側に説明を求めるissue(#1984)を作成しました。メンテナーからの返答や皆さんからのフィードバックに基づいて上述の設計や振る舞いを調整する予定です。
🔗「推測型」機能
推測型(guessed types)はRuby LSPに実験的に追加された機能で、レシーバーの型を識別子名ベースで決定することを試みます。これにより型情報が提供され、コード補完やナビゲーションの改善に役立ちます。
この機能はデフォルトでは無効ですが、VS CodeのrubyLsp.enableExperimentalFeatures
オプションの設定で有効にできます。
🔗 使い方
Ruby LSPは、変数の識別子名をクラスと照合する形で変数の型を推測します。たとえば、user
という名前の変数には、User
という型が代入されると推測できます(User
クラスが存在する場合)。
user.name # `User`型になると推測する
@post.like! # `Post`型になると推測する
Ruby LSPが変数の型を推測することで、コードナビゲーション機能をより多くのケースに拡張できるようになります。
🔗 重要な注意事項
- 識別子は複雑な型を表現するには適していないので、名前が一致しないと解釈ミスにつながりやすくなります
- この機能を使うだけのために識別子名を変更することは推奨されません
詳しくは以下のドキュメントを参照してください。
参考: ruby-lsp/DESIGN_AND_ROADMAP.md at main · Shopify/ruby-lsp
🔗 まとめ
2024年上半期のRuby LSPには、特にコードナビゲーション方面が大幅に強化されました。
Ruby LSPは、シングルトンメソッドやローカル変数や継承のサポートからERBサポートまで、Ruby開発者を楽にするためにさまざまな進化を繰り返しています。
Railsアドオンも大幅に強化され、モデルやビューやコントローラ間を移動しやすくなりました。先祖階層リクエストや推測型などの実験的な機能によって、Ruby LSPへの新しいLSP機能の導入や創造的な方法の模索が続けられています。
Ruby LSPプロジェクトは主にShopifyのRuby Developer Experienceチームによって推進されていますが、私たちが目指しているのは、Rubyコミュニティ全体にとって有用なオープンソースプロジェクトとして運営し続けることです。そういうわけで、Ruby LSPプロジェクトにissueやフィードバックを送ったり、Ruby LSPプロジェクトやRailsアドオンのコードに貢献いただいているすべての皆さんに感謝したいと思います。特に、Shopify以外のコントリビュータとしてプロジェクトに継続的に貢献いただいている@snutijおよび@Earlopainに感謝いたします。
皆さんにも、Ruby LSPの新機能をぜひお試しいただき、GitHub issuesやRuby DX Slackまでフィードバックをお寄せください。
概要
CC BY-NC-SA 4.0 Deedに基づいて翻訳・公開いたします。
原文公開日: 2024/07/23
CC BY-NC-SA 4.0 Deed | 表示 - 非営利 - 継承 4.0 国際 | Creative Commons
日本語タイトルは内容に即したものにしました。
原文の目次は省略しました。