Skip to content

Commit

Permalink
Support m:Module#anchor, closes #1832
Browse files Browse the repository at this point in the history
  • Loading branch information
wojtekmach committed Dec 19, 2023
1 parent 6ba8d3e commit d2cbfb4
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 36 deletions.
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,9 @@ Link to extra pages in another application using the syntax `` [Writing Document

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

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

* `` `e:elixir:syntax-reference.md#expressions` `` will create a link to `https://hexdocs.pm/elixir/syntax-reference.html#expressions`

### Erlang

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

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

* `` `m:argparse#quick-start` `` will create a link to `https://erlang.org/doc/man/argparse#quick-start`
* `` `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`
* `` `m:argparse#quick-start` `` will create a link to `https://erlang.org/doc/man/argparse#quick-start`

* `` `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`

<!-- tabs-close -->

Expand Down
17 changes: 8 additions & 9 deletions lib/ex_doc/autolink.ex
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,7 @@ defmodule ExDoc.Autolink do
true <- is_binary(path) and path != "" and not (path =~ @ref_regex),
true <- Path.extname(path) in @builtin_ext do
if file = config.extras[Path.basename(path)] do
fragment = (uri.fragment && "#" <> uri.fragment) || ""
file <> config.ext <> fragment
append_fragment(file <> config.ext, uri.fragment)
else
maybe_warn(config, nil, nil, %{file_path: path, original_text: link})
nil
Expand Down Expand Up @@ -350,14 +349,14 @@ defmodule ExDoc.Autolink do
nil ->
case string do
"m:" <> rest ->
destructure [rest, fragment], String.split(rest, "#", parts: 2)

# TODO: rename :custom_link to :strict i.e. we expect ref to be valid
# force custom_link mode because of m: prefix.
case config.language.parse_module(rest, :custom_link) do
{:module, module} ->
module_url(module, :custom_link, config, rest)

{:module, module, anchor} ->
module_url(module, "#" <> anchor, :custom_link, config, rest)
url = module_url(module, :custom_link, config, rest)
url && append_fragment(url, fragment)

:error ->
nil
Expand All @@ -371,9 +370,6 @@ defmodule ExDoc.Autolink do
{:module, module} ->
module_url(module, mode, config, string)

{:module, module, anchor} ->
module_url(module, "#" <> anchor, mode, config, string)

:error ->
nil
end
Expand Down Expand Up @@ -604,4 +600,7 @@ defmodule ExDoc.Autolink do
# for the rest, it can either be undefined or private
defp format_visibility(:undefined, _kind), do: "undefined or private"
defp format_visibility(visibility, _kind), do: "#{visibility}"

defp append_fragment(url, nil), do: url
defp append_fragment(url, fragment), do: url <> "#" <> fragment
end
10 changes: 9 additions & 1 deletion lib/ex_doc/language/elixir.ex
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,11 @@ defmodule ExDoc.Language.Elixir do

case Autolink.url(code, :regular_link, config) do
url when is_binary(url) ->
code = remove_prefix(code)
code =
code
|> remove_prefix()
|> remove_fragment()

{:a, [href: url], [{:code, attrs, [code], meta}], %{}}

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

defp remove_fragment(string) do
string |> String.split("#") |> hd()
end

defp safe_format_string!(string) do
try do
string
Expand Down
20 changes: 2 additions & 18 deletions lib/ex_doc/language/erlang.ex
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ defmodule ExDoc.Language.Erlang do
def parse_module_function(string) do
case String.split(string, ":") do
[module_string, function_string] ->
with {:module, module} <- parse_module_string(module_string, :custom_link),
with {:module, module} <- parse_module(module_string, :custom_link),
{:function, function} <- parse_function(function_string) do
{:remote, module, function}
end
Expand Down Expand Up @@ -489,23 +489,7 @@ defmodule ExDoc.Language.Erlang do
end

@impl true
def parse_module(string, mode) do
case String.split(string, "#", parts: 2) do
[mod, anchor] ->
case parse_module_string(mod, mode) do
{:module, mod} ->
{:module, mod, anchor}

:error ->
:error
end

[mod] ->
parse_module_string(mod, mode)
end
end

defp parse_module_string(string, _mode) do
def parse_module(string, _mode) do
case :erl_scan.string(String.to_charlist(string)) do
{:ok, [{:atom, _, module}], _} when is_atom(module) ->
{:module, module}
Expand Down
29 changes: 25 additions & 4 deletions test/ex_doc/language/elixir_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,34 @@ defmodule ExDoc.Language.ElixirTest do
assert autolink_doc("`PATH`") == ~s|<code class="inline">PATH</code>|
end

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

test "m:module" do
assert autolink_doc("`m:String`") ==
~s|<a href="https://hexdocs.pm/elixir/String.html"><code class="inline">String</code></a>|
end

test "m:module fragment" do
assert autolink_doc("`m:String#fragment`") ==
~s|<a href="https://hexdocs.pm/elixir/String.html#fragment"><code class="inline">String</code></a>|
end

test "m:module with Erlang module" do
assert autolink_doc("`m::array`") ==
~s|<a href="https://www.erlang.org/doc/man/array.html"><code class="inline">:array</code></a>|
end

test "m:module with Erlang module and fragment" do
assert autolink_doc("`m::array#fragment`") ==
~s|<a href="https://www.erlang.org/doc/man/array.html#fragment"><code class="inline">:array</code></a>|
end

test "module with fragment without m: does not link" do
assert autolink_doc("`String#anchor`") == ~s|<code class="inline">String#anchor</code>|
end

test "unknown module" do
assert autolink_doc("`Unknown`") == ~s|<code class="inline">Unknown</code>|
assert autolink_doc("`:unknown`") == ~s|<code class="inline">:unknown</code>|
Expand All @@ -38,9 +62,6 @@ defmodule ExDoc.Language.ElixirTest do
assert autolink_doc("`ExDoc.Markdown`") ==
~s|<a href="ExDoc.Markdown.html"><code class="inline">ExDoc.Markdown</code></a>|

assert autolink_doc("`m:ExDoc.Markdown`") ==
~s|<a href="ExDoc.Markdown.html"><code class="inline">ExDoc.Markdown</code></a>|

assert autolink_doc("`String`", apps: [:elixir]) ==
~s|<a href="String.html"><code class="inline">String</code></a>|
end
Expand Down

0 comments on commit d2cbfb4

Please sign in to comment.