Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

find elixir modules that require adding an alias #155

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions lib/elixir_sense.ex
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,8 @@ defmodule ElixirSense do
name: "insert_at", metadata: %{}, snippet: nil, visibility: :public,
spec: "@spec insert_at(list, integer, any) :: list", summary: "Returns a list with `value` inserted at the specified `index`."}]
"""
@spec suggestions(String.t(), pos_integer, pos_integer) :: [Suggestion.suggestion()]
def suggestions(buffer, line, column) do
@spec suggestions(String.t(), pos_integer, pos_integer, keyword()) :: [Suggestion.suggestion()]
def suggestions(buffer, line, column, opts \\ []) do
hint = Source.prefix(buffer, line, column)
buffer_file_metadata = Parser.parse_string(buffer, true, true, line)
{text_before, text_after} = Source.split_at(buffer, line, column)
Expand All @@ -220,7 +220,7 @@ defmodule ElixirSense do
at_module_body?: Metadata.at_module_body?(buffer_file_metadata, env)
}

Suggestion.find(hint, env, buffer_file_metadata, cursor_context, module_store)
Suggestion.find(hint, env, buffer_file_metadata, cursor_context, module_store, opts)
end

@doc """
Expand Down
35 changes: 33 additions & 2 deletions lib/elixir_sense/core/metadata.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ defmodule ElixirSense.Core.Metadata do
types: State.types_t(),
specs: State.specs_t(),
structs: State.structs_t(),
error: nil | term
error: nil | term,
first_alias_positions: map(),
moduledoc_positions: map()
}

defstruct source: "",
Expand All @@ -28,7 +30,9 @@ defmodule ElixirSense.Core.Metadata do
types: %{},
specs: %{},
structs: %{},
error: nil
error: nil,
first_alias_positions: %{},
moduledoc_positions: %{}

@type signature_t :: %{
name: String.t(),
Expand Down Expand Up @@ -58,6 +62,33 @@ defmodule ElixirSense.Core.Metadata do
end
end

def get_position_to_insert_alias(%__MODULE__{} = metadata, line) do
env = get_env(metadata, line)
module = env.module

cond do
Map.has_key?(metadata.first_alias_positions, module) ->
Map.get(metadata.first_alias_positions, module)

Map.has_key?(metadata.moduledoc_positions, module) ->
Map.get(metadata.moduledoc_positions, module)

true ->
mod_info = Map.get(metadata.mods_funs_to_positions, {env.module, nil, nil})

case mod_info do
%State.ModFunInfo{positions: [{line, column}]} ->
# Hacky :shrug
line_offset = 1
column_offset = -8
{line + line_offset, column + column_offset}

_ ->
nil
end
end
end

def get_calls(%__MODULE__{} = metadata, line) do
case Map.get(metadata.calls, line) do
nil -> []
Expand Down
18 changes: 13 additions & 5 deletions lib/elixir_sense/core/metadata_builder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,9 @@ defmodule ElixirSense.Core.MetadataBuilder do
{get_binding_type(state, param), true}
end

state =
add_moduledoc_positions(state, [line: line, column: column], [{name, meta, params}], line)

new_ast = {:@, meta_attr, [{name, add_no_call(meta), params}]}
pre_module_attribute(new_ast, state, {line, column}, name, type, is_definition)
end
Expand Down Expand Up @@ -704,46 +707,50 @@ defmodule ElixirSense.Core.MetadataBuilder do

# alias with `as` option
defp pre(
{:alias, [line: line, column: _column],
{:alias, [line: line, column: column],
[{_, _, module_expression = [_ | _]}, [as: alias_expression]]} = ast,
state
) do
module = concat_module_expression(state, module_expression)
alias_tuple = alias_tuple(module, alias_expression)
state = add_first_alias_positions(state, line, column)
pre_alias(ast, state, line, alias_tuple)
end

# alias for submodule of __MODULE__ with `as` option
defp pre(
{:alias, [line: line, column: _column], [{:__MODULE__, _, nil}, [as: alias_expression]]} =
{:alias, [line: line, column: column], [{:__MODULE__, _, nil}, [as: alias_expression]]} =
ast,
state
) do
module = get_current_module(state)
alias_tuple = alias_tuple(module, alias_expression)
state = add_first_alias_positions(state, line, column)
pre_alias(ast, state, line, alias_tuple)
end

# alias atom module with `as` option
defp pre({:alias, [line: line, column: _column], [mod, [as: alias_expression]]} = ast, state)
defp pre({:alias, [line: line, column: column], [mod, [as: alias_expression]]} = ast, state)
when is_atom(mod) do
alias_tuple = alias_tuple(mod, alias_expression)
state = add_first_alias_positions(state, line, column)
pre_alias(ast, state, line, alias_tuple)
end

# alias
defp pre(
{:alias, [line: line, column: _column],
{:alias, [line: line, column: column],
[{:__aliases__, _, module_expression = [_ | _]}, _opts]} = ast,
state
) do
module = concat_module_expression(state, module_expression)
alias_tuple = {Module.concat([List.last(module_expression)]), module}
state = add_first_alias_positions(state, line, column)
pre_alias(ast, state, line, alias_tuple)
end

# alias atom module
defp pre({:alias, [line: line, column: _column], [mod, _opts]} = ast, state)
defp pre({:alias, [line: line, column: column], [mod, _opts]} = ast, state)
when is_atom(mod) do
alias_tuple =
if Introspection.elixir_module?(mod) do
Expand All @@ -752,6 +759,7 @@ defmodule ElixirSense.Core.MetadataBuilder do
{mod, mod}
end

state = add_first_alias_positions(state, line, column)
pre_alias(ast, state, line, alias_tuple)
end

Expand Down
4 changes: 3 additions & 1 deletion lib/elixir_sense/core/parser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ defmodule ElixirSense.Core.Parser do
mods_funs_to_positions: acc.mods_funs_to_positions,
lines_to_env: acc.lines_to_env,
vars_info_per_scope_id: acc.vars_info_per_scope_id,
calls: acc.calls
calls: acc.calls,
first_alias_positions: acc.first_alias_positions,
moduledoc_positions: acc.moduledoc_positions
}
end

Expand Down
75 changes: 74 additions & 1 deletion lib/elixir_sense/core/state.ex
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ defmodule ElixirSense.Core.State do
calls: calls_t,
structs: structs_t,
types: types_t,
first_alias_positions: map(),
moduledoc_positions: map(),
# TODO
binding_context: list
}
Expand All @@ -76,7 +78,9 @@ defmodule ElixirSense.Core.State do
calls: %{},
structs: %{},
types: %{},
binding_context: []
binding_context: [],
first_alias_positions: %{},
moduledoc_positions: %{}

defmodule Env do
@moduledoc """
Expand Down Expand Up @@ -303,6 +307,75 @@ defmodule ElixirSense.Core.State do
%__MODULE__{state | lines_to_env: Map.put(state.lines_to_env, line, env)}
end

def add_moduledoc_positions(
%__MODULE__{} = state,
[line: line, column: column],
[{:moduledoc, _meta, [here_doc]}],
line
)
when is_integer(line) and is_binary(here_doc) do
module_name = module_name(state)

new_line_count = here_doc |> String.split("\n") |> Enum.count()
line_to_insert_alias = new_line_count + line + 1

%__MODULE__{
state
| moduledoc_positions:
Map.put(state.moduledoc_positions, module_name, {line_to_insert_alias, column})
}
end

def add_moduledoc_positions(
%__MODULE__{} = state,
[line: line, column: column],
[{:moduledoc, _meta, [params]}],
line
)
when is_integer(line) and is_boolean(params) do
module_name = module_name(state)

line_to_insert_alias = line + 1

%__MODULE__{
state
| moduledoc_positions:
Map.put(state.moduledoc_positions, module_name, {line_to_insert_alias, column})
}
end

def add_moduledoc_positions(state, _, _, _), do: state

def add_first_alias_positions(%__MODULE__{} = state, line, column)
when is_integer(line) and is_integer(column) do
current_scope = hd(hd(state.scopes))

is_module? = is_atom(current_scope)

if is_module? do
module_name = module_name(state)

%__MODULE__{
state
| first_alias_positions:
Map.put_new(state.first_alias_positions, module_name, {line, column})
}
else
state
end
end

defp module_name(state) do
hd(state.scopes)
|> Enum.reverse()
|> after_elixir_prefix()
|> Enum.filter(&is_atom/1)
|> Module.concat()
end

defp after_elixir_prefix([Elixir | rest]), do: rest
defp after_elixir_prefix(rest), do: rest

def add_call_to_line(%__MODULE__{} = state, {mod, func, arity}, {line, _column} = position) do
call = %CallInfo{mod: mod, func: func, arity: arity, position: position}

Expand Down
23 changes: 20 additions & 3 deletions lib/elixir_sense/providers/suggestion.ex
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ defmodule ElixirSense.Providers.Suggestion do
overridable: &Reducers.Overridable.add_overridable/5,
param_options: &Reducers.Params.add_options/5,
typespecs: &Reducers.TypeSpecs.add_types/5,
populate_common: &Reducers.Common.populate/5,
populate_common: &Reducers.Common.populate/6,
variables: &Reducers.Common.add_variables/5,
modules: &Reducers.Common.add_modules/5,
functions: &Reducers.Common.add_functions/5,
Expand All @@ -99,12 +99,14 @@ defmodule ElixirSense.Providers.Suggestion do
bitstring_options: &Reducers.Bitstring.add_bitstring_options/5
]

@add_opts_for [:populate_common]

@doc """
Finds all suggestions for a hint based on context information.
"""
@spec find(String.t(), State.Env.t(), Metadata.t(), cursor_context, ModuleStore.t()) ::
@spec find(String.t(), State.Env.t(), Metadata.t(), cursor_context, ModuleStore.t(), keyword()) ::
[suggestion()]
def find(hint, env, buffer_metadata, cursor_context, module_store) do
def find(hint, env, buffer_metadata, cursor_context, module_store, opts \\ []) do
plugins = module_store.by_behaviour[ElixirSense.Plugin] || []

reducers =
Expand All @@ -114,6 +116,7 @@ defmodule ElixirSense.Providers.Suggestion do
{module, &module.reduce/5}
end)
|> Enum.concat(@reducers)
|> maybe_add_opts(opts)

context =
plugins
Expand All @@ -139,4 +142,18 @@ defmodule ElixirSense.Providers.Suggestion do
|> Enum.reduce(item, fn module, item -> module.decorate(item) end)
end
end

defp maybe_add_opts(reducers, opts) do
Enum.map(reducers, fn {name, reducer} ->
if name in @add_opts_for do
{name, reducer_with_opts(reducer, opts)}
else
{name, reducer}
end
end)
end

defp reducer_with_opts(fun, opts) do
fn a, b, c, d, e -> fun.(a, b, c, d, e, opts) end
end
end
Loading