diff --git a/lib/ex_doc/formatter/html/autolink.ex b/lib/ex_doc/formatter/html/autolink.ex
index ae7c5a976..7a9169096 100644
--- a/lib/ex_doc/formatter/html/autolink.ex
+++ b/lib/ex_doc/formatter/html/autolink.ex
@@ -415,32 +415,51 @@ defmodule ExDoc.Formatter.HTML.Autolink do
Project functions are specified in `project_funs` as a list of
`Module.fun/arity` tuples.
- Ignores functions which are already wrapped in markdown url syntax,
- e.g. `[Module.test/1](url)`. If the function doesn't touch the leading
- or trailing `]`, e.g. `[my link Module.link/1 is here](url)`, the Module.fun/arity
+ Functions wrapped in markdown url syntax can link to other docs if
+ the url is wrapped in backticks, otherwise the url is used as is.
+ If the function doesn't touch the leading or trailing `]`, e.g.
+ `[my link Module.link/1 is here](url)`, the Module.fun/arity
will get translated to the new href of the function.
"""
def elixir_functions(bin, project_funs, extension \\ ".html", lib_dirs \\ elixir_lib_dirs()) when is_binary(bin) do
- module_re = Regex.source(~r{(([A-Z][A-Za-z_\d]+)\.)+})
- fun_re = Regex.source(~r{([ct]:)?(#{module_re}([a-z_]+[A-Za-z_\d]*[\\?\\!]?|[\{\}=&\\|\\.<>~*^@\\+\\%\\!-]+)/\d+)})
- regex = ~r{(?
- {prefix, module, function, arity} = split_function(match)
+ bin
+ |> replace_custom_links(project_funs, extension, lib_dirs)
+ |> replace_normal_links(project_funs, extension, lib_dirs)
+ end
- cond do
- match in project_funs ->
- "[`#{module}.#{function}/#{arity}`](#{module}#{extension}##{prefix}#{enc_h function}/#{arity})"
+ module_re = Regex.source(~r{(([A-Z][A-Za-z_\d]+)\.)+})
+ fun_re = Regex.source(~r{([ct]:)?(#{module_re}([a-z_]+[A-Za-z_\d]*[\\?\\!]?|[\{\}=&\\|\\.<>~*^@\\+\\%\\!-]+)/\d+)})
+ @custom_re ~r{\[(.*)\]\(`(#{fun_re})`\)}
+ @normal_re ~r{(? module, lib_dirs) ->
- "[`#{module}.#{function}/#{arity}`](#{doc}#{module}.html##{prefix}#{enc_h function}/#{arity})"
+ def replace_custom_links(bin, project_funs, extension, lib_dirs) do
+ Regex.replace(@custom_re, bin, fn all, text, match ->
+ replacement(all, match, project_funs, extension, lib_dirs, text)
+ end)
+ end
- true ->
- all
- end
+ def replace_normal_links(bin, project_funs, extension, lib_dirs) do
+ Regex.replace(@normal_re, bin, fn all, match ->
+ replacement(all, match, project_funs, extension, lib_dirs)
end)
end
+ def replacement(all, match, project_funs, extension, lib_dirs, text \\ nil) do
+ {prefix, module, function, arity} = split_function(match)
+ text = text || "`#{module}.#{function}/#{arity}`"
+
+ cond do
+ match in project_funs ->
+ "[#{text}](#{module}#{extension}##{prefix}#{enc_h function}/#{arity})"
+
+ doc = lib_dirs_to_doc("Elixir." <> module, lib_dirs) ->
+ "[#{text}](#{doc}#{module}.html##{prefix}#{enc_h function}/#{arity})"
+
+ true ->
+ all
+ end
+ end
+
@doc """
Create links to elixir modules defined in the project and
in Elixir itself.
@@ -458,10 +477,13 @@ defmodule ExDoc.Formatter.HTML.Autolink do
cond do
match == module_id ->
"[`#{match}`](#{match}#{extension}#content)"
+
match in modules ->
"[`#{match}`](#{match}#{extension})"
+
doc = lib_dirs_to_doc("Elixir." <> match, lib_dirs) ->
"[`#{match}`](#{doc}#{match}.html)"
+
true ->
all
end
diff --git a/test/ex_doc/formatter/html/autolink_test.exs b/test/ex_doc/formatter/html/autolink_test.exs
index eb1258ccc..bc3f3b659 100644
--- a/test/ex_doc/formatter/html/autolink_test.exs
+++ b/test/ex_doc/formatter/html/autolink_test.exs
@@ -121,14 +121,32 @@ defmodule ExDoc.Formatter.HTML.AutolinkTest do
test "autolink functions doesn't create links for pre-linked Mod.functions docs" do
assert Autolink.elixir_functions("[`Mod.example/1`]()", ["Mod.example/1"]) == "[`Mod.example/1`]()"
assert Autolink.elixir_functions("[the `Mod.example/1`]()", ["Mod.example/1"]) == "[the `Mod.example/1`]()"
+
+ assert Autolink.elixir_functions("[`Mod.example/1`](foo)", ["Mod.example/1"]) == "[`Mod.example/1`](foo)"
+ assert Autolink.elixir_functions("[the `Mod.example/1`](foo)", ["Mod.example/1"]) == "[the `Mod.example/1`](foo)"
+ end
+
+ test "autolink functions create custom links" do
+ assert Autolink.elixir_functions("[`example`](`Mod.example/1`)", ["Mod.example/1"]) ==
+ "[`example`](Mod.html#example/1)"
+ assert Autolink.elixir_functions("[the `example`](`Mod.example/1`)", ["Mod.example/1"]) ==
+ "[the `example`](Mod.html#example/1)"
+ assert Autolink.elixir_functions("[the `Mod.example/1`](`Mod.example/1`)", ["Mod.example/1"]) ==
+ "[the `Mod.example/1`](Mod.html#example/1)"
+
+ assert Autolink.elixir_functions("[`callback(a)`](`c:Foo.foo/1`)", ["c:Foo.foo/1"]) ==
+ "[`callback(a)`](Foo.html#c:foo/1)"
+
+ assert Autolink.elixir_functions("[the `upcase`](`String.upcase/1`)", []) ==
+ "[the `upcase`](#{@elixir_docs}elixir/String.html#upcase/1)"
end
test "autolink functions to types in the project" do
# use the same approach for elixir_functions as for local_docs
assert Autolink.elixir_functions("`t:MyModule.my_type/0`",
- ["t:MyModule.my_type/0"]) == "[`MyModule.my_type/0`](MyModule.html#t:my_type/0)"
+ ["t:MyModule.my_type/0"]) == "[`MyModule.my_type/0`](MyModule.html#t:my_type/0)"
assert Autolink.elixir_functions("`t:MyModule.my_type`",
- ["t:MyModule.my_type/0"]) == "`t:MyModule.my_type`"
+ ["t:MyModule.my_type/0"]) == "`t:MyModule.my_type`"
end
# elixir_modules