diff --git a/lib/plug/debugger.ex b/lib/plug/debugger.ex
index 2bbe8654..1974db6b 100644
--- a/lib/plug/debugger.ex
+++ b/lib/plug/debugger.ex
@@ -233,7 +233,7 @@ defmodule Plug.Debugger do
assigns =
Keyword.merge(assigns,
conn: conn,
- message: message,
+ message: maybe_autolink(message),
markdown: markdown,
style: style,
banner: banner,
@@ -549,4 +549,40 @@ defmodule Plug.Debugger do
defp maybe_merge_dark_styles(style, default_dark_style) do
Map.put(style, :dark, default_dark_style)
end
+
+ defp maybe_autolink(message) do
+ splitted =
+ Regex.split(~r/`[A-Z][A-Za-z0-9_.]+\.[a-z][A-Za-z0-9_!?]*\/\d+`/, message,
+ include_captures: true,
+ trim: true
+ )
+
+ Enum.map(splitted, &maybe_format_function_reference/1)
+ |> IO.iodata_to_binary()
+ end
+
+ defp maybe_format_function_reference("`" <> reference = text) do
+ reference = String.trim_trailing(reference, "`")
+
+ with {:ok, m, f, a} <- get_mfa(reference),
+ url when is_binary(url) <- get_doc(m, f, a, Application.get_application(m)) do
+ ~s[`#{h(reference)}`]
+ else
+ _ -> h(text)
+ end
+ end
+
+ defp maybe_format_function_reference(text), do: h(text)
+
+ def get_mfa(capture) do
+ [function_path, arity] = String.split(capture, "/")
+ {arity, ""} = Integer.parse(arity)
+ parts = String.split(function_path, ".")
+ {function_str, parts} = List.pop_at(parts, -1)
+ module = Module.safe_concat(parts)
+ function = String.to_existing_atom(function_str)
+ {:ok, module, function, arity}
+ rescue
+ _ -> :error
+ end
end
diff --git a/lib/plug/templates/debugger.html.eex b/lib/plug/templates/debugger.html.eex
index 7f4b4d6a..fdc8f20a 100644
--- a/lib/plug/templates/debugger.html.eex
+++ b/lib/plug/templates/debugger.html.eex
@@ -884,7 +884,7 @@
at <%= h method(@conn) %>
<%= h @conn.request_path %>
- <%= h @message %>
+ <%= @message %>
<%= for %{label: label, encoded_handler: encoded_handler} <- @actions do %>
diff --git a/mix.exs b/mix.exs
index e6ee853a..90ce33fc 100644
--- a/mix.exs
+++ b/mix.exs
@@ -26,7 +26,8 @@ defmodule Plug.MixProject do
groups_for_extras: groups_for_extras(),
source_ref: "v#{@version}",
source_url: "https://github.com/elixir-plug/plug"
- ]
+ ],
+ test_ignore_filters: [&String.starts_with?(&1, "test/fixtures/")]
]
end
diff --git a/test/plug/adapters/test/conn_test.exs b/test/plug/adapters/test/conn_test.exs
index 2977aa5c..84a80fe1 100644
--- a/test/plug/adapters/test/conn_test.exs
+++ b/test/plug/adapters/test/conn_test.exs
@@ -161,13 +161,13 @@ defmodule Plug.Adapters.Test.ConnTest do
end
test "use existing conn.remote_ip if exists" do
- conn_with_remote_ip = %Plug.Conn{conn(:get, "/") | remote_ip: {151, 236, 219, 228}}
+ conn_with_remote_ip = %{conn(:get, "/") | remote_ip: {151, 236, 219, 228}}
child_conn = Plug.Adapters.Test.Conn.conn(conn_with_remote_ip, :get, "/", foo: "bar")
assert child_conn.remote_ip == {151, 236, 219, 228}
end
test "use existing conn.port if exists" do
- conn_with_port = %Plug.Conn{conn(:get, "/") | port: 4200}
+ conn_with_port = %{conn(:get, "/") | port: 4200}
child_conn = Plug.Adapters.Test.Conn.conn(conn_with_port, :get, "/", foo: "bar")
assert child_conn.port == 4200
end
diff --git a/test/plug/debugger_test.exs b/test/plug/debugger_test.exs
index e77bdcf4..618a84d3 100644
--- a/test/plug/debugger_test.exs
+++ b/test/plug/debugger_test.exs
@@ -599,4 +599,30 @@ defmodule Plug.DebuggerTest do
assert conn.resp_body =~ " end"
end
+
+ test "links to hexdocs" do
+ conn =
+ conn(:get, "/foo/bar")
+ |> put_req_header("accept", "text/html")
+ |> render([], fn -> raise "please use `Plug.Conn.send_resp/3` instead" end)
+
+ assert conn.resp_body =~
+ ~r(`Plug.Conn.send_resp/3`)
+ end
+
+ test "does not create new atoms" do
+ conn =
+ conn(:get, "/foo/bar")
+ |> put_req_header("accept", "text/html")
+ |> render([], fn ->
+ raise "please use `NotExisting.not_existing_atom_for_test_does_not_create_new_atom/1` instead"
+ end)
+
+ assert conn.resp_body =~
+ ~r(`NotExisting.not_existing_atom_for_test_does_not_create_new_atom/1`)
+
+ assert_raise ArgumentError, fn ->
+ String.to_existing_atom("not_existing_atom_for_test_does_not_create_new_atom")
+ end
+ end
end