Skip to content

Commit ed4f241

Browse files
committed
fix: documentation retrieval for callbacks (#60)
The usage_rules.docs task failed to retrieve documentation for Elixir callbacks. This fix adds a mechanism to detect callback hints from IEx and use the b/1 helper for retrieval. - Improve callback detection using a specific regex that includes the input name - Add comprehensive tests for the docs task covering modules, functions, and callbacks
1 parent 3c60c64 commit ed4f241

2 files changed

Lines changed: 80 additions & 1 deletion

File tree

lib/mix/tasks/usage_rules.docs.ex

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,26 @@ defmodule Mix.Tasks.UsageRules.Docs do
6363
quote do
6464
require IEx.Helpers
6565

66-
IEx.Helpers.h(unquote(quoted))
66+
original_gl = Process.group_leader()
67+
{:ok, cap} = StringIO.open("")
68+
Process.group_leader(self(), cap)
69+
70+
try do
71+
IEx.Helpers.h(unquote(quoted))
72+
{_, output} = StringIO.contents(cap)
73+
74+
# Use regex with case insensitivity to detect the hint about callbacks
75+
if String.match?(output, ~r/No documentation for function #{Regex.escape(unquote(module))} was found,.*callback.*same name/i) do
76+
Process.group_leader(self(), original_gl)
77+
IEx.Helpers.b(unquote(quoted))
78+
else
79+
Process.group_leader(self(), original_gl)
80+
IO.write(output)
81+
end
82+
after
83+
Process.group_leader(self(), original_gl)
84+
StringIO.close(cap)
85+
end
6786
end
6887
)
6988
end
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# SPDX-FileCopyrightText: 2025 usage_rules contributors <https://github.com/ash-project/usage_rules/graphs/contributors>
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
defmodule Mix.Tasks.UsageRules.DocsTest do
6+
use ExUnit.Case
7+
8+
import ExUnit.CaptureIO
9+
10+
alias Mix.Tasks.UsageRules.Docs
11+
12+
defp strip_ansi(string) do
13+
String.replace(string, ~r/\e\[[0-9;]*m/, "")
14+
end
15+
16+
test "shows documentation for a module" do
17+
output = capture_io(fn ->
18+
Docs.run(["Enum"])
19+
end) |> strip_ansi()
20+
21+
assert output =~ "Searching local docs for"
22+
assert output =~ "Enum"
23+
assert output =~ "Functions for working with collections"
24+
end
25+
26+
test "shows documentation for a function" do
27+
output = capture_io(fn ->
28+
Docs.run(["Enum.map/2"])
29+
end) |> strip_ansi()
30+
31+
assert output =~ "Searching local docs for"
32+
assert output =~ "Enum.map/2"
33+
assert output =~ "Returns a list where each element is the result of invoking"
34+
end
35+
36+
test "shows documentation for a callback" do
37+
output = capture_io(fn ->
38+
Docs.run(["GenServer.handle_call"])
39+
end) |> strip_ansi()
40+
41+
assert output =~ "Searching local docs for"
42+
assert output =~ "GenServer.handle_call"
43+
assert output =~ "Invoked to handle synchronous call/3 messages"
44+
refute output =~ "No documentation for function GenServer.handle_call was found"
45+
end
46+
47+
test "handles invalid expressions" do
48+
assert_raise Mix.Error, ~r/Invalid module or function/, fn ->
49+
Docs.run(["invalid expression"])
50+
end
51+
end
52+
53+
test "handles non-existent modules" do
54+
output = capture_io(fn ->
55+
Docs.run(["NonExistentModule"])
56+
end)
57+
58+
assert output =~ "Could not load module NonExistentModule"
59+
end
60+
end

0 commit comments

Comments
 (0)