From a35f79bd90d01dc7854114c860b3ec425a78177a Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Sat, 4 Jul 2026 14:42:41 +0200 Subject: [PATCH 1/3] Resolve goto-def/refs/hover at the end of a symbol (#1038 / #1027) Bump elixir_sense to 9f6b3fe, which makes `SurroundContext.Toxic.surround_context` resolve the trailing edge of a symbol. All four navigation locators (definition, references, implementation, hover) consume it, so the cursor placed at the visual end of a name now resolves instead of returning nothing (#1038), and the end of an alias before a `.` resolves to the module rather than the remote call (#1027). Add end-to-end coverage in the definition locator test for both cases. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01XRd15BX6wBpZ8ThaNSo232 --- .../providers/definition/locator_test.exs | 37 +++++++++++++++++++ dep_versions.exs | 2 +- mix.lock | 2 +- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/apps/language_server/test/providers/definition/locator_test.exs b/apps/language_server/test/providers/definition/locator_test.exs index a7cef96a7..54dde6919 100644 --- a/apps/language_server/test/providers/definition/locator_test.exs +++ b/apps/language_server/test/providers/definition/locator_test.exs @@ -18,6 +18,43 @@ defmodule ElixirLS.LanguageServer.Providers.Definition.LocatorTest do assert nil == Locator.definition("__MODULE__", 1, 1) end + # elixir-lsp/elixir-ls#1038: goto-definition must work when the cursor is at the very end of a + # symbol (one column past its last character), not only when it is on a character of the symbol. + test "find definition with the cursor at the end of the symbol (#1038)" do + buffer = """ + defmodule MyModule do + def my_func(a), do: a + + def caller do + my_func(1) + end + end + """ + + # `my_func` is on line 5 at columns 5..11; column 12 is the end of the symbol. + for col <- [8, 11, 12] do + assert %Location{type: :function, line: 2} = Locator.definition(buffer, 5, col), + "expected my_func/1 definition at line 5 column #{col}" + end + end + + # elixir-lsp/elixir-ls#1027: at the end of an alias that is the LHS of a remote call, the cursor + # resolves to the module, not the function. + test "cursor at the end of an alias before `.` resolves the module (#1027)" do + buffer = """ + defmodule MyModule do + alias ElixirSenseExample.ModuleWithFunctions + + def caller do + ModuleWithFunctions.function_arity_zero() + end + end + """ + + # `ModuleWithFunctions` is on line 5 at columns 5..23; column 24 is on the `.` (its end). + assert %Location{type: :module} = Locator.definition(buffer, 5, 24) + end + test "find module definition inside Phoenix's scope" do _define_existing_atom = ExampleWeb diff --git a/dep_versions.exs b/dep_versions.exs index 813214c6b..c85eb2b99 100644 --- a/dep_versions.exs +++ b/dep_versions.exs @@ -1,5 +1,5 @@ [ - elixir_sense: "89eda3fc4df9cea0d70c528f3e7e240a70c860f8", + elixir_sense: "9f6b3fe25e64e984c09f74786071356354ec16f7", dialyxir_vendored: "accfec9393079abc4a82b7e79a4997f59f085b67", jason_v: "f1c10fa9c445cb9f300266122ef18671054b2330", erl2ex_vendored: "04f93e55f46d35d0aa3c149616f2c7a6a1ad9311", diff --git a/mix.lock b/mix.lock index 446788822..dfb425689 100644 --- a/mix.lock +++ b/mix.lock @@ -2,7 +2,7 @@ "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir_vendored": {:git, "https://github.com/elixir-lsp/dialyxir.git", "accfec9393079abc4a82b7e79a4997f59f085b67", [ref: "accfec9393079abc4a82b7e79a4997f59f085b67"]}, - "elixir_sense": {:git, "https://github.com/elixir-lsp/elixir_sense.git", "89eda3fc4df9cea0d70c528f3e7e240a70c860f8", [ref: "89eda3fc4df9cea0d70c528f3e7e240a70c860f8"]}, + "elixir_sense": {:git, "https://github.com/elixir-lsp/elixir_sense.git", "9f6b3fe25e64e984c09f74786071356354ec16f7", [ref: "9f6b3fe25e64e984c09f74786071356354ec16f7"]}, "erl2ex_vendored": {:git, "https://github.com/elixir-lsp/erl2ex.git", "04f93e55f46d35d0aa3c149616f2c7a6a1ad9311", [ref: "04f93e55f46d35d0aa3c149616f2c7a6a1ad9311"]}, "erlex_vendored": {:git, "https://github.com/elixir-lsp/erlex.git", "50b8307f90451a5d0288fb239fb6405b5ca1f1a4", [ref: "50b8307f90451a5d0288fb239fb6405b5ca1f1a4"]}, "jason_v": {:git, "https://github.com/elixir-lsp/jason.git", "f1c10fa9c445cb9f300266122ef18671054b2330", [ref: "f1c10fa9c445cb9f300266122ef18671054b2330"]}, From b071697f93157df5b514a9ac5c99020d898e1321 Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Sat, 4 Jul 2026 14:52:09 +0200 Subject: [PATCH 2/3] Bump elixir_sense: restrict #1027 alias override (fixes :timer.sleep hover) elixir_sense ee88a26f restricts the end-of-alias-before-dot override to `:alias` only, so hovering the `.` in an erlang remote call like `:timer.sleep(...)` keeps resolving the function instead of the module. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01XRd15BX6wBpZ8ThaNSo232 --- dep_versions.exs | 2 +- mix.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dep_versions.exs b/dep_versions.exs index c85eb2b99..f23db6c1d 100644 --- a/dep_versions.exs +++ b/dep_versions.exs @@ -1,5 +1,5 @@ [ - elixir_sense: "9f6b3fe25e64e984c09f74786071356354ec16f7", + elixir_sense: "ee88a26f471cfc46a898d5a2c4c30bb7ad8beff4", dialyxir_vendored: "accfec9393079abc4a82b7e79a4997f59f085b67", jason_v: "f1c10fa9c445cb9f300266122ef18671054b2330", erl2ex_vendored: "04f93e55f46d35d0aa3c149616f2c7a6a1ad9311", diff --git a/mix.lock b/mix.lock index dfb425689..66f10beff 100644 --- a/mix.lock +++ b/mix.lock @@ -2,7 +2,7 @@ "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir_vendored": {:git, "https://github.com/elixir-lsp/dialyxir.git", "accfec9393079abc4a82b7e79a4997f59f085b67", [ref: "accfec9393079abc4a82b7e79a4997f59f085b67"]}, - "elixir_sense": {:git, "https://github.com/elixir-lsp/elixir_sense.git", "9f6b3fe25e64e984c09f74786071356354ec16f7", [ref: "9f6b3fe25e64e984c09f74786071356354ec16f7"]}, + "elixir_sense": {:git, "https://github.com/elixir-lsp/elixir_sense.git", "ee88a26f471cfc46a898d5a2c4c30bb7ad8beff4", [ref: "ee88a26f471cfc46a898d5a2c4c30bb7ad8beff4"]}, "erl2ex_vendored": {:git, "https://github.com/elixir-lsp/erl2ex.git", "04f93e55f46d35d0aa3c149616f2c7a6a1ad9311", [ref: "04f93e55f46d35d0aa3c149616f2c7a6a1ad9311"]}, "erlex_vendored": {:git, "https://github.com/elixir-lsp/erlex.git", "50b8307f90451a5d0288fb239fb6405b5ca1f1a4", [ref: "50b8307f90451a5d0288fb239fb6405b5ca1f1a4"]}, "jason_v": {:git, "https://github.com/elixir-lsp/jason.git", "f1c10fa9c445cb9f300266122ef18671054b2330", [ref: "f1c10fa9c445cb9f300266122ef18671054b2330"]}, From e3bddcbe53fd682d35eff76d66944f5a149a6d33 Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Sat, 4 Jul 2026 15:55:25 +0200 Subject: [PATCH 3/3] Address review: trailing-edge coverage for references/implementation/hover Per review of #1270, the trailing-edge regression coverage previously only exercised the definition locator. Add equivalent "cursor at end of symbol / end of alias before `.`" cases for the References and Implementation locators and the Hover provider, which share the same SurroundContext.Toxic resolution, so those paths can't regress independently. Also bump elixir_sense to 9460c606 (review fixes: lazy classification, operator contexts preserved, qualified-alias override). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01XRd15BX6wBpZ8ThaNSo232 --- .../test/providers/hover_test.exs | 26 +++++++++++++++++++ .../providers/implementation/locator_test.exs | 16 ++++++++++++ .../providers/references/locator_test.exs | 21 +++++++++++++++ dep_versions.exs | 2 +- mix.lock | 2 +- 5 files changed, 65 insertions(+), 2 deletions(-) diff --git a/apps/language_server/test/providers/hover_test.exs b/apps/language_server/test/providers/hover_test.exs index 0f3d9d72c..c72b7c558 100644 --- a/apps/language_server/test/providers/hover_test.exs +++ b/apps/language_server/test/providers/hover_test.exs @@ -56,6 +56,32 @@ defmodule ElixirLS.LanguageServer.Providers.HoverTest do ) end + # elixir-lsp/elixir-ls#1027: at the end of the alias `IO` (on the `.` before `inspect`) the module + # is hovered, not the remote function - the same trailing-edge resolution the definition locator uses. + test "elixir module hover at the end of an alias before `.` (#1027)" do + text = """ + defmodule MyModule do + def hello() do + IO.inspect("hello world") + end + end + """ + + # `IO` is on line 3, columns 5-6; the caret sits just past it, on the `.` (0-based char 6). + {line, char} = {2, 6} + parser_context = ParserContextBuilder.from_string(text) + + {line, char} = + SourceFile.lsp_position_to_elixir(parser_context.source_file.text, {line, char}) + + assert {:ok, + %GenLSP.Structures.Hover{ + contents: %GenLSP.Structures.MarkupContent{kind: "markdown", value: v} + }} = Hover.hover(parser_context, line, char) + + assert String.starts_with?(v, "```elixir\nIO\n```\n\n*module*") + end + test "function hover" do text = """ defmodule MyModule do diff --git a/apps/language_server/test/providers/implementation/locator_test.exs b/apps/language_server/test/providers/implementation/locator_test.exs index 05ed0d161..2f63eb31f 100644 --- a/apps/language_server/test/providers/implementation/locator_test.exs +++ b/apps/language_server/test/providers/implementation/locator_test.exs @@ -80,6 +80,22 @@ defmodule ElixirLS.LanguageServer.Providers.Implementation.LocatorTest do "ElixirSenseExample.ExampleBehaviourWithDocCallbackNoImpl" end + # elixir-lsp/elixir-ls#1038: implementations resolve with the cursor at the end of the behaviour + # alias (one column past its last character), not only when it is on a character of the name. + test "find implementations of behaviour module with cursor at the end of the alias (#1038)" do + buffer = """ + defmodule ElixirSenseExample.ExampleBehaviourWithDoc do + end + """ + + # the alias starts at column 11 and is 42 characters long, so column 53 is its end (on the space). + mid = Locator.implementations(buffer, 1, 32) + at_end = Locator.implementations(buffer, 1, 53) + + assert length(at_end) == 2 + assert at_end == mid + end + test "find protocol implementations" do buffer = """ defprotocol ElixirSenseExample.ExampleProtocol do diff --git a/apps/language_server/test/providers/references/locator_test.exs b/apps/language_server/test/providers/references/locator_test.exs index cb1daef1a..1283026e7 100644 --- a/apps/language_server/test/providers/references/locator_test.exs +++ b/apps/language_server/test/providers/references/locator_test.exs @@ -158,6 +158,27 @@ defmodule ElixirLS.LanguageServer.Providers.References.LocatorTest do %{start: %{line: 65, column: 79}, end: %{line: 65, column: 83}} end + # elixir-lsp/elixir-ls#1038: references resolve with the cursor at the end of the symbol + # (one column past its last character), not only when it is on a character of the name. + test "find references with cursor at the end of a function definition name (#1038)", %{ + trace: trace + } do + buffer = """ + defmodule ElixirSense.Providers.ReferencesTest.Modules.Callee1 do + def func() do + IO.puts "" + end + end + """ + + # `func` is on line 2 at columns 7-10; column 11 is the end of the symbol (on the `(`). + mid = Locator.references(buffer, 2, 10, trace) + at_end = Locator.references(buffer, 2, 11, trace) + + assert at_end != [] + assert at_end == mid + end + test "find references with cursor over a function definition with default arg", %{trace: trace} do buffer = """ defmodule ElixirSenseExample.Subscription do diff --git a/dep_versions.exs b/dep_versions.exs index f23db6c1d..36ab2a73c 100644 --- a/dep_versions.exs +++ b/dep_versions.exs @@ -1,5 +1,5 @@ [ - elixir_sense: "ee88a26f471cfc46a898d5a2c4c30bb7ad8beff4", + elixir_sense: "9460c60651c2419d81fa447d70d3e1596e30dab0", dialyxir_vendored: "accfec9393079abc4a82b7e79a4997f59f085b67", jason_v: "f1c10fa9c445cb9f300266122ef18671054b2330", erl2ex_vendored: "04f93e55f46d35d0aa3c149616f2c7a6a1ad9311", diff --git a/mix.lock b/mix.lock index 66f10beff..3d591610f 100644 --- a/mix.lock +++ b/mix.lock @@ -2,7 +2,7 @@ "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir_vendored": {:git, "https://github.com/elixir-lsp/dialyxir.git", "accfec9393079abc4a82b7e79a4997f59f085b67", [ref: "accfec9393079abc4a82b7e79a4997f59f085b67"]}, - "elixir_sense": {:git, "https://github.com/elixir-lsp/elixir_sense.git", "ee88a26f471cfc46a898d5a2c4c30bb7ad8beff4", [ref: "ee88a26f471cfc46a898d5a2c4c30bb7ad8beff4"]}, + "elixir_sense": {:git, "https://github.com/elixir-lsp/elixir_sense.git", "9460c60651c2419d81fa447d70d3e1596e30dab0", [ref: "9460c60651c2419d81fa447d70d3e1596e30dab0"]}, "erl2ex_vendored": {:git, "https://github.com/elixir-lsp/erl2ex.git", "04f93e55f46d35d0aa3c149616f2c7a6a1ad9311", [ref: "04f93e55f46d35d0aa3c149616f2c7a6a1ad9311"]}, "erlex_vendored": {:git, "https://github.com/elixir-lsp/erlex.git", "50b8307f90451a5d0288fb239fb6405b5ca1f1a4", [ref: "50b8307f90451a5d0288fb239fb6405b5ca1f1a4"]}, "jason_v": {:git, "https://github.com/elixir-lsp/jason.git", "f1c10fa9c445cb9f300266122ef18671054b2330", [ref: "f1c10fa9c445cb9f300266122ef18671054b2330"]},