Last active
March 5, 2022 07:22
-
-
Save lkarthee/35d1177885a69a494a2f2030e19faca7 to your computer and use it in GitHub Desktop.
mix phx.routes_check - Before you use this - code should be placed in <project_root>/lib/mix/tasks/phx.routes_check.ex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Mix.Tasks.Phx.RoutesCheck do | |
use Mix.Task | |
@shortdoc "Checks all routes for undefined modules and functions." | |
@version 1 | |
# Code modified from | |
# phx.routes.ex -https://github.com/phoenixframework/phoenix/blob/22a71bd339783aa08a727b24ef8822fc5d66a511/lib/mix/tasks/phx.routes.ex#L1) | |
# console_formatter.ex - https://github.com/phoenixframework/phoenix/blob/22a71bd339783aa08a727b24ef8822fc5d66a511/lib/phoenix/router/console_formatter.ex#L1 | |
@moduledoc """ | |
Checks all routes for undefined modules and functions. | |
$ mix phx.routes_check | |
$ mix phx.routes_check MyApp.AnotherRouter | |
For more information about routers, see phx.routes help: | |
$ mix help phx.routes | |
""" | |
@doc false | |
def run(args, base \\ Mix.Phoenix.base()) do | |
Mix.Task.run("compile", args) | |
{router_mod, opts} = | |
case OptionParser.parse(args, switches: [endpoint: :string, router: :string]) do | |
{opts, [passed_router], _} -> {router(passed_router, base), opts} | |
{opts, [], _} -> {router(opts[:router], base), opts} | |
end | |
Mix.shell().info("\nChecking Routes for undefined modules and functions... ") | |
check_routes(router_mod, endpoint(opts[:endpoint], base)) | |
end | |
def check_routes(router, endpoint \\ nil) do | |
routes = | |
router | |
|> get_routes() | |
|> Enum.reverse() | |
routes = find_missing(routes) | |
column_widths = | |
routes | |
|> Enum.map(fn {_, route} -> route end) | |
|> calculate_column_widths(endpoint) | |
if Enum.empty?(routes) do | |
Mix.shell().info("Checking Complete\nNo errors found") | |
else | |
Mix.shell().info("Checking Complete\nFound following errors:") | |
routes | |
|> Enum.map_join("", &format_msg(&1, column_widths)) | |
|> Mix.shell().error() | |
end | |
end | |
defp get_routes(router) do | |
if function_exported?(Phoenix.Router, routes, 1) do | |
Phoenix.Router.routes(router), | |
else | |
router.__routes__() | |
end | |
end | |
defp find_missing(routes) do | |
Enum.reduce(routes, [], fn route, acc -> | |
cond do | |
route.plug == Phoenix.LiveView.Plug and | |
is_atom(route.plug_opts) and | |
loaded(elem(route.metadata.phoenix_live_view, 0)) == nil -> | |
[{:live_module, route} | acc] | |
route.plug == Phoenix.LiveView.Plug -> | |
acc | |
loaded(route.plug) == nil -> | |
[{:module, route} | acc] | |
is_atom(route.plug_opts) and | |
function_exported?(route.plug, route.plug_opts, 2) == false -> | |
[{:func, route} | acc] | |
true -> | |
acc | |
end | |
end) | |
end | |
defp format_msg({error, route}, column_widths) do | |
msg = | |
case error do | |
:live_module -> | |
" - module #{inspect(route.plug_opts)} is not available\n\n" | |
:module -> | |
" - module #{inspect(route.plug)} is not available\n\n" | |
:func -> | |
" - function #{inspect(route.plug)}.#{route.plug_opts}/2 is undefined\n\n" | |
end | |
format_route(route, column_widths) <> msg | |
end | |
defp calculate_column_widths(routes, endpoint) do | |
sockets = endpoint && endpoint.__sockets__() || [] | |
widths = | |
Enum.reduce(routes, {0, 0, 0}, fn route, acc -> | |
%{verb: verb, path: path, helper: helper} = route | |
verb = verb_name(verb) | |
{verb_len, path_len, route_name_len} = acc | |
route_name = route_name(helper) | |
{max(verb_len, String.length(verb)), | |
max(path_len, String.length(path)), | |
max(route_name_len, String.length(route_name))} | |
end) | |
Enum.reduce(sockets, widths, fn {path, _mod, _opts}, acc -> | |
{verb_len, path_len, route_name_len} = acc | |
{verb_len, | |
max(path_len, String.length(path <> "/websocket")), | |
max(route_name_len, String.length("websocket"))} | |
end) | |
end | |
defp format_route(route, column_widths) do | |
%{verb: verb, path: path, plug: plug, plug_opts: plug_opts, helper: helper} = route | |
verb = verb_name(verb) | |
route_name = route_name(helper) | |
{verb_len, path_len, route_name_len} = column_widths | |
module_name = | |
case plug do | |
Phoenix.LiveView.Plug -> | |
"#{inspect(plug_opts)}\n" | |
_ -> | |
"#{inspect(plug)} #{inspect(plug_opts)}\n" | |
end | |
String.pad_leading(route_name, route_name_len) <> " " <> | |
String.pad_trailing(verb, verb_len) <> " " <> | |
String.pad_trailing(path, path_len) <> " " <> | |
module_name | |
end | |
defp route_name(nil), do: "" | |
defp route_name(name), do: name <> "_path" | |
defp verb_name(verb), do: verb |> to_string() |> String.upcase() | |
defp endpoint(nil, base) do | |
loaded(web_mod(base, "Endpoint")) | |
end | |
defp endpoint(module, _base) do | |
loaded(Module.concat([module])) | |
end | |
defp router(nil, base) do | |
if Mix.Project.umbrella?() do | |
Mix.raise """ | |
umbrella applications require an explicit router to be given to phx.routes, for example: | |
$ mix phx.routes MyAppWeb.Router | |
""" | |
end | |
web_router = web_mod(base, "Router") | |
old_router = app_mod(base, "Router") | |
loaded(web_router) || loaded(old_router) || Mix.raise """ | |
no router found at #{inspect web_router} or #{inspect old_router}. | |
An explicit router module may be given to phx.routes, for example: | |
$ mix phx.routes MyAppWeb.Router | |
""" | |
end | |
defp router(router_name, _base) do | |
arg_router = Module.concat([router_name]) | |
loaded(arg_router) || Mix.raise "the provided router, #{inspect(arg_router)}, does not exist" | |
end | |
defp loaded(module) do | |
if Code.ensure_loaded?(module), do: module | |
end | |
defp app_mod(base, name), do: Module.concat([base, name]) | |
defp web_mod(base, name) do | |
Module.concat(["#{base}Web", name]) | |
end | |
end |
Let's break out line #37 into a get_routes/1
- 37 Phoenix.Router.routes(router)
+ 37 get_routes(router)
+ 57 defp get_routes(router) do
+ 58 if function_exported?(Phoenix.Router, routes, 1),
+ 59 do: Phoenix.Router.routes(router),
+ 60 else: router.__routes__()
+ 61 end
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi @hudsonbay
You're using phoenix 1.5.8 which doesn't have that function defined, it was introduced in 1.6.0-rc.0
You should be able to replace line #37 with
router.__routes__()