Skip to content

Commit 3076af5

Browse files
committed
Rename migration files when renaming migration class
1 parent 2a66611 commit 3076af5

3 files changed

Lines changed: 115 additions & 0 deletions

File tree

lib/ruby_lsp/ruby_lsp_rails/addon.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
require_relative "definition"
1717
require_relative "rails_test_style"
1818
require_relative "completion"
19+
require_relative "rename"
1920
require_relative "indexing_enhancement"
2021

2122
module RubyLsp
@@ -138,6 +139,14 @@ def create_completion_listener(response_builder, node_context, dispatcher, uri)
138139
Completion.new(@rails_runner_client, response_builder, node_context, dispatcher, uri)
139140
end
140141

142+
# @override
143+
#: (String fully_qualified_name, String new_name, Array[(Interface::RenameFile | Interface::TextDocumentEdit)] document_changes) -> void
144+
def collect_file_renames(fully_qualified_name, new_name, document_changes)
145+
return unless @global_state
146+
147+
Rename.new(@global_state.index, fully_qualified_name, new_name, document_changes)
148+
end
149+
141150
#: (Array[{uri: String, type: Integer}] changes) -> void
142151
def workspace_did_change_watched_files(changes)
143152
if changes.any? { |c| c[:uri].end_with?("db/schema.rb") || c[:uri].end_with?("structure.sql") }
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# typed: strict
2+
# frozen_string_literal: true
3+
4+
module RubyLsp
5+
module Rails
6+
class Rename
7+
#: (RubyIndexer::Index index, String fully_qualified_name, String new_name, Array[(Interface::RenameFile | Interface::TextDocumentEdit)] document_changes) -> void
8+
def initialize(index, fully_qualified_name, new_name, document_changes)
9+
index[fully_qualified_name]&.each do |entry|
10+
file_path = entry.uri.full_path
11+
next unless file_path
12+
13+
next unless file_path.include?("/db/migrate/")
14+
15+
old_file_name = entry.file_name
16+
old_snake = file_from_constant_name(entry.name.split("::").last)
17+
18+
next unless old_file_name.match?(/\A\d+_#{Regexp.escape(old_snake)}\.rb\z/)
19+
20+
timestamp_prefix = old_file_name.delete_suffix("_#{old_snake}.rb")
21+
new_snake = file_from_constant_name(new_name.split("::").last)
22+
23+
new_uri = URI::Generic.from_path(
24+
path: File.join(File.dirname(file_path), "#{timestamp_prefix}_#{new_snake}.rb"),
25+
).to_s
26+
27+
document_changes << Interface::RenameFile.new(kind: "rename", old_uri: entry.uri.to_s, new_uri: new_uri)
28+
end
29+
end
30+
31+
private
32+
33+
#: (String constant_name) -> String
34+
def file_from_constant_name(constant_name)
35+
constant_name
36+
.gsub(/([a-z])([A-Z])|([A-Z])([A-Z][a-z])/, '\1\3_\2\4')
37+
.downcase
38+
end
39+
end
40+
end
41+
end

test/ruby_lsp_rails/rename_test.rb

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# typed: true
2+
# frozen_string_literal: true
3+
4+
require "test_helper"
5+
6+
module RubyLsp
7+
module Rails
8+
class RenameTest < ActiveSupport::TestCase
9+
test "renames migration file to match new class name" do
10+
document_changes = collect_file_renames(
11+
"#{dummy_root}/db/migrate/20210901000000_create_foos.rb",
12+
"class CreateFoos < ActiveRecord::Migration[7.0]; end",
13+
"CreateFoos",
14+
"CreateBars",
15+
)
16+
17+
assert_equal(1, document_changes.size)
18+
rename = document_changes.first
19+
assert_instance_of(Interface::RenameFile, rename)
20+
assert_equal(
21+
URI::Generic.from_path(path: "#{dummy_root}/db/migrate/20210901000000_create_foos.rb").to_s,
22+
rename.old_uri,
23+
)
24+
assert_equal(
25+
URI::Generic.from_path(path: "#{dummy_root}/db/migrate/20210901000000_create_bars.rb").to_s,
26+
rename.new_uri,
27+
)
28+
end
29+
30+
test "does nothing for non-migration files" do
31+
document_changes = collect_file_renames(
32+
"#{dummy_root}/app/models/foo.rb",
33+
"class Foo < ApplicationRecord; end",
34+
"Foo",
35+
"Bar",
36+
)
37+
38+
assert_empty(document_changes)
39+
end
40+
41+
test "does nothing when file name does not match class name" do
42+
document_changes = collect_file_renames(
43+
"#{dummy_root}/db/migrate/20210901000000_something_else.rb",
44+
"class CreateFoos < ActiveRecord::Migration[7.0]; end",
45+
"CreateFoos",
46+
"CreateBars",
47+
)
48+
49+
assert_empty(document_changes)
50+
end
51+
52+
private
53+
54+
def collect_file_renames(file_path, source, old_name, new_name)
55+
index = RubyIndexer::Index.new
56+
uri = URI::Generic.from_path(path: file_path)
57+
index.index_single(uri, source)
58+
59+
document_changes = []
60+
Rename.new(index, old_name, new_name, document_changes)
61+
document_changes
62+
end
63+
end
64+
end
65+
end

0 commit comments

Comments
 (0)