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

Made aliases better handle the __aliases__ special form #393

Merged
merged 4 commits into from
Oct 2, 2023
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
51 changes: 42 additions & 9 deletions apps/common/lib/lexical/ast.ex
Original file line number Diff line number Diff line change
Expand Up @@ -463,17 +463,18 @@ defmodule Lexical.Ast do
|> expand_aliases(document, quoted_document, position)
end

def expand_aliases(
[first | rest] = segments,
%Document{} = document,
quoted_document,
%Position{} = position
) do
def expand_aliases(segments, %Document{} = document, quoted_document, %Position{} = position)
when is_list(segments) do
with {:ok, aliases_mapping} <- Aliases.at(document, quoted_document, position),
{:ok, resolved} <- Map.fetch(aliases_mapping, first) do
{:ok, Module.concat([resolved | rest])}
{:ok, resolved} <- resolve_alias(segments, aliases_mapping) do
{:ok, Module.concat(resolved)}
else
_ -> {:ok, Module.concat(segments)}
_ ->
if Enum.all?(segments, &is_atom/1) do
{:ok, Module.concat(segments)}
else
:error
end
end
end

Expand All @@ -482,7 +483,39 @@ defmodule Lexical.Ast do
:error
end

# Expands aliases given the rules in the special form
# https://hexdocs.pm/elixir/1.13.4/Kernel.SpecialForms.html#__aliases__/1
def reify_alias(current_module, [:"Elixir" | _] = reified) do
zachallaun marked this conversation as resolved.
Show resolved Hide resolved
[current_module | reified]
end

def reify_alias(current_module, [:__MODULE__ | rest]) do
[current_module | rest]
end

def reify_alias(current_module, [atom | _rest] = reified) when is_atom(atom) do
[current_module | reified]
end

def reify_alias(current_module, [unreified | rest]) do
env = %Macro.Env{module: current_module}
reified = Macro.expand(unreified, env)

[reified | rest]
end

# private
defp resolve_alias([first | _] = segments, aliases_mapping) when is_tuple(first) do
with {:ok, current_module} <- Map.fetch(aliases_mapping, :__MODULE__) do
{:ok, reify_alias(current_module, segments)}
end
end

defp resolve_alias([first | rest], aliases_mapping) do
with {:ok, resolved} <- Map.fetch(aliases_mapping, first) do
{:ok, [resolved | rest]}
end
end

defp do_string_to_quoted(string) when is_binary(string) do
Code.string_to_quoted(string,
Expand Down
3 changes: 2 additions & 1 deletion apps/common/lib/lexical/ast/aliases.ex
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ defmodule Lexical.Ast.Aliases do
end

defmodule Reducer do
alias Lexical.Ast
defstruct scopes: []

def new do
Expand Down Expand Up @@ -106,7 +107,7 @@ defmodule Lexical.Ast.Aliases do
module_name

current_module ->
Module.split(current_module) ++ module_name
Ast.reify_alias(current_module, module_name)
end

current_module_alias =
Expand Down
24 changes: 21 additions & 3 deletions apps/common/lib/lexical/ast/module.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,26 @@ defmodule Lexical.Ast.Module do
@doc """
Formats a module name as a string.
"""
@spec name(module()) :: String.t()
def name(module) when is_atom(module) do
module |> to_string() |> String.replace_prefix("Elixir.", "")
@spec name(module() | Macro.t() | String.t()) :: String.t()
def name([{:__MODULE__, _, _} | rest]) do
[__MODULE__ | rest]
|> Module.concat()
|> name()
end

def name(module_name) when is_list(module_name) do
module_name
|> Module.concat()
|> name()
end

def name(module_name) when is_binary(module_name) do
module_name
end

def name(module_name) when is_atom(module_name) do
module_name
|> inspect()
|> name()
end
end
14 changes: 14 additions & 0 deletions apps/common/test/lexical/ast/aliases_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,20 @@ defmodule Lexical.Ast.AliasesTest do
assert aliases[:Child] == Grandparent.Parent.Child
assert aliases[:__MODULE__] == Grandparent.Parent.Child
end

test "with a child that has an explicit parent" do
{:ok, aliases} =
~q[
defmodule Parent do
defmodule __MODULE__.Child do
|
end
end
]
|> aliases_at_cursor()

assert aliases[:__MODULE__] == Parent.Child
end
end

describe "alias scopes" do
Expand Down
17 changes: 17 additions & 0 deletions apps/common/test/lexical/ast_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,23 @@ defmodule Lexical.AstTest do
end
end

describe "expand_aliases/4" do
test "works with __MODULE__ aliases" do
{position, document} =
~q[
defmodule Parent do
defmodule __MODULE__.Child do
|
end
end
]
|> pop_cursor(as: :document)

assert {:ok, Parent.Child} =
Ast.expand_aliases([quote(do: __MODULE__), nil], document, position)
end
end

defp ast(s) do
case Ast.from(s) do
{:ok, {:__block__, _, [node]}} -> node
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,28 +152,12 @@ defmodule Lexical.RemoteControl.Search.Indexer.Extractors.Module do
defp to_range(%Document{} = document, module_name, {line, column}) do
module_length =
module_name
|> module_name()
|> Ast.Module.name()
|> String.length()

Range.new(
Position.new(document, line, column),
Position.new(document, line, column + module_length)
)
end

defp module_name(module_name) when is_list(module_name) do
module_name
|> Module.concat()
|> module_name()
end

defp module_name(module_name) when is_binary(module_name) do
module_name
end

defp module_name(module_name) when is_atom(module_name) do
module_name
|> inspect()
|> module_name()
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -387,5 +387,24 @@ defmodule Lexical.RemoteControl.Search.Indexer.SourceTest do
assert child_alias.subtype == :reference
assert child_alias.subject == Something.Else.Other
end

test "works with __MODULE__" do
{:ok, [parent, child], _} =
~q[
defmodule Parent do
defmodule __MODULE__.Child do
end
end
]
|> index()

assert parent.parent == :root
assert parent.type == :module
assert parent.subtype == :definition

assert child.parent == parent.ref
assert child.type == :module
assert child.subtype == :definition
end
end
end