From c1f7f2cd522bc97949b331def3bc76103a476485 Mon Sep 17 00:00:00 2001 From: Kaden Wilkinson Date: Wed, 16 Feb 2022 15:22:39 -0700 Subject: [PATCH] Add --warnings-as-errors flag for non-zero exit code --- lib/ex_doc/application.ex | 7 +++- lib/ex_doc/cli.ex | 68 ++++++++++++++++++++-------------- lib/ex_doc/config.ex | 6 ++- lib/ex_doc/language.ex | 2 + lib/ex_doc/markdown/earmark.ex | 1 + lib/ex_doc/retriever.ex | 1 + lib/ex_doc/warning_counter.ex | 17 +++++++++ lib/mix/tasks/docs.ex | 16 +++++++- test/ex_doc/cli_test.exs | 22 +++++++++++ test/mix/tasks/docs_test.exs | 9 +++++ 10 files changed, 116 insertions(+), 33 deletions(-) create mode 100644 lib/ex_doc/warning_counter.ex diff --git a/lib/ex_doc/application.ex b/lib/ex_doc/application.ex index a9816c5ee..e4bc48318 100644 --- a/lib/ex_doc/application.ex +++ b/lib/ex_doc/application.ex @@ -17,6 +17,11 @@ defmodule ExDoc.Application do match?("makeup_" <> _, Atom.to_string(app)), do: Application.ensure_all_started(app) - Supervisor.start_link([ExDoc.Refs], strategy: :one_for_one) + children = [ + ExDoc.Refs, + ExDoc.WarningCounter + ] + + Supervisor.start_link(children, strategy: :one_for_one) end end diff --git a/lib/ex_doc/cli.ex b/lib/ex_doc/cli.ex index fdbc71ae6..33bbd466f 100644 --- a/lib/ex_doc/cli.ex +++ b/lib/ex_doc/cli.ex @@ -29,14 +29,25 @@ defmodule ExDoc.CLI do source_ref: :string, version: :boolean, formatter: :keep, - quiet: :boolean + quiet: :boolean, + warnings_as_errors: :boolean ] ) - if List.keymember?(opts, :version, 0) do - print_version() - else - generate(args, opts, generator) + cond do + List.keymember?(opts, :version, 0) -> + print_version() + + opts[:warnings_as_errors] == true and ExDoc.WarningCounter.count() > 0 -> + IO.puts( + :stderr, + "Doc generation failed due to warnings while using the --warnings-as-errors option" + ) + + exit({:shutdown, ExDoc.WarningCounter.count()}) + + true -> + generate(args, opts, generator) end end @@ -159,29 +170,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 - -n, --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 - -p, --homepage-url URL to link to for the site 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 - --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 the image logo of the project (only PNG or JPEG accepted) - The image size will be 64x64 and copied to the assets directory - -m, --main The entry-point page in docs, default: "api-reference" - --package Hex package name - --proglang The project's programming language, default: "elixir" - --source-ref Branch/commit/tag used for source link inference, default: "master" - -u, --source-url URL to the source code - -o, --output Path to output docs, default: "doc" - -v, --version Print ExDoc version - -q, --quiet Only output warnings and errors + PROJECT Project name + VERSION Version number + BEAMS Path to compiled beam files + -n, --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 + -p, --homepage-url URL to link to for the site 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 + --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 the image logo of the project (only PNG or JPEG accepted) + The image size will be 64x64 and copied to the assets directory + -m, --main The entry-point page in docs, default: "api-reference" + --package Hex package name + --proglang The project's programming language, default: "elixir" + --source-ref Branch/commit/tag used for source link inference, default: "master" + -u, --source-url URL to the source code + -o, --output Path to output docs, default: "doc" + -v, --version Print ExDoc version + -q, --quiet Only output warnings and errors + --warnings-as-errors Exit with non-zero status if doc generation has one or more warnings ## Custom config diff --git a/lib/ex_doc/config.ex b/lib/ex_doc/config.ex index 0cbbb00aa..1f47b125d 100644 --- a/lib/ex_doc/config.ex +++ b/lib/ex_doc/config.ex @@ -40,7 +40,8 @@ defmodule ExDoc.Config do version: nil, authors: nil, skip_undefined_reference_warnings_on: [], - package: nil + package: nil, + warnings_as_errors: false @type t :: %__MODULE__{ apps: [atom()], @@ -75,7 +76,8 @@ defmodule ExDoc.Config do version: nil | String.t(), authors: nil | [String.t()], skip_undefined_reference_warnings_on: [String.t()], - package: :atom | nil + package: :atom | nil, + warnings_as_errors: boolean() } @spec build(String.t(), String.t(), Keyword.t()) :: ExDoc.Config.t() diff --git a/lib/ex_doc/language.ex b/lib/ex_doc/language.ex index d05e89a9a..db01de818 100644 --- a/lib/ex_doc/language.ex +++ b/lib/ex_doc/language.ex @@ -135,6 +135,8 @@ defmodule ExDoc.Language do def get(:erlang, _module), do: {:ok, ExDoc.Language.Erlang} def get(language, module) when is_atom(language) and is_atom(module) do + ExDoc.WarningCounter.increment() + IO.warn( "skipping module #{module}, reason: unsupported language (#{language})", [] diff --git a/lib/ex_doc/markdown/earmark.ex b/lib/ex_doc/markdown/earmark.ex index 00454289b..46396b2e2 100644 --- a/lib/ex_doc/markdown/earmark.ex +++ b/lib/ex_doc/markdown/earmark.ex @@ -48,6 +48,7 @@ defmodule ExDoc.Markdown.Earmark do defp print_messages(messages, options) do for {severity, line, message} <- messages do file = options[:file] + ExDoc.WarningCounter.increment() IO.warn("#{inspect(__MODULE__)} (#{severity}) #{file}:#{line} #{message}", []) end end diff --git a/lib/ex_doc/retriever.ex b/lib/ex_doc/retriever.ex index 9467b489b..1da2a770a 100644 --- a/lib/ex_doc/retriever.ex +++ b/lib/ex_doc/retriever.ex @@ -77,6 +77,7 @@ defmodule ExDoc.Retriever do docs {:error, reason} -> + ExDoc.WarningCounter.increment() IO.warn("skipping module #{inspect(module)}, reason: #{reason}", []) false end diff --git a/lib/ex_doc/warning_counter.ex b/lib/ex_doc/warning_counter.ex new file mode 100644 index 000000000..813f47ff3 --- /dev/null +++ b/lib/ex_doc/warning_counter.ex @@ -0,0 +1,17 @@ +defmodule ExDoc.WarningCounter do + @moduledoc false + + use Agent + + def start_link(_opts) do + Agent.start_link(fn -> 0 end, name: __MODULE__) + end + + def count do + Agent.get(__MODULE__, & &1) + end + + def increment do + Agent.update(__MODULE__, &(&1 + 1)) + end +end diff --git a/lib/mix/tasks/docs.ex b/lib/mix/tasks/docs.ex index 957d0e9e8..51b6fd389 100644 --- a/lib/mix/tasks/docs.ex +++ b/lib/mix/tasks/docs.ex @@ -24,6 +24,8 @@ defmodule Mix.Tasks.Docs do * `--open` - open browser window pointed to the documentation + * `--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. @@ -309,7 +311,8 @@ defmodule Mix.Tasks.Docs do formatter: :keep, language: :string, output: :string, - open: :boolean + open: :boolean, + warnings_as_errors: :boolean ] @aliases [n: :canonical, f: :formatter, o: :output] @@ -363,7 +366,16 @@ defmodule Mix.Tasks.Docs do browser_open(index) end - index + if options[:warnings_as_errors] == true and ExDoc.WarningCounter.count() > 0 do + Mix.shell().info([ + :red, + "Doc generation failed due to warnings while using the --warnings-as-errors option" + ]) + + exit({:shutdown, ExDoc.WarningCounter.count()}) + else + index + end end end diff --git a/test/ex_doc/cli_test.exs b/test/ex_doc/cli_test.exs index 0d89c2289..002f24291 100644 --- a/test/ex_doc/cli_test.exs +++ b/test/ex_doc/cli_test.exs @@ -35,6 +35,28 @@ defmodule ExDoc.CLITest do assert io == "ExDoc v#{ExDoc.version()}\n" end + describe "--warnings-as-errors" do + test "exits with 0 with no warnings" do + assert {"ExDoc", "1.2.3", [_, _, warnings_as_errors: true]} = + run(["ExDoc", "1.2.3", @ebin, "--warnings-as-errors"]) + end + + test "exits with 1 with warnings" do + fun = fn -> + io = + capture_io(:stderr, fn -> + assert {"ExDoc", "1.2.3", [_, _, warnings_as_errors: true]} = + run(["ExDoc", "1.2.3", @ebin, "--warnings-as-errors"]) + end) + + assert io =~ + "Doc generation failed due to warnings while using the --warnings-as-errors option\n" + end + + assert catch_exit(fun.()) == {:shutdown, 1} + end + end + test "too many arguments" do assert catch_exit(run(["ExDoc", "1.2.3", "/", "kaboom"])) == {:shutdown, 1} end diff --git a/test/mix/tasks/docs_test.exs b/test/mix/tasks/docs_test.exs index 24f963892..b7cc4498c 100644 --- a/test/mix/tasks/docs_test.exs +++ b/test/mix/tasks/docs_test.exs @@ -181,4 +181,13 @@ defmodule Mix.Tasks.DocsTest do ] = run([], app: :umbrella, apps_path: "apps/", docs: [ignore_apps: [:foo]]) end) end + + test "accepts warnings_as_errors in :warnings_as_errors" do + assert [ + {"ex_doc", "dev", + [formatter: "html", deps: _, apps: _, source_beam: _, warnings_as_errors: true]}, + {"ex_doc", "dev", + [formatter: "epub", deps: _, apps: _, source_beam: _, warnings_as_errors: true]} + ] = run([], app: :ex_doc, docs: [warnings_as_errors: true]) + end end