Packages
ex_doc
0.25.4
0.40.3
0.40.2
0.40.1
0.40.0
0.39.3
0.39.2
0.39.1
0.39.0
0.38.4
0.38.3
0.38.2
0.38.1
0.38.0
0.37.3
0.37.2
0.37.1
0.37.0
0.37.0-rc.2
0.37.0-rc.1
0.37.0-rc.0
0.36.1
0.36.0
0.35.1
0.35.0
0.34.2
0.34.1
0.34.0
0.33.0
0.32.2
0.32.1
0.32.0
0.31.2
0.31.1
0.31.0
0.30.9
0.30.8
0.30.7
0.30.6
0.30.5
0.30.4
0.30.3
0.30.2
0.30.1
0.30.0
0.29.4
0.29.3
0.29.2
0.29.1
0.29.0
0.28.6
0.28.5
0.28.4
0.28.3
0.28.2
0.28.1
0.28.0
0.27.3
0.27.2
0.27.1
0.27.0
0.26.0
0.25.5
0.25.4
0.25.3
0.25.2
0.25.1
0.25.0
0.24.2
0.24.1
0.24.0
0.23.0
0.22.6
0.22.5
0.22.4
0.22.2
0.22.1
0.22.0
0.21.3
0.21.2
0.21.1
0.21.0
0.20.2
0.20.1
0.20.0
0.19.3
0.19.2
0.19.1
0.19.0
0.19.0-rc
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
retired
0.17.1
0.17.0
retired
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.1
0.15.0
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.2
0.13.1
0.13.0
0.12.0
0.11.5
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.0
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.3
0.7.2
0.7.1
0.7.0
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
ExDoc is a documentation generation tool for Elixir
Current section
Files
Jump to
Current section
Files
lib/ex_doc/autolink.ex
defmodule ExDoc.Autolink do
@moduledoc false
# * `:apps` - the apps that the docs are being generated for. When linking modules they are
# checked if they are part of the app and based on that the links are relative or absolute.
#
# * `:current_module` - the module that the docs are being generated for. Used to link local
# calls and see if remote calls are in the same app.
#
# * `:module_id` - id of the module being documented (e.g.: `"String"`)
#
# * `:file` - source file location
#
# * `:line` - line number of the beginning of the documentation
#
# * `:id` - a module/function/etc being documented (e.g.: `"String.upcase/2"`)
#
# * `:ext` - the extension (`".html"`, "`.xhtml"`, etc)
#
# * `:extras` - list of extras
#
# * `:skip_undefined_reference_warnings_on` - list of modules to skip the warning on
defstruct [
:current_module,
:module_id,
:id,
:line,
file: "nofile",
apps: [],
extras: [],
deps: [],
ext: ".html",
siblings: [],
skip_undefined_reference_warnings_on: []
]
@hexdocs "https://hexdocs.pm/"
@otpdocs "https://erlang.org/doc/man/"
def app_module_url(:ex_doc, module, %{current_module: module} = config) do
path = module |> inspect() |> String.trim_leading(":")
ex_doc_app_url(module, config, path, config.ext, "#content")
end
def app_module_url(:ex_doc, module, config) do
path = module |> inspect() |> String.trim_leading(":")
ex_doc_app_url(module, config, path, config.ext, "")
end
def app_module_url(:otp, module, _config) do
@otpdocs <> "#{module}.html"
end
def app_module_url(:no_tool, _, _) do
nil
end
# TODO: make more generic
@doc false
def ex_doc_app_url(module, config, path, ext, suffix) do
if app = app(module) do
if app in config.apps do
path <> ext <> suffix
else
config.deps
|> Keyword.get_lazy(app, fn -> @hexdocs <> "#{app}" end)
|> String.trim_trailing("/")
|> Kernel.<>("/" <> path <> ".html" <> suffix)
end
else
path <> ext <> suffix
end
end
defp app(module) do
{_, app} = app_info(module)
app
end
@doc false
def tool(module, config) do
if match?("Elixir." <> _, Atom.to_string(module)) do
:ex_doc
else
{otp, app} = app_info(module)
apps = Enum.uniq(config.apps ++ Keyword.keys(config.deps))
if otp == true and app not in apps do
:otp
else
:ex_doc
end
end
end
defp app_info(module) do
case :code.which(module) do
:preloaded ->
{true, :erts}
maybe_path ->
otp? = is_list(maybe_path) and List.starts_with?(maybe_path, :code.lib_dir())
app =
case :application.get_application(module) do
{:ok, app} ->
app
_ ->
with true <- is_list(maybe_path),
[_, "ebin", app, "lib" | _] <- maybe_path |> Path.split() |> Enum.reverse() do
String.to_atom(app)
else
_ -> nil
end
end
{otp?, app}
end
end
def maybe_warn(ref, config, visibility, metadata) do
skipped = config.skip_undefined_reference_warnings_on
file = Path.relative_to(config.file, File.cwd!())
line = config.line
unless Enum.any?([config.id, config.module_id, file], &(&1 in skipped)) do
warn(ref, {file, line}, config.id, visibility, metadata)
end
end
defp warn(message, {file, line}, id) do
warning = IO.ANSI.format([:yellow, "warning: ", :reset])
stacktrace =
" #{file}" <>
if(line, do: ":#{line}", else: "") <>
if(id, do: ": #{id}", else: "")
IO.puts(:stderr, [warning, message, ?\n, stacktrace, ?\n])
end
defp warn(ref, file_line, id, visibility, metadata)
defp warn(
{:module, _module},
{file, line},
id,
visibility,
%{mix_task: true, original_text: original_text}
) do
message =
"documentation references \"#{original_text}\" but it is " <>
format_visibility(visibility, :module)
warn(message, {file, line}, id)
end
defp warn(
{:module, _module},
{file, line},
id,
visibility,
%{original_text: original_text}
) do
message =
"documentation references module \"#{original_text}\" but it is " <>
format_visibility(visibility, :module)
warn(message, {file, line}, id)
end
defp warn(
nil,
{file, line},
id,
_visibility,
%{file_path: _file_path, original_text: original_text}
) do
message = "documentation references file \"#{original_text}\" but it does not exist"
warn(message, {file, line}, id)
end
defp warn(
{kind, _module, _name, _arity},
{file, line},
id,
visibility,
%{original_text: original_text}
) do
message =
"documentation references #{kind} \"#{original_text}\" but it is " <>
format_visibility(visibility, kind)
warn(message, {file, line}, id)
end
defp warn(message, {file, line}, id, _, _) when is_binary(message) do
warn(message, {file, line}, id)
end
# there is not such a thing as private callback or private module
defp format_visibility(visibility, kind) when kind in [:module, :callback], do: "#{visibility}"
# typep is defined as :hidden, since there is no :private visibility value
# but type defined with @doc false also is the stored the same way.
defp format_visibility(:hidden, :type), do: "hidden or private"
# 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}"
end