diff --git a/apps/remote_control/lib/lexical/remote_control/code_intelligence/rename.ex b/apps/remote_control/lib/lexical/remote_control/code_intelligence/rename.ex index 485a884b2..0ace4c41f 100644 --- a/apps/remote_control/lib/lexical/remote_control/code_intelligence/rename.ex +++ b/apps/remote_control/lib/lexical/remote_control/code_intelligence/rename.ex @@ -52,8 +52,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.Rename do defp search_related_candidates(document, position, entity, range) do cursor_entity_string = cursor_entity_string(range) - entities = - exacts(entity, cursor_entity_string) + entities = exacts(entity, cursor_entity_string) # Users won't always want to rename descendants of a module. # For instance, when there are no more submodules after the cursor. @@ -116,7 +115,9 @@ defmodule Lexical.RemoteControl.CodeIntelligence.Rename do defp adjust_range(entries, entity) do for entry <- entries, uri = Document.Path.ensure_uri(entry.path), - {:ok, range} = resolve_entity_range(uri, entry.range.start, entity) do + range_result = resolve_entity_range(uri, entry.range.start, entity), + match?({:ok, _}, range_result) do + {_, range} = range_result %{entry | range: range} end end @@ -125,15 +126,21 @@ defmodule Lexical.RemoteControl.CodeIntelligence.Rename do with {:ok, _} <- Document.Store.open_temporary(uri), {:ok, document, analysis} <- Document.Store.fetch(uri, :analysis), {:ok, result, range} <- resolve_module(analysis, position) do - if result == entity do - {:ok, range} - else - last_part_length = result |> last_part() |> String.length() - # Move the cursor to the next part: - # `|Parent.Next.Target.Child` -> 'Parent.|Next.Target.Child' -> 'Parent.Next.|Target.Child' - character = position.character + last_part_length + 1 - position = Position.new(document, position.line, character) - resolve_entity_range(uri, position, entity) + cond do + result == entity -> + {:ok, range} + + not has_dots_in_name?(entity) -> + # When the descendant entry name is the same as the target + :error + + true -> + last_part_length = result |> last_part() |> String.length() + # Move the cursor to the next part: + # `|Parent.Next.Target.Child` -> 'Parent.|Next.Target.Child' -> 'Parent.Next.|Target.Child' + character = position.character + last_part_length + 1 + position = Position.new(document, position.line, character) + resolve_entity_range(uri, position, entity) end else _ -> @@ -142,6 +149,10 @@ defmodule Lexical.RemoteControl.CodeIntelligence.Rename do end end + defp has_dots_in_name?(entity) do + entity |> inspect() |> String.contains?(".") + end + defp last_part(entity) when is_atom(entity) do entity |> inspect() diff --git a/apps/remote_control/test/lexical/remote_control/code_intelligence/rename_test.exs b/apps/remote_control/test/lexical/remote_control/code_intelligence/rename_test.exs index 86a756520..5c7245da2 100644 --- a/apps/remote_control/test/lexical/remote_control/code_intelligence/rename_test.exs +++ b/apps/remote_control/test/lexical/remote_control/code_intelligence/rename_test.exs @@ -232,6 +232,25 @@ defmodule Lexical.RemoteControl.CodeIntelligence.RenameTest do assert result =~ ~S[defmodule TopLevel.Foo.Renamed do] assert result =~ ~S[alias TopLevel.Foo.Renamed] end + + test "succeeds even if there are descendants with the same name" do + {:ok, result} = + ~q[ + defmodule TopLevel.Foo do + defmodule Foo do # skip this + end + end + + defmodule TopLevel.Bar do + alias TopLevel.|Foo.Foo + end + ] + |> rename("Renamed") + + assert result =~ ~S[defmodule TopLevel.Renamed do] + assert result =~ ~S[defmodule Foo do # skip this] + assert result =~ ~S[alias TopLevel.Renamed.Foo] + end end describe "rename struct" do