Class: RubyLsp::Rails::CodeLens
- Inherits:
-
Object
- Object
- RubyLsp::Rails::CodeLens
- Includes:
- Requests::Support::Common, ActiveSupportTestCaseHelper
- Defined in:
- lib/ruby_lsp/ruby_lsp_rails/code_lens.rb
Overview

This feature adds Code Lens features for Rails applications.
For Active Support test cases:
-
Run tests in the VS Terminal
-
Run tests in the VS Code Test Explorer
-
Debug tests
-
Run migrations in the VS Terminal
For Rails controllers:
-
See the path corresponding to an action
-
Click on the action’s Code Lens to jump to its declaration in the routes.
Note: This depends on a support for the ‘rubyLsp.openFile` command. For the VS Code extension this is built-in, but for other editors this may require some custom configuration.
The [code lens](microsoft.github.io/language-server-protocol/specification#textDocument_codeLens) request informs the editor of runnable commands such as tests. It’s available for tests which inherit from ‘ActiveSupport::TestCase` or one of its descendants, such as `ActionDispatch::IntegrationTest`.
# Example:
For the following code, Code Lenses will be added above the class definition above each test method.
“‘ruby Run class HelloTest < ActiveSupport::TestCase # <- Will show code lenses above for running or debugging the whole test
test "outputs hello" do # <- Will show code lenses above for running or debugging this test
# ...
end
test "outputs goodbye" do # <- Will show code lenses above for running or debugging this test
# ...
end
end “‘
# Example: “‘ruby Run class AddFirstNameToUsers < ActiveRecord::Migration
# ...
end “‘
The code lenses will be displayed above the class and above each test method.
Note: When using the Test Explorer view, if your code contains a statement to pause execution (e.g. ‘debugger`) it will cause the test runner to hang.
For the following code, assuming the routing contains ‘resources :users`, a Code Lens will be seen above each action.
“‘ruby class UsersController < ApplicationController
GET /users(.:format)
def index # <- Will show code lens above for the path
end
end “‘
Note: Complex routing configurations may not be supported.
Instance Method Summary collapse
-
#initialize(client, global_state, response_builder, uri, dispatcher) ⇒ CodeLens
constructor
: (RunnerClient, GlobalState, ResponseBuilders::CollectionResponseBuilder, URI::Generic, Prism::Dispatcher) -> void.
-
#on_call_node_enter(node) ⇒ Object
: (Prism::CallNode node) -> void.
-
#on_class_node_enter(node) ⇒ Object
: (Prism::ClassNode node) -> void.
-
#on_class_node_leave(node) ⇒ Object
: (Prism::ClassNode node) -> void.
-
#on_def_node_enter(node) ⇒ Object
Although uncommon, Rails tests can be written with the classic “def test_name” syntax.
-
#on_module_node_enter(node) ⇒ Object
: (Prism::ModuleNode node) -> void.
-
#on_module_node_leave(node) ⇒ Object
: (Prism::ModuleNode node) -> void.
Methods included from ActiveSupportTestCaseHelper
Constructor Details
#initialize(client, global_state, response_builder, uri, dispatcher) ⇒ CodeLens
: (RunnerClient, GlobalState, ResponseBuilders::CollectionResponseBuilder, URI::Generic, Prism::Dispatcher) -> void
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/ruby_lsp/ruby_lsp_rails/code_lens.rb', line 79 def initialize(client, global_state, response_builder, uri, dispatcher) @client = client @global_state = global_state @response_builder = response_builder @path = uri.to_standardized_path #: String? @group_id = 1 #: Integer @group_id_stack = [] #: Array[Integer] @constant_name_stack = [] #: Array[[String, String?]] dispatcher.register( self, :on_call_node_enter, :on_class_node_enter, :on_def_node_enter, :on_class_node_leave, :on_module_node_enter, :on_module_node_leave, ) end |
Instance Method Details
#on_call_node_enter(node) ⇒ Object
: (Prism::CallNode node) -> void
100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/ruby_lsp/ruby_lsp_rails/code_lens.rb', line 100 def on_call_node_enter(node) # Remove this method once the rollout is complete return if @global_state.enabled_feature?(:fullTestDiscovery) content = extract_test_case_name(node) return unless content line_number = node.location.start_line command = "#{test_command} #{@path}:#{line_number}" add_test_code_lens(node, name: content, command: command, kind: :example) end |
#on_class_node_enter(node) ⇒ Object
: (Prism::ClassNode node) -> void
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/ruby_lsp/ruby_lsp_rails/code_lens.rb', line 133 def on_class_node_enter(node) class_name = node.constant_path.slice superclass_name = node.superclass&.slice # We need to use a stack because someone could define a nested class # inside a controller. When we exit that nested class declaration, we are # back in a controller context. This part is used in other places in the LSP @constant_name_stack << [class_name, superclass_name] # Remove this entire if block once the rollout is complete if class_name.end_with?("Test") && !@global_state.enabled_feature?(:fullTestDiscovery) fully_qualified_name = @constant_name_stack.map(&:first).join("::") command = "#{test_command} #{@path} --name \"/#{Shellwords.escape(fully_qualified_name)}(#|::)/\"" add_test_code_lens(node, name: class_name, command: command, kind: :group) @group_id_stack.push(@group_id) @group_id += 1 end if @path && superclass_name&.start_with?("ActiveRecord::Migration") command = "#{migrate_command} VERSION=#{migration_version}" add_migrate_code_lens(node, name: class_name, command: command) end end |
#on_class_node_leave(node) ⇒ Object
: (Prism::ClassNode node) -> void
158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/ruby_lsp/ruby_lsp_rails/code_lens.rb', line 158 def on_class_node_leave(node) class_name = node.constant_path.slice if class_name.end_with?("Test") @group_id_stack.pop end # Remove everything but the `@constant_name_stack.pop` once the rollout is complete return if @global_state.enabled_feature?(:fullTestDiscovery) @constant_name_stack.pop end |
#on_def_node_enter(node) ⇒ Object
Although uncommon, Rails tests can be written with the classic “def test_name” syntax. : (Prism::DefNode node) -> void
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/ruby_lsp/ruby_lsp_rails/code_lens.rb', line 114 def on_def_node_enter(node) # Remove this entire unless block once the rollout is complete unless @global_state.enabled_feature?(:fullTestDiscovery) method_name = node.name.to_s if method_name.start_with?("test_") line_number = node.location.start_line command = "#{test_command} #{@path}:#{line_number}" add_test_code_lens(node, name: method_name, command: command, kind: :example) end end if controller? add_route_code_lens_to_action(node) add_jump_to_view(node) end end |
#on_module_node_enter(node) ⇒ Object
: (Prism::ModuleNode node) -> void
171 172 173 |
# File 'lib/ruby_lsp/ruby_lsp_rails/code_lens.rb', line 171 def on_module_node_enter(node) @constant_name_stack << [node.constant_path.slice, nil] end |
#on_module_node_leave(node) ⇒ Object
: (Prism::ModuleNode node) -> void
176 177 178 |
# File 'lib/ruby_lsp/ruby_lsp_rails/code_lens.rb', line 176 def on_module_node_leave(node) @constant_name_stack.pop end |