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 2 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
4 changes: 2 additions & 2 deletions lib/elixir_sense.ex
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ defmodule ElixirSense do
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()]
lukaszsamson marked this conversation as resolved.
Show resolved Hide resolved
def suggestions(buffer, line, column) do
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
29 changes: 28 additions & 1 deletion lib/elixir_sense/core/metadata.ex
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ defmodule ElixirSense.Core.Metadata do
types: %{},
specs: %{},
structs: %{},
error: nil
error: nil,
first_alias_positions: nil,
lukaszsamson marked this conversation as resolved.
Show resolved Hide resolved
moduledoc_positions: nil

@type signature_t :: %{
name: String.t(),
Expand Down Expand Up @@ -58,6 +60,31 @@ 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 + 1, column - 10 + 2}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do these numbers represent?

I think you could extract them to variables, just to get the magic-number look out of it:

Suggested change
{line + 1, column - 10 + 2}
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)

line_to_insert_alias =
here_doc |> String.split("\n") |> Enum.count() |> then(fn count -> line + count + 1 end)

%__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()
|> then(fn
[Elixir | rest] -> rest
rest -> rest
end)
|> Enum.filter(&is_atom/1)
|> Module.concat()
end

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
21 changes: 19 additions & 2 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()) ::
lukaszsamson marked this conversation as resolved.
Show resolved Hide resolved
[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