Skip to content
Open
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
9 changes: 9 additions & 0 deletions lib/ruby_lsp/ruby_lsp_rails/addon.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
require_relative "definition"
require_relative "rails_test_style"
require_relative "completion"
require_relative "rename"
require_relative "indexing_enhancement"

module RubyLsp
Expand Down Expand Up @@ -138,6 +139,14 @@ def create_completion_listener(response_builder, node_context, dispatcher, uri)
Completion.new(@rails_runner_client, response_builder, node_context, dispatcher, uri)
end

# @override
#: (String fully_qualified_name, String new_name, Array[(Interface::RenameFile | Interface::TextDocumentEdit)] document_changes) -> void
def collect_file_renames(fully_qualified_name, new_name, document_changes)
return unless @global_state

Rename.new(@global_state.index, fully_qualified_name, new_name, document_changes)
end

#: (Array[{uri: String, type: Integer}] changes) -> void
def workspace_did_change_watched_files(changes)
if changes.any? { |c| c[:uri].end_with?("db/schema.rb") || c[:uri].end_with?("structure.sql") }
Expand Down
41 changes: 41 additions & 0 deletions lib/ruby_lsp/ruby_lsp_rails/rename.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# typed: strict
# frozen_string_literal: true

module RubyLsp
module Rails
class Rename
#: (RubyIndexer::Index index, String fully_qualified_name, String new_name, Array[(Interface::RenameFile | Interface::TextDocumentEdit)] document_changes) -> void
def initialize(index, fully_qualified_name, new_name, document_changes)
index[fully_qualified_name]&.each do |entry|
file_path = entry.uri.full_path
next unless file_path

next unless file_path.include?("/db/migrate/")

old_file_name = entry.file_name
old_snake = file_from_constant_name(entry.name.split("::").last)

next unless old_file_name.match?(/\A\d+_#{Regexp.escape(old_snake)}\.rb\z/)

timestamp_prefix = old_file_name.delete_suffix("_#{old_snake}.rb")
new_snake = file_from_constant_name(new_name.split("::").last)

new_uri = URI::Generic.from_path(
path: File.join(File.dirname(file_path), "#{timestamp_prefix}_#{new_snake}.rb"),
).to_s

document_changes << Interface::RenameFile.new(kind: "rename", old_uri: entry.uri.to_s, new_uri: new_uri)
end
end

private

#: (String constant_name) -> String
def file_from_constant_name(constant_name)
constant_name
.gsub(/([a-z])([A-Z])|([A-Z])([A-Z][a-z])/, '\1\3_\2\4')
.downcase
end
end
end
end
65 changes: 65 additions & 0 deletions test/ruby_lsp_rails/rename_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# typed: true
# frozen_string_literal: true

require "test_helper"

module RubyLsp
module Rails
class RenameTest < ActiveSupport::TestCase
test "renames migration file to match new class name" do
document_changes = collect_file_renames(
"#{dummy_root}/db/migrate/20210901000000_create_foos.rb",
"class CreateFoos < ActiveRecord::Migration[7.0]; end",
"CreateFoos",
"CreateBars",
)

assert_equal(1, document_changes.size)
rename = document_changes.first
assert_instance_of(Interface::RenameFile, rename)
assert_equal(
URI::Generic.from_path(path: "#{dummy_root}/db/migrate/20210901000000_create_foos.rb").to_s,
rename.old_uri,
)
assert_equal(
URI::Generic.from_path(path: "#{dummy_root}/db/migrate/20210901000000_create_bars.rb").to_s,
rename.new_uri,
)
end

test "does nothing for non-migration files" do
document_changes = collect_file_renames(
"#{dummy_root}/app/models/foo.rb",
"class Foo < ApplicationRecord; end",
"Foo",
"Bar",
)

assert_empty(document_changes)
end

test "does nothing when file name does not match class name" do
document_changes = collect_file_renames(
"#{dummy_root}/db/migrate/20210901000000_something_else.rb",
"class CreateFoos < ActiveRecord::Migration[7.0]; end",
"CreateFoos",
"CreateBars",
)

assert_empty(document_changes)
end

private

def collect_file_renames(file_path, source, old_name, new_name)
index = RubyIndexer::Index.new
uri = URI::Generic.from_path(path: file_path)
index.index_single(uri, source)

document_changes = []
Rename.new(index, old_name, new_name, document_changes)
document_changes
end
end
end
end