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
21 changes: 15 additions & 6 deletions lib/ex_doc/autolink.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,22 @@ defmodule ExDoc.Autolink do
#
# * `:module_id` - id of the module being documented (e.g.: `"String"`)
#
# * `:id` - id of the thing being documented (e.g.: `"String.upcase/2"`, `"library-guidelines"`, etc)
# * `:file` - source file location
#
# * `:id` - a module/function/etc being documented (e.g.: `"String.upcase/2"`)
#
# * `:ext` - the extension (`".html"`, "`.xhtml"`, etc)
#
# * `:skip_undefined_reference_warnings_on` - list of modules to skip the warning on

@enforce_keys [:app, :file]

defstruct [
:app,
:current_module,
:module_id,
:id,
:file,
ext: ".html",
skip_undefined_reference_warnings_on: []
]
Expand Down Expand Up @@ -446,17 +452,20 @@ defmodule ExDoc.Autolink do

defp maybe_warn({kind, module, name, arity}, config) do
skipped = config.skip_undefined_reference_warnings_on
file = Path.relative_to(config.file, File.cwd!())

if config.module_id not in skipped and config.id not in skipped do
warn({kind, module, name, arity}, config.id)
unless Enum.any?([config.id, config.module_id, file], &(&1 in skipped)) do
warn({kind, module, name, arity}, file, config.id)
end
end

defp warn({kind, module, name, arity}, id) do
defp warn({kind, module, name, arity}, file, id) do
message =
"documentation references #{kind} #{inspect(module)}.#{name}/#{arity}" <>
" but it doesn't exist or isn't public (parsing #{id} docs)"
" but it is undefined or private"

IO.warn(message, [])
warning = IO.ANSI.format([:yellow, "warning: ", :reset])
stacktrace = " #{file}" <> ((id && ": " <> id) || "")
IO.puts(:stderr, [warning, message, ?\n, stacktrace, ?\n])
end
end
5 changes: 3 additions & 2 deletions lib/ex_doc/formatter/html.ex
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ defmodule ExDoc.Formatter.HTML do
current_module: node.module,
ext: ext,
skip_undefined_reference_warnings_on: config.skip_undefined_reference_warnings_on,
module_id: node.id
module_id: node.id,
file: node.source_path
]

docs =
Expand Down Expand Up @@ -282,7 +283,7 @@ defmodule ExDoc.Formatter.HTML do
defp build_extra(input, id, title, groups, config, ext) do
autolink_opts = [
app: config.app,
id: id,
file: input,
ext: ext,
skip_undefined_reference_warnings_on: config.skip_undefined_reference_warnings_on
]
Expand Down
6 changes: 3 additions & 3 deletions lib/mix/tasks/docs.ex
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,9 @@ defmodule Mix.Tasks.Docs do
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`
reference in the current project docs e.g. because of a typo. This list controls
which docs pages to skip the warnings on, which is useful for e.g. deprecation pages;
default: `[]`.
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: `[]`.

## Groups

Expand Down
3 changes: 2 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ defmodule ExDoc.Mixfile do
ExDoc.ModuleNode,
ExDoc.TypeNode
]
]
],
skip_undefined_reference_warnings_on: ["CHANGELOG.md"]
]
end

Expand Down
31 changes: 23 additions & 8 deletions test/ex_doc/autolink_test.exs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
defmodule ExDoc.AutolinkTest do
use ExUnit.Case, async: true
doctest ExDoc.Autolink
import ExUnit.CaptureIO

defp sigil_t(text, []) do
{:code, [], [text]}
Expand Down Expand Up @@ -297,9 +298,21 @@ defmodule ExDoc.AutolinkTest do
{{:type, Foo, :bad, 0}, false}
])

assert_warn(fn ->
assert_unchanged(~t"Foo.bar/1")
end)
captured =
assert_warn(fn ->
assert_unchanged(~t"Foo.bar/1", file: "lib/foo.ex", id: nil)
end)

assert captured =~ "documentation references function Foo.bar/1"
assert captured =~ ~r{lib/foo.ex\n$}

captured =
assert_warn(fn ->
assert_unchanged(~t"Foo.bar/1", file: "lib/foo.ex", id: "Foo.foo/0")
end)

assert captured =~ "documentation references function Foo.bar/1"
assert captured =~ ~r{lib/foo.ex: Foo.foo/0\n$}

assert_warn(fn ->
assert_unchanged(~t"String.upcase/9")
Expand Down Expand Up @@ -329,7 +342,7 @@ defmodule ExDoc.AutolinkTest do

## Helpers

@default_options [app: :myapp, current_module: MyModule, module_id: "MyModule"]
@default_options [app: :myapp, current_module: MyModule, module_id: "MyModule", file: "nofile"]

defp autolinked(node, options \\ []) do
case ExDoc.Autolink.doc(node, Keyword.merge(@default_options, options)) do
Expand All @@ -338,12 +351,14 @@ defmodule ExDoc.AutolinkTest do
end
end

defp assert_unchanged(node, options \\ []) do
assert ExDoc.Autolink.doc(node, Keyword.merge(@default_options, options)) == node
defp assert_warn(fun) do
captured = capture_io(:stderr, fun)
captured =~ "documentation references"
captured
end

defp assert_warn(fun) do
assert ExUnit.CaptureIO.capture_io(:stderr, fun) =~ "documentation references"
defp assert_unchanged(node, options \\ []) do
assert ExDoc.Autolink.doc(node, Keyword.merge(@default_options, options)) == node
end

defp typespec(ast, options \\ []) do
Expand Down
19 changes: 15 additions & 4 deletions test/ex_doc/formatter/html_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,21 @@ defmodule ExDoc.Formatter.HTMLTest do
generate_docs(doc_config(skip_undefined_reference_warnings_on: []))
end)

assert output =~ ~r"Warnings.bar/0 .* \(parsing Warnings docs\)"
assert output =~ ~r"Warnings.bar/0 .* \(parsing Warnings.foo/0 docs\)"
assert output =~ ~r"Warnings.bar/0 .* \(parsing c:Warnings.handle_foo/0 docs\)"
assert output =~ ~r"Warnings.bar/0 .* \(parsing t:Warnings.t/0 docs\)"
assert output =~ ~r"Warnings.bar/0.*\n test/fixtures/warnings.ex: Warnings"
assert output =~ ~r"Warnings.bar/0.*\n test/fixtures/warnings.ex: Warnings.foo/0"
assert output =~ ~r"Warnings.bar/0.*\n test/fixtures/warnings.ex: c:Warnings.handle_foo/0"
assert output =~ ~r"Warnings.bar/0.*\n test/fixtures/warnings.ex: t:Warnings.t/0"
end

test "warns on undefined functions in file" do
output =
capture_io(:stderr, fn ->
generate_docs(
doc_config(skip_undefined_reference_warnings_on: ["test/fixtures/warnings.ex"])
)
end)

assert output == ""
end

test "generates headers for index.html and module pages" do
Expand Down