Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 39 additions & 17 deletions lib/ex_doc/formatter/html/autolink.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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{(?<!\[)`\s*(#{fun_re})\s*`(?!\])}

Regex.replace(regex, bin, fn all, match ->
{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{(?<!\[)`\s*(#{fun_re})\s*`(?!\])}

doc = lib_dirs_to_doc("Elixir." <> 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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function takes too many parameters (arity is 6, max is 5).

{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.
Expand All @@ -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
Expand Down
22 changes: 20 additions & 2 deletions test/ex_doc/formatter/html/autolink_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down