Skip to content

Commit

Permalink
Improve Phoenix integration (#281)
Browse files Browse the repository at this point in the history
* add phoenix scope utils

* refactor source to be more readable

* add phoenix plugin

* phoenix goto def

* improve scopes module readability

* remove redundant `is_list/1` guard

* define `suggestions/4` only on elixir versions >= 1.14.0

* require elixir >=1.14 to run tests

* fixup! require elixir >=1.14 to run tests

* add @moduledoc false

* expand variables and module attributes on scope

* fixup! expand variables and module attributes on scope

* fixup! expand variables and module attributes on scope

---------

Co-authored-by: Gustavo Aguiar <[email protected]>
  • Loading branch information
gugahoa and Gustavo Aguiar committed Nov 27, 2023
1 parent 56a5cdb commit 02c101d
Show file tree
Hide file tree
Showing 9 changed files with 563 additions and 23 deletions.
60 changes: 45 additions & 15 deletions lib/elixir_sense/core/source.ex
Original file line number Diff line number Diff line change
Expand Up @@ -400,25 +400,26 @@ defmodule ElixirSense.Core.Source do

# TODO refactor to use Macro.path on elixir 1.14
with {:ok, ast} <- NormalizedCode.Fragment.container_cursor_to_quoted(prefix, columns: true),
{_, {:ok, call, npar, meta, options, cursor_at_option, option}} <-
Macro.prewalk(ast, nil, &find_call_pre/2),
{{m, elixir_prefix}, f} when f not in @excluded_funs <- get_mod_fun(call, binding_env) do
{_, {:ok, call_info}} <- Macro.prewalk(ast, nil, &find_call_pre/2),
{{m, elixir_prefix}, f} when f not in @excluded_funs <-
get_mod_fun(call_info.call, binding_env) do
%{
candidate: {m, f},
elixir_prefix: elixir_prefix,
npar: npar,
pos: {{meta[:line], meta[:column]}, {meta[:line], nil}},
cursor_at_option: cursor_at_option,
options_so_far: options,
option: option
params: call_info.params,
npar: call_info.npar,
pos: {{call_info.meta[:line], call_info.meta[:column]}, {call_info.meta[:line], nil}},
cursor_at_option: call_info.cursor_at_option,
options_so_far: call_info.options,
option: call_info.option
}
else
_ -> nil
end
end

def find_call_pre(ast, {:ok, call, npar, meta, options, cursor_at_option, option}),
do: {ast, {:ok, call, npar, meta, options, cursor_at_option, option}}
def find_call_pre(ast, {:ok, call_info}),
do: {ast, {:ok, call_info}}

# transform `a |> b(c)` calls into `b(a, c)`
def find_call_pre({:|>, _, [params_1, {call, meta, params_rest}]}, state) do
Expand All @@ -440,20 +441,45 @@ defmodule ElixirSense.Core.Source do
defp find_cursor_in_params(params, call, meta) do
case Enum.reverse(params) do
[{:__cursor__, _, []} | rest] ->
{:ok, call, length(rest), meta, [], :maybe, nil}
{:ok,
%{
call: call,
params: Enum.reverse(rest),
npar: length(rest),
meta: meta,
options: [],
cursor_at_option: :maybe,
option: nil
}}

[keyword_list | rest] when is_list(keyword_list) ->
case Enum.reverse(keyword_list) do
[{:__cursor__, _, []} | kl_rest] ->
if Keyword.keyword?(kl_rest) do
{:ok, call, length(rest), meta, Enum.reverse(kl_rest) |> Enum.map(&elem(&1, 0)),
true, nil}
{:ok,
%{
call: call,
params: Enum.reverse(rest),
npar: length(rest),
meta: meta,
options: Enum.reverse(kl_rest) |> Enum.map(&elem(&1, 0)),
cursor_at_option: true,
option: nil
}}
end

[{atom, {:__cursor__, _, []}} | kl_rest] when is_atom(atom) ->
if Keyword.keyword?(kl_rest) do
{:ok, call, length(rest), meta, Enum.reverse(kl_rest) |> Enum.map(&elem(&1, 0)),
false, atom}
{:ok,
%{
call: call,
params: Enum.reverse(rest),
npar: length(rest),
meta: meta,
options: Enum.reverse(kl_rest) |> Enum.map(&elem(&1, 0)),
cursor_at_option: false,
option: atom
}}
end

_ ->
Expand Down Expand Up @@ -504,6 +530,10 @@ defmodule ElixirSense.Core.Source do
def get_mod_fun([atom, fun], _binding_env) when is_atom(atom), do: {{atom, false}, fun}
def get_mod_fun(_, _binding_env), do: nil

def get_mod([{:__aliases__, _, list} | _rest], binding_env) do
get_mod(list, binding_env)
end

def get_mod([{:__MODULE__, _, nil} | rest], binding_env) do
if binding_env.current_module not in [nil, Elixir] do
mod =
Expand Down
105 changes: 105 additions & 0 deletions lib/elixir_sense/plugins/phoenix.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
defmodule ElixirSense.Plugins.Phoenix do
@moduledoc false

@behaviour ElixirSense.Plugin

use ElixirSense.Providers.Suggestion.GenericReducer

alias ElixirSense.Core.Source

Check warning on line 8 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 25.x)

unused alias Source

Check warning on line 8 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 22.x)

unused alias Source

Check warning on line 8 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 24.x)

unused alias Source

Check warning on line 8 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 23.x)

unused alias Source

Check warning on line 8 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 22.x)

unused alias Source

Check warning on line 8 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 25.x)

unused alias Source

Check warning on line 8 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 24.x)

unused alias Source

Check warning on line 8 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 23.x)

unused alias Source
alias ElixirSense.Core.Binding

Check warning on line 9 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 25.x)

unused alias Binding

Check warning on line 9 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 22.x)

unused alias Binding

Check warning on line 9 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 24.x)

unused alias Binding

Check warning on line 9 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 23.x)

unused alias Binding

Check warning on line 9 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 22.x)

unused alias Binding

Check warning on line 9 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 25.x)

unused alias Binding

Check warning on line 9 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 24.x)

unused alias Binding

Check warning on line 9 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 23.x)

unused alias Binding
alias ElixirSense.Core.Introspection
alias ElixirSense.Core.ModuleStore
alias ElixirSense.Plugins.Phoenix.Scope

Check warning on line 12 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 25.x)

unused alias Scope

Check warning on line 12 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 22.x)

unused alias Scope

Check warning on line 12 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 24.x)

unused alias Scope

Check warning on line 12 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 23.x)

unused alias Scope

Check warning on line 12 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 22.x)

unused alias Scope

Check warning on line 12 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 25.x)

unused alias Scope

Check warning on line 12 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 24.x)

unused alias Scope

Check warning on line 12 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 23.x)

unused alias Scope
alias ElixirSense.Plugins.Util
alias ElixirSense.Providers.Suggestion.Matcher

Check warning on line 14 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 25.x)

unused alias Matcher

Check warning on line 14 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 22.x)

unused alias Matcher

Check warning on line 14 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 24.x)

unused alias Matcher

Check warning on line 14 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 23.x)

unused alias Matcher

Check warning on line 14 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 22.x)

unused alias Matcher

Check warning on line 14 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 25.x)

unused alias Matcher

Check warning on line 14 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 24.x)

unused alias Matcher

Check warning on line 14 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 23.x)

unused alias Matcher

@phoenix_route_funcs ~w(

Check warning on line 16 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 25.x)

module attribute @phoenix_route_funcs was set but never used

Check warning on line 16 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 22.x)

module attribute @phoenix_route_funcs was set but never used

Check warning on line 16 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 24.x)

module attribute @phoenix_route_funcs was set but never used

Check warning on line 16 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 23.x)

module attribute @phoenix_route_funcs was set but never used

Check warning on line 16 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 22.x)

module attribute @phoenix_route_funcs was set but never used

Check warning on line 16 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 25.x)

module attribute @phoenix_route_funcs was set but never used

Check warning on line 16 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 24.x)

module attribute @phoenix_route_funcs was set but never used

Check warning on line 16 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 23.x)

module attribute @phoenix_route_funcs was set but never used
get put patch trace
delete head options
forward connect post
)a

@impl true
def setup(context) do
ModuleStore.ensure_compiled(context, Phoenix.Router)
end

if Version.match?(System.version(), ">= 1.14.0") do
@impl true
def suggestions(hint, {Phoenix.Router, func, 1, _info}, _list, opts)
when func in @phoenix_route_funcs do
binding = Binding.from_env(opts.env, opts.buffer_metadata)
{_, scope_alias} = Scope.within_scope(opts.cursor_context.text_before, binding)

case find_controllers(opts.module_store, opts.env, hint, scope_alias) do
[] -> :ignore
controllers -> {:override, controllers}
end
end

def suggestions(
hint,
{Phoenix.Router, func, 2, %{params: [_path, module]}},
_list,
opts
)
when func in @phoenix_route_funcs do
binding_env = Binding.from_env(opts.env, opts.buffer_metadata)
{_, scope_alias} = Scope.within_scope(opts.cursor_context.text_before)
{module, _} = Source.get_mod([module], binding_env)

module = Module.safe_concat(scope_alias, module)

suggestions =
for {export, {2, :function}} when export not in ~w(action call)a <-
Introspection.get_exports(module),
name = inspect(export),
Matcher.match?(name, hint) do
%{
type: :generic,
kind: :function,
label: name,
insert_text: Util.trim_leading_for_insertion(hint, name),
detail: "Phoenix action"
}
end

{:override, suggestions}
end
end

@impl true
def suggestions(_hint, _func_call, _list, _opts) do
:ignore
end

defp find_controllers(module_store, env, hint, scope_alias) do

Check warning on line 76 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 25.x)

function find_controllers/4 is unused

Check warning on line 76 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 22.x)

function find_controllers/4 is unused

Check warning on line 76 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 24.x)

function find_controllers/4 is unused

Check warning on line 76 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 23.x)

function find_controllers/4 is unused

Check warning on line 76 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 22.x)

function find_controllers/4 is unused

Check warning on line 76 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 25.x)

function find_controllers/4 is unused

Check warning on line 76 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 24.x)

function find_controllers/4 is unused

Check warning on line 76 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 23.x)

function find_controllers/4 is unused
[prefix | _] =
env.module
|> inspect()
|> String.split(".")

for module <- module_store.list,
mod_str = inspect(module),
Util.match_module?(mod_str, prefix),
mod_str =~ "Controller",
Util.match_module?(mod_str, hint) do
{doc, _} = Introspection.get_module_docs_summary(module)

%{
type: :generic,
kind: :class,
label: mod_str,
insert_text: skip_scope_alias(scope_alias, mod_str),
detail: "Phoenix controller",
documentation: doc
}
end
|> Enum.sort_by(& &1.label)
end

defp skip_scope_alias(nil, insert_text), do: insert_text

Check warning on line 101 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 25.x)

function skip_scope_alias/2 is unused

Check warning on line 101 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 22.x)

function skip_scope_alias/2 is unused

Check warning on line 101 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 24.x)

function skip_scope_alias/2 is unused

Check warning on line 101 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 23.x)

function skip_scope_alias/2 is unused

Check warning on line 101 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 22.x)

function skip_scope_alias/2 is unused

Check warning on line 101 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 25.x)

function skip_scope_alias/2 is unused

Check warning on line 101 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 24.x)

function skip_scope_alias/2 is unused

Check warning on line 101 in lib/elixir_sense/plugins/phoenix.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 23.x)

function skip_scope_alias/2 is unused

defp skip_scope_alias(scope_alias, insert_text),
do: String.replace_prefix(insert_text, "#{inspect(scope_alias)}.", "")
end
110 changes: 110 additions & 0 deletions lib/elixir_sense/plugins/phoenix/scope.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
defmodule ElixirSense.Plugins.Phoenix.Scope do
@moduledoc false

alias ElixirSense.Core.Source
alias ElixirSense.Core.Binding

import Module, only: [safe_concat: 2, safe_concat: 1]

def within_scope(buffer, binding_env \\ %Binding{}) do
{:ok, ast} = Code.Fragment.container_cursor_to_quoted(buffer)

with {true, scopes_ast} <- get_scopes(ast),
scopes_ast = Enum.reverse(scopes_ast),
scope_alias <- get_scope_alias(scopes_ast, binding_env) do
{true, scope_alias}
end
end

defp get_scopes(ast) do
path = Macro.path(ast, &match?({:__cursor__, _, _}, &1))

scopes =
path
|> Enum.filter(&match?({:scope, _, _}, &1))
|> Enum.map(fn {:scope, meta, params} ->
params = Enum.reject(params, &match?([{:do, _} | _], &1))
{:scope, meta, params}
end)

case scopes do
[] -> {false, nil}
scopes -> {true, scopes}
end
end

defp get_scope_alias(scopes_ast, binding_env, module \\ nil)

# is this possible? scope do ... end
defp get_scope_alias([{:scope, _, []}], _binding_env, module), do: module

# scope "/" do ... end
defp get_scope_alias([{:scope, _, [scope_params]}], _binding_env, module)
when not is_list(scope_params),
do: module

# scope path: "/", alias: ExampleWeb do ... end
defp get_scope_alias([{:scope, _, [scope_params]}], binding_env, module) do
scope_alias = Keyword.get(scope_params, :alias)
scope_alias = get_mod(scope_alias, binding_env)
safe_concat(module, scope_alias)
end

# scope "/", alias: ExampleWeb do ... end
defp get_scope_alias(
[{:scope, _, [_scope_path, scope_params]}],
binding_env,
module
)
when is_list(scope_params) do
scope_alias = Keyword.get(scope_params, :alias)
scope_alias = get_mod(scope_alias, binding_env)
safe_concat(module, scope_alias)
end

# scope "/", ExampleWeb do ... end
defp get_scope_alias(
[{:scope, _, [_scope_path, scope_alias]}],
binding_env,
module
) do
scope_alias = get_mod(scope_alias, binding_env)
safe_concat(module, scope_alias)
end

# scope "/", ExampleWeb, host: "api." do ... end
defp get_scope_alias(
[{:scope, _, [_scope_path, scope_alias, _scope_params]}],
binding_env,
module
) do
scope_alias = get_mod(scope_alias, binding_env)
safe_concat(module, scope_alias)
end

# recurse
defp get_scope_alias([head | tail], binding_env, module) do
scope_alias = get_scope_alias([head], binding_env, module)
safe_concat([module, scope_alias, get_scope_alias(tail, binding_env)])
end

defp get_mod({:__aliases__, _, [scope_alias]}, binding_env) do
get_mod(scope_alias, binding_env)
end

defp get_mod({name, _, nil}, binding_env) when is_atom(name) do
case Binding.expand(binding_env, {:variable, name}) do
{:atom, atom} ->
atom

_ ->
nil
end
end

defp get_mod(scope_alias, binding_env) do
with {mod, _} <- Source.get_mod([scope_alias], binding_env) do
mod
end
end
end
30 changes: 22 additions & 8 deletions lib/elixir_sense/providers/definition.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ defmodule ElixirSense.Providers.Definition do
alias ElixirSense.Core.State.ModFunInfo
alias ElixirSense.Core.State.TypeInfo
alias ElixirSense.Core.State.VarInfo
alias ElixirSense.Core.Source
alias ElixirSense.Core.SurroundContext
alias ElixirSense.Location
alias ElixirSense.Plugins.Phoenix.Scope

@doc """
Finds out where a module, function, macro or variable was defined.
Expand Down Expand Up @@ -174,14 +176,7 @@ defmodule ElixirSense.Providers.Definition do
scope: scope
} = env

m =
case module do
{:atom, a} ->
a

_ ->
nil
end
m = get_module(module, context, env, metadata)

case {m, function}
|> Introspection.actual_mod_fun(
Expand Down Expand Up @@ -249,4 +244,23 @@ defmodule ElixirSense.Providers.Definition do
end
end
end

defp get_module(module, %{end: {line, col}}, env, metadata) do
with {true, module} <- get_phoenix_module(module, env) do
text_before = Source.text_before(metadata.source, line, col)

case Scope.within_scope(text_before) do
{false, _} -> module
{true, scope_alias} -> Module.safe_concat(scope_alias, module)
end
end
end

defp get_phoenix_module(module, env) do
case {Phoenix.Router in env.requires, module} do
{true, {:atom, module}} -> {true, module}
{false, {:atom, module}} -> module
_ -> nil
end
end
end
21 changes: 21 additions & 0 deletions test/elixir_sense/definition_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,27 @@ defmodule ElixirSense.Providers.DefinitionTest do
assert nil == ElixirSense.definition("__MODULE__", 1, 1)
end

@tag requires_elixir_1_14: true
test "find module definition inside Phoenix's scope" do
_define_existing_atom = ExampleWeb

buffer = """
defmodule ExampleWeb.Router do
import Phoenix.Router
scope "/", ExampleWeb do
get "/", PageController, :home
end
end
"""

%Location{type: :module, file: file, line: line, column: column} =
ElixirSense.definition(buffer, 5, 15)

assert file =~ "elixir_sense/test/support/plugins/phoenix/page_controller.ex"
assert read_line(file, {line, column}) =~ "ExampleWeb.PageController"
end

test "find definition of aliased modules in `use`" do
buffer = """
defmodule MyModule do
Expand Down
Loading

0 comments on commit 02c101d

Please sign in to comment.