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

Added safe_split to Ast.Module #743

Merged
merged 2 commits into from
May 19, 2024
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
49 changes: 49 additions & 0 deletions apps/common/lib/lexical/ast/module.ex
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,53 @@ defmodule Lexical.Ast.Module do
|> String.split(".")
|> List.last()
end

@doc """
Splits a module into is parts, but handles erlang modules

Module.split will explode violently when called on an erlang module. This
implementation will tell you which kind of module it has split, and return the
pieces. You can also use the options to determine if the pieces are returned as
strings or atoms

Options:
`as` :atoms or :binaries. Default is :binary. Determines what type the elements
of the returned list are.

Returns:
A tuple where the first element is either `:elixir` or `:erlang`, which tells you
the kind of module that has been split. The second element is a list of the
module's components. Note: Erlang modules will only ever have a single component.
"""
@type split_opt :: {:as, :binaries | :atoms}
@type split_opts :: [split_opt()]
@type split_return :: {:elixir | :erlang, [String.t()] | [atom()]}

@spec safe_split(module()) :: split_return()
@spec safe_split(module(), split_opts()) :: split_return()
def safe_split(module, opts \\ [])

def safe_split(module, opts) when is_atom(module) do
string_name = Atom.to_string(module)

{type, split_module} =
case String.split(string_name, ".") do
["Elixir" | rest] ->
{:elixir, rest}

[_erlang_module] = module ->
{:erlang, module}
end

split_module =
case Keyword.get(opts, :as, :binaries) do
:binaries ->
split_module

:atoms ->
Enum.map(split_module, &String.to_atom/1)
end

{type, split_module}
end
end
26 changes: 26 additions & 0 deletions apps/common/test/lexical/ast/module_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
defmodule Lexical.Ast.ModuleTest do
import Lexical.Ast.Module
use ExUnit.Case, async: true

describe "safe_split/2" do
test "splits elixir modules into binaries by default" do
assert {:elixir, ~w(Lexical Document Store)} == safe_split(Lexical.Document.Store)
end

test "splits elixir modules into binaries" do
assert {:elixir, ~w(Lexical Document Store)} ==
safe_split(Lexical.Document.Store, as: :binaries)
end

test "splits elixir modules into atoms" do
assert {:elixir, ~w(Lexical Document Store)a} ==
safe_split(Lexical.Document.Store, as: :atoms)
end

test "splits erlang modules" do
assert {:erlang, ["ets"]} = safe_split(:ets)
assert {:erlang, ["ets"]} = safe_split(:ets, as: :binaries)
assert {:erlang, [:ets]} = safe_split(:ets, as: :atoms)
end
end
end
6 changes: 2 additions & 4 deletions apps/remote_control/lib/lexical/remote_control/analyzer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,8 @@ defmodule Lexical.RemoteControl.Analyzer do

def expand_alias(module, %Analysis{} = analysis, %Position{} = position)
when is_atom(module) and not is_nil(module) do
module
|> Module.split()
|> Enum.map(&String.to_atom/1)
|> expand_alias(analysis, position)
{:elixir, segments} = Ast.Module.safe_split(module, as: :atoms)
expand_alias(segments, analysis, position)
end

def expand_alias(empty, _, _) when empty in [nil, []] do
Expand Down
10 changes: 4 additions & 6 deletions apps/remote_control/lib/lexical/remote_control/build/error.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
defmodule Lexical.RemoteControl.Build.Error do
alias Lexical.Ast
alias Lexical.Document
alias Lexical.Plugin.V1.Diagnostic.Result
alias Lexical.RemoteControl.Build.Error.Location
Expand Down Expand Up @@ -274,12 +275,9 @@ defmodule Lexical.RemoteControl.Build.Error do
end

defp safe_split(module) do
module
|> Atom.to_string()
|> String.split(".")
|> case do
[erlang_module] -> String.to_atom(erlang_module)
["Elixir" | elixir_module_path] -> Enum.map(elixir_module_path, &String.to_atom/1)
case Ast.Module.safe_split(module, as: :atoms) do
{:elixir, segments} -> segments
{:erlang, [erlang_module]} -> erlang_module
end
end

Expand Down
9 changes: 4 additions & 5 deletions apps/remote_control/lib/mix/tasks/namespace.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ defmodule Mix.Tasks.Namespace do

This task takes a single argument, which is the full path to the release.
"""
alias Lexical.Ast
alias Mix.Tasks.Namespace.Transform
use Mix.Task

Expand Down Expand Up @@ -96,11 +97,9 @@ defmodule Mix.Tasks.Namespace do
end

defp safe_split_module(module) do
module_string = Atom.to_string(module)

case String.split(module_string, ".") do
["Elixir" | rest] -> rest
_ -> []
case Ast.Module.safe_split(module) do
{:elixir, segments} -> segments
{:erlang, _} -> []
end
end

Expand Down
Loading