Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b4ad1ce
temp: commented formatter dep imports
gp-pereira Mar 4, 2025
56d203d
feat: added refactorex as a dependency
gp-pereira Mar 4, 2025
8d73fa4
feat: added refactorex as a code action handler
gp-pereira Mar 4, 2025
28aa84e
temp: skipping broken remote control tests
gp-pereira Mar 4, 2025
59d4fde
refactor: less restrict refactorex dependency version
gp-pereira Mar 4, 2025
ebc9996
refactor: removed useless test
gp-pereira Mar 4, 2025
a362383
feat: range subtree (selection) extraction algorithm - inlined from r…
gp-pereira Mar 7, 2025
0ea28d6
Revert "feat: range subtree (selection) extraction algorithm - inline…
gp-pereira Mar 9, 2025
9987517
refactor: using Document.fragment/3 to find the selection, then posit…
gp-pereira Mar 9, 2025
82266c2
refactor: improved test infrastructure and multiline selection test
gp-pereira Mar 9, 2025
d65b698
fix: fixed the formatter import deps problem
gp-pereira Mar 11, 2025
2400b0b
Revert "temp: commented formatter dep imports"
gp-pereira Mar 11, 2025
de8c2f4
feat: introducing context.trigger_kind to Code Actions so they can de…
gp-pereira Mar 11, 2025
4999d81
refactor: adding more supported code actions
gp-pereira Mar 11, 2025
c7dcf6e
refactor: removed Logger used for debugging
gp-pereira Mar 11, 2025
648fffe
[bugfix] Only index if there's stuff to index
scohen Mar 4, 2025
9dcc8c0
refactor: sort aliases
gp-pereira Mar 11, 2025
8ce74bb
refactor: remove unnecessary changes.
gp-pereira Apr 1, 2025
c8a09f8
refactor: better naming the test cases
gp-pereira Apr 1, 2025
bc6ad96
Revert "temp: skipping broken remote control tests"
gp-pereira Apr 1, 2025
e637c57
Update Nix hash of Mix deps
gp-pereira Apr 1, 2025
536e259
fix: removed GenLSP from refactorex so it is not imported into remote…
gp-pereira Apr 2, 2025
2e9882c
Update Nix hash of Mix deps
gp-pereira Apr 2, 2025
36ec2ae
fix: setting up Project before tests
gp-pereira Apr 3, 2025
57a50ad
refactor: bump Sourceror and using updates on Refactorex
gp-pereira Apr 17, 2025
7c4c972
Update Nix hash of Mix deps
gp-pereira Apr 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/common/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ defmodule Common.MixProject do
Mix.Credo.dependency(),
Mix.Dialyzer.dependency(),
{:snowflake, "~> 1.0"},
{:sourceror, "~> 1.7"},
{:sourceror, "~> 1.9"},
{:stream_data, "~> 1.1", only: [:test], runtime: false},
{:patch, "~> 0.15", only: [:test], optional: true, runtime: false}
]
Expand Down
2 changes: 1 addition & 1 deletion apps/common/mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"patch": {:hex, :patch, "0.15.0", "947dd6a8b24a2d2d1137721f20bb96a8feb4f83248e7b4ad88b4871d52807af5", [:mix], [], "hexpm", "e8dadf9b57b30e92f6b2b1ce2f7f57700d14c66d4ed56ee27777eb73fb77e58d"},
"snowflake": {:hex, :snowflake, "1.0.4", "8433b4e04fbed19272c55e1b7de0f7a1ee1230b3ae31a813b616fd6ef279e87a", [:mix], [], "hexpm", "badb07ebb089a5cff737738297513db3962760b10fe2b158ae3bebf0b4d5be13"},
"sourceror": {:hex, :sourceror, "1.7.1", "599d78f4cc2be7d55c9c4fd0a8d772fd0478e3a50e726697c20d13d02aa056d4", [:mix], [], "hexpm", "cd6f268fe29fa00afbc535e215158680a0662b357dc784646d7dff28ac65a0fc"},
"sourceror": {:hex, :sourceror, "1.9.0", "3bf5fe2d017aaabe3866d8a6da097dd7c331e0d2d54e59e21c2b066d47f1e08e", [:mix], [], "hexpm", "d20a9dd5efe162f0d75a307146faa2e17b823ea4f134f662358d70f0332fed82"},
"statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"},
"stream_data": {:hex, :stream_data, "1.1.3", "15fdb14c64e84437901258bb56fc7d80aaf6ceaf85b9324f359e219241353bfb", [:mix], [], "hexpm", "859eb2be72d74be26c1c4f272905667672a52e44f743839c57c7ee73a1a66420"},
}
2 changes: 1 addition & 1 deletion apps/lexical_credo/mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"},
"nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
"snowflake": {:hex, :snowflake, "1.0.4", "8433b4e04fbed19272c55e1b7de0f7a1ee1230b3ae31a813b616fd6ef279e87a", [:mix], [], "hexpm", "badb07ebb089a5cff737738297513db3962760b10fe2b158ae3bebf0b4d5be13"},
"sourceror": {:hex, :sourceror, "1.7.1", "599d78f4cc2be7d55c9c4fd0a8d772fd0478e3a50e726697c20d13d02aa056d4", [:mix], [], "hexpm", "cd6f268fe29fa00afbc535e215158680a0662b357dc784646d7dff28ac65a0fc"},
"sourceror": {:hex, :sourceror, "1.9.0", "3bf5fe2d017aaabe3866d8a6da097dd7c331e0d2d54e59e21c2b066d47f1e08e", [:mix], [], "hexpm", "d20a9dd5efe162f0d75a307146faa2e17b823ea4f134f662358d70f0332fed82"},
"statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"},
"stream_data": {:hex, :stream_data, "1.1.3", "15fdb14c64e84437901258bb56fc7d80aaf6ceaf85b9324f359e219241353bfb", [:mix], [], "hexpm", "859eb2be72d74be26c1c4f272905667672a52e44f743839c57c7ee73a1a66420"},
}
2 changes: 1 addition & 1 deletion apps/proto/mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"snowflake": {:hex, :snowflake, "1.0.4", "8433b4e04fbed19272c55e1b7de0f7a1ee1230b3ae31a813b616fd6ef279e87a", [:mix], [], "hexpm", "badb07ebb089a5cff737738297513db3962760b10fe2b158ae3bebf0b4d5be13"},
"sourceror": {:hex, :sourceror, "1.7.1", "599d78f4cc2be7d55c9c4fd0a8d772fd0478e3a50e726697c20d13d02aa056d4", [:mix], [], "hexpm", "cd6f268fe29fa00afbc535e215158680a0662b357dc784646d7dff28ac65a0fc"},
"sourceror": {:hex, :sourceror, "1.9.0", "3bf5fe2d017aaabe3866d8a6da097dd7c331e0d2d54e59e21c2b066d47f1e08e", [:mix], [], "hexpm", "d20a9dd5efe162f0d75a307146faa2e17b823ea4f134f662358d70f0332fed82"},
"statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"},
"stream_data": {:hex, :stream_data, "1.1.3", "15fdb14c64e84437901258bb56fc7d80aaf6ceaf85b9324f359e219241353bfb", [:mix], [], "hexpm", "859eb2be72d74be26c1c4f272905667672a52e44f743839c57c7ee73a1a66420"},
}
2 changes: 1 addition & 1 deletion apps/protocol/mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"patch": {:hex, :patch, "0.15.0", "947dd6a8b24a2d2d1137721f20bb96a8feb4f83248e7b4ad88b4871d52807af5", [:mix], [], "hexpm", "e8dadf9b57b30e92f6b2b1ce2f7f57700d14c66d4ed56ee27777eb73fb77e58d"},
"snowflake": {:hex, :snowflake, "1.0.4", "8433b4e04fbed19272c55e1b7de0f7a1ee1230b3ae31a813b616fd6ef279e87a", [:mix], [], "hexpm", "badb07ebb089a5cff737738297513db3962760b10fe2b158ae3bebf0b4d5be13"},
"sourceror": {:hex, :sourceror, "1.7.1", "599d78f4cc2be7d55c9c4fd0a8d772fd0478e3a50e726697c20d13d02aa056d4", [:mix], [], "hexpm", "cd6f268fe29fa00afbc535e215158680a0662b357dc784646d7dff28ac65a0fc"},
"sourceror": {:hex, :sourceror, "1.9.0", "3bf5fe2d017aaabe3866d8a6da097dd7c331e0d2d54e59e21c2b066d47f1e08e", [:mix], [], "hexpm", "d20a9dd5efe162f0d75a307146faa2e17b823ea4f134f662358d70f0332fed82"},
"statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"},
"stream_data": {:hex, :stream_data, "1.1.3", "15fdb14c64e84437901258bb56fc7d80aaf6ceaf85b9324f359e219241353bfb", [:mix], [], "hexpm", "859eb2be72d74be26c1c4f272905667672a52e44f743839c57c7ee73a1a66420"},
}
4 changes: 3 additions & 1 deletion apps/remote_control/lib/lexical/remote_control.ex
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ defmodule Lexical.RemoteControl do

defdelegate list_modules, to: :code, as: :all_available

defdelegate code_actions(document, range, diagnostics, kinds), to: CodeAction, as: :for_range
defdelegate code_actions(document, range, diagnostics, kinds, trigger_kind),
to: CodeAction,
as: :for_range

defdelegate complete(env), to: RemoteControl.Completion, as: :elixir_sense_expand

Expand Down
6 changes: 4 additions & 2 deletions apps/remote_control/lib/lexical/remote_control/api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,15 @@ defmodule Lexical.RemoteControl.Api do
%Document{} = document,
%Range{} = range,
diagnostics,
kinds
kinds,
trigger_kind
) do
RemoteControl.call(project, RemoteControl, :code_actions, [
document,
range,
diagnostics,
kinds
kinds,
trigger_kind
])
end

Expand Down
42 changes: 23 additions & 19 deletions apps/remote_control/lib/lexical/remote_control/code_action.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ defmodule Lexical.RemoteControl.CodeAction do
| :source_organize_imports
| :source_fix_all

@type trigger_kind :: :invoked | :automatic

@type t :: %__MODULE__{
title: String.t(),
kind: code_action_kind,
Expand All @@ -30,33 +32,35 @@ defmodule Lexical.RemoteControl.CodeAction do
Handlers.ReplaceWithUnderscore,
Handlers.OrganizeAliases,
Handlers.AddAlias,
Handlers.RemoveUnusedAlias
Handlers.RemoveUnusedAlias,
Handlers.Refactorex
]

@spec new(Lexical.uri(), String.t(), code_action_kind(), Changes.t()) :: t()
def new(uri, title, kind, changes) do
%__MODULE__{uri: uri, title: title, changes: changes, kind: kind}
end

@spec for_range(Document.t(), Range.t(), [Diagnostic.t()], [code_action_kind] | :all) :: [t()]
def for_range(%Document{} = doc, %Range{} = range, diagnostics, kinds) do
results =
Enum.flat_map(@handlers, fn handler ->
if applies?(kinds, handler) do
handler.actions(doc, range, diagnostics)
else
[]
end
end)

results
@spec for_range(
Document.t(),
Range.t(),
[Diagnostic.t()],
[code_action_kind] | :all,
trigger_kind
) :: [t()]
def for_range(%Document{} = doc, %Range{} = range, diagnostics, kinds, trigger_kind) do
Enum.flat_map(@handlers, fn handler ->
if handle_kinds?(handler, kinds) and handle_trigger_kind?(handler, trigger_kind) do
handler.actions(doc, range, diagnostics)
else
[]
end
end)
end

defp applies?(:all, _handler_module) do
true
end
defp handle_kinds?(_handler, :all), do: true
defp handle_kinds?(handler, kinds), do: kinds -- handler.kinds() != kinds

defp applies?(kinds, handler_module) do
kinds -- handler_module.kinds() != kinds
end
defp handle_trigger_kind?(handler, trigger_kind),
do: handler.trigger_kind() in [trigger_kind, :all]
end
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ defmodule Lexical.RemoteControl.CodeAction.Handler do

@callback actions(Document.t(), Range.t(), [Diagnostic.t()]) :: [CodeAction.t()]
@callback kinds() :: [CodeAction.code_action_kind()]
@callback trigger_kind() :: CodeAction.trigger_kind() | :all
end
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ defmodule Lexical.RemoteControl.CodeAction.Handlers.AddAlias do
[:quick_fix]
end

@impl CodeAction.Handler
def trigger_kind, do: :all

defp build_code_action(%Analysis{} = analysis, range, current_aliases, potential_alias_module) do
case Ast.Module.safe_split(potential_alias_module, as: :atoms) do
{:erlang, _} ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ defmodule Lexical.RemoteControl.CodeAction.Handlers.OrganizeAliases do
[:source, :source_organize_imports]
end

@impl CodeAction.Handler
def trigger_kind, do: :all

defp check_aliases(%Document{}, %Analysis{} = analysis, %Range{} = range) do
case Analysis.module_scope(analysis, range) do
%Scope{aliases: [_ | _]} -> :ok
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
defmodule Lexical.RemoteControl.CodeAction.Handlers.Refactorex do
alias Lexical.Document
alias Lexical.Document.Changes
alias Lexical.Document.Range

alias Lexical.RemoteControl
alias Lexical.RemoteControl.CodeAction
alias Lexical.RemoteControl.CodeMod

alias Refactorex.Refactor

@behaviour CodeAction.Handler

@impl CodeAction.Handler
def actions(%Document{} = doc, %Range{} = range, _diagnostics) do
with {:ok, target} <- line_or_selection(doc, range),
{:ok, ast} <- Sourceror.parse_string(Document.to_string(doc)) do
ast
|> Sourceror.Zipper.zip()
|> Refactor.available_refactorings(target, true)
|> Enum.map(fn refactoring ->
CodeAction.new(
doc.uri,
refactoring.title,
map_kind(refactoring.kind),
ast_to_changes(doc, refactoring.refactored)
)
end)
else
_ -> []
end
end

@impl CodeAction.Handler
def kinds, do: [:refactor]

@impl CodeAction.Handler
def trigger_kind, do: :invoked

defp line_or_selection(_, %{start: start, end: start}), do: {:ok, start.line}

defp line_or_selection(doc, %{start: start} = range) do
doc
|> Document.fragment(range.start, range.end)
|> Sourceror.parse_string(line: start.line, column: start.character)
end

defp map_kind("quickfix"), do: :quick_fix
defp map_kind(kind), do: :"#{String.replace(kind, ".", "_")}"

defp ast_to_changes(doc, ast) do
{formatter, opts} = CodeMod.Format.formatter_for_file(RemoteControl.get_project(), doc.uri)

ast
|> Sourceror.to_string(
formatter: formatter,
locals_without_parens: opts[:locals_without_parens] || []
)
|> then(&CodeMod.Diff.diff(doc, &1))
|> then(&Changes.new(doc, &1))
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ defmodule Lexical.RemoteControl.CodeAction.Handlers.RemoveUnusedAlias do
[:source]
end

@impl CodeAction.Handler
def trigger_kind, do: :all

defp to_edit(%Document{} = document, %Position{} = position, %Diagnostic{} = diagnostic) do
with {:ok, module_string} <- fetch_unused_alias_module_string(diagnostic),
{:ok, _doc, %Analysis{} = analysis} <- Document.Store.fetch(document.uri, :analysis),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ defmodule Lexical.RemoteControl.CodeAction.Handlers.ReplaceRemoteFunction do
[:quick_fix]
end

@impl CodeAction.Handler
def trigger_kind, do: :all

@spec to_code_actions(Document.t(), non_neg_integer(), module(), String.t(), [atom()]) ::
[CodeAction.t()]
defp to_code_actions(%Document{} = doc, line_number, module, function, suggestions) do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ defmodule Lexical.RemoteControl.CodeAction.Handlers.ReplaceWithUnderscore do
[:quick_fix]
end

@impl CodeAction.Handler
def trigger_kind, do: :all

@spec to_changes(Document.t(), non_neg_integer(), String.t() | atom) ::
{:ok, Changes.t()} | :error
defp to_changes(%Document{} = document, line_number, variable_name) do
Expand Down
93 changes: 49 additions & 44 deletions apps/remote_control/lib/lexical/remote_control/search/indexer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -101,57 +101,62 @@ defmodule Lexical.RemoteControl.Search.Indexer do

total_bytes = paths_to_sizes |> Enum.map(&elem(&1, 1)) |> Enum.sum()

{on_update_progess, on_complete} = Progress.begin_percent("Indexing source code", total_bytes)
if total_bytes > 0 do
{on_update_progess, on_complete} =
Progress.begin_percent("Indexing source code", total_bytes)

initial_state = {0, []}
initial_state = {0, []}

chunk_fn = fn {path, file_size}, {block_size, paths} ->
new_block_size = file_size + block_size
new_paths = [path | paths]
chunk_fn = fn {path, file_size}, {block_size, paths} ->
new_block_size = file_size + block_size
new_paths = [path | paths]

if new_block_size >= @bytes_per_block do
{:cont, new_paths, initial_state}
else
{:cont, {new_block_size, new_paths}}
if new_block_size >= @bytes_per_block do
{:cont, new_paths, initial_state}
else
{:cont, {new_block_size, new_paths}}
end
end
end

after_fn = fn
{_, []} ->
{:cont, []}
after_fn = fn
{_, []} ->
{:cont, []}

{_, paths} ->
{:cont, paths, []}
end

paths_to_sizes
|> Stream.chunk_while(initial_state, chunk_fn, after_fn)
|> Task.async_stream(
fn chunk ->
block_bytes = chunk |> Enum.map(&Map.get(path_to_size_map, &1)) |> Enum.sum()
result = Enum.map(chunk, processor)
on_update_progess.(block_bytes, "Indexing")
result
end,
timeout: timeout
)
|> Stream.flat_map(fn
{:ok, entry_chunks} -> entry_chunks
_ -> []
end)
# The next bit is the only way i could figure out how to
# call complete once the stream was realized
|> Stream.transform(
fn -> nil end,
fn chunk_items, acc ->
# By the chunk items list directly, each transformation
# will flatten the resulting steam
{chunk_items, acc}
end,
fn _acc ->
on_complete.()
{_, paths} ->
{:cont, paths, []}
end
)

paths_to_sizes
|> Stream.chunk_while(initial_state, chunk_fn, after_fn)
|> Task.async_stream(
fn chunk ->
block_bytes = chunk |> Enum.map(&Map.get(path_to_size_map, &1)) |> Enum.sum()
result = Enum.map(chunk, processor)
on_update_progess.(block_bytes, "Indexing")
result
end,
timeout: timeout
)
|> Stream.flat_map(fn
{:ok, entry_chunks} -> entry_chunks
_ -> []
end)
# The next bit is the only way i could figure out how to
# call complete once the stream was realized
|> Stream.transform(
fn -> nil end,
fn chunk_items, acc ->
# By the chunk items list directly, each transformation
# will flatten the resulting steam
{chunk_items, acc}
end,
fn _acc ->
on_complete.()
end
)
else
[]
end
end

defp path_to_sizes(paths) do
Expand Down
4 changes: 3 additions & 1 deletion apps/remote_control/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ defmodule Lexical.RemoteControl.MixProject do
{:patch, "~> 0.15", only: [:dev, :test], optional: true, runtime: false},
{:path_glob, "~> 0.2", optional: true},
{:phoenix_live_view, "~> 1.0", only: [:test], optional: true, runtime: false},
{:sourceror, "~> 1.7"}
{:sourceror, "~> 1.9"},
{:stream_data, "~> 1.1", only: [:test], runtime: false},
{:refactorex, "~> 0.1.51"}
]
end

Expand Down
Loading