2022-10-24

プロダクトコードとテストコード間をジャンプする機能を rspec.nvim に実装した

以下の gif のように、プロダクトコードとテストコード間をジャンプできる機能を rspec.nvim に実装した。 :RSpecJump というコマンドでジャンプできる。

RSpecJumpのデモ

※ rspec.nvim については 前回の記事 で紹介しています。

実装した動機

アプリケーション開発をしている中で、プロダクトコードから関連するテストコード、逆にテストコードから関連するプロダクトコードへの行き来、というのは頻繁に行うと思う。

しかしながら単純に行き来しようとすると面倒で、特にプロジェクトが大きくなってファイル数が増えると同名のファイルも出てきたりして、どんどん探しづらくなると思う。ファイルエクスプローラから該当するファイルを目視で探すのは辛すぎるし、telescope.nvim のような Fuzzy Finder を使っても1つに絞れるようにタイプしながら探すのはなかなかに手間がかかる。

自分は主に Ruby, Rails によく触れるので、rails.vim というプラグインを使っており、rails.vim が提供する :A コマンドでプロダクトコードとテストコード間のジャンプをしていた。この機能をとても気に入っていて、でも rails ではなく rspec を単体で使う場合(例えばgemとか)でも便利だよなと思い、自作の rspec.nvim にも持ってくることにした。

あわよくば、自分のプラグインリストから rails.vim を外してしまうことも狙って実装することにした (利用するプラグインは必要最小限にしたい派です)。

実装しながら考えたこと

基本方針として、開いているファイル(バッファ)の名称から対応するテストコードもしくはプロダクトコードを推測し、実際に存在した場合にジャンプするという実装になっている。

ここでは プロダクトコード -> テストコードのジャンプ を例とするが、最も単純なケースでは、spec ディレクトリ以下にプロダクトコードと同じ階層構造にあるものとし、ファイル名の末尾を _spec.rb に変換するだけでうまくいく。

foo/bar.rb -> spec/foo/bar_spec.rb

しかし、gem の場合は lib/ 以下に配置する規則があるので、 libspec に置き換えるようにする必要がある。

lib/foo/bar.rb -> spec/foo/bar_spec.rb

同じように Rails アプリケーションの場合は app/ 以下を追加で考慮する必要がある。

app/models/foo.rb -> spec/models/foo_spec.rb

さらに、Railsには特殊な命名規則の spec があり、特別な考慮が必要だった。
例えば Request spec は、 app/controllers/foo_controller.rbspec/requests/foo_spec.rb に変換したい。

app/controllers/foo_controller.rb -> spec/requests/foo_spec.rb

また、すでに非推奨ではあるが、Controller spec を使っている可能性も考慮したいので、controllerファイルの場合は Request spec と Controller spec の両方を推測できるようにしている。

app/controllers/foo_controller.rb -> spec/requests/foo_spec.rb
                                  -> spec/controllers/foo_controller_spec.rb

Request specの例のように複数の候補が推測されることがあるので、1つに決め打ちはせず、複数のファイルを候補として持ちつつ、最後に優先順位に沿って探索して、実際に存在したファイルへジャンプする方式をとった。

このような方式なため、特殊なディレクトリ構造のフレームワークは現状うまく動かない。軽く調べたところ Hanamiapps というディレクトリを持っているが、この変換には対応していない。この対応自体は簡単なのだが、実際に Hanami を使っていないので、中途半端にやるよりはやらないほうがいいかと思い手を付けなかった。

次に考えていること

RSpecJump の派生として、推測したファイルがいずれも存在しなかった場合に、ファイルの作成をしてくれる force オプション的な機能を実装したいと思っている。

新規でファイルを追加した場合に特に便利なはず!少なくとも自分はほしい。