Skip to content

Commit

Permalink
fix: correctly process broken code when searching local variables (#282)
Browse files Browse the repository at this point in the history
* fix error when searching local variables in broken code

* fix test name
  • Loading branch information
biletskyy authored Oct 16, 2023
1 parent 04b9b7e commit d1f3876
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 44 deletions.
92 changes: 51 additions & 41 deletions lib/next_ls/helpers/ast_helpers/variables.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,52 +9,62 @@ defmodule NextLS.ASTHelpers.Variables do
@spec get_variable_definition(String.t(), {integer(), integer()}) :: {atom(), {Range.t(), Range.t()}} | nil
def get_variable_definition(file, position) do
file = File.read!(file)
ast = Code.string_to_quoted!(file, columns: true)

{_ast, %{vars: vars}} =
Macro.traverse(
ast,
%{vars: [], symbols: %{}, sym_ranges: [], scope: []},
&prewalk/2,
&postwalk/2
)

Enum.find_value(vars, fn %{name: name, sym_range: range, ref_range: ref_range} ->
if position_in_range?(position, ref_range), do: {name, range}, else: nil
end)

case Code.string_to_quoted(file, columns: true) do
{:ok, ast} ->
{_ast, %{vars: vars}} =
Macro.traverse(
ast,
%{vars: [], symbols: %{}, sym_ranges: [], scope: []},
&prewalk/2,
&postwalk/2
)

Enum.find_value(vars, fn %{name: name, sym_range: range, ref_range: ref_range} ->
if position_in_range?(position, ref_range), do: {name, range}, else: nil
end)

_error ->
nil
end
end

@spec list_variable_references(String.t(), {integer(), integer()}) :: [{atom(), {Range.t(), Range.t()}}]
def list_variable_references(file, position) do
file = File.read!(file)
ast = Code.string_to_quoted!(file, columns: true)

{_ast, %{vars: vars}} =
Macro.traverse(
ast,
%{vars: [], symbols: %{}, sym_ranges: [], scope: []},
&prewalk/2,
&postwalk/2
)

symbol =
Enum.find_value(vars, fn %{name: name, sym_range: range, ref_range: ref_range} ->
if position_in_range?(position, ref_range), do: {name, range}, else: nil
end)

position =
case symbol do
nil -> position
{_, {line.._, column.._}} -> {line, column}
end

Enum.reduce(vars, [], fn val, acc ->
if position_in_range?(position, val.sym_range) do
[{val.name, val.ref_range} | acc]
else
acc
end
end)

case Code.string_to_quoted(file, columns: true) do
{:ok, ast} ->
{_ast, %{vars: vars}} =
Macro.traverse(
ast,
%{vars: [], symbols: %{}, sym_ranges: [], scope: []},
&prewalk/2,
&postwalk/2
)

symbol =
Enum.find_value(vars, fn %{name: name, sym_range: range, ref_range: ref_range} ->
if position_in_range?(position, ref_range), do: {name, range}, else: nil
end)

position =
case symbol do
nil -> position
{_, {line.._, column.._}} -> {line, column}
end

Enum.reduce(vars, [], fn val, acc ->
if position_in_range?(position, val.sym_range) do
[{val.name, val.ref_range} | acc]
else
acc
end
end)

_error ->
[]
end
end

# search symbols in function and macro definition args and increase scope
Expand Down
26 changes: 23 additions & 3 deletions test/next_ls/helpers/ast_helpers/variables_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,17 @@ defmodule NextLS.ASTHelpers.VariablesTest do
end
""")

[source: source]
broken = Path.join(tmp_dir, "my_proj/lib/broken.ex")

File.write!(broken, """
defmodule Broken do
def foo(bar) do
{:ok, bar}
end
# end
""")

[source: source, broken: broken]
end

describe "get_variable_definition/2" do
Expand All @@ -154,12 +164,17 @@ defmodule NextLS.ASTHelpers.VariablesTest do

test "returns nil when position is not a variable reference", %{source: source} do
symbol = Variables.get_variable_definition(source, {7, 6})
assert symbol == nil
refute symbol
end

test "returns nil when position is a variable symbol", %{source: source} do
symbol = Variables.get_variable_definition(source, {5, 5})
assert symbol == nil
refute symbol
end

test "returns nil when source code is broken", %{broken: broken} do
symbol = Variables.get_variable_definition(broken, {1, 10})
refute symbol
end
end

Expand Down Expand Up @@ -332,5 +347,10 @@ defmodule NextLS.ASTHelpers.VariablesTest do
assert length(refs3) == 1
assert {:var, {127..127, 17..19}} in refs3
end

test "returns empty list when source code is broken", %{broken: broken} do
symbol = Variables.list_variable_references(broken, {2, 10})
assert Enum.empty?(symbol)
end
end
end

0 comments on commit d1f3876

Please sign in to comment.