Skip to content

Commit 096cb2b

Browse files
committed
Add code lens for running migrations
Adds code lens to run migrations to specific versions in the terminal. This is convenient because it allows developers to quickly rollback or fast-forward to specific schema versions with a click.
1 parent 5afb7a3 commit 096cb2b

File tree

2 files changed

+69
-6
lines changed

2 files changed

+69
-6
lines changed

lib/ruby_lsp/ruby_lsp_rails/code_lens.rb

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ module Rails
99
# - Run tests in the VS Terminal
1010
# - Run tests in the VS Code Test Explorer
1111
# - Debug tests
12+
# - Run migrations in the VS Terminal
1213
#
1314
# The
1415
# [code lens](https://microsoft.github.io/language-server-protocol/specification#textDocument_codeLens)
@@ -31,13 +32,23 @@ module Rails
3132
# end
3233
# ````
3334
#
35+
# # Example:
36+
# ```ruby
37+
# Run in terminal
38+
# class AddFirstNameToUsers < ActiveRecord::Migration[7.1]
39+
# # ...
40+
# end
41+
# ````
42+
#
3443
# The code lenses will be displayed above the class and above each test method.
3544
class CodeLens < ::RubyLsp::Listener
3645
extend T::Sig
3746
extend T::Generic
3847

3948
ResponseType = type_member { { fixed: T::Array[::RubyLsp::Interface::CodeLens] } }
40-
BASE_COMMAND = "bin/rails test"
49+
MIGRATE_COMMAND = "bin/rails db:migrate"
50+
TEST_COMMAND = "bin/rails test"
51+
BASE_COMMAND = TEST_COMMAND # TODO: Deprecate?
4152

4253
sig { override.returns(ResponseType) }
4354
attr_reader :_response
@@ -78,29 +89,37 @@ def on_call_node_enter(node)
7889
return unless content && !content.empty?
7990

8091
line_number = node.location.start_line
81-
command = "#{BASE_COMMAND} #{@path}:#{line_number}"
92+
command = "#{TEST_COMMAND} #{@path}:#{line_number}"
8293
add_test_code_lens(node, name: content, command: command, kind: :example)
8394
end
8495

8596
# Although uncommon, Rails tests can be written with the classic "def test_name" syntax.
8697
sig { params(node: Prism::DefNode).void }
8798
def on_def_node_enter(node)
8899
method_name = node.name.to_s
100+
89101
if method_name.start_with?("test_")
90102
line_number = node.location.start_line
91-
command = "#{BASE_COMMAND} #{@path}:#{line_number}"
103+
command = "#{TEST_COMMAND} #{@path}:#{line_number}"
92104
add_test_code_lens(node, name: method_name, command: command, kind: :example)
93105
end
94106
end
95107

96108
sig { params(node: Prism::ClassNode).void }
97109
def on_class_node_enter(node)
98110
class_name = node.constant_path.slice
111+
superclass_name = node.superclass&.slice
112+
99113
if class_name.end_with?("Test")
100-
command = "#{BASE_COMMAND} #{@path}"
114+
command = "#{TEST_COMMAND} #{@path}"
101115
add_test_code_lens(node, name: class_name, command: command, kind: :group)
102116
end
103117

118+
if superclass_name&.start_with?("ActiveRecord::Migration")
119+
command = "#{MIGRATE_COMMAND} VERSION=#{migration_version}"
120+
add_migrate_code_lens(node, name: class_name, command: command)
121+
end
122+
104123
@group_id_stack.push(@group_id)
105124
@group_id += 1
106125
end
@@ -112,6 +131,36 @@ def on_class_node_leave(node)
112131

113132
private
114133

134+
sig { returns(T.nilable(String)) }
135+
def migration_version
136+
File.basename(T.must(@path)).split("_").first
137+
end
138+
139+
sig { params(node: Prism::Node, name: String, command: String).void }
140+
def add_migrate_code_lens(node, name:, command:)
141+
return unless @path
142+
143+
arguments = [
144+
@path,
145+
name,
146+
command,
147+
{
148+
start_line: node.location.start_line - 1,
149+
start_column: node.location.start_column,
150+
end_line: node.location.end_line - 1,
151+
end_column: node.location.end_column,
152+
},
153+
]
154+
155+
@_response << create_code_lens(
156+
node,
157+
title: "Run in terminal",
158+
command_name: "rubyLsp.runMigrationInTerminal",
159+
arguments: arguments,
160+
data: { type: "migrate" },
161+
)
162+
end
163+
115164
sig { params(node: Prism::Node, name: String, command: String, kind: Symbol).void }
116165
def add_test_code_lens(node, name:, command:, kind:)
117166
return unless @path

test/ruby_lsp_rails/code_lens_test.rb

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,10 +182,24 @@ class NestedTest < ActiveSupport::TestCase
182182
assert_empty(data)
183183
end
184184

185+
test "recognizes migrations" do
186+
response = generate_code_lens_for_source(<<~RUBY, file: "file://db/migrate/123456_add_first_name_to_users.rb")
187+
class AddFirstNameToUsers < ActiveRecord::Migration[7.1]
188+
def change
189+
add_column(:users, :first_name, :string)
190+
end
191+
end
192+
RUBY
193+
194+
assert_equal(1, response.size)
195+
assert_match("Run in terminal", response[0].command.title)
196+
assert_equal("bin/rails db:migrate VERSION=123456", response[0].command.arguments[2])
197+
end
198+
185199
private
186200

187-
def generate_code_lens_for_source(source)
188-
uri = URI("file:///fake.rb")
201+
def generate_code_lens_for_source(source, file: "file:///fake.rb")
202+
uri = URI(file)
189203
store = RubyLsp::Store.new
190204
store.set(uri: uri, source: source, version: 1)
191205

0 commit comments

Comments
 (0)