Skip to content

Commit

Permalink
Add --warnings-as-errors flag for non-zero exit code
Browse files Browse the repository at this point in the history
  • Loading branch information
eksperimental committed Nov 24, 2024
1 parent f2369e0 commit 1529acb
Show file tree
Hide file tree
Showing 14 changed files with 387 additions and 51 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ ex_doc-*.tar

node_modules/
/test/fixtures/umbrella/_build/
/test/fixtures/single/_build/
/test/fixtures/single/doc/
/test/tmp/
/tmp/
/npm-debug.log
Expand Down
80 changes: 54 additions & 26 deletions lib/ex_doc/cli.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,37 @@ defmodule ExDoc.CLI do
quiet: :boolean,
source_ref: :string,
source_url: :string,
version: :boolean
version: :boolean,
warnings_as_errors: :boolean
]
)

if List.keymember?(opts, :version, 0) do
IO.puts("ExDoc v#{ExDoc.version()}")
else
generate(args, opts, generator)
results = generate(args, opts, generator)
error_results = Enum.filter(results, &(elem(&1, 0) == :error))

if error_results == [] do
Enum.map(results, fn {:ok, value} -> value end)
else
formatters = Enum.map(error_results, &elem(&1, 1).formatter)

format_message =
case formatters do
[formatter] -> "#{formatter} format"
_ -> "#{Enum.join(formatters, ", ")} formats"
end

message =
"Documents have been generated, but generation for #{format_message} failed due to warnings while using the --warnings-as-errors option."

message_formatted = IO.ANSI.format([:red, message, :reset])

IO.puts(:stderr, message_formatted)

exit({:shutdown, 1})
end
end
end

Expand Down Expand Up @@ -71,7 +94,11 @@ defmodule ExDoc.CLI do
quiet? ||
IO.puts(IO.ANSI.format([:green, "View #{inspect(formatter)} docs at #{inspect(index)}"]))

index
if opts[:warnings_as_errors] == true and ExDoc.Utils.warned?() do
{:error, %{reason: :warnings_as_errors, formatter: formatter}}
else
{:ok, index}
end
end
end

Expand Down Expand Up @@ -164,29 +191,30 @@ defmodule ExDoc.CLI do
ex_doc "Project" "1.0.0" "_build/dev/lib/project/ebin" -c "docs.exs"
Options:
PROJECT Project name
VERSION Version number
BEAMS Path to compiled beam files
--canonical Indicate the preferred URL with rel="canonical" link element
-c, --config Give configuration through a file instead of a command line.
See "Custom config" section below for more information.
-f, --formatter Docs formatter to use (html or epub), default: html and epub
--homepage-url URL to link to for the site name
--language Identify the primary language of the documents, its value must be
a valid [BCP 47](https://tools.ietf.org/html/bcp47) language tag, default: "en"
-l, --logo Path to a logo image for the project. Must be PNG, JPEG or SVG. The image will
be placed in the output "assets" directory.
-m, --main The entry-point page in docs, default: "api-reference"
-o, --output Path to output docs, default: "doc"
--package Hex package name
--paths Prepends the given path to Erlang code path. The path might contain a glob
pattern but in that case, remember to quote it: --paths "_build/dev/lib/*/ebin".
This option can be given multiple times
--proglang The project's programming language, default: "elixir"
-q, --quiet Only output warnings and errors
--source-ref Branch/commit/tag used for source link inference, default: "master"
-u, --source-url URL to the source code
-v, --version Print ExDoc version
PROJECT Project name
VERSION Version number
BEAMS Path to compiled beam files
--canonical Indicate the preferred URL with rel="canonical" link element
-c, --config Give configuration through a file instead of a command line.
See "Custom config" section below for more information.
-f, --formatter Docs formatter to use (html or epub), default: html and epub
--homepage-url URL to link to for the site name
--language Identify the primary language of the documents, its value must be
a valid [BCP 47](https://tools.ietf.org/html/bcp47) language tag, default: "en"
-l, --logo Path to a logo image for the project. Must be PNG, JPEG or SVG. The image will
be placed in the output "assets" directory.
-m, --main The entry-point page in docs, default: "api-reference"
-o, --output Path to output docs, default: "doc"
--package Hex package name
--paths Prepends the given path to Erlang code path. The path might contain a glob
pattern but in that case, remember to quote it: --paths "_build/dev/lib/*/ebin".
This option can be given multiple times.
--proglang The project's programming language, default: "elixir".
-q, --quiet Only output warnings and errors.
--source-ref Branch/commit/tag used for source link inference, default: "master".
-u, --source-url URL to the source code.
-v, --version Print ExDoc version.
--warnings-as-errors Exit with non-zero status if doc generation produces warnings.
## Custom config
Expand Down
6 changes: 4 additions & 2 deletions lib/ex_doc/config.ex
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ defmodule ExDoc.Config do
source_url: nil,
source_url_pattern: nil,
title: nil,
version: nil
version: nil,
warnings_as_errors: false

@type t :: %__MODULE__{
annotations_for_docs: (map() -> list()),
Expand Down Expand Up @@ -88,7 +89,8 @@ defmodule ExDoc.Config do
source_url: nil | String.t(),
source_url_pattern: nil | String.t(),
title: nil | String.t(),
version: nil | String.t()
version: nil | String.t(),
warnings_as_errors: boolean()
}

@spec build(String.t(), String.t(), Keyword.t()) :: ExDoc.Config.t()
Expand Down
7 changes: 5 additions & 2 deletions lib/ex_doc/formatter/epub.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ defmodule ExDoc.Formatter.EPUB do
@assets_dir "OEBPS/assets"
alias __MODULE__.{Assets, Templates}
alias ExDoc.Formatter.HTML
alias ExDoc.Utils

@doc """
Generate EPUB documentation for the given modules.
Generates EPUB documentation for the given modules.
"""
@spec run([ExDoc.ModuleNode.t()], [ExDoc.ModuleNode.t()], ExDoc.Config.t()) :: String.t()
def run(project_nodes, filtered_modules, config) when is_map(config) do
Utils.unset_warned()

config = normalize_config(config)
File.rm_rf!(config.output)
File.mkdir_p!(Path.join(config.output, "OEBPS"))
Expand Down Expand Up @@ -66,7 +69,7 @@ defmodule ExDoc.Formatter.EPUB do
html = Templates.extra_template(config, title, title_content, content)

if File.regular?(output) do
ExDoc.Utils.warn("file #{Path.relative_to_cwd(output)} already exists", [])
Utils.warn("file #{Path.relative_to_cwd(output)} already exists", [])
end

File.write!(output, html)
Expand Down
6 changes: 4 additions & 2 deletions lib/ex_doc/formatter/html.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ defmodule ExDoc.Formatter.HTML do
@assets_dir "assets"

@doc """
Generate HTML documentation for the given modules.
Generates HTML documentation for the given modules.
"""
@spec run([ExDoc.ModuleNode.t()], [ExDoc.ModuleNode.t()], ExDoc.Config.t()) :: String.t()
def run(project_nodes, filtered_modules, config) when is_map(config) do
Utils.unset_warned()

config = normalize_config(config)
config = %{config | output: Path.expand(config.output)}

Expand Down Expand Up @@ -528,7 +530,7 @@ defmodule ExDoc.Formatter.HTML do

defp generate_redirect(filename, config, redirect_to) do
unless case_sensitive_file_regular?("#{config.output}/#{redirect_to}") do
ExDoc.Utils.warn("#{filename} redirects to #{redirect_to}, which does not exist", [])
Utils.warn("#{filename} redirects to #{redirect_to}, which does not exist", [])
end

content = Templates.redirect_template(config, redirect_to)
Expand Down
40 changes: 35 additions & 5 deletions lib/ex_doc/utils.ex
Original file line number Diff line number Diff line change
@@ -1,19 +1,49 @@
defmodule ExDoc.Utils do
@moduledoc false

@elixir_gte_1_14? Version.match?(System.version(), ">= 1.14.0")

@doc """
Emits a warning.
"""
if Version.match?(System.version(), ">= 1.14.0") do
def warn(message, stacktrace_info) do
def warn(message, stacktrace_info) do
set_warned()

# TODO: remove check when we require Elixir v1.14
if @elixir_gte_1_14? do
IO.warn(message, stacktrace_info)
end
else
def warn(message, _stacktrace_info) do
else
IO.warn(message, [])
end
end

@doc """
Stores that a warning has been generated.
"""
def set_warned() do
unless warned?() do
:persistent_term.put({__MODULE__, :warned?}, true)
end

true
end

@doc """
Removes that a warning has been generated.
"""
def unset_warned() do
if warned?() do
:persistent_term.put({__MODULE__, :warned?}, false)
end
end

@doc """
Returns `true` if any warning has been generated during the document building. Otherwise returns `false`.
"""
def warned?() do
:persistent_term.get({__MODULE__, :warned?}, false)
end

@doc """
Runs the `before_closing_head_tag` callback.
"""
Expand Down
52 changes: 45 additions & 7 deletions lib/mix/tasks/docs.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ defmodule Mix.Tasks.Docs do
* `--proglang` - Chooses the main programming language: `elixir`
or `erlang`
* `--warnings-as-errors` - Exits with non-zero exit code if any warnings are found
The command line options have higher precedence than the options
specified in your `mix.exs` file below.
Expand Down Expand Up @@ -325,7 +327,8 @@ defmodule Mix.Tasks.Docs do
language: :string,
open: :boolean,
output: :string,
proglang: :string
proglang: :string,
warnings_as_errors: :boolean
]

@aliases [
Expand Down Expand Up @@ -383,17 +386,52 @@ defmodule Mix.Tasks.Docs do
|> normalize_formatters()
|> put_package(config)

Code.prepend_path(options[:source_beam])

for path <- Keyword.get_values(options, :paths),
path <- Path.wildcard(path) do
Code.prepend_path(path)
end

Mix.shell().info("Generating docs...")

for formatter <- options[:formatters] do
index = generator.(project, version, Keyword.put(options, :formatter, formatter))
Mix.shell().info([:green, "View #{inspect(formatter)} docs at #{inspect(index)}"])
results =
for formatter <- options[:formatters] do
index = generator.(project, version, Keyword.put(options, :formatter, formatter))
Mix.shell().info([:green, "View #{inspect(formatter)} docs at #{inspect(index)}"])

if cli_opts[:open] do
browser_open(index)
if cli_opts[:open] do
browser_open(index)
end

if options[:warnings_as_errors] == true and ExDoc.Utils.warned?() do
{:error, %{reason: :warnings_as_errors, formatter: formatter}}
else
{:ok, index}
end
end

index
error_results = Enum.filter(results, &(elem(&1, 0) == :error))

if error_results == [] do
Enum.map(results, fn {:ok, value} -> value end)
else
formatters = Enum.map(error_results, &elem(&1, 1).formatter)

format_message =
case formatters do
[formatter] -> "#{formatter} format"
_ -> "#{Enum.join(formatters, ", ")} formats"
end

message =
"Documents have been generated, but generation for #{format_message} failed due to warnings while using the --warnings-as-errors option."

message_formatted = IO.ANSI.format([:red, message, :reset])

IO.puts(:stderr, message_formatted)

exit({:shutdown, 1})
end
end

Expand Down
2 changes: 1 addition & 1 deletion mix.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
%{
"earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"},
"easyhtml": {:hex, :easyhtml, "0.3.2", "050adfc8074f53b261f7dfe83303d864f1fbf5988245b369f8fdff1bf4c4b3e6", [:mix], [{:floki, "~> 0.35", [hex: :floki, repo: "hexpm", optional: false]}], "hexpm", "b6a936f91612a4870aa3e828cd8da5a08d9e3b6221b4d3012b6ec70b87845d06"},
"floki": {:hex, :floki, "0.36.2", "a7da0193538c93f937714a6704369711998a51a6164a222d710ebd54020aa7a3", [:mix], [], "hexpm", "a8766c0bc92f074e5cb36c4f9961982eda84c5d2b8e979ca67f5c268ec8ed580"},
"floki": {:hex, :floki, "0.36.3", "1102f93b16a55bc5383b85ae3ec470f82dee056eaeff9195e8afdf0ef2a43c30", [:mix], [], "hexpm", "fe0158bff509e407735f6d40b3ee0d7deb47f3f3ee7c6c182ad28599f9f6b27a"},
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
"makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"},
"makeup_c": {:hex, :makeup_c, "0.1.1", "14250b1a69770b1892f4113129417a2df098e2a72b9e1477aa9096e9e6c473a6", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "89e9cf45372822d354c19a7e18d77f84cfd70e2d206ac987eb15a1b8357f2869"},
Expand Down
49 changes: 48 additions & 1 deletion test/ex_doc/formatter/epub_test.exs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
defmodule ExDoc.Formatter.EPUBTest do
use ExUnit.Case, async: true
use ExUnit.Case, async: false

import ExUnit.CaptureIO

alias ExDoc.Utils

@moduletag :tmp_dir

Expand Down Expand Up @@ -237,4 +241,47 @@ defmodule ExDoc.Formatter.EPUBTest do
after
File.rm_rf!("test/tmp/epub_assets")
end

describe "warnings" do
@describetag :warnings

test "multiple warnings are registered when using warnings_as_errors: true", context do
Utils.unset_warned()

output =
capture_io(:stderr, fn ->
generate_docs(
doc_config(context,
skip_undefined_reference_warnings_on: [],
warnings_as_errors: true
)
)
end)

# TODO: remove check when we require Elixir v1.16
if Version.match?(System.version(), ">= 1.16.0-rc") do
assert output =~ ~S|moduledoc `Warnings.bar/0`|
assert output =~ ~S|typedoc `Warnings.bar/0`|
assert output =~ ~S|doc callback `Warnings.bar/0`|
assert output =~ ~S|doc `Warnings.bar/0`|
end

assert Utils.warned?() == true
end

test "warnings are registered even with warnings_as_errors: false", context do
Utils.unset_warned()

capture_io(:stderr, fn ->
generate_docs(
doc_config(context,
skip_undefined_reference_warnings_on: [],
warnings_as_errors: false
)
)
end)

assert Utils.warned?() == true
end
end
end
Loading

0 comments on commit 1529acb

Please sign in to comment.