Skip to content

Commit d2cbfb4

Browse files
committed
Support m:Module#anchor, closes #1832
1 parent 6ba8d3e commit d2cbfb4

File tree

5 files changed

+50
-36
lines changed

5 files changed

+50
-36
lines changed

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -224,8 +224,9 @@ Link to extra pages in another application using the syntax `` [Writing Document
224224

225225
It is also possible to place anchors after the module name and extra pages. For example:
226226

227-
* `` `Keyword#module-duplicate-keys-and-ordering` `` will create a link to `https://hexdocs.pm/elixir/Keyword.html#module-duplicate-keys-and-ordering`
228-
* `` `e:elixir:syntax-reference.md#expressions` `` will create a link to `https://hexdocs.pm/elixir/syntax-reference.html#expressions`
227+
* `` `m:Keyword#module-duplicate-keys-and-ordering` `` will create a link to `https://hexdocs.pm/elixir/Keyword.html#module-duplicate-keys-and-ordering`
228+
229+
* `` `e:elixir:syntax-reference.md#expressions` `` will create a link to `https://hexdocs.pm/elixir/syntax-reference.html#expressions`
229230

230231
### Erlang
231232

@@ -241,8 +242,9 @@ Link to extra pages in another application using the syntax `` [Using unicode](`
241242

242243
It is also possible to place anchors after the module name and extra pages. For example:
243244

244-
* `` `m:argparse#quick-start` `` will create a link to `https://erlang.org/doc/man/argparse#quick-start`
245-
* `` `e:stdlib:unicode-usage.md#what-unicode-is` `` will create a link to `https://erlang.org/doc/apps/stdlib/unicode-usage.html#what-unicode-is`
245+
* `` `m:argparse#quick-start` `` will create a link to `https://erlang.org/doc/man/argparse#quick-start`
246+
247+
* `` `e:stdlib:unicode-usage.md#what-unicode-is` `` will create a link to `https://erlang.org/doc/apps/stdlib/unicode-usage.html#what-unicode-is`
246248

247249
<!-- tabs-close -->
248250

lib/ex_doc/autolink.ex

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -231,8 +231,7 @@ defmodule ExDoc.Autolink do
231231
true <- is_binary(path) and path != "" and not (path =~ @ref_regex),
232232
true <- Path.extname(path) in @builtin_ext do
233233
if file = config.extras[Path.basename(path)] do
234-
fragment = (uri.fragment && "#" <> uri.fragment) || ""
235-
file <> config.ext <> fragment
234+
append_fragment(file <> config.ext, uri.fragment)
236235
else
237236
maybe_warn(config, nil, nil, %{file_path: path, original_text: link})
238237
nil
@@ -350,14 +349,14 @@ defmodule ExDoc.Autolink do
350349
nil ->
351350
case string do
352351
"m:" <> rest ->
352+
destructure [rest, fragment], String.split(rest, "#", parts: 2)
353+
353354
# TODO: rename :custom_link to :strict i.e. we expect ref to be valid
354355
# force custom_link mode because of m: prefix.
355356
case config.language.parse_module(rest, :custom_link) do
356357
{:module, module} ->
357-
module_url(module, :custom_link, config, rest)
358-
359-
{:module, module, anchor} ->
360-
module_url(module, "#" <> anchor, :custom_link, config, rest)
358+
url = module_url(module, :custom_link, config, rest)
359+
url && append_fragment(url, fragment)
361360

362361
:error ->
363362
nil
@@ -371,9 +370,6 @@ defmodule ExDoc.Autolink do
371370
{:module, module} ->
372371
module_url(module, mode, config, string)
373372

374-
{:module, module, anchor} ->
375-
module_url(module, "#" <> anchor, mode, config, string)
376-
377373
:error ->
378374
nil
379375
end
@@ -604,4 +600,7 @@ defmodule ExDoc.Autolink do
604600
# for the rest, it can either be undefined or private
605601
defp format_visibility(:undefined, _kind), do: "undefined or private"
606602
defp format_visibility(visibility, _kind), do: "#{visibility}"
603+
604+
defp append_fragment(url, nil), do: url
605+
defp append_fragment(url, fragment), do: url <> "#" <> fragment
607606
end

lib/ex_doc/language/elixir.ex

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,11 @@ defmodule ExDoc.Language.Elixir do
599599

600600
case Autolink.url(code, :regular_link, config) do
601601
url when is_binary(url) ->
602-
code = remove_prefix(code)
602+
code =
603+
code
604+
|> remove_prefix()
605+
|> remove_fragment()
606+
603607
{:a, [href: url], [{:code, attrs, [code], meta}], %{}}
604608

605609
:remove_link ->
@@ -623,6 +627,10 @@ defmodule ExDoc.Language.Elixir do
623627
defp remove_prefix("m:" <> rest), do: rest
624628
defp remove_prefix(rest), do: rest
625629

630+
defp remove_fragment(string) do
631+
string |> String.split("#") |> hd()
632+
end
633+
626634
defp safe_format_string!(string) do
627635
try do
628636
string

lib/ex_doc/language/erlang.ex

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ defmodule ExDoc.Language.Erlang do
443443
def parse_module_function(string) do
444444
case String.split(string, ":") do
445445
[module_string, function_string] ->
446-
with {:module, module} <- parse_module_string(module_string, :custom_link),
446+
with {:module, module} <- parse_module(module_string, :custom_link),
447447
{:function, function} <- parse_function(function_string) do
448448
{:remote, module, function}
449449
end
@@ -489,23 +489,7 @@ defmodule ExDoc.Language.Erlang do
489489
end
490490

491491
@impl true
492-
def parse_module(string, mode) do
493-
case String.split(string, "#", parts: 2) do
494-
[mod, anchor] ->
495-
case parse_module_string(mod, mode) do
496-
{:module, mod} ->
497-
{:module, mod, anchor}
498-
499-
:error ->
500-
:error
501-
end
502-
503-
[mod] ->
504-
parse_module_string(mod, mode)
505-
end
506-
end
507-
508-
defp parse_module_string(string, _mode) do
492+
def parse_module(string, _mode) do
509493
case :erl_scan.string(String.to_charlist(string)) do
510494
{:ok, [{:atom, _, module}], _} when is_atom(module) ->
511495
{:module, module}

test/ex_doc/language/elixir_test.exs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,34 @@ defmodule ExDoc.Language.ElixirTest do
2424
assert autolink_doc("`PATH`") == ~s|<code class="inline">PATH</code>|
2525
end
2626

27-
test "erlang module" do
27+
test "erlang module does not link" do
2828
assert autolink_doc("`:array`") == ~s|<code class="inline">:array</code>|
2929
end
3030

31+
test "m:module" do
32+
assert autolink_doc("`m:String`") ==
33+
~s|<a href="https://hexdocs.pm/elixir/String.html"><code class="inline">String</code></a>|
34+
end
35+
36+
test "m:module fragment" do
37+
assert autolink_doc("`m:String#fragment`") ==
38+
~s|<a href="https://hexdocs.pm/elixir/String.html#fragment"><code class="inline">String</code></a>|
39+
end
40+
41+
test "m:module with Erlang module" do
42+
assert autolink_doc("`m::array`") ==
43+
~s|<a href="https://www.erlang.org/doc/man/array.html"><code class="inline">:array</code></a>|
44+
end
45+
46+
test "m:module with Erlang module and fragment" do
47+
assert autolink_doc("`m::array#fragment`") ==
48+
~s|<a href="https://www.erlang.org/doc/man/array.html#fragment"><code class="inline">:array</code></a>|
49+
end
50+
51+
test "module with fragment without m: does not link" do
52+
assert autolink_doc("`String#anchor`") == ~s|<code class="inline">String#anchor</code>|
53+
end
54+
3155
test "unknown module" do
3256
assert autolink_doc("`Unknown`") == ~s|<code class="inline">Unknown</code>|
3357
assert autolink_doc("`:unknown`") == ~s|<code class="inline">:unknown</code>|
@@ -38,9 +62,6 @@ defmodule ExDoc.Language.ElixirTest do
3862
assert autolink_doc("`ExDoc.Markdown`") ==
3963
~s|<a href="ExDoc.Markdown.html"><code class="inline">ExDoc.Markdown</code></a>|
4064

41-
assert autolink_doc("`m:ExDoc.Markdown`") ==
42-
~s|<a href="ExDoc.Markdown.html"><code class="inline">ExDoc.Markdown</code></a>|
43-
4465
assert autolink_doc("`String`", apps: [:elixir]) ==
4566
~s|<a href="String.html"><code class="inline">String</code></a>|
4667
end

0 commit comments

Comments
 (0)