diff --git a/lib/mix/tasks/convert_to_verified_routes.ex b/lib/mix/tasks/convert_to_verified_routes.ex deleted file mode 100644 index ad643d2b..00000000 --- a/lib/mix/tasks/convert_to_verified_routes.ex +++ /dev/null @@ -1,233 +0,0 @@ -defmodule Mix.Tasks.ConvertToVerifiedRoutes do - @moduledoc """ - Replaces routes with verified routes. - Forked from - https://gist.github.com/andreaseriksson/e454b9244a734310d4ab74d8595f98cd - https://gist.github.com/jiegillet/e6357c82e36a848ad59295eb3d5a1135 - - This requires all routes to consistently be aliased with - alias PhilomenaWeb.Router.Helpers, as: Routes - - Run with - mix convert_to_verified_routes - """ - - use Mix.Task - - @regex ~r/(Routes\.)([a-zA-Z0-9_]+)(path|url)\(/ - @web_module PhilomenaWeb - - def run(_) do - Path.wildcard("test/**/*.ex*") - |> Enum.concat(Path.wildcard("lib/**/*.ex*")) - |> Enum.concat(Path.wildcard("lib/**/*.eex*")) - |> Enum.concat(Path.wildcard("lib/**/*.slime")) - |> Enum.sort() - |> Enum.reject(&String.contains?(&1, "convert_to_verified_routes.ex")) - |> Enum.filter(&(&1 |> File.read!() |> String.contains?("Routes."))) - |> Enum.each(&format_file/1) - - :ok - end - - def format_file(filename) do - Mix.shell().info(filename) - - formatted_content = - filename - |> File.read!() - |> format_string() - - File.write!(filename, [formatted_content]) - end - - def format_string(source) do - case Regex.run(@regex, source, capture: :first, return: :index) do - [{index, length}] -> - # Compute full length of expression - length = nibble_expression(source, index, length) - - # Convert to verified route format - route = format_route(String.slice(source, index, length)) - - # Split string around expression - prefix = String.slice(source, 0, index) - suffix = String.slice(source, index + length, String.length(source)) - - # Insert verified route and rerun - format_string("#{prefix}#{route}#{suffix}") - - _ -> - source - end - end - - defp nibble_expression(source, index, length) do - if index + length > String.length(source) do - raise "Failed to match route expression" - end - - case Code.string_to_quoted(String.slice(source, index, length)) do - {:ok, _macro} -> - length - - _ -> - nibble_expression(source, index, length + 1) - end - end - - defp format_route(route) do - ast = - Code.string_to_quoted!(route, - literal_encoder: &{:ok, {:__block__, &2, [&1]}}, - unescape: false, - token_metadata: true - ) - - ast - |> Macro.prewalk(&replace_route/1) - |> Code.quoted_to_algebra(escape: false) - |> Inspect.Algebra.format(:infinity) - end - - defp decode_literal(literal) when is_binary(literal) or is_integer(literal) do - {:ok, literal} - end - - defp decode_literal({:__block__, _, [literal]}) do - {:ok, literal} - end - - defp decode_literal(node), do: {:error, node} - - defp encode_literal(literal) do - {:__block__, [], [literal]} - end - - # Routes.url(MyAppWeb.Endpoint) - defp replace_route({{:., _, [{:__aliases__, _, [:Routes]}, :url]}, _, [_conn_or_endpoint]}) do - {:url, [], [{:sigil_p, [delimiter: "\""], [{:<<>>, [], ["/"]}, []]}]} - end - - # Routes.static_path(conn, "/images/favicon.ico") - defp replace_route({{:., _, [{:__aliases__, _, [:Routes]}, :static_path]}, _, args}) do - [_conn_or_endpoint, path] = args - - case decode_literal(path) do - {:ok, path} -> {:sigil_p, [delimiter: "\""], [{:<<>>, [], [path]}, []]} - _ -> {:sigil_p, [delimiter: "\""], [path, []]} - end - end - - # Routes.static_url(conn, "/images/favicon.ico") - defp replace_route({{:., _, [{:__aliases__, _, [:Routes]}, :static_url]}, _, args}) do - [_conn_or_endpoint, path] = args - - sigil = - case decode_literal(path) do - {:ok, path} -> {:sigil_p, [delimiter: "\""], [{:<<>>, [], [path]}, []]} - _ -> {:sigil_p, [delimiter: "\""], [path, []]} - end - - {:url, [], [sigil]} - end - - # Routes.some_path(conn, :action, "en", query_params) - defp replace_route( - {{:., _, [{:__aliases__, _, [:Routes]}, path_name]}, _, [_ | _] = args} = node - ) do - [_conn_or_endpoint, action | params] = args - - action = - case decode_literal(action) do - {:ok, action} -> action - _ -> action - end - - path_name = "#{path_name}" - - case find_verified_route(path_name, action, params) do - :ok -> node - route -> route - end - end - - defp replace_route(node), do: node - - defp find_verified_route(path_name, action, arguments) do - # pleaaaase don't have a route named Routes.product_url_path(conn, :index) - trimmed_path = path_name |> String.trim_trailing("_path") |> String.trim_trailing("_url") - - route = - Phoenix.Router.routes(@web_module.Router) - |> Enum.find(fn %{helper: helper, plug_opts: plug_opts} -> - plug_opts == action && is_binary(helper) && trimmed_path == helper - end) - - case route do - %{path: path} -> - {path_bits, query_params} = - path - |> String.split("/", trim: true) - |> replace_path_variables(arguments, []) - - path_bits = - path_bits - |> Enum.flat_map(fn bit -> ["/", bit] end) - |> format_for_sigil_binary_args(query_params) - - sigil = {:sigil_p, [delimiter: "\""], [{:<<>>, [], path_bits}, []]} - - if String.ends_with?(path_name, "_url") do - {:url, [], [sigil]} - else - sigil - end - - _ -> - Mix.shell().error( - "Could not find route #{path_name}, with action #{inspect(action)} and arguments #{inspect(arguments)}" - ) - end - end - - defp replace_path_variables([], arguments, path_bits) do - {Enum.reverse(path_bits), arguments} - end - - defp replace_path_variables(path, [], path_bits) do - {Enum.reverse(path_bits) ++ path, []} - end - - # conceptually /post/:post_id -> /post/#{id} - defp replace_path_variables([path_piece | rest], [arg | args], path_bits) do - if String.starts_with?(path_piece, ":") do - replace_path_variables(rest, args, [arg | path_bits]) - else - replace_path_variables(rest, [arg | args], [path_piece | path_bits]) - end - end - - defp format_for_sigil_binary_args(path_bits, [_ | _] = query_params) do - format_for_sigil_binary_args(path_bits ++ ["?" | query_params], []) - end - - defp format_for_sigil_binary_args(path_bits, []) do - path_bits - |> Enum.map(&decode_literal/1) - |> Enum.map(fn - {:ok, bit} when is_binary(bit) -> - bit - - {:ok, bit} when is_atom(bit) or is_integer(bit) -> - to_string(bit) - - {_, bit} -> - {:"::", [], - [ - {{:., [], [Kernel, :to_string]}, [from_interpolation: true], [encode_literal(bit)]}, - {:binary, [], Elixir} - ]} - end) - end -end