Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve detection for rails as the testing framework #2161

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 deletions lib/ruby_lsp/global_state.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# typed: strict
# frozen_string_literal: true

require "ruby_lsp/listeners/rails_app"

module RubyLsp
class GlobalState
extend T::Sig
Expand Down Expand Up @@ -133,9 +135,9 @@ def detect_test_library(dependencies)
"rspec"
# A Rails app may have a dependency on minitest, but we would instead want to use the Rails test runner provided
# by ruby-lsp-rails. A Rails app doesn't need to depend on the rails gem itself, individual components like
# activestorage may be added to the gemfile so that other components aren't downloaded. Check for the presence
# of bin/rails to support these cases.
elsif bin_rails_present
# activestorage may be added to the gemfile so that other components aren't downloaded. Check for inheritance of
# `Rails::Application` in config/application.rb to support these cases.
elsif rails_app?
"rails"
# NOTE: Intentionally ends with $ to avoid mis-matching minitest-reporters, etc. in a Rails app.
elsif dependencies.any?(/^minitest$/)
Expand Down Expand Up @@ -166,8 +168,24 @@ def detect_typechecker(dependencies)
end

sig { returns(T::Boolean) }
def bin_rails_present
File.exist?(File.join(workspace_path, "bin/rails"))
def rails_app?
config = rails_app_config
return false unless config

result = Prism.parse(config)
return false unless result

dispatcher = Prism::Dispatcher.new
listener = Listeners::RailsApp.new(dispatcher)
dispatcher.dispatch(result.value)

listener.rails_app
Comment on lines +178 to +182
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would probably have done it with a simple regex, but your solution looks much cleaner 👌🏼

end

sig { returns(T.nilable(String)) }
def rails_app_config
path = File.join(workspace_path, "config/application.rb")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if it's possible that the file is not right there, but would be nested deeper? I'm not too familiar with the different way people configure their project in relation with the workspace. At the same time, it might be best to stay with the simple solution.

Copy link
Contributor

@andyw8 andyw8 Jun 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's possible in theory for it to be in other locations, but would be very unusual, so I don't think we should worry about that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(and we already require that the Rails app is at the root of a workspace, so that's not a concern).

File.read(path) if File.exist?(path)
end

sig { returns(T::Boolean) }
Expand Down
28 changes: 28 additions & 0 deletions lib/ruby_lsp/listeners/rails_app.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# typed: strict
# frozen_string_literal: true

module RubyLsp
module Listeners
class RailsApp
extend T::Sig

sig { returns(T::Boolean) }
attr_reader :rails_app

sig { params(dispatcher: Prism::Dispatcher).void }
def initialize(dispatcher)
@rails_app = T.let(false, T::Boolean)
dispatcher.register(self, :on_class_node_enter)
end

sig { params(node: Prism::ClassNode).void }
def on_class_node_enter(node)
superclass = node.superclass
case superclass
when Prism::ConstantPathNode
@rails_app = true if superclass.full_name == "Rails::Application"
end
end
end
end
end
24 changes: 22 additions & 2 deletions test/global_state_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,35 @@ def test_detects_test_unit
assert_equal("test-unit", state.test_library)
end

def test_detects_rails_if_minitest_is_present_and_bin_rails_exists
def test_detects_rails_if_minitest_is_present_and_rails_file_exists
stub_direct_dependencies("minitest" => "1.2.3")

state = GlobalState.new
state.stubs(:bin_rails_present).returns(true)
state.stubs(:rails_app_config).returns(<<~RUBY)
module MyApp
class Application < Rails::Application
end
end
RUBY

state.apply_options({})
assert_equal("rails", state.test_library)
end

def test_doesnt_detects_rails_if_minitest_is_present_and_rails_file_contains_bogus
stub_direct_dependencies("minitest" => "1.2.3")

state = GlobalState.new
state.stubs(:rails_app_config).returns(<<~RUBY)
puts "Hello World!"
class Test
end
RUBY

state.apply_options({})
assert_equal("minitest", state.test_library)
end

def test_detects_rspec_if_both_rails_and_rspec_are_present
stub_direct_dependencies("rspec" => "1.2.3")

Expand Down
Loading