Skip to content

Commit 07d16d2

Browse files
committed
fix query bindings extraction
1 parent c41ac0f commit 07d16d2

File tree

2 files changed

+87
-9
lines changed

2 files changed

+87
-9
lines changed

apps/language_server/lib/language_server/providers/plugins/ecto/query.ex

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -240,15 +240,13 @@ defmodule ElixirLS.LanguageServer.Plugins.Ecto.Query do
240240

241241
from_matches = Regex.scan(~r/^.+\(?\s*(#{@binding_r})/u, func_code)
242242

243-
# TODO this code is broken
244-
# depends on join positions that we are unable to get from AST
245-
# line and col was previously assigned to each option in Source.which_func
246-
join_matches =
247-
for join when join in @joins <- func_info.options_so_far,
248-
code = Source.text_after(prefix, line, col),
249-
match <- Regex.scan(~r/^#{Regex.escape(join)}\:\s*(#{@binding_r})/u, code) do
250-
match
251-
end
243+
joins_pattern =
244+
@joins
245+
|> Enum.map(&Regex.escape(to_string(&1)))
246+
|> Enum.join("|")
247+
248+
join_regex = ~r/(?:^|\s)(?:#{joins_pattern})\s*:\s*(#{@binding_r})/u
249+
join_matches = Regex.scan(join_regex, func_code)
252250

253251
matches = from_matches ++ join_matches
254252

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
defmodule ElixirLS.LanguageServer.Plugins.Ecto.QueryBindingsTest do
2+
use ExUnit.Case
3+
4+
alias ElixirLS.LanguageServer.Plugins.Ecto.Query
5+
alias ElixirSense.Core.{Source, Parser, Metadata, Binding}
6+
7+
defp cursor(text) do
8+
{_, cursors} =
9+
Source.walk_text(text, {false, []}, fn
10+
"#", rest, _, _, {_comment?, cursors} -> {rest, {true, cursors}}
11+
"\n", rest, _, _, {_comment?, cursors} -> {rest, {false, cursors}}
12+
"^", rest, line, col, {true, cursors} -> {rest, {true, [%{line: line - 1, col: col} | cursors]}}
13+
_, rest, _, _, acc -> {rest, acc}
14+
end)
15+
16+
List.first(Enum.reverse(cursors))
17+
end
18+
19+
defp env_and_meta(buffer, {line, col}) do
20+
metadata = Parser.parse_string(buffer, true, false, {line, col})
21+
{prefix, suffix} = Source.prefix_suffix(buffer, line, col)
22+
23+
surround =
24+
case {prefix, suffix} do
25+
{"", ""} -> nil
26+
_ -> {{line, col - String.length(prefix)}, {line, col + String.length(suffix)}}
27+
end
28+
29+
env = Metadata.get_cursor_env(metadata, {line, col}, surround)
30+
{env, metadata}
31+
end
32+
33+
defp extract_bindings(buffer) do
34+
cur = cursor(buffer)
35+
{env, meta} = env_and_meta(buffer, {cur.line, cur.col})
36+
prefix = Source.text_before(buffer, cur.line, cur.col)
37+
binding_env = Binding.from_env(env, meta, {cur.line, cur.col})
38+
func_info = Source.which_func(prefix, binding_env)
39+
Query.extract_bindings(prefix, func_info, env, meta)
40+
end
41+
42+
test "extract binding from from clause" do
43+
buffer = """
44+
import Ecto.Query
45+
alias ElixirLS.LanguageServer.Plugins.Ecto.FakeSchemas.Post
46+
47+
from p in Post,
48+
where: true
49+
# ^
50+
"""
51+
52+
assert %{"p" => %{type: ElixirLS.LanguageServer.Plugins.Ecto.FakeSchemas.Post}} =
53+
extract_bindings(buffer)
54+
end
55+
56+
test "extract bindings from join clauses" do
57+
buffer = """
58+
import Ecto.Query
59+
alias ElixirLS.LanguageServer.Plugins.Ecto.FakeSchemas.Post
60+
alias ElixirLS.LanguageServer.Plugins.Ecto.FakeSchemas.Comment
61+
alias ElixirLS.LanguageServer.Plugins.Ecto.FakeSchemas.User
62+
63+
from(
64+
p in Post,
65+
join: c in Comment,
66+
left_join: u in assoc(p, :user),
67+
where: true
68+
# ^
69+
)
70+
"""
71+
72+
result = extract_bindings(buffer)
73+
74+
assert %{
75+
"p" => %{type: ElixirLS.LanguageServer.Plugins.Ecto.FakeSchemas.Post},
76+
"c" => %{type: ElixirLS.LanguageServer.Plugins.Ecto.FakeSchemas.Comment},
77+
"u" => %{type: ElixirLS.LanguageServer.Plugins.Ecto.FakeSchemas.User}
78+
} = result
79+
end
80+
end

0 commit comments

Comments
 (0)