Skip to content

Commit

Permalink
Add code lens for running migrations
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
gmcgibbon committed Jun 27, 2024
1 parent 5704d93 commit 3c76670
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 2 deletions.
47 changes: 47 additions & 0 deletions lib/ruby_lsp/ruby_lsp_rails/code_lens.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module Rails
# - Run tests in the VS Terminal
# - Run tests in the VS Code Test Explorer
# - Debug tests
# - Run migrations in the VS Terminal
#
# The
# [code lens](https://microsoft.github.io/language-server-protocol/specification#textDocument_codeLens)
Expand All @@ -34,6 +35,14 @@ module Rails
# end
# ````
#
# # Example:
# ```ruby
# Run in terminal
# class AddFirstNameToUsers < ActiveRecord::Migration[7.1]
# # ...
# 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
Expand All @@ -43,6 +52,8 @@ class CodeLens
include Requests::Support::Common
include ActiveSupportTestCaseHelper

MIGRATE_COMMAND = "bin/rails db:migrate"

sig do
params(
response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CodeLens],
Expand Down Expand Up @@ -74,6 +85,7 @@ def on_call_node_enter(node)
sig { params(node: Prism::DefNode).void }
def on_def_node_enter(node)
method_name = node.name.to_s

if method_name.start_with?("test_")
line_number = node.location.start_line
command = "#{test_command} #{@path}:#{line_number}"
Expand All @@ -84,12 +96,19 @@ def on_def_node_enter(node)
sig { params(node: Prism::ClassNode).void }
def on_class_node_enter(node)
class_name = node.constant_path.slice
superclass_name = node.superclass&.slice

if class_name.end_with?("Test")
command = "#{test_command} #{@path}"
add_test_code_lens(node, name: class_name, command: command, kind: :group)
@group_id_stack.push(@group_id)
@group_id += 1
end

if superclass_name&.start_with?("ActiveRecord::Migration")
command = "#{MIGRATE_COMMAND} VERSION=#{migration_version}"
add_migrate_code_lens(node, name: class_name, command: command)
end
end

sig { params(node: Prism::ClassNode).void }
Expand All @@ -111,6 +130,34 @@ def test_command
end
end

sig { returns(T.nilable(String)) }
def migration_version
File.basename(T.must(@path)).split("_").first
end

sig { params(node: Prism::Node, name: String, command: String).void }
def add_migrate_code_lens(node, name:, command:)
return unless @path

arguments = [
command,
{
start_line: node.location.start_line - 1,
start_column: node.location.start_column,
end_line: node.location.end_line - 1,
end_column: node.location.end_column,
},
]

@response_builder << create_code_lens(
node,
title: "Run in terminal",
command_name: "rubyLsp.runMigrationInTerminal",
arguments: arguments,
data: { type: "migrate" },
)
end

sig { params(node: Prism::Node, name: String, command: String, kind: Symbol).void }
def add_test_code_lens(node, name:, command:, kind:)
return unless @path
Expand Down
18 changes: 16 additions & 2 deletions test/ruby_lsp_rails/code_lens_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -273,10 +273,24 @@ class Test < ActiveSupport::TestCase
assert_equal("ruby bin/rails test /fake.rb", response[0].command.arguments[2])
end

test "recognizes migrations" do
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

assert_equal(1, response.size)
assert_match("Run in terminal", response[0].command.title)
assert_equal("bin/rails db:migrate VERSION=123456", response[0].command.arguments[0])
end

private

def generate_code_lens_for_source(source)
with_server(source) do |server, uri|
def generate_code_lens_for_source(source, file: "/fake.rb")
with_server(source, URI(file)) do |server, uri|
server.process_message(
id: 1,
method: "textDocument/codeLens",
Expand Down

0 comments on commit 3c76670

Please sign in to comment.