Skip to content

Commit 03b36f6

Browse files
author
Étienne Lévesque
committed
Refactor provider to include test name in lenses
1 parent 01b0758 commit 03b36f6

File tree

1 file changed

+152
-46
lines changed
  • apps/language_server/lib/language_server/providers/code_lens

1 file changed

+152
-46
lines changed

apps/language_server/lib/language_server/providers/code_lens/test.ex

Lines changed: 152 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,182 @@
1+
defmodule DescribeBlock do
2+
alias ElixirSense.Core.State.Env
3+
4+
@struct_keys [:line, :name, :body_scope_id]
5+
6+
@enforce_keys @struct_keys
7+
defstruct @struct_keys
8+
9+
def find_block_info(line, lines_to_env_list, lines_to_env_list_length, source_lines) do
10+
name = get_name(source_lines, line)
11+
12+
body_scope_id =
13+
get_body_scope_id(
14+
line,
15+
lines_to_env_list,
16+
lines_to_env_list_length
17+
)
18+
19+
%DescribeBlock{line: line, body_scope_id: body_scope_id, name: name}
20+
end
21+
22+
defp get_name(source_lines, declaration_line) do
23+
%{"name" => name} =
24+
~r/^\s*describe "(?<name>.*)" do/
25+
|> Regex.named_captures(Enum.at(source_lines, declaration_line - 1))
26+
27+
name
28+
end
29+
30+
defp get_body_scope_id(
31+
declaration_line,
32+
lines_to_env_list,
33+
lines_to_env_list_length
34+
) do
35+
env_index =
36+
lines_to_env_list
37+
|> Enum.find_index(fn {line, _env} -> line == declaration_line end)
38+
39+
{_line, %{scope_id: declaration_scope_id}} =
40+
lines_to_env_list
41+
|> Enum.at(env_index)
42+
43+
with true = env_index + 1 < lines_to_env_list_length,
44+
next_env = Enum.at(lines_to_env_list, env_index + 1),
45+
{_line, %Env{scope_id: body_scope_id}} <- next_env,
46+
true = body_scope_id != declaration_scope_id do
47+
body_scope_id
48+
else
49+
_ -> nil
50+
end
51+
end
52+
end
53+
54+
defmodule TestBlock do
55+
@struct_keys [:name, :describe, :line]
56+
57+
@enforce_keys @struct_keys
58+
defstruct @struct_keys
59+
end
60+
161
defmodule ElixirLS.LanguageServer.Providers.CodeLens.Test do
262
alias ElixirLS.LanguageServer.Providers.CodeLens
363
alias ElixirLS.LanguageServer.SourceFile
464
alias ElixirSense.Core.Parser
565
alias ElixirSense.Core.Metadata
66+
alias ElixirSense.Core.State.Env
667

768
@run_test_command "elixir.test.run"
869

970
def code_lens(uri, text) do
1071
with {:ok, buffer_file_metadata} <- parse_source(text) do
72+
source_lines = SourceFile.lines(text)
73+
1174
file_path = SourceFile.path_from_uri(uri)
1275

13-
function_lenses = get_function_lenses(buffer_file_metadata, file_path)
14-
module_lenses = get_module_lenses(buffer_file_metadata, file_path)
76+
calls_list =
77+
buffer_file_metadata.calls
78+
|> Enum.map(fn {_k, v} -> v end)
79+
|> List.flatten()
80+
81+
lines_to_env_list = Map.to_list(buffer_file_metadata.lines_to_env)
82+
83+
describe_blocks = find_describe_blocks(lines_to_env_list, calls_list, source_lines)
84+
describe_lenses = get_describe_lenses(describe_blocks, file_path)
85+
86+
test_lenses =
87+
lines_to_env_list
88+
|> find_test_blocks(calls_list, describe_blocks, source_lines)
89+
|> get_test_lenses(file_path)
1590

16-
{:ok, function_lenses ++ module_lenses}
91+
module_lenses =
92+
buffer_file_metadata
93+
|> get_test_modules()
94+
|> get_module_lenses(file_path)
95+
96+
{:ok, test_lenses ++ describe_lenses ++ module_lenses}
1797
end
1898
end
1999

20-
defp get_module_lenses(%Metadata{} = metadata, file_path) do
21-
metadata
22-
|> get_test_modules()
23-
|> Enum.map(&build_test_module_code_lens(file_path, &1))
100+
def get_test_lenses(test_blocks, file_path) do
101+
test_blocks
102+
|> Enum.map(fn block ->
103+
CodeLens.build_code_lens(block.line, "Run test", @run_test_command, %{
104+
"filePath" => file_path,
105+
"describe" =>
106+
if block.describe != nil do
107+
block.describe.name
108+
else
109+
nil
110+
end,
111+
"testName" => block.name
112+
})
113+
end)
24114
end
25115

26-
defp get_test_modules(%Metadata{lines_to_env: lines_to_env}) do
27-
lines_to_env
28-
|> Enum.group_by(fn {_line, env} -> env.module end)
29-
|> Enum.filter(fn {_module, module_lines_to_env} -> is_test_module?(module_lines_to_env) end)
30-
|> Enum.map(fn {module, [{line, _env} | _rest]} -> {module, line} end)
116+
def get_describe_lenses(describe_blocks, file_path) do
117+
describe_blocks
118+
|> Enum.map(fn block ->
119+
CodeLens.build_code_lens(block.line, "Run tests", @run_test_command, %{
120+
"filePath" => file_path,
121+
"describe" => block.name
122+
})
123+
end)
31124
end
32125

33-
defp get_function_lenses(%Metadata{} = metadata, file_path) do
34-
runnable_functions = [{:test, 3}, {:test, 2}, {:describe, 2}]
35-
36-
calls_list =
37-
metadata.calls
38-
|> Enum.map(fn {_k, v} -> v end)
39-
|> List.flatten()
40-
41-
lines_to_env_list = Map.to_list(metadata.lines_to_env)
126+
defp find_test_blocks(lines_to_env_list, calls_list, describe_blocks, source_lines) do
127+
runnable_functions = [{:test, 3}, {:test, 2}]
42128

43129
for func <- runnable_functions,
44130
{line, _col} <- calls_to(calls_list, func),
45131
is_test_module?(lines_to_env_list, line) do
46-
build_function_test_code_lens(func, file_path, line)
132+
{_line, %{scope_id: scope_id}} =
133+
Enum.find(lines_to_env_list, fn {env_line, _env} -> env_line == line end)
134+
135+
describe =
136+
describe_blocks
137+
|> Enum.find(nil, fn describe ->
138+
describe.body_scope_id == scope_id
139+
end)
140+
141+
%{"name" => test_name} =
142+
~r/^\s*test "(?<name>.*)"(,.*)? do/
143+
|> Regex.named_captures(Enum.at(source_lines, line - 1))
144+
145+
%TestBlock{name: test_name, describe: describe, line: line}
146+
end
147+
end
148+
149+
defp find_describe_blocks(lines_to_env_list, calls_list, source_lines) do
150+
lines_to_env_list_length = length(lines_to_env_list)
151+
152+
for {line, _col} <- calls_to(calls_list, {:describe, 2}),
153+
is_test_module?(lines_to_env_list, line) do
154+
DescribeBlock.find_block_info(
155+
line,
156+
lines_to_env_list,
157+
lines_to_env_list_length,
158+
source_lines
159+
)
47160
end
48161
end
49162

163+
defp get_module_lenses(test_modules, file_path) do
164+
test_modules
165+
|> Enum.map(fn {module, line} ->
166+
CodeLens.build_code_lens(line, "Run tests in module", @run_test_command, %{
167+
"filePath" => file_path,
168+
"module" => module
169+
})
170+
end)
171+
end
172+
173+
defp get_test_modules(%Metadata{lines_to_env: lines_to_env}) do
174+
lines_to_env
175+
|> Enum.group_by(fn {_line, env} -> env.module end)
176+
|> Enum.filter(fn {_module, module_lines_to_env} -> is_test_module?(module_lines_to_env) end)
177+
|> Enum.map(fn {module, [{line, _env} | _rest]} -> {module, line} end)
178+
end
179+
50180
defp is_test_module?(lines_to_env), do: is_test_module?(lines_to_env, :infinity)
51181

52182
defp is_test_module?(lines_to_env, line) when is_list(lines_to_env) do
@@ -65,32 +195,8 @@ defmodule ElixirLS.LanguageServer.Providers.CodeLens.Test do
65195
call_info.func == function and call_info.arity === arity do
66196
call_info.position
67197
end
68-
69-
# calls_list
70-
# |> Enum.filter(fn call_info -> call_info.func == function and call_info.arity === arity end)
71-
# |> Enum.map(fn call -> call.position end)
72-
end
73-
74-
defp build_test_module_code_lens(file_path, {module, line}) do
75-
CodeLens.build_code_lens(line, "Run tests in module", @run_test_command, %{
76-
"file_path" => file_path,
77-
"module" => module
78-
})
79198
end
80199

81-
defp build_function_test_code_lens(title, file_path, line) when is_binary(title) do
82-
CodeLens.build_code_lens(line, title, @run_test_command, %{
83-
"file_path" => file_path,
84-
"line" => line
85-
})
86-
end
87-
88-
defp build_function_test_code_lens({:test, _arity}, file_path, line),
89-
do: build_function_test_code_lens("Run test", file_path, line)
90-
91-
defp build_function_test_code_lens({:describe, _arity}, file_path, line),
92-
do: build_function_test_code_lens("Run tests", file_path, line)
93-
94200
defp parse_source(text) do
95201
buffer_file_metadata =
96202
text

0 commit comments

Comments
 (0)