Skip to content

Commit

Permalink
Add methods to get positions of first alias and moduledoc to be useful
Browse files Browse the repository at this point in the history
for inserting alias
  • Loading branch information
ajayvigneshk committed Aug 27, 2022
1 parent 077fc72 commit a3e66a9
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 8 deletions.
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,
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}

_ ->
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
76 changes: 75 additions & 1 deletion lib/elixir_sense/core/state.ex
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ defmodule ElixirSense.Core.State do
calls: calls_t,
structs: structs_t,
types: types_t,
# TODO[ajay]
first_alias_positions: map(),
moduledoc_positions: map(),
# TODO
binding_context: list
}
Expand All @@ -76,7 +79,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 +308,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
47 changes: 47 additions & 0 deletions test/elixir_sense/core/metadata_builder_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,37 @@ defmodule ElixirSense.Core.MetadataBuilderTest do
) =~ "def function_arity_zero"
end

test "moduledoc heredoc version" do
state =
"""
defmodule Outer do
@moduledoc \"\"\"
This is the here doc version
\"\"\"
defmodule Inner do
@moduledoc \"\"\"
This is the Inner modules moduledoc
\"\"\"
end
end
"""
|> string_to_state

assert %{Outer => {5, 3}, Outer.Inner => {9, 5}} = state.moduledoc_positions
end

test "moduledoc boolean version" do
state =
"""
defmodule Outer do
@moduledoc false
end
"""
|> string_to_state

assert %{Outer => {3, 3}} = state.moduledoc_positions
end

test "module attributes" do
state =
"""
Expand Down Expand Up @@ -3382,6 +3413,22 @@ defmodule ElixirSense.Core.MetadataBuilderTest do
} = state.mods_funs_to_positions
end

test "first_alias_positions" do
state =
"""
defmodule OuterMod do
alias Foo.Bar
alias Foo1.Bar1
defmodule InnerMod do
alias Baz.Quz
end
end
"""
|> string_to_state

assert %{OuterMod => {2, 3}, OuterMod.InnerMod => {5, 5}} = state.first_alias_positions
end

test "use" do
state =
"""
Expand Down
67 changes: 67 additions & 0 deletions test/elixir_sense/core/metadata_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,71 @@ defmodule ElixirSense.Core.MetadataTest do
env = Metadata.get_env(metadata, 17)
refute Metadata.at_module_body?(metadata, env)
end

test "get_position_to_insert_alias when aliases exist" do
code = """
defmodule MyModule do
alias Foo.Bar #2
def foo do
IO.puts() #5
end
defmodule Inner do
alias Foo.Bar #9
def bar do
IO.puts() #11
end
end
end
"""

line_number = 5
metadata = Parser.parse_string(code, true, true, line_number)
position = Metadata.get_position_to_insert_alias(metadata, line_number)

assert {2, 3} == position

line_number = 11
metadata = Parser.parse_string(code, true, true, line_number)
position = Metadata.get_position_to_insert_alias(metadata, line_number)

assert {9, 5} == position
end

test "get_position_to_insert_alias when moduledoc exists" do
code = """
defmodule MyModule do
@moduledoc \"\"\"
New module without any aliases
\"\"\"
def foo do
#7
end
end
"""

line_number = 7
metadata = Parser.parse_string(code, true, true, line_number)
position = Metadata.get_position_to_insert_alias(metadata, line_number)

assert {5, 3} == position
end

test "get_position_to_insert_alias when neither alias nor moduledoc exists" do
code = """
defmodule MyModule do
def foo do
#3
end
end
"""

line_number = 3
metadata = Parser.parse_string(code, true, true, line_number)
position = Metadata.get_position_to_insert_alias(metadata, line_number)

assert {2, 3} == position
end
end

0 comments on commit a3e66a9

Please sign in to comment.