|  | 
| 1 | 1 | defmodule NextLS.ASTHelpers do | 
| 2 | 2 |   @moduledoc false | 
| 3 | 3 | 
 | 
| 4 |  | -  @spec get_attribute_reference_name(String.t(), integer(), integer()) :: String.t() | nil | 
| 5 |  | -  def get_attribute_reference_name(file, line, column) do | 
| 6 |  | -    ast = ast_from_file(file) | 
| 7 |  | - | 
| 8 |  | -    {_ast, name} = | 
| 9 |  | -      Macro.prewalk(ast, nil, fn | 
| 10 |  | -        {:@, [line: ^line, column: ^column], [{name, _meta, nil}]} = ast, _acc -> {ast, "@#{name}"} | 
| 11 |  | -        other, acc -> {other, acc} | 
| 12 |  | -      end) | 
|  | 4 | +  defmodule Attributes do | 
|  | 5 | +    @moduledoc false | 
|  | 6 | +    @spec get_attribute_reference_name(String.t(), integer(), integer()) :: String.t() | nil | 
|  | 7 | +    def get_attribute_reference_name(file, line, column) do | 
|  | 8 | +      ast = ast_from_file(file) | 
|  | 9 | + | 
|  | 10 | +      {_ast, name} = | 
|  | 11 | +        Macro.prewalk(ast, nil, fn | 
|  | 12 | +          {:@, [line: ^line, column: ^column], [{name, _meta, nil}]} = ast, _acc -> {ast, "@#{name}"} | 
|  | 13 | +          other, acc -> {other, acc} | 
|  | 14 | +        end) | 
|  | 15 | + | 
|  | 16 | +      name | 
|  | 17 | +    end | 
| 13 | 18 | 
 | 
| 14 |  | -    name | 
| 15 |  | -  end | 
|  | 19 | +    @spec get_module_attributes(String.t(), module()) :: [{atom(), String.t(), integer(), integer()}] | 
|  | 20 | +    def get_module_attributes(file, module) do | 
|  | 21 | +      reserved_attributes = Module.reserved_attributes() | 
| 16 | 22 | 
 | 
| 17 |  | -  @spec get_module_attributes(String.t(), module()) :: [{atom(), String.t(), integer(), integer()}] | 
| 18 |  | -  def get_module_attributes(file, module) do | 
| 19 |  | -    reserved_attributes = Module.reserved_attributes() | 
|  | 23 | +      symbols = parse_symbols(file, module) | 
| 20 | 24 | 
 | 
| 21 |  | -    symbols = parse_symbols(file, module) | 
|  | 25 | +      Enum.filter(symbols, fn | 
|  | 26 | +        {:attribute, "@" <> name, _, _} -> | 
|  | 27 | +          not Map.has_key?(reserved_attributes, String.to_atom(name)) | 
| 22 | 28 | 
 | 
| 23 |  | -    Enum.filter(symbols, fn | 
| 24 |  | -      {:attribute, "@" <> name, _, _} -> | 
| 25 |  | -        not Map.has_key?(reserved_attributes, String.to_atom(name)) | 
|  | 29 | +        _other -> | 
|  | 30 | +          false | 
|  | 31 | +      end) | 
|  | 32 | +    end | 
| 26 | 33 | 
 | 
| 27 |  | -      _other -> | 
| 28 |  | -        false | 
| 29 |  | -    end) | 
| 30 |  | -  end | 
|  | 34 | +    defp parse_symbols(file, module) do | 
|  | 35 | +      ast = ast_from_file(file) | 
| 31 | 36 | 
 | 
| 32 |  | -  defp parse_symbols(file, module) do | 
| 33 |  | -    ast = ast_from_file(file) | 
|  | 37 | +      {_ast, %{symbols: symbols}} = | 
|  | 38 | +        Macro.traverse(ast, %{modules: [], symbols: []}, &prewalk/2, &postwalk(&1, &2, module)) | 
| 34 | 39 | 
 | 
| 35 |  | -    {_ast, %{symbols: symbols}} = | 
| 36 |  | -      Macro.traverse(ast, %{modules: [], symbols: []}, &prewalk/2, &postwalk(&1, &2, module)) | 
|  | 40 | +      symbols | 
|  | 41 | +    end | 
| 37 | 42 | 
 | 
| 38 |  | -    symbols | 
| 39 |  | -  end | 
|  | 43 | +    # add module name to modules stack on enter | 
|  | 44 | +    defp prewalk({:defmodule, _, [{:__aliases__, _, module_name_atoms} | _]} = ast, acc) do | 
|  | 45 | +      modules = [module_name_atoms | acc.modules] | 
|  | 46 | +      {ast, %{acc | modules: modules}} | 
|  | 47 | +    end | 
| 40 | 48 | 
 | 
| 41 |  | -  # add module name to modules stack on enter | 
| 42 |  | -  defp prewalk({:defmodule, _, [{:__aliases__, _, module_name_atoms} | _]} = ast, acc) do | 
| 43 |  | -    modules = [module_name_atoms | acc.modules] | 
| 44 |  | -    {ast, %{acc | modules: modules}} | 
| 45 |  | -  end | 
|  | 49 | +    defp prewalk(ast, acc), do: {ast, acc} | 
|  | 50 | + | 
|  | 51 | +    defp postwalk({:@, meta, [{name, _, args}]} = ast, acc, module) when is_list(args) do | 
|  | 52 | +      ast_module = | 
|  | 53 | +        acc.modules | 
|  | 54 | +        |> Enum.reverse() | 
|  | 55 | +        |> List.flatten() | 
|  | 56 | +        |> Module.concat() | 
|  | 57 | + | 
|  | 58 | +      if module == ast_module do | 
|  | 59 | +        symbols = [{:attribute, "@#{name}", meta[:line], meta[:column]} | acc.symbols] | 
|  | 60 | +        {ast, %{acc | symbols: symbols}} | 
|  | 61 | +      else | 
|  | 62 | +        {ast, acc} | 
|  | 63 | +      end | 
|  | 64 | +    end | 
| 46 | 65 | 
 | 
| 47 |  | -  defp prewalk(ast, acc), do: {ast, acc} | 
|  | 66 | +    # remove module name from modules stack on exit | 
|  | 67 | +    defp postwalk({:defmodule, _, [{:__aliases__, _, _modules} | _]} = ast, acc, _module) do | 
|  | 68 | +      [_exit_mudule | modules] = acc.modules | 
|  | 69 | +      {ast, %{acc | modules: modules}} | 
|  | 70 | +    end | 
| 48 | 71 | 
 | 
| 49 |  | -  defp postwalk({:@, meta, [{name, _, args}]} = ast, acc, module) when is_list(args) do | 
| 50 |  | -    ast_module = | 
| 51 |  | -      acc.modules | 
| 52 |  | -      |> Enum.reverse() | 
| 53 |  | -      |> List.flatten() | 
| 54 |  | -      |> Module.concat() | 
|  | 72 | +    defp postwalk(ast, acc, _module), do: {ast, acc} | 
| 55 | 73 | 
 | 
| 56 |  | -    if module == ast_module do | 
| 57 |  | -      symbols = [{:attribute, "@#{name}", meta[:line], meta[:column]} | acc.symbols] | 
| 58 |  | -      {ast, %{acc | symbols: symbols}} | 
| 59 |  | -    else | 
| 60 |  | -      {ast, acc} | 
|  | 74 | +    defp ast_from_file(file) do | 
|  | 75 | +      file |> File.read!() |> Code.string_to_quoted!(columns: true) | 
| 61 | 76 |     end | 
| 62 | 77 |   end | 
| 63 | 78 | 
 | 
| 64 |  | -  # remove module name from modules stack on exit | 
| 65 |  | -  defp postwalk({:defmodule, _, [{:__aliases__, _, _modules} | _]} = ast, acc, _module) do | 
| 66 |  | -    [_exit_mudule | modules] = acc.modules | 
| 67 |  | -    {ast, %{acc | modules: modules}} | 
| 68 |  | -  end | 
| 69 |  | - | 
| 70 |  | -  defp postwalk(ast, acc, _module), do: {ast, acc} | 
| 71 |  | - | 
| 72 |  | -  defp ast_from_file(file) do | 
| 73 |  | -    file |> File.read!() |> Code.string_to_quoted!(columns: true) | 
|  | 79 | +  defmodule Aliases do | 
|  | 80 | +    @moduledoc """ | 
|  | 81 | +    Responsible for extracting the relevant portion from a single or multi alias. | 
|  | 82 | +
 | 
|  | 83 | +    ## Example | 
|  | 84 | +
 | 
|  | 85 | +    ```elixir | 
|  | 86 | +    alias Foo.Bar.Baz | 
|  | 87 | +    #     ^^^^^^^^^^^ | 
|  | 88 | +
 | 
|  | 89 | +    alias Foo.Bar.{Baz, Bing} | 
|  | 90 | +    #              ^^^  ^^^^ | 
|  | 91 | +
 | 
|  | 92 | +    alias Foo.Bar.{ | 
|  | 93 | +      Baz, | 
|  | 94 | +    # ^^^ | 
|  | 95 | +      Bing | 
|  | 96 | +    # ^^^^ | 
|  | 97 | +    } | 
|  | 98 | +    ``` | 
|  | 99 | +    """ | 
|  | 100 | + | 
|  | 101 | +    def extract_alias_range(code, {start, stop}, ale) do | 
|  | 102 | +      lines = | 
|  | 103 | +        code | 
|  | 104 | +        |> String.split("\n") | 
|  | 105 | +        |> Enum.map(&String.split(&1, "")) | 
|  | 106 | +        |> Enum.slice((start.line - 1)..(stop.line - 1)) | 
|  | 107 | + | 
|  | 108 | +      code = | 
|  | 109 | +        if start.line == stop.line do | 
|  | 110 | +          [line] = lines | 
|  | 111 | + | 
|  | 112 | +          line | 
|  | 113 | +          |> Enum.slice(start.col..stop.col) | 
|  | 114 | +          |> Enum.join() | 
|  | 115 | +        else | 
|  | 116 | +          [first | rest] = lines | 
|  | 117 | +          first = Enum.drop(first, start.col) | 
|  | 118 | + | 
|  | 119 | +          [last | rest] = Enum.reverse(rest) | 
|  | 120 | + | 
|  | 121 | +          length = Enum.count(last) | 
|  | 122 | +          last = Enum.drop(last, -(length - stop.col - 1)) | 
|  | 123 | + | 
|  | 124 | +          Enum.map_join([first | Enum.reverse([last | rest])], "\n", &Enum.join(&1, "")) | 
|  | 125 | +        end | 
|  | 126 | + | 
|  | 127 | +      {_, range} = | 
|  | 128 | +        code | 
|  | 129 | +        |> Code.string_to_quoted!(columns: true, column: start.col, token_metadata: true) | 
|  | 130 | +        |> Macro.prewalk(nil, fn ast, range -> | 
|  | 131 | +          range = | 
|  | 132 | +            case ast do | 
|  | 133 | +              {:__aliases__, meta, aliases} -> | 
|  | 134 | +                if ale == List.last(aliases) do | 
|  | 135 | +                  {{meta[:line] + start.line - 1, meta[:column]}, | 
|  | 136 | +                   {meta[:last][:line] + start.line - 1, meta[:last][:column] + String.length(to_string(ale)) - 1}} | 
|  | 137 | +                else | 
|  | 138 | +                  range | 
|  | 139 | +                end | 
|  | 140 | + | 
|  | 141 | +              _ -> | 
|  | 142 | +                range | 
|  | 143 | +            end | 
|  | 144 | + | 
|  | 145 | +          {ast, range} | 
|  | 146 | +        end) | 
|  | 147 | + | 
|  | 148 | +      range | 
|  | 149 | +    end | 
| 74 | 150 |   end | 
| 75 | 151 | end | 
0 commit comments