-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
* 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
There are no files selected for viewing
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 GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 25.x)
Check warning on line 8 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 22.x)
Check warning on line 8 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 24.x)
Check warning on line 8 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 23.x)
Check warning on line 8 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 22.x)
Check warning on line 8 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 25.x)
Check warning on line 8 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 24.x)
|
||
alias ElixirSense.Core.Binding | ||
Check warning on line 9 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 25.x)
Check warning on line 9 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 22.x)
Check warning on line 9 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 24.x)
Check warning on line 9 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 23.x)
Check warning on line 9 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 22.x)
Check warning on line 9 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 25.x)
Check warning on line 9 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 24.x)
|
||
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 GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 25.x)
Check warning on line 12 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 22.x)
Check warning on line 12 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 24.x)
Check warning on line 12 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 23.x)
Check warning on line 12 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 22.x)
Check warning on line 12 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 25.x)
Check warning on line 12 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 24.x)
|
||
alias ElixirSense.Plugins.Util | ||
alias ElixirSense.Providers.Suggestion.Matcher | ||
Check warning on line 14 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 25.x)
Check warning on line 14 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 22.x)
Check warning on line 14 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 24.x)
Check warning on line 14 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 23.x)
Check warning on line 14 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 22.x)
Check warning on line 14 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 25.x)
Check warning on line 14 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 24.x)
|
||
|
||
@phoenix_route_funcs ~w( | ||
Check warning on line 16 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 25.x)
Check warning on line 16 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 22.x)
Check warning on line 16 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 24.x)
Check warning on line 16 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 23.x)
Check warning on line 16 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 22.x)
Check warning on line 16 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 25.x)
Check warning on line 16 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 24.x)
|
||
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 GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 25.x)
Check warning on line 76 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 22.x)
Check warning on line 76 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 24.x)
Check warning on line 76 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 23.x)
Check warning on line 76 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 22.x)
Check warning on line 76 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 25.x)
Check warning on line 76 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 24.x)
|
||
[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 GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 25.x)
Check warning on line 101 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 22.x)
Check warning on line 101 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 24.x)
Check warning on line 101 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 23.x)
Check warning on line 101 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 22.x)
Check warning on line 101 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 25.x)
Check warning on line 101 in lib/elixir_sense/plugins/phoenix.ex GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 24.x)
|
||
|
||
defp skip_scope_alias(scope_alias, insert_text), | ||
do: String.replace_prefix(insert_text, "#{inspect(scope_alias)}.", "") | ||
end |
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 |