Skip to content

Commit f14848d

Browse files
lukaszsamsonmsaraiva
authored andcommitted
provide submodule suggestions in v1.2 alias syntax (#55)
1 parent 714cfaf commit f14848d

File tree

4 files changed

+174
-11
lines changed

4 files changed

+174
-11
lines changed

lib/elixir_sense/core/source.ex

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,21 @@ defmodule ElixirSense.Core.Source do
161161
end
162162
end
163163

164+
def get_v12_module_prefix(text_before) do
165+
with %{"module" => module_str} <-
166+
Regex.named_captures(
167+
~r/(alias|require|import|use)\s+(?<module>[^\s^\{^\}]+?)\.\{[^\}]*?$/,
168+
text_before
169+
),
170+
{:ok, ast} <- Code.string_to_quoted(module_str),
171+
{:ok, module} <- extract_module(ast) do
172+
module
173+
else
174+
_ ->
175+
nil
176+
end
177+
end
178+
164179
defp extract_module({:__aliases__, _, module_list}) do
165180
{:ok, Module.concat(module_list)}
166181
end

lib/elixir_sense/providers/suggestion.ex

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,8 @@ defmodule ElixirSense.Providers.Suggestion do
166166
vars,
167167
attributes,
168168
module,
169-
mods_and_funs
169+
mods_and_funs,
170+
text_before
170171
)
171172

172173
[%{type: :hint, value: "#{hint}"} | fields ++ rest]
@@ -202,7 +203,7 @@ defmodule ElixirSense.Providers.Suggestion do
202203
vars = Enum.map(vars, fn v -> v.name end)
203204

204205
%{hint: hint_suggestion, suggestions: mods_and_funcs} =
205-
find_hint_mods_funcs(hint, imports, aliases, module, mods_and_funs)
206+
find_hint_mods_funcs(hint, imports, aliases, module, mods_and_funs, text_before)
206207

207208
callbacks_or_returns =
208209
case scope do
@@ -227,12 +228,13 @@ defmodule ElixirSense.Providers.Suggestion do
227228
vars,
228229
attributes,
229230
module,
230-
mods_and_funs
231+
mods_and_funs,
232+
text_before
231233
) do
232234
vars = Enum.map(vars, fn v -> v.name end)
233235

234236
%{hint: hint_suggestion, suggestions: mods_and_funcs} =
235-
find_hint_mods_funcs(hint, imports, aliases, module, mods_and_funs)
237+
find_hint_mods_funcs(hint, imports, aliases, module, mods_and_funs, text_before)
236238

237239
[hint_suggestion]
238240
|> Kernel.++(find_attributes(attributes, hint))
@@ -277,20 +279,42 @@ defmodule ElixirSense.Providers.Suggestion do
277279
end
278280
end
279281

280-
@spec find_hint_mods_funcs(String.t(), [module], [{module, module}], module, %{}) :: %{
281-
hint: hint,
282-
suggestions: [mod | func]
283-
}
284-
defp find_hint_mods_funcs(hint, imports, aliases, module, mods_and_funs) do
282+
@spec find_hint_mods_funcs(String.t(), [module], [{module, module}], module, %{}, String.t()) ::
283+
%{
284+
hint: hint,
285+
suggestions: [mod | func]
286+
}
287+
defp find_hint_mods_funcs(hint, imports, aliases, module, mods_and_funs, text_before) do
285288
env = %Complete.Env{
286289
aliases: aliases,
287290
scope_module: module,
288291
imports: imports,
289292
mods_and_funs: mods_and_funs
290293
}
291294

292-
{hint_suggestion, suggestions} = Complete.run(hint, env)
293-
%{hint: hint_suggestion, suggestions: suggestions}
295+
{hint, prefix} =
296+
case Source.get_v12_module_prefix(text_before) do
297+
nil ->
298+
{hint, ""}
299+
300+
module ->
301+
# v1.2 alias syntax detected
302+
# prepend module prefix before running completion
303+
prefix = inspect(module) <> "."
304+
{prefix <> hint, prefix}
305+
end
306+
307+
{%{type: :hint, value: prefixed_value}, suggestions} = Complete.run(hint, env)
308+
309+
# drop module prefix from hint if added
310+
value =
311+
if prefix != "" do
312+
prefixed_value |> String.replace_leading(prefix, "")
313+
else
314+
prefixed_value
315+
end
316+
317+
%{hint: %{type: :hint, value: value}, suggestions: suggestions}
294318
end
295319

296320
@spec find_vars([String.t()], String.t()) :: [variable]

test/elixir_sense/core/source_test.exs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,4 +561,98 @@ defmodule ElixirSense.Core.SourceTest do
561561
assert which_struct(text_before(code, 5, 7)) == {Mod, [:field1]}
562562
end
563563
end
564+
565+
describe "alias syntax v1.2" do
566+
test "single level" do
567+
code = """
568+
defmodule MyMod do
569+
def my_func(par1) do
570+
alias Mod.{
571+
"""
572+
573+
assert get_v12_module_prefix(code) == Mod
574+
end
575+
576+
test "single level with submodule" do
577+
code = """
578+
defmodule MyMod do
579+
def my_func(par1) do
580+
require Mod.{Su
581+
"""
582+
583+
assert get_v12_module_prefix(code) == Mod
584+
end
585+
586+
test "single level with submodules" do
587+
code = """
588+
defmodule MyMod do
589+
def my_func(par1) do
590+
use Mod.{Su.Bmod, Other
591+
"""
592+
593+
assert get_v12_module_prefix(code) == Mod
594+
end
595+
596+
test "multi level with submodules" do
597+
code = """
598+
defmodule MyMod do
599+
def my_func(par1) do
600+
import Mod.Sub.{
601+
"""
602+
603+
assert get_v12_module_prefix(code) == Mod.Sub
604+
end
605+
606+
test "__MODULE__ special form level with submodules" do
607+
code = """
608+
defmodule MyMod do
609+
def my_func(par1) do
610+
alias __MODULE__.{
611+
"""
612+
613+
assert get_v12_module_prefix(code) == :__MODULE__
614+
end
615+
616+
test "atom module special form level with submodules" do
617+
code = """
618+
defmodule MyMod do
619+
def my_func(par1) do
620+
alias :"Elixir.Mod".{
621+
"""
622+
623+
assert get_v12_module_prefix(code) == Mod
624+
end
625+
626+
test "nil when closed" do
627+
code = """
628+
defmodule MyMod do
629+
def my_func(par1) do
630+
alias Mod.{}
631+
"""
632+
633+
assert get_v12_module_prefix(code) == nil
634+
end
635+
636+
test "nil when not on last line" do
637+
code = """
638+
defmodule MyMod do
639+
def my_func(par1) do
640+
alias Mod.{A, B}
641+
alias C
642+
"""
643+
644+
assert get_v12_module_prefix(code) == nil
645+
end
646+
647+
test "multiline" do
648+
code = """
649+
defmodule MyMod do
650+
def my_func(par1) do
651+
alias Mod.{A,
652+
B
653+
"""
654+
655+
assert get_v12_module_prefix(code) == Mod
656+
end
657+
end
564658
end

test/elixir_sense/suggestions_test.exs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1166,6 +1166,36 @@ defmodule ElixirSense.SuggestionsTest do
11661166
assert Enum.any?(list, fn %{type: type} -> type == :field end) == false
11671167
end
11681168

1169+
test "suggest modules to alias" do
1170+
buffer = """
1171+
defmodule MyModule do
1172+
alias Str
1173+
end
1174+
"""
1175+
1176+
list =
1177+
ElixirSense.suggestions(buffer, 2, 12)
1178+
|> Enum.filter(fn s -> s.type == :module end)
1179+
1180+
assert [
1181+
%{name: "Stream"},
1182+
%{name: "String"},
1183+
%{name: "StringIO"}
1184+
] = list
1185+
end
1186+
1187+
test "suggest modules to alias v1.2 syntax" do
1188+
buffer = """
1189+
defmodule MyModule do
1190+
alias Stream.{Re
1191+
end
1192+
"""
1193+
1194+
list = ElixirSense.suggestions(buffer, 2, 19)
1195+
1196+
assert [%{type: :hint, value: "Reducers"}, %{name: "Reducers", type: :module}] = list
1197+
end
1198+
11691199
describe "suggestion for param options" do
11701200
test "suggest more than one option" do
11711201
buffer = "Local.func_with_options("

0 commit comments

Comments
 (0)