From d1f3876e8c00a5d43a1fb02b4b2e92cab373068f Mon Sep 17 00:00:00 2001 From: Dmytro Biletskyy Date: Mon, 16 Oct 2023 15:19:11 +0300 Subject: [PATCH] fix: correctly process broken code when searching local variables (#282) * fix error when searching local variables in broken code * fix test name --- lib/next_ls/helpers/ast_helpers/variables.ex | 92 ++++++++++--------- .../helpers/ast_helpers/variables_test.exs | 26 +++++- 2 files changed, 74 insertions(+), 44 deletions(-) diff --git a/lib/next_ls/helpers/ast_helpers/variables.ex b/lib/next_ls/helpers/ast_helpers/variables.ex index 8e115927..1db64841 100644 --- a/lib/next_ls/helpers/ast_helpers/variables.ex +++ b/lib/next_ls/helpers/ast_helpers/variables.ex @@ -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 diff --git a/test/next_ls/helpers/ast_helpers/variables_test.exs b/test/next_ls/helpers/ast_helpers/variables_test.exs index e8065aab..63019566 100644 --- a/test/next_ls/helpers/ast_helpers/variables_test.exs +++ b/test/next_ls/helpers/ast_helpers/variables_test.exs @@ -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 @@ -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 @@ -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