Skip to content
Closed
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
69 changes: 58 additions & 11 deletions lib/ex_doc/autolink.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ defmodule ExDoc.Autolink do
#
# * `:extras` - list of extras
#
# * `:skip_undefined_reference_warnings_on` - list of modules to skip the warning on
# * `:skip_reference_warnings_to` - list of references and/or files, to skip warning when calling the given reference
#
# * `:skip_undefined_reference_warnings_on` - list of references and/or files to skip the warning on

@enforce_keys [:app, :file]

Expand All @@ -36,6 +38,7 @@ defmodule ExDoc.Autolink do
extras: [],
ext: ".html",
siblings: [],
skip_reference_warnings_to: [],
skip_undefined_reference_warnings_on: []
]

Expand All @@ -50,6 +53,18 @@ defmodule ExDoc.Autolink do

def doc(ast, options \\ []) do
config = struct!(__MODULE__, options)

config =
config
|> Map.update!(
:skip_reference_warnings_to,
&Enum.map(&1, fn x -> strip_elixir_namespace(x) end)
)
|> Map.update!(
:skip_undefined_reference_warnings_on,
&Enum.map(&1, fn x -> strip_elixir_namespace(x) end)
)

walk(ast, config)
end

Expand Down Expand Up @@ -487,15 +502,29 @@ defmodule ExDoc.Autolink do
end
end

defp fragment(:ex_doc, kind, name, arity) do
prefix =
case kind do
:function -> ""
:callback -> "c:"
:type -> "t:"
end
defp strip_elixir_namespace(module) when is_atom(module),
do: strip_elixir_namespace("#{module}")

defp strip_elixir_namespace("Elixir." <> rest), do: rest
defp strip_elixir_namespace("c:Elixir." <> rest), do: "c:" <> rest
defp strip_elixir_namespace("t:Elixir." <> rest), do: "t:" <> rest
defp strip_elixir_namespace(rest) when is_binary(rest), do: rest

defp prefix(kind)
defp prefix(:function), do: ""
defp prefix(:callback), do: "c:"
defp prefix(:type), do: "t:"

defp ref_id({:module, module}) do
strip_elixir_namespace(module)
end

"#" <> prefix <> "#{T.enc(Atom.to_string(name))}/#{arity}"
defp ref_id({kind, module, name, arity}) do
prefix(kind) <> "#{strip_elixir_namespace(module)}.#{name}/#{arity}"
end

defp fragment(:ex_doc, kind, name, arity) do
"#" <> prefix(kind) <> "#{T.enc(Atom.to_string(name))}/#{arity}"
end

defp fragment(:otp, kind, name, arity) do
Expand Down Expand Up @@ -530,11 +559,29 @@ defmodule ExDoc.Autolink do
end

defp maybe_warn(ref, config, visibility, metadata) do
skipped = config.skip_undefined_reference_warnings_on
file = Path.relative_to(config.file, File.cwd!())
file_path = Map.get(metadata, :file_path, nil)
line = config.line
skip_on = config.skip_undefined_reference_warnings_on
skip_to = config.skip_reference_warnings_to
ref_list_on = [config.id, config.module_id, file]

ref_list_to =
cond do
match?({_, _, _, _}, ref) or match?({:module, _}, ref) ->
[ref_id(ref)]

file_path != nil ->
[file_path]

unless Enum.any?([config.id, config.module_id, file], &(&1 in skipped)) do
true ->
[]
end

if Enum.any?(ref_list_on, &(&1 in skip_on)) or
Enum.any?(ref_list_to, &(&1 in skip_to)) do
nil
else
warn(ref, {file, line}, config.id, visibility, metadata)
end
end
Expand Down
3 changes: 2 additions & 1 deletion lib/ex_doc/cli.ex
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,8 @@ defmodule ExDoc.CLI do
groups_for_functions: [
Guards: & &1[:guard] == true
],
skip_undefined_reference_warnings_on: ["compatibility-and-deprecations"],
skip_reference_warnings_to: ["t:ModuleWithDocFalse.element/0"],
skip_undefined_reference_warnings_on: ["lib/elixir/pages/compatibility-and-deprecations.md"],
groups_for_modules: [
...
]
Expand Down
2 changes: 2 additions & 0 deletions lib/ex_doc/config.ex
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ defmodule ExDoc.Config do
title: nil,
version: nil,
authors: nil,
skip_reference_warnings_to: [],
skip_undefined_reference_warnings_on: []

@type t :: %__MODULE__{
Expand Down Expand Up @@ -80,6 +81,7 @@ defmodule ExDoc.Config do
title: nil | String.t(),
version: nil | String.t(),
authors: nil | [String.t()],
skip_reference_warnings_to: [String.t()],
skip_undefined_reference_warnings_on: [String.t()]
}
end
2 changes: 2 additions & 0 deletions lib/ex_doc/formatter/html.ex
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ defmodule ExDoc.Formatter.HTML do
current_module: node.module,
ext: ext,
extras: extra_paths(config),
skip_reference_warnings_to: config.skip_reference_warnings_to,
skip_undefined_reference_warnings_on: config.skip_undefined_reference_warnings_on,
module_id: node.id,
file: node.source_path,
Expand Down Expand Up @@ -289,6 +290,7 @@ defmodule ExDoc.Formatter.HTML do
file: input,
ext: ext,
extras: extra_paths(config),
skip_reference_warnings_to: config.skip_reference_warnings_to,
skip_undefined_reference_warnings_on: config.skip_undefined_reference_warnings_on
]

Expand Down
9 changes: 6 additions & 3 deletions lib/mix/tasks/docs.ex
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,13 @@ defmodule Mix.Tasks.Docs do
* `:ignore_apps` - Apps to be ignored when generating documentation in an umbrella project.
Receives a list of atoms. Example: `[:first_app, :second_app]`.

* `:skip_undefined_reference_warnings_on` - ExDoc warns when it can't create a `Mod.fun/arity`
* `:skip_reference_warnings_to` - Ignores warnings emitted when a given reference or file is linked to; default: `[]`.

* `:skip_undefined_reference_warnings_on` - Ignores warnings emitted within a given reference or file. ExDoc warns when it can't create a `Mod.fun/arity`
reference in the current project docs e.g. because of a typo. This list controls where to
skip the warnings, for a given module/function/callback/type (e.g.: `["Foo", "Bar.baz/0"]`)
or on a given file (e.g.: `["pages/deprecations.md"]`); default: `[]`.
skip the warnings for a given module/function/callback/type defined in the listed references (e.g.: `["Foo", "Bar.baz/0"]`)
or on a give file (e.g.: `["pages/deprecations.md"]`); default: `[]`.


## Groups

Expand Down
3 changes: 2 additions & 1 deletion test/ex_doc/formatter/epub_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ defmodule ExDoc.Formatter.EPUBTest do
source_root: beam_dir(),
source_beam: beam_dir(),
extras: ["test/fixtures/README.md"],
skip_undefined_reference_warnings_on: ["Warnings"]
skip_reference_warnings_to: [],
skip_undefined_reference_warnings_on: ["Warnings", "Warnings.Submodule"]
]
end

Expand Down
76 changes: 74 additions & 2 deletions test/ex_doc/formatter/html_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@ defmodule ExDoc.Formatter.HTMLTest do
end

defp generate_docs(config) do
config = Keyword.put_new(config, :skip_undefined_reference_warnings_on, ["Warnings"])
config =
config
|> Keyword.put_new(:skip_reference_warnings_to, [])
|> Keyword.put_new(:skip_undefined_reference_warnings_on, ["Warnings", "Warnings.Submodule"])

ExDoc.generate_docs(config[:project], config[:version], config)
end

Expand Down Expand Up @@ -121,13 +125,81 @@ defmodule ExDoc.Formatter.HTMLTest do
output =
capture_io(:stderr, fn ->
generate_docs(
doc_config(skip_undefined_reference_warnings_on: ["test/fixtures/warnings.ex"])
doc_config(
skip_undefined_reference_warnings_on: [
"test/fixtures/warnings.ex"
]
)
)
end)

assert output == ""
end

describe "skip_reference_warnings_to" do
test "emit warnings" do
output =
capture_io(:stderr, fn ->
generate_docs(doc_config(skip_undefined_reference_warnings_on: ["Warnings"]))
end)

assert output =~ ~r{documentation references file \"unknown.md\" but it does not exist\n}
assert output =~ ~r{documentation references module \"UnknownModule\" but it is undefined\n}

assert output =~
~r{documentation references module \"Elixir.UnknownModule\" but it is undefined\n}

assert output =~
~r{documentation references \"CompiledWithDocs.function/0\" but it is undefined or private\n}

assert output =~
~r{documentation references \"c:CompiledWithDocs.callback/1\" but it is undefined\n}

assert output =~
~r{documentation references \"t:CompiledWithDocs.type/2\" but it is undefined or private\n}
end

test "skip warnings" do
output =
capture_io(:stderr, fn ->
generate_docs(
doc_config(
skip_reference_warnings_to: [
"unknown.md",
"UnknownModule",
"CompiledWithDocs.function/0",
"c:CompiledWithDocs.callback/1",
"t:CompiledWithDocs.type/2"
],
skip_undefined_reference_warnings_on: ["Warnings"]
)
)
end)

assert output == ""
end

test "skip warnings withing Elixir namespace" do
output =
capture_io(:stderr, fn ->
generate_docs(
doc_config(
skip_reference_warnings_to: [
"unknown.md",
"Elixir.UnknownModule",
"Elixir.CompiledWithDocs.function/0",
"c:Elixir.CompiledWithDocs.callback/1",
"t:Elixir.CompiledWithDocs.type/2"
],
skip_undefined_reference_warnings_on: ["Warnings"]
)
)
end)

assert output == ""
end
end

test "generates headers for index.html and module pages" do
generate_docs(doc_config(main: "RandomError"))
content_index = File.read!("#{output_dir()}/index.html")
Expand Down
18 changes: 18 additions & 0 deletions test/fixtures/warnings.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,21 @@ defmodule Warnings do
"""
def foo(), do: :ok
end

defmodule Warnings.Submodule do
@moduledoc """
Refer to:
- [this file](unknown.md)
- [this module](`UnknownModule`)
- [this function](`CompiledWithDocs.function/0`)
- [this callback](`c:CompiledWithDocs.callback/1`)
- [this type](`t:CompiledWithDocs.type/2`)

Refer to with Elixir namespace:
- [this module](`Elixir.UnknownModule`)
- [this function](`Elixir.CompiledWithDocs.function/0`)
- [this callback](`c:Elixir.CompiledWithDocs.callback/1`)
- [this type](`t:Elixir.CompiledWithDocs.type/2`)

"""
end