Repository: Shopify/ruby-lsp-rails (Ruby)

Issue:
Controller code lens doesn't work alongside RSpec
### Description

I use vs code inside docker with ruby-lsp. Everything works find except code lens:
```
2024-07-15 11:40:36.550 [info] (test-app) [Trace - 11:40:36 AM] Sending request 'textDocument/codeLens - (10)'.
2024-07-15 11:40:36.550 [info] (test-app) Params: {
    "textDocument": {
        "uri": "file:///test-app/app/controllers/posts_controller.rb"
    }
}
2024-07-15 11:40:36.732 [info] (test-app) [Trace - 11:40:36 AM] Received response 'textDocument/codeLens - (10)' in 182ms.
2024-07-15 11:40:36.732 [info] (test-app) Result: []
```

I created a new rails app and scaffolded a PostController. So, nothing unusual here:
```ruby
class PostsController < ApplicationController
  before_action :set_post, only: %i[ show edit update destroy ]

  # GET /posts or /posts.json
  def index
    @posts = Post.all
  end

  # GET /posts/1 or /posts/1.json
  def show
  end

  # GET /posts/new
  def new
    @post = Post.new
  end

  # GET /posts/1/edit
  def edit
  end

  # POST /posts or /posts.json
  def create
    @post = Post.new(post_params)

    respond_to do |format|
      if @post.save
        format.html { redirect_to post_url(@post), notice: "Post was successfully created." }
        format.json { render :show, status: :created, location: @post }
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /posts/1 or /posts/1.json
  def update
    respond_to do |format|
      if @post.update(post_params)
        format.html { redirect_to post_url(@post), notice: "Post was successfully updated." }
        format.json { render :show, status: :ok, location: @post }
      else
        format.html { render :edit, status: :unprocessable_entity }
        format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /posts/1 or /posts/1.json
  def destroy
    @post.destroy!

    respond_to do |format|
      format.html { redirect_to posts_url, notice: "Post was successfully destroyed." }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_post
      @post = Post.find(params[:id])
    end

    # Only allow a list of trusted parameters through.
    def post_params
      params.require(:post).permit(:title, :body)
    end
end
```

```
GEM
  remote: https://rubygems.org/
  specs:
    actioncable (7.1.3.4)
      actionpack (= 7.1.3.4)
      activesupport (= 7.1.3.4)
      nio4r (~> 2.0)
      websocket-driver (>= 0.6.1)
      zeitwerk (~> 2.6)
    actionmailbox (7.1.3.4)
      actionpack (= 7.1.3.4)
      activejob (= 7.1.3.4)
      activerecord (= 7.1.3.4)
      activestorage (= 7.1.3.4)
      activesupport (= 7.1.3.4)
      mail (>= 2.7.1)
      net-imap
      net-pop
      net-smtp
    actionmailer (7.1.3.4)
      actionpack (= 7.1.3.4)
      actionview (= 7.1.3.4)
      activejob (= 7.1.3.4)
      activesupport (= 7.1.3.4)
      mail (~> 2.5, >= 2.5.4)
      net-imap
      net-pop
      net-smtp
      rails-dom-testing (~> 2.2)
    actionpack (7.1.3.4)
      actionview (= 7.1.3.4)
      activesupport (= 7.1.3.4)
      nokogiri (>= 1.8.5)
      racc
      rack (>= 2.2.4)
      rack-session (>= 1.0.1)
      rack-test (>= 0.6.3)
      rails-dom-testing (~> 2.2)
      rails-html-sanitizer (~> 1.6)
    actiontext (7.1.3.4)
      actionpack (= 7.1.3.4)
      activerecord (= 7.1.3.4)
      activestorage (= 7.1.3.4)
      activesupport (= 7.1.3.4)
      globalid (>= 0.6.0)
      nokogiri (>= 1.8.5)
    actionview (7.1.3.4)
      activesupport (= 7.1.3.4)
      builder (~> 3.1)
      erubi (~> 1.11)
      rails-dom-testing (~> 2.2)
      rails-html-sanitizer (~> 1.6)
    activejob (7.1.3.4)
      activesupport (= 7.1.3.4)
      globalid (>= 0.3.6)
    activemodel (7.1.3.4)
      activesupport (= 7.1.3.4)
    activerecord (7.1.3.4)
      activemodel (= 7.1.3.4)
      activesupport (= 7.1.3.4)
      timeout (>= 0.4.0)
    activestorage (7.1.3.4)
      actionpack (= 7.1.3.4)
      activejob (= 7.1.3.4)
      activerecord (= 7.1.3.4)
      activesupport (= 7.1.3.4)
      marcel (~> 1.0)
    activesupport (7.1.3.4)
      base64
      bigdecimal
      concurrent-ruby (~> 1.0, >= 1.0.2)
      connection_pool (>= 2.2.5)
      drb
      i18n (>= 1.6, < 2)
      minitest (>= 5.1)
      mutex_m
      tzinfo (~> 2.0)
    addressable (2.8.7)
      public_suffix (>= 2.0.2, < 7.0)
    ast (2.4.2)
    base64 (0.2.0)
    bigdecimal (3.1.8)
    bindex (0.8.1)
    bootsnap (1.18.3)
      msgpack (~> 1.2)
    builder (3.3.0)
    capybara (3.40.0)
      addressable
      matrix
      mini_mime (>= 0.1.3)
      nokogiri (~> 1.11)
      rack (>= 1.6.0)
      rack-test (>= 0.6.3)
      regexp_parser (>= 1.5, < 3.0)
      xpath (~> 3.2)
    concurrent-ruby (1.3.3)
    connection_pool (2.4.1)
    crass (1.0.6)
    date (3.3.4)
    debug (1.9.2)
      irb (~> 1.10)
      reline (>= 0.3.8)
    drb (2.2.1)
    erubi (1.13.0)
    globalid (1.2.1)
      activesupport (>= 6.1)
    i18n (1.14.5)
      concurrent-ruby (~> 1.0)
    importmap-rails (2.0.1)
      actionpack (>= 6.0.0)
      activesupport (>= 6.0.0)
      railties (>= 6.0.0)
    io-console (0.7.2)
    irb (1.14.0)
      rdoc (>= 4.0.0)
      reline (>= 0.4.2)
    jbuilder (2.12.0)
      actionview (>= 5.0.0)
      activesupport (>= 5.0.0)
    json (2.7.2)
    language_server-protocol (3.17.0.3)
    lint_roller (1.1.0)
    logger (1.6.0)
    loofah (2.22.0)
      crass (~> 1.0.2)
      nokogiri (>= 1.12.0)
    mail (2.8.1)
      mini_mime (>= 0.1.1)
      net-imap
      net-pop
      net-smtp
    marcel (1.0.4)
    matrix (0.4.2)
    mini_mime (1.1.5)
    minitest (5.24.1)
    msgpack (1.7.2)
    mutex_m (0.2.0)
    net-imap (0.4.14)
      date
      net-protocol
    net-pop (0.1.2)
      net-protocol
    net-protocol (0.2.2)
      timeout
    net-smtp (0.5.0)
      net-protocol
    nio4r (2.7.3)
    nokogiri (1.16.6-aarch64-linux)
      racc (~> 1.4)
    nokogiri (1.16.6-x86_64-linux)
      racc (~> 1.4)
    parallel (1.25.1)
    parser (3.3.4.0)
      ast (~> 2.4.1)
      racc
    prism (0.30.0)
    psych (5.1.2)
      stringio
    public_suffix (6.0.0)
    puma (6.4.2)
      nio4r (~> 2.0)
    racc (1.8.0)
    rack (3.1.7)
    rack-session (2.0.0)
      rack (>= 3.0.0)
    rack-test (2.1.0)
      rack (>= 1.3)
    rackup (2.1.0)
      rack (>= 3)
      webrick (~> 1.8)
    rails (7.1.3.4)
      actioncable (= 7.1.3.4)
      actionmailbox (= 7.1.3.4)
      actionmailer (= 7.1.3.4)
      actionpack (= 7.1.3.4)
      actiontext (= 7.1.3.4)
      actionview (= 7.1.3.4)
      activejob (= 7.1.3.4)
      activemodel (= 7.1.3.4)
      activerecord (= 7.1.3.4)
      activestorage (= 7.1.3.4)
      activesupport (= 7.1.3.4)
      bundler (>= 1.15.0)
      railties (= 7.1.3.4)
    rails-dom-testing (2.2.0)
      activesupport (>= 5.0.0)
      minitest
      nokogiri (>= 1.6)
    rails-html-sanitizer (1.6.0)
      loofah (~> 2.21)
      nokogiri (~> 1.14)
    railties (7.1.3.4)
      actionpack (= 7.1.3.4)
      activesupport (= 7.1.3.4)
      irb
      rackup (>= 1.0.0)
      rake (>= 12.2)
      thor (~> 1.0, >= 1.2.2)
      zeitwerk (~> 2.6)
    rainbow (3.1.1)
    rake (13.2.1)
    rbs (3.5.2)
      logger
    rdoc (6.7.0)
      psych (>= 4.0.0)
    regexp_parser (2.9.2)
    reline (0.5.9)
      io-console (~> 0.5)
    rexml (3.3.1)
      strscan
    rubocop (1.64.1)
      json (~> 2.3)
      language_server-protocol (>= 3.17.0)
      parallel (~> 1.10)
      parser (>= 3.3.0.2)
      rainbow (>= 2.2.2, < 4.0)
      regexp_parser (>= 1.8, < 3.0)
      rexml (>= 3.2.5, < 4.0)
      rubocop-ast (>= 1.31.1, < 2.0)
      ruby-progressbar (~> 1.7)
      unicode-display_width (>= 2.4.0, < 3.0)
    rubocop-ast (1.31.3)
      parser (>= 3.3.1.0)
    rubocop-performance (1.21.1)
      rubocop (>= 1.48.1, < 2.0)
      rubocop-ast (>= 1.31.1, < 2.0)
    ruby-lsp (0.17.7)
      language_server-protocol (~> 3.17.0)
      prism (>= 0.29.0, < 0.31)
      rbs (>= 3, < 4)
      sorbet-runtime (>= 0.5.10782)
    ruby-progressbar (1.13.0)
    rubyzip (2.3.2)
    selenium-webdriver (4.22.0)
      base64 (~> 0.2)
      logger (~> 1.4)
      rexml (~> 3.2, >= 3.2.5)
      rubyzip (>= 1.2.2, < 3.0)
      websocket (~> 1.0)
    sorbet-runtime (0.5.11481)
    sprockets (4.2.1)
      concurrent-ruby (~> 1.0)
      rack (>= 2.2.4, < 4)
    sprockets-rails (3.5.1)
      actionpack (>= 6.1)
      activesupport (>= 6.1)
      sprockets (>= 3.0.0)
    sqlite3 (1.7.3-aarch64-linux)
    sqlite3 (1.7.3-x86_64-linux)
    standard (1.39.1)
      language_server-protocol (~> 3.17.0.2)
      lint_roller (~> 1.0)
      rubocop (~> 1.64.0)
      standard-custom (~> 1.0.0)
      standard-performance (~> 1.4)
    standard-custom (1.0.2)
      lint_roller (~> 1.0)
      rubocop (~> 1.50)
    standard-performance (1.4.0)
      lint_roller (~> 1.1)
      rubocop-performance (~> 1.21.0)
    stimulus-rails (1.3.3)
      railties (>= 6.0.0)
    stringio (3.1.1)
    strscan (3.1.0)
    thor (1.3.1)
    timeout (0.4.1)
    turbo-rails (2.0.5)
      actionpack (>= 6.0.0)
      activejob (>= 6.0.0)
      railties (>= 6.0.0)
    tzinfo (2.0.6)
      concurrent-ruby (~> 1.0)
    unicode-display_width (2.5.0)
    web-console (4.2.1)
      actionview (>= 6.0.0)
      activemodel (>= 6.0.0)
      bindex (>= 0.4.0)
      railties (>= 6.0.0)
    webrick (1.8.1)
    websocket (1.2.11)
    websocket-driver (0.7.6)
      websocket-extensions (>= 0.1.0)
    websocket-extensions (0.1.5)
    xpath (3.2.0)
      nokogiri (~> 1.8)
    zeitwerk (2.6.16)

PLATFORMS
  aarch64-linux
  x86_64-linux

DEPENDENCIES
  bootsnap
  capybara
  debug
  importmap-rails
  jbuilder
  puma (>= 5.0)
  rails (~> 7.1.3, >= 7.1.3.4)
  ruby-lsp
  selenium-webdriver
  sprockets-rails
  sqlite3 (~> 1.4)
  standard
  stimulus-rails
  turbo-rails
  tzinfo-data
  web-console

RUBY VERSION
   ruby 3.2.4p170

BUNDLED WITH
   2.4.19
```


Discussion:
@pauldruziak can you check which version of ruby-lsp-rails is active? It should show in the Ruby LSP output panel when it starts up.
@andyw8 
```
2024-07-15 14:07:06.535 [info] (test-app) Initializing Ruby LSP v0.17.7...
```
and just in case
```
    "clientInfo": {
        "name": "Visual Studio Code",
        "version": "1.91.1"
    },
```
@pauldruziak I was asking about `ruby-lsp-rails`, not `ruby-lsp`.

If you don't see it at all, then it may indicate a problem with it detecting that your repo is using Rails. 
@andyw8 oh, right. I can't find any lines related to ruby-lsp-rails. And when I run "Ruby LSP: Display addons" it also shows nothing.
@andyw8 I added `ruby-lsp-rails` to Gemfile, and now I got:
```
2024-07-15 16:55:41.886 [info] (test-app) Activating Ruby LSP Rails addon v0.3.10
```
and code lens start working too
Thanks, you _shouldn't_ need to manually add it though, it is automatically added to the `.ruby-lsp/Gemfile` when Rails is detected:

https://github.com/Shopify/ruby-lsp/blob/e7f54ad7da01b4f9a0a54399d37c8fd15875b4a1/lib/ruby_lsp/setup_bundler.rb#L151-L153

Is there a chance you can try it outside of Docker?
@andyw8 in the test project:

1. After I removed `ruby-lsp` and `ruby-lsp-rails` gems it stars working as expected
2. But then I added `ruby-lsp-rspec` it stopped working again

In the working project: I removed all gems, and after that it stopped working. I see the line `Activating Ruby LSP Rails addon v0.3.10`, but `codeLens` still returns empty results `Result: []`


@pauldruziak I'm seeing that when `ruby-lsp-rspec` is added it does indeed cause the code lenses to no longer appear. Could you please log an issue for that in https://github.com/st0012/ruby-lsp-rspec? (cc @st0012).

I'll close the issue here for now, we can re-open if it turns out changes are needed in Ruby LSP.
^ Ignore the above, @st0012 has identified the cause. We need to make a change in ruby-lsp-rails. I will transfer the issue to that repo, then add further details.
Here's the problem:

Until recently, the only thing that code lenses were used for was on tests. And since ruby-lsp-rails only supports "rails" tests, we return early unless that's the test framework in use:

https://github.com/Shopify/ruby-lsp-rails/blob/e380769e7fb4a53c1310512e606026b206bc5b0e/lib/ruby_lsp/ruby_lsp_rails/addon.rb#L54

But now that we're also using code lenses for other purposes, we need to change this, e.g. to split this behaviour into two listeners, initialized separately.
As a quicker fix, we can also just check for the test library inside code lens and then look into splitting listeners.
There is some problem with rspec, but the original problem is unrelated to RSpec. 

I removed all ruby-lsp gems from the Gemfile, and I see the next line:
```
2024-07-16 10:56:16.880 [info] (wealthbox) Finished initializing Ruby LSP!
Activating Ruby LSP Rails addon v0.3.10
Ruby LSP Rails booting server
```

but it still returns empty results:
```
2024-07-16 10:57:39.571 [info] (project) [Trace - 10:57:39 AM] Sending request 'textDocument/codeLens - (24)'.
2024-07-16 10:57:39.571 [info] (project) Params: {
    "textDocument": {
        "uri": "file:///project/app/controllers/users_controller.rb"
    }
}


2024-07-16 10:57:39.575 [info] (project) [Trace - 10:57:39 AM] Received response 'textDocument/codeLens - (24)' in 4ms.
2024-07-16 10:57:39.575 [info] (project) Result: []
```
@pauldruziak try doing a quick edit in the file.

If a code lens request happens before the Rails runtime integration is running, we may end up caching the empty array as the result for that file. If you close and re-open or if you make an edit, you will bust the cache and it should show up.
@vinistock it doens't help 😕



Patch:
diff --git a/lib/ruby_lsp/ruby_lsp_rails/addon.rb b/lib/ruby_lsp/ruby_lsp_rails/addon.rb
index 39154b2b..fedcb96f 100644
--- a/lib/ruby_lsp/ruby_lsp_rails/addon.rb
+++ b/lib/ruby_lsp/ruby_lsp_rails/addon.rb
@@ -51,9 +51,7 @@ def deactivate
         ).void
       end
       def create_code_lens_listener(response_builder, uri, dispatcher)
-        return unless T.must(@global_state).test_library == "rails"
-
-        CodeLens.new(@client, response_builder, uri, dispatcher)
+        CodeLens.new(@client, T.must(@global_state), response_builder, uri, dispatcher)
       end
 
       sig do
diff --git a/lib/ruby_lsp/ruby_lsp_rails/code_lens.rb b/lib/ruby_lsp/ruby_lsp_rails/code_lens.rb
index ed327cf8..c8e25b68 100644
--- a/lib/ruby_lsp/ruby_lsp_rails/code_lens.rb
+++ b/lib/ruby_lsp/ruby_lsp_rails/code_lens.rb
@@ -79,13 +79,15 @@ class CodeLens
       sig do
         params(
           client: RunnerClient,
+          global_state: GlobalState,
           response_builder:  ResponseBuilders::CollectionResponseBuilder[Interface::CodeLens],
           uri: URI::Generic,
           dispatcher: Prism::Dispatcher,
         ).void
       end
-      def initialize(client, response_builder, uri, dispatcher)
+      def initialize(client, global_state, response_builder, uri, dispatcher)
         @client = client
+        @global_state = global_state
         @response_builder = response_builder
         @path = T.let(uri.to_standardized_path, T.nilable(String))
         @group_id = T.let(1, Integer)
@@ -230,6 +232,7 @@ def add_migrate_code_lens(node, name:, command:)
       sig { params(node: Prism::Node, name: String, command: String, kind: Symbol).void }
       def add_test_code_lens(node, name:, command:, kind:)
         return unless @path
+        return unless @global_state.test_library == "rails"
 
         arguments = [
           @path,


Test patch:
diff --git a/test/ruby_lsp_rails/code_lens_test.rb b/test/ruby_lsp_rails/code_lens_test.rb
index 22fbcee6..26fc7dc1 100644
--- a/test/ruby_lsp_rails/code_lens_test.rb
+++ b/test/ruby_lsp_rails/code_lens_test.rb
@@ -312,6 +312,29 @@ def index
         assert_empty(response)
       end
 
+      test "returns code lenses despite different test framework" do
+        RubyLsp::GlobalState.any_instance.stubs(:test_library).returns("rspec")
+
+        response = generate_code_lens_for_source(<<~RUBY)
+          class UsersController < ApplicationController
+            def index
+            end
+          end
+        RUBY
+
+        refute_empty(response)
+
+        response = generate_code_lens_for_source(<<~RUBY, file: "file://db/migrate/123456_add_first_name_to_users.rb")
+          class AddFirstNameToUsers < ActiveRecord::Migration[7.1]
+            def change
+              add_column(:users, :first_name, :string)
+            end
+          end
+        RUBY
+
+        refute_empty(response)
+      end
+
       private
 
       attr_reader :ruby