diff --git a/lib/eex/lib/eex.ex b/lib/eex/lib/eex.ex index 2e3c07f5eed..6d743c1ed5c 100644 --- a/lib/eex/lib/eex.ex +++ b/lib/eex/lib/eex.ex @@ -118,6 +118,15 @@ defmodule EEx do | {:expr | :start_expr | :middle_expr | :end_expr, marker, charlist, metadata} | {:eof, metadata} + @type tokenize_opt :: + {:file, binary()} + | {:line, line} + | {:column, column} + | {:indentation, non_neg_integer} + | {:trim, boolean()} + + @type compile_opt :: tokenize_opt | {:engine, module()} | {:parser_options, Code.parser_opts()} + @doc """ Generates a function definition from the given string. @@ -220,7 +229,7 @@ defmodule EEx do "3" """ - @spec compile_string(String.t(), keyword) :: Macro.t() + @spec compile_string(String.t(), [compile_opt]) :: Macro.t() def compile_string(source, options \\ []) when is_binary(source) and is_list(options) do case tokenize(source, options) do {:ok, tokens} -> @@ -259,7 +268,7 @@ defmodule EEx do #=> "3" """ - @spec compile_file(Path.t(), keyword) :: Macro.t() + @spec compile_file(Path.t(), [compile_opt]) :: Macro.t() def compile_file(filename, options \\ []) when is_list(options) do filename = IO.chardata_to_string(filename) options = Keyword.merge([file: filename, line: 1], options) @@ -277,7 +286,7 @@ defmodule EEx do "foo baz" """ - @spec eval_string(String.t(), keyword, keyword) :: String.t() + @spec eval_string(String.t(), keyword, [compile_opt]) :: String.t() def eval_string(source, bindings \\ [], options \\ []) when is_binary(source) and is_list(bindings) and is_list(options) do compiled = compile_string(source, options) @@ -299,7 +308,7 @@ defmodule EEx do #=> "foo baz" """ - @spec eval_file(Path.t(), keyword, keyword) :: String.t() + @spec eval_file(Path.t(), keyword, [compile_opt]) :: String.t() def eval_file(filename, bindings \\ [], options \\ []) when is_list(bindings) and is_list(options) do filename = IO.chardata_to_string(filename) @@ -339,7 +348,7 @@ defmodule EEx do Note new tokens may be added in the future. """ @doc since: "1.14.0" - @spec tokenize([char()] | String.t(), opts :: keyword) :: + @spec tokenize([char()] | String.t(), [tokenize_opt]) :: {:ok, [token()]} | {:error, String.t(), metadata()} def tokenize(contents, opts \\ []) do EEx.Compiler.tokenize(contents, opts) diff --git a/lib/eex/lib/eex/compiler.ex b/lib/eex/lib/eex/compiler.ex index 44003e0970f..f401adb553d 100644 --- a/lib/eex/lib/eex/compiler.ex +++ b/lib/eex/lib/eex/compiler.ex @@ -10,6 +10,22 @@ defmodule EEx.Compiler do @h_spaces [?\s, ?\t] @all_spaces [?\s, ?\t, ?\n, ?\r] + @typedoc """ + Options for EEx compilation functions. + + These options control various aspects of EEx template compilation including + file information, parsing behavior, and the template engine to use. + """ + @type compile_opts :: [ + file: String.t(), + line: pos_integer(), + column: pos_integer(), + indentation: non_neg_integer(), + trim: boolean(), + parser_options: Code.parser_opts(), + engine: module() + ] + @doc """ Tokenize EEx contents. """ @@ -290,7 +306,7 @@ defmodule EEx.Compiler do and the engine together by handling the tokens and invoking the engine every time a full expression or text is received. """ - @spec compile([EEx.token()], String.t(), keyword) :: Macro.t() + @spec compile([EEx.token()], String.t(), compile_opts) :: Macro.t() def compile(tokens, source, opts) do file = opts[:file] || "nofile" line = opts[:line] || 1 diff --git a/lib/eex/lib/eex/engine.ex b/lib/eex/lib/eex/engine.ex index 10b01b82b9f..8bb1858bcda 100644 --- a/lib/eex/lib/eex/engine.ex +++ b/lib/eex/lib/eex/engine.ex @@ -14,12 +14,29 @@ defmodule EEx.Engine do @type state :: term + @typedoc """ + Options passed to engine initialization. + + These are the same options passed to `EEx.Compiler.compile/3`, + allowing engines to access compilation settings and customize + their behavior accordingly. + """ + @type init_opts :: [ + file: String.t(), + line: pos_integer(), + column: pos_integer(), + indentation: non_neg_integer(), + trim: boolean(), + parser_options: Code.parser_opts(), + engine: module() + ] + @doc """ Called at the beginning of every template. It must return the initial state. """ - @callback init(opts :: keyword) :: state + @callback init(opts :: init_opts) :: state @doc """ Called at the end of every template. diff --git a/lib/elixir/lib/calendar.ex b/lib/elixir/lib/calendar.ex index 48420bd5f08..89160389b8d 100644 --- a/lib/elixir/lib/calendar.ex +++ b/lib/elixir/lib/calendar.ex @@ -162,6 +162,22 @@ defmodule Calendar do """ @type time_zone_database :: module() + @typedoc """ + Options for formatting dates and times with `strftime/3`. + """ + @type strftime_opts :: [ + preferred_datetime: String.t(), + preferred_date: String.t(), + preferred_time: String.t(), + am_pm_names: (:am | :pm -> String.t()) | (:am | :pm, map() -> String.t()), + month_names: (pos_integer() -> String.t()) | (pos_integer(), map() -> String.t()), + abbreviated_month_names: + (pos_integer() -> String.t()) | (pos_integer(), map() -> String.t()), + day_of_week_names: (pos_integer() -> String.t()) | (pos_integer(), map() -> String.t()), + abbreviated_day_of_week_names: + (pos_integer() -> String.t()) | (pos_integer(), map() -> String.t()) + ] + @doc """ Returns how many days there are in the given month of the given year. """ @@ -617,7 +633,7 @@ defmodule Calendar do """ @doc since: "1.11.0" - @spec strftime(map(), String.t(), keyword()) :: String.t() + @spec strftime(map(), String.t(), strftime_opts()) :: String.t() def strftime(date_or_time_or_datetime, string_format, user_options \\ []) when is_map(date_or_time_or_datetime) and is_binary(string_format) do parse( diff --git a/lib/elixir/lib/calendar/duration.ex b/lib/elixir/lib/calendar/duration.ex index 520b39d59c4..b96e8b8f030 100644 --- a/lib/elixir/lib/calendar/duration.ex +++ b/lib/elixir/lib/calendar/duration.ex @@ -161,6 +161,22 @@ defmodule Duration do """ @type duration :: t | [unit_pair] + @typedoc """ + Options for `Duration.to_string/2`. + """ + @type to_string_opts :: [ + units: [ + year: String.t(), + month: String.t(), + week: String.t(), + day: String.t(), + hour: String.t(), + minute: String.t(), + second: String.t() + ], + separator: String.t() + ] + @microseconds_per_second 1_000_000 @doc """ @@ -436,6 +452,7 @@ defmodule Duration do """ @doc since: "1.18.0" + @spec to_string(t, to_string_opts) :: String.t() def to_string(%Duration{} = duration, opts \\ []) do units = Keyword.get(opts, :units, []) separator = Keyword.get(opts, :separator, " ") diff --git a/lib/elixir/lib/code.ex b/lib/elixir/lib/code.ex index 8e5c91f5da6..c098599f868 100644 --- a/lib/elixir/lib/code.ex +++ b/lib/elixir/lib/code.ex @@ -248,6 +248,49 @@ defmodule Code do """ @type position() :: line() | {line :: pos_integer(), column :: pos_integer()} + @typedoc """ + Options for code formatting functions. + """ + @type format_opts :: [ + file: binary(), + line: pos_integer(), + line_length: pos_integer(), + locals_without_parens: keyword(), + force_do_end_blocks: boolean(), + migrate: boolean(), + migrate_bitstring_modifiers: boolean(), + migrate_call_parens_on_pipe: boolean(), + migrate_charlists_as_sigils: boolean(), + migrate_unless: boolean() + ] + + @typedoc """ + Options for parsing functions that convert strings to quoted expressions. + """ + @type parser_opts :: [ + file: binary(), + line: pos_integer(), + column: pos_integer(), + indentation: non_neg_integer(), + columns: boolean(), + unescape: boolean(), + existing_atoms_only: boolean(), + token_metadata: boolean(), + literal_encoder: (term(), Macro.metadata() -> term()), + static_atoms_encoder: (atom() -> term()), + emit_warnings: boolean() + ] + + @typedoc """ + Options for environment evaluation functions like eval_string/3 and eval_quoted/3. + """ + @type env_eval_opts :: [ + file: binary(), + line: pos_integer(), + module: module(), + prune_binding: boolean() + ] + @boolean_compiler_options [ :docs, :debug_info, @@ -560,7 +603,7 @@ defmodule Code do [a: 1, b: 2] """ - @spec eval_string(List.Chars.t(), binding, Macro.Env.t() | keyword) :: {term, binding} + @spec eval_string(List.Chars.t(), binding, Macro.Env.t() | env_eval_opts) :: {term, binding} def eval_string(string, binding \\ [], opts \\ []) def eval_string(string, binding, %Macro.Env{} = env) do @@ -615,7 +658,8 @@ defmodule Code do """ @doc since: "1.15.0" - @spec with_diagnostics(keyword(), (-> result)) :: {result, [diagnostic(:warning | :error)]} + @spec with_diagnostics([log: boolean()], (-> result)) :: + {result, [diagnostic(:warning | :error)]} when result: term() def with_diagnostics(opts \\ [], fun) do value = :erlang.get(:elixir_code_diagnostics) @@ -648,7 +692,7 @@ defmodule Code do Defaults to `true`. """ @doc since: "1.15.0" - @spec print_diagnostic(diagnostic(:warning | :error), keyword()) :: :ok + @spec print_diagnostic(diagnostic(:warning | :error), snippet: boolean()) :: :ok def print_diagnostic(diagnostic, opts \\ []) do read_snippet? = Keyword.get(opts, :snippet, true) :elixir_errors.print_diagnostic(diagnostic, read_snippet?) @@ -1035,7 +1079,7 @@ defmodule Code do address the deprecation warnings. """ @doc since: "1.6.0" - @spec format_string!(binary, keyword) :: iodata + @spec format_string!(binary, format_opts) :: iodata def format_string!(string, opts \\ []) when is_binary(string) and is_list(opts) do line_length = Keyword.get(opts, :line_length, 98) @@ -1060,7 +1104,7 @@ defmodule Code do available options. """ @doc since: "1.6.0" - @spec format_file!(binary, keyword) :: iodata + @spec format_file!(binary, format_opts) :: iodata def format_file!(file, opts \\ []) when is_binary(file) and is_list(opts) do string = File.read!(file) formatted = format_string!(string, [file: file, line: 1] ++ opts) @@ -1098,7 +1142,7 @@ defmodule Code do [a: 1, b: 2] """ - @spec eval_quoted(Macro.t(), binding, Macro.Env.t() | keyword) :: {term, binding} + @spec eval_quoted(Macro.t(), binding, Macro.Env.t() | env_eval_opts) :: {term, binding} def eval_quoted(quoted, binding \\ [], env_or_opts \\ []) do {value, binding, _env} = eval_verify(:eval_quoted, [quoted, binding, env_for_eval(env_or_opts)]) @@ -1129,8 +1173,15 @@ defmodule Code do * `:line` - the line on which the script starts * `:module` - the module to run the environment on + + * `:prune_binding` - (since v1.14.2) prune binding to keep only + variables read or written by the evaluated code. Note that + variables used by modules are always pruned, even if later used + by the modules. You can submit to the `:on_module` tracer event + and access the variables used by the module from its environment. """ @doc since: "1.14.0" + @spec env_for_eval(Macro.Env.t() | env_eval_opts) :: Macro.Env.t() def env_for_eval(env_or_opts), do: :elixir.env_for_eval(env_or_opts) @doc """ @@ -1144,15 +1195,11 @@ defmodule Code do ## Options - * `:prune_binding` - (since v1.14.2) prune binding to keep only - variables read or written by the evaluated code. Note that - variables used by modules are always pruned, even if later used - by the modules. You can submit to the `:on_module` tracer event - and access the variables used by the module from its environment. + It accepts the same options as `env_for_eval/1`. """ @doc since: "1.14.0" - @spec eval_quoted_with_env(Macro.t(), binding, Macro.Env.t(), keyword) :: + @spec eval_quoted_with_env(Macro.t(), binding, Macro.Env.t(), env_eval_opts) :: {term, binding, Macro.Env.t()} def eval_quoted_with_env(quoted, binding, %Macro.Env{} = env, opts \\ []) when is_list(binding) do @@ -1263,7 +1310,7 @@ defmodule Code do {:error, {[line: 1, column: 4], "syntax error before: ", "\"3\""}} """ - @spec string_to_quoted(List.Chars.t(), keyword) :: + @spec string_to_quoted(List.Chars.t(), parser_opts) :: {:ok, Macro.t()} | {:error, {location :: keyword, binary | {binary, binary}, binary}} def string_to_quoted(string, opts \\ []) when is_list(opts) do file = Keyword.get(opts, :file, "nofile") @@ -1290,7 +1337,7 @@ defmodule Code do Check `string_to_quoted/2` for options information. """ - @spec string_to_quoted!(List.Chars.t(), keyword) :: Macro.t() + @spec string_to_quoted!(List.Chars.t(), parser_opts) :: Macro.t() def string_to_quoted!(string, opts \\ []) when is_list(opts) do file = Keyword.get(opts, :file, "nofile") line = Keyword.get(opts, :line, 1) @@ -1341,7 +1388,7 @@ defmodule Code do """ @doc since: "1.13.0" - @spec string_to_quoted_with_comments(List.Chars.t(), keyword) :: + @spec string_to_quoted_with_comments(List.Chars.t(), parser_opts) :: {:ok, Macro.t(), list(map())} | {:error, {location :: keyword, term, term}} def string_to_quoted_with_comments(string, opts \\ []) when is_list(opts) do charlist = to_charlist(string) @@ -1371,7 +1418,7 @@ defmodule Code do Check `string_to_quoted/2` for options information. """ @doc since: "1.13.0" - @spec string_to_quoted_with_comments!(List.Chars.t(), keyword) :: {Macro.t(), list(map())} + @spec string_to_quoted_with_comments!(List.Chars.t(), parser_opts) :: {Macro.t(), list(map())} def string_to_quoted_with_comments!(string, opts \\ []) do charlist = to_charlist(string) @@ -1456,6 +1503,9 @@ defmodule Code do ## Options + This function accepts all options supported by `format_string!/2` for controlling + code formatting, plus these additional options: + * `:comments` - the list of comments associated with the quoted expression. Defaults to `[]`. It is recommended that both `:token_metadata` and `:literal_encoder` options are given to `string_to_quoted_with_comments/2` @@ -1466,17 +1516,14 @@ defmodule Code do `string_to_quoted/2`, setting this option to `false` will prevent it from escaping the sequences twice. Defaults to `true`. - * `:locals_without_parens` - a keyword list of name and arity - pairs that should be kept without parens whenever possible. - The arity may be the atom `:*`, which implies all arities of - that name. The formatter already includes a list of functions - and this option augments this list. - - * `:syntax_colors` - a keyword list of colors the output is colorized. - See `Inspect.Opts` for more information. + See `format_string!/2` for the full list of formatting options including + `:file`, `:line`, `:line_length`, `:locals_without_parens`, `:force_do_end_blocks`, + `:syntax_colors`, and all migration options like `:migrate_charlists_as_sigils`. """ @doc since: "1.13.0" - @spec quoted_to_algebra(Macro.t(), keyword) :: Inspect.Algebra.t() + @spec quoted_to_algebra(Macro.t(), [ + Code.Formatter.to_algebra_opt() | Code.Normalizer.normalize_opt() + ]) :: Inspect.Algebra.t() def quoted_to_algebra(quoted, opts \\ []) do quoted |> Code.Normalizer.normalize(opts) diff --git a/lib/elixir/lib/code/formatter.ex b/lib/elixir/lib/code/formatter.ex index 170edf71299..a4417282d04 100644 --- a/lib/elixir/lib/code/formatter.ex +++ b/lib/elixir/lib/code/formatter.ex @@ -155,9 +155,32 @@ defmodule Code.Formatter do @do_end_keywords [:rescue, :catch, :else, :after] + @typedoc """ + Options for `to_algebra/2`. + + These options include all the standard code formatting options plus + additional context like comments and syntax highlighting colors. + """ + @type to_algebra_opt :: + {:comments, [term()]} + | {:syntax_colors, keyword()} + | {:sigils, keyword()} + | {:file, binary()} + | {:line, pos_integer()} + | {:line_length, pos_integer()} + | {:locals_without_parens, keyword()} + | {:force_do_end_blocks, boolean()} + | {:migrate, boolean()} + | {:migrate_bitstring_modifiers, boolean()} + | {:migrate_call_parens_on_pipe, boolean()} + | {:migrate_charlists_as_sigils, boolean()} + | {:migrate_unless, boolean()} + | {atom(), term()} + @doc """ Converts the quoted expression into an algebra document. """ + @spec to_algebra(Macro.t(), [to_algebra_opt]) :: Inspect.Algebra.t() def to_algebra(quoted, opts \\ []) do comments = Keyword.get(opts, :comments, []) diff --git a/lib/elixir/lib/code/fragment.ex b/lib/elixir/lib/code/fragment.ex index ca71fe21ff2..fff274a34ec 100644 --- a/lib/elixir/lib/code/fragment.ex +++ b/lib/elixir/lib/code/fragment.ex @@ -11,6 +11,26 @@ defmodule Code.Fragment do @type position :: {line :: pos_integer(), column :: pos_integer()} + @typedoc """ + Options for cursor context functions. + + Currently, these options are not used but reserved for future extensibility. + """ + @type cursor_opts :: [] + + @typedoc """ + Options for converting code fragments to quoted expressions. + """ + @type container_cursor_to_quoted_opts :: [ + file: String.t(), + line: pos_integer(), + column: pos_integer(), + columns: boolean(), + token_metadata: boolean(), + literal_encoder: (term(), Macro.metadata() -> term()), + trailing_fragment: String.t() + ] + @doc ~S""" Returns the list of lines in the given string, preserving their line endings. @@ -172,7 +192,7 @@ defmodule Code.Fragment do references, and more. """ @doc since: "1.13.0" - @spec cursor_context(List.Chars.t(), keyword()) :: + @spec cursor_context(List.Chars.t(), cursor_opts()) :: {:alias, charlist} | {:alias, inside_alias, charlist} | {:block_keyword_or_binary_operator, charlist} @@ -662,7 +682,7 @@ defmodule Code.Fragment do of examples and their return values. """ @doc since: "1.13.0" - @spec surround_context(List.Chars.t(), position(), keyword()) :: + @spec surround_context(List.Chars.t(), position(), cursor_opts()) :: %{begin: position, end: position, context: context} | :none when context: {:alias, charlist} @@ -1215,7 +1235,7 @@ defmodule Code.Fragment do """ @doc since: "1.13.0" - @spec container_cursor_to_quoted(List.Chars.t(), keyword()) :: + @spec container_cursor_to_quoted(List.Chars.t(), container_cursor_to_quoted_opts()) :: {:ok, Macro.t()} | {:error, {location :: keyword, binary | {binary, binary}, binary}} def container_cursor_to_quoted(fragment, opts \\ []) do {trailing_fragment, opts} = Keyword.pop(opts, :trailing_fragment) diff --git a/lib/elixir/lib/code/normalizer.ex b/lib/elixir/lib/code/normalizer.ex index c3dd67a3af9..e45a5aee6a8 100644 --- a/lib/elixir/lib/code/normalizer.ex +++ b/lib/elixir/lib/code/normalizer.ex @@ -10,10 +10,20 @@ defmodule Code.Normalizer do is_binary(x) or is_atom(x) + @typedoc """ + Options for `normalize/2`. + """ + @type normalize_opt :: + {:line, pos_integer() | nil} + | {:escape, boolean()} + | {:locals_without_parens, keyword()} + | {atom(), term()} + @doc """ Wraps literals in the quoted expression to conform to the AST format expected by the formatter. """ + @spec normalize(Macro.t(), [normalize_opt]) :: Macro.t() def normalize(quoted, opts \\ []) do line = Keyword.get(opts, :line, nil) escape = Keyword.get(opts, :escape, true) diff --git a/lib/elixir/lib/config.ex b/lib/elixir/lib/config.ex index b18fbd271c3..409512b84f2 100644 --- a/lib/elixir/lib/config.ex +++ b/lib/elixir/lib/config.ex @@ -98,6 +98,12 @@ defmodule Config do (assembled with `mix release`). """ + @type config_opts :: [ + imports: [Path.t()] | :disabled, + env: atom(), + target: atom() + ] + @opts_key {__MODULE__, :opts} @config_key {__MODULE__, :config} @imports_key {__MODULE__, :imports} @@ -306,7 +312,7 @@ defmodule Config do end @doc false - @spec __eval__!(Path.t(), binary(), keyword) :: {keyword, [Path.t()] | :disabled} + @spec __eval__!(Path.t(), binary(), config_opts) :: {keyword, [Path.t()] | :disabled} def __eval__!(file, content, opts \\ []) when is_binary(file) and is_list(opts) do env = Keyword.get(opts, :env) target = Keyword.get(opts, :target) diff --git a/lib/elixir/lib/config/provider.ex b/lib/elixir/lib/config/provider.ex index 756d583e074..b039a2233c7 100644 --- a/lib/elixir/lib/config/provider.ex +++ b/lib/elixir/lib/config/provider.ex @@ -111,6 +111,16 @@ defmodule Config.Provider do """ @type config_path :: {:system, binary(), binary()} | binary() + @typedoc """ + Options for `init/3`. + """ + @type init_opts :: [ + extra_config: config(), + prune_runtime_sys_config_after_boot: boolean(), + reboot_system_after_config: boolean(), + validate_compile_env: [{atom(), [atom()], term()}] + ] + @doc """ Invoked when initializing a config provider. @@ -196,6 +206,7 @@ defmodule Config.Provider do @reboot_mode_key :config_provider_reboot_mode @doc false + @spec init([{module(), term()}], config_path(), init_opts()) :: config() def init(providers, config_path, opts \\ []) when is_list(providers) and is_list(opts) do validate_config_path!(config_path) providers = for {provider, init} <- providers, do: {provider, provider.init(init)} diff --git a/lib/elixir/lib/config/reader.ex b/lib/elixir/lib/config/reader.ex index 1997bb1cb0e..3a259d3602e 100644 --- a/lib/elixir/lib/config/reader.ex +++ b/lib/elixir/lib/config/reader.ex @@ -46,6 +46,12 @@ defmodule Config.Reader do @behaviour Config.Provider + @type config_opts :: [ + imports: [Path.t()] | :disabled, + env: atom(), + target: atom() + ] + @impl true def init(opts) when is_list(opts) do {path, opts} = Keyword.pop!(opts, :path) @@ -68,7 +74,7 @@ defmodule Config.Reader do Accepts the same options as `read!/2`. """ @doc since: "1.11.0" - @spec eval!(Path.t(), binary, keyword) :: keyword + @spec eval!(Path.t(), binary, config_opts) :: keyword def eval!(file, contents, opts \\ []) when is_binary(file) and is_binary(contents) and is_list(opts) do Config.__eval__!(Path.expand(file), contents, opts) |> elem(0) @@ -90,7 +96,7 @@ defmodule Config.Reader do """ @doc since: "1.9.0" - @spec read!(Path.t(), keyword) :: keyword + @spec read!(Path.t(), config_opts) :: keyword def read!(file, opts \\ []) when is_binary(file) and is_list(opts) do file = Path.expand(file) Config.__eval__!(file, File.read!(file), opts) |> elem(0) @@ -104,7 +110,7 @@ defmodule Config.Reader do option cannot be disabled in `read_imports!/2`. """ @doc since: "1.9.0" - @spec read_imports!(Path.t(), keyword) :: {keyword, [Path.t()]} + @spec read_imports!(Path.t(), config_opts) :: {keyword, [Path.t()]} def read_imports!(file, opts \\ []) when is_binary(file) and is_list(opts) do if opts[:imports] == :disabled do raise ArgumentError, ":imports must be a list of paths" diff --git a/lib/elixir/lib/inspect/algebra.ex b/lib/elixir/lib/inspect/algebra.ex index 9c0175865dd..776dfbcd4bc 100644 --- a/lib/elixir/lib/inspect/algebra.ex +++ b/lib/elixir/lib/inspect/algebra.ex @@ -115,11 +115,28 @@ defmodule Inspect.Opts do width: non_neg_integer | :infinity } + @typedoc """ + Options for building an `Inspect.Opts` struct with `new/1`. + """ + @type new_opt :: + {:base, :decimal | :binary | :hex | :octal} + | {:binaries, :infer | :as_binaries | :as_strings} + | {:charlists, :infer | :as_lists | :as_charlists} + | {:custom_options, keyword} + | {:inspect_fun, (any, t -> Inspect.Algebra.t())} + | {:limit, non_neg_integer | :infinity} + | {:pretty, boolean} + | {:printable_limit, non_neg_integer | :infinity} + | {:safe, boolean} + | {:structs, boolean} + | {:syntax_colors, [{color_key, IO.ANSI.ansidata()}]} + | {:width, non_neg_integer | :infinity} + @doc """ Builds an `Inspect.Opts` struct. """ @doc since: "1.13.0" - @spec new(keyword()) :: t + @spec new([new_opt()]) :: t def new(opts) do struct(%Inspect.Opts{inspect_fun: default_inspect_fun()}, opts) end @@ -324,6 +341,14 @@ defmodule Inspect.Algebra do quote do: {:doc_color, unquote(doc), unquote(color)} end + @typedoc """ + Options for container documents. + """ + @type container_opts :: [ + separator: String.t(), + break: :strict | :flex | :maybe + ] + @docs [ :doc_break, :doc_collapse, @@ -440,7 +465,14 @@ defmodule Inspect.Algebra do updated options from inspection. """ @doc since: "1.6.0" - @spec container_doc(t, [term], t, Inspect.Opts.t(), (term, Inspect.Opts.t() -> t), keyword()) :: + @spec container_doc( + t, + [term], + t, + Inspect.Opts.t(), + (term, Inspect.Opts.t() -> t), + container_opts() + ) :: t def container_doc(left, collection, right, inspect_opts, fun, opts \\ []) do container_doc_with_opts(left, collection, right, inspect_opts, fun, opts) |> elem(0) @@ -496,7 +528,7 @@ defmodule Inspect.Algebra do t, Inspect.Opts.t(), (term, Inspect.Opts.t() -> t), - keyword() + container_opts() ) :: {t, Inspect.Opts.t()} def container_doc_with_opts(left, collection, right, inspect_opts, fun, opts \\ []) diff --git a/lib/elixir/lib/io.ex b/lib/elixir/lib/io.ex index e5c012ffd35..123441bd279 100644 --- a/lib/elixir/lib/io.ex +++ b/lib/elixir/lib/io.ex @@ -128,6 +128,22 @@ defmodule IO do @type nodata :: {:error, term} | :eof @type chardata :: String.t() | maybe_improper_list(char | chardata, String.t() | []) + @type inspect_opts :: [Inspect.Opts.new_opt() | {:label, term}] + + @typedoc """ + Stacktrace information as keyword options for `warn/2`. + + At least `:file` is required. Other options are optional and used + to provide more precise location information. + """ + @type warn_stacktrace_opts :: [ + file: String.t(), + line: pos_integer(), + column: pos_integer(), + module: module(), + function: {atom(), arity()} + ] + defguardp is_device(term) when is_atom(term) or is_pid(term) defguardp is_iodata(data) when is_list(data) or is_binary(data) @@ -346,7 +362,10 @@ defmodule IO do #=> my_app.ex:4: MyApp.main/1 """ - @spec warn(chardata | String.Chars.t(), Exception.stacktrace() | keyword() | Macro.Env.t()) :: + @spec warn( + chardata | String.Chars.t(), + Exception.stacktrace() | warn_stacktrace_opts() | Macro.Env.t() + ) :: :ok def warn(message, stacktrace_info) @@ -476,7 +495,7 @@ defmodule IO do after: [2, 4, 6] """ - @spec inspect(item, keyword) :: item when item: var + @spec inspect(item, inspect_opts) :: item when item: var def inspect(item, opts \\ []) do inspect(:stdio, item, opts) end @@ -486,7 +505,7 @@ defmodule IO do See `inspect/2` for a full list of options. """ - @spec inspect(device, item, keyword) :: item when item: var + @spec inspect(device, item, inspect_opts) :: item when item: var def inspect(device, item, opts) when is_device(device) and is_list(opts) do label = if label = opts[:label], do: [to_chardata(label), ": "], else: [] opts = Inspect.Opts.new(opts) diff --git a/lib/elixir/lib/io/ansi/docs.ex b/lib/elixir/lib/io/ansi/docs.ex index f47d5365415..0291c62dfda 100644 --- a/lib/elixir/lib/io/ansi/docs.ex +++ b/lib/elixir/lib/io/ansi/docs.ex @@ -5,6 +5,20 @@ defmodule IO.ANSI.Docs do @moduledoc false + @type print_opts :: [ + enabled: boolean(), + doc_bold: [IO.ANSI.ansicode()], + doc_code: [IO.ANSI.ansicode()], + doc_headings: [IO.ANSI.ansicode()], + doc_metadata: [IO.ANSI.ansicode()], + doc_quote: [IO.ANSI.ansicode()], + doc_inline_code: [IO.ANSI.ansicode()], + doc_table_heading: [IO.ANSI.ansicode()], + doc_title: [IO.ANSI.ansicode()], + doc_underline: [IO.ANSI.ansicode()], + width: pos_integer() + ] + @bullet_text_unicode "• " @bullet_text_ascii "* " @bullets [?*, ?-, ?+] @@ -52,7 +66,7 @@ defmodule IO.ANSI.Docs do See `default_options/0` for docs on the supported options. """ - @spec print_headings([String.t()], keyword) :: :ok + @spec print_headings([String.t()], print_opts) :: :ok def print_headings(headings, options \\ []) do # It's possible for some of the headings to contain newline characters (`\n`), so in order to prevent it from # breaking the output from `print_headings/2`, as `print_headings/2` tries to pad the whole heading, we first split @@ -77,7 +91,7 @@ defmodule IO.ANSI.Docs do See `default_options/0` for docs on the supported options. """ - @spec print_metadata(map, keyword) :: :ok + @spec print_metadata(map, print_opts) :: :ok def print_metadata(metadata, options \\ []) when is_map(metadata) do options = Keyword.merge(default_options(), options) print_each_metadata(metadata, options) && IO.write("\n") @@ -115,7 +129,7 @@ defmodule IO.ANSI.Docs do It takes a set of `options` defined in `default_options/0`. """ - @spec print(term(), String.t(), keyword) :: :ok + @spec print(term(), String.t(), print_opts) :: :ok def print(doc, format, options \\ []) def print(doc, "text/markdown", options) when is_binary(doc) and is_list(options) do diff --git a/lib/elixir/lib/json.ex b/lib/elixir/lib/json.ex index 8b143965f9e..6f431d616a9 100644 --- a/lib/elixir/lib/json.ex +++ b/lib/elixir/lib/json.ex @@ -328,6 +328,22 @@ defmodule JSON do | {:invalid_byte, non_neg_integer(), byte()} | {:unexpected_sequence, non_neg_integer(), binary()} + @typedoc """ + Decoders for customizing JSON decoding behavior. + """ + @type decoders :: [ + array_start: (term() -> term()), + array_push: (term(), term() -> term()), + array_finish: (term(), term() -> {term(), term()}), + object_start: (term() -> term()), + object_push: (term(), term(), term() -> term()), + object_finish: (term(), term() -> {term(), term()}), + float: (String.t() -> term()), + integer: (String.t() -> term()), + string: (String.t() -> term()), + null: term() + ] + @doc ~S""" Decodes the given JSON. @@ -381,7 +397,7 @@ defmodule JSON do For streaming decoding, see Erlang's [`:json`](`:json`) module. """ - @spec decode(binary(), term(), keyword()) :: + @spec decode(binary(), term(), decoders()) :: {term(), term(), binary()} | {:error, decode_error_reason()} def decode(binary, acc, decoders) when is_binary(binary) and is_list(decoders) do decoders = Keyword.put_new(decoders, :null, nil) diff --git a/lib/elixir/lib/kernel.ex b/lib/elixir/lib/kernel.ex index d14399fd499..0fa9ecb94d5 100644 --- a/lib/elixir/lib/kernel.ex +++ b/lib/elixir/lib/kernel.ex @@ -2456,7 +2456,7 @@ defmodule Kernel do See the "Deriving" section of the documentation of the `Inspect` protocol for more information. """ - @spec inspect(Inspect.t(), keyword) :: String.t() + @spec inspect(Inspect.t(), [Inspect.Opts.new_opt()]) :: String.t() def inspect(term, opts \\ []) when is_list(opts) do opts = Inspect.Opts.new(opts) diff --git a/lib/elixir/lib/kernel/parallel_compiler.ex b/lib/elixir/lib/kernel/parallel_compiler.ex index 24f36f824a7..dca630d9f4c 100644 --- a/lib/elixir/lib/kernel/parallel_compiler.ex +++ b/lib/elixir/lib/kernel/parallel_compiler.ex @@ -16,6 +16,38 @@ defmodule Kernel.ParallelCompiler do @type warning() :: {file :: Path.t(), Code.position(), message :: String.t()} @type error() :: {file :: Path.t(), Code.position(), message :: String.t()} + @typedoc """ + Options for parallel compilation functions. + """ + @type compile_opts :: [ + after_compile: (-> term()), + each_file: (Path.t() -> term()), + each_long_compilation: (Path.t() -> term()) | (Path.t(), pid() -> term()), + each_long_verification: (module() -> term()) | (module(), pid() -> term()), + each_module: (Path.t(), module(), binary() -> term()), + each_cycle: ([module()], [Code.diagnostic(:warning)] -> + {:compile, [module()], [Code.diagnostic(:warning)]} + | {:runtime, [module()], [Code.diagnostic(:warning)]}), + long_compilation_threshold: pos_integer(), + long_verification_threshold: pos_integer(), + verification: boolean(), + profile: :time, + dest: Path.t(), + beam_timestamp: term(), + return_diagnostics: boolean(), + max_concurrency: pos_integer() + ] + + @typedoc """ + Options for requiring files in parallel. + """ + @type require_opts :: [ + each_file: (Path.t() -> term()), + each_module: (Path.t(), module(), binary() -> term()), + max_concurrency: pos_integer(), + return_diagnostics: boolean() + ] + @doc """ Starts a task for parallel compilation. """ @@ -185,7 +217,7 @@ defmodule Kernel.ParallelCompiler do """ @doc since: "1.6.0" - @spec compile([Path.t()], keyword()) :: + @spec compile([Path.t()], compile_opts()) :: {:ok, [atom], [warning] | info()} | {:error, [error] | [Code.diagnostic(:error)], [warning] | info()} def compile(files, options \\ []) when is_list(options) do @@ -198,7 +230,7 @@ defmodule Kernel.ParallelCompiler do See `compile/2` for more information. """ @doc since: "1.6.0" - @spec compile_to_path([Path.t()], Path.t(), keyword()) :: + @spec compile_to_path([Path.t()], Path.t(), compile_opts()) :: {:ok, [atom], [warning] | info()} | {:error, [error] | [Code.diagnostic(:error)], [warning] | info()} def compile_to_path(files, path, options \\ []) when is_binary(path) and is_list(options) do @@ -233,9 +265,12 @@ defmodule Kernel.ParallelCompiler do Setting this option to 1 will compile files sequentially. Defaults to the number of schedulers online, or at least 2. + * `:return_diagnostics` - when `true`, returns structured diagnostics + as maps instead of the legacy format. Defaults to `false`. + """ @doc since: "1.6.0" - @spec require([Path.t()], keyword()) :: + @spec require([Path.t()], require_opts()) :: {:ok, [atom], [warning] | info()} | {:error, [error] | [Code.diagnostic(:error)], [warning] | info()} def require(files, options \\ []) when is_list(options) do diff --git a/lib/elixir/lib/macro.ex b/lib/elixir/lib/macro.ex index 30cce303e71..5b9ce6e4f3b 100644 --- a/lib/elixir/lib/macro.ex +++ b/lib/elixir/lib/macro.ex @@ -197,6 +197,15 @@ defmodule Macro do @typedoc "A captured remote function in the format of &Mod.fun/arity" @type captured_remote_function :: fun + @type escape_opts :: [ + unquote: boolean(), + prune_metadata: boolean() + ] + + @type inspect_atom_opts :: [ + escape: (binary(), char() -> binary()) + ] + @doc """ Breaks a pipeline expression into a list. @@ -835,7 +844,7 @@ defmodule Macro do bound), while `quote/2` produces syntax trees for expressions. """ - @spec escape(term, keyword) :: t() + @spec escape(term, escape_opts) :: t() def escape(expr, opts \\ []) do unquote = Keyword.get(opts, :unquote, false) kind = if Keyword.get(opts, :prune_metadata, false), do: :prune_metadata, else: :none @@ -2399,7 +2408,7 @@ defmodule Macro do """ @doc since: "1.14.0" - @spec inspect_atom(:literal | :key | :remote_call, atom, keyword) :: binary + @spec inspect_atom(:literal | :key | :remote_call, atom, inspect_atom_opts) :: binary def inspect_atom(source_format, atom, opts \\ []) def inspect_atom(:literal, atom, _opts) when is_nil(atom) or is_boolean(atom) do diff --git a/lib/elixir/lib/macro/env.ex b/lib/elixir/lib/macro/env.ex index f48ca1a31bd..8e0016431bb 100644 --- a/lib/elixir/lib/macro/env.ex +++ b/lib/elixir/lib/macro/env.ex @@ -70,6 +70,42 @@ defmodule Macro.Env do @typep tracers :: [module] @typep versioned_vars :: %{optional(variable) => var_version :: non_neg_integer} + @type define_import_opts :: [ + trace: boolean(), + emit_warnings: boolean(), + info_callback: (atom() -> [{atom(), arity()}]), + only: :functions | :macros | [{atom(), arity()}], + except: [{atom(), arity()}], + warn: boolean() + ] + + @type define_alias_opts :: [ + trace: boolean(), + as: atom(), + warn: boolean() + ] + + @type define_require_opts :: [ + trace: boolean(), + as: atom(), + warn: boolean() + ] + + @type expand_alias_opts :: [ + trace: boolean() + ] + + @type expand_import_opts :: [ + allow_locals: boolean(), + check_deprecations: boolean(), + trace: boolean() + ] + + @type expand_require_opts :: [ + check_deprecations: boolean(), + trace: boolean() + ] + @type t :: %{ __struct__: __MODULE__, aliases: aliases, @@ -331,7 +367,7 @@ defmodule Macro.Env do """ @doc since: "1.17.0" - @spec define_require(t, Macro.metadata(), module) :: {:ok, t} + @spec define_require(t, Macro.metadata(), module, define_require_opts) :: {:ok, t} def define_require(env, meta, module, opts \\ []) when is_list(meta) and is_atom(module) and is_list(opts) do {trace, opts} = Keyword.pop(opts, :trace, true) @@ -391,7 +427,8 @@ defmodule Macro.Env do """ @doc since: "1.17.0" - @spec define_import(t, Macro.metadata(), module, keyword) :: {:ok, t} | {:error, String.t()} + @spec define_import(t, Macro.metadata(), module, define_import_opts) :: + {:ok, t} | {:error, String.t()} def define_import(env, meta, module, opts \\ []) when is_list(meta) and is_atom(module) and is_list(opts) do {trace, opts} = Keyword.pop(opts, :trace, true) @@ -441,7 +478,8 @@ defmodule Macro.Env do """ @doc since: "1.17.0" - @spec define_alias(t, Macro.metadata(), module, keyword) :: {:ok, t} | {:error, String.t()} + @spec define_alias(t, Macro.metadata(), module, define_alias_opts) :: + {:ok, t} | {:error, String.t()} def define_alias(env, meta, module, opts \\ []) when is_list(meta) and is_atom(module) and is_list(opts) do {trace, opts} = Keyword.pop(opts, :trace, true) @@ -487,7 +525,7 @@ defmodule Macro.Env do """ @doc since: "1.17.0" - @spec expand_alias(t, keyword, [atom()], keyword) :: + @spec expand_alias(t, keyword, [atom()], expand_alias_opts) :: {:alias, atom()} | :error def expand_alias(env, meta, list, opts \\ []) when is_list(meta) and is_list(list) and is_list(opts) do @@ -527,7 +565,7 @@ defmodule Macro.Env do """ @doc since: "1.17.0" - @spec expand_import(t, keyword, atom(), arity(), keyword) :: + @spec expand_import(t, keyword, atom(), arity(), expand_import_opts) :: {:macro, module(), (Macro.metadata(), args :: [Macro.t()] -> Macro.t())} | {:function, module(), atom()} | {:error, :not_found | {:conflict, module()} | {:ambiguous, [module()]}} @@ -583,7 +621,7 @@ defmodule Macro.Env do """ @doc since: "1.17.0" - @spec expand_require(t, keyword, module(), atom(), arity(), keyword) :: + @spec expand_require(t, keyword, module(), atom(), arity(), expand_require_opts) :: {:macro, module(), (Macro.metadata(), args :: [Macro.t()] -> Macro.t())} | :error def expand_require(env, meta, module, name, arity, opts \\ []) diff --git a/lib/elixir/lib/module.ex b/lib/elixir/lib/module.ex index 067668ddc70..8f8549b8649 100644 --- a/lib/elixir/lib/module.ex +++ b/lib/elixir/lib/module.ex @@ -687,6 +687,16 @@ defmodule Module do @type definition :: {atom, arity} @type def_kind :: :def | :defp | :defmacro | :defmacrop + @type create_opts :: [ + file: binary(), + line: pos_integer(), + generated: boolean() + ] + + @type get_definition_opts :: [ + skip_clauses: boolean() + ] + @extra_error_msg_defines? "Use Kernel.function_exported?/3 and Kernel.macro_exported?/3 " <> "to check for public functions and macros instead" @@ -918,7 +928,7 @@ defmodule Module do when defining the module, while `Kernel.defmodule/2` automatically uses the environment it is invoked at. """ - @spec create(module, Macro.t(), Macro.Env.t() | keyword) :: {:module, module, binary, term} + @spec create(module, Macro.t(), Macro.Env.t() | create_opts) :: {:module, module, binary, term} def create(module, quoted, opts) def create(module, quoted, %Macro.Env{} = env) when is_atom(module) do @@ -1423,7 +1433,7 @@ defmodule Module do only an interest in fetching the kind and the metadata """ - @spec get_definition(module, definition, keyword) :: + @spec get_definition(module, definition, get_definition_opts) :: {:v1, def_kind, meta :: keyword, [{meta :: keyword, arguments :: [Macro.t()], guards :: [Macro.t()], Macro.t()}]} | nil diff --git a/lib/elixir/lib/module/parallel_checker.ex b/lib/elixir/lib/module/parallel_checker.ex index 5841a8b6718..2ffdbf69490 100644 --- a/lib/elixir/lib/module/parallel_checker.ex +++ b/lib/elixir/lib/module/parallel_checker.ex @@ -11,9 +11,20 @@ defmodule Module.ParallelChecker do @type warning() :: term() @type mode() :: :erlang | :elixir | :protocol + @typedoc """ + Options for `start_link/1`. + """ + @type start_link_opts :: [ + {:max_concurrency, pos_integer()} + | {:long_verification_threshold, pos_integer()} + | {:each_long_verification, (module() -> term()) | (module(), pid() -> term())} + | {atom(), term()} + ] + @doc """ Initializes the parallel checker process. """ + @spec start_link(start_link_opts()) :: {:ok, cache()} def start_link(opts \\ []) do :proc_lib.start_link(__MODULE__, :init, [opts]) end diff --git a/lib/elixir/lib/module/types/descr.ex b/lib/elixir/lib/module/types/descr.ex index be375e4d83e..e6d36431136 100644 --- a/lib/elixir/lib/module/types/descr.ex +++ b/lib/elixir/lib/module/types/descr.ex @@ -51,6 +51,13 @@ defmodule Module.Types.Descr do # Type definitions + @typedoc """ + Options for `to_quoted/2`. + """ + @type to_quoted_opts :: [ + collapse_structs: boolean() + ] + defguard is_descr(descr) when is_map(descr) or descr == :term defp descr_key?(:term, _key), do: true @@ -543,6 +550,7 @@ defmodule Module.Types.Descr do * `:collapse_structs` - do not show struct fields that match their default type """ + @spec to_quoted(term(), to_quoted_opts) :: Macro.t() def to_quoted(descr, opts \\ []) do if term_type?(descr) do {:term, [], []} @@ -611,7 +619,13 @@ defmodule Module.Types.Descr do @doc """ Converts a descr to its quoted string representation. + + ## Options + + * `:collapse_structs` - do not show struct fields that match + their default type """ + @spec to_quoted(term(), to_quoted_opts) :: String.t() def to_quoted_string(descr, opts \\ []) do descr |> to_quoted(opts) diff --git a/lib/elixir/lib/module/types/helpers.ex b/lib/elixir/lib/module/types/helpers.ex index e652575a13b..8e879e409ab 100644 --- a/lib/elixir/lib/module/types/helpers.ex +++ b/lib/elixir/lib/module/types/helpers.ex @@ -6,6 +6,13 @@ defmodule Module.Types.Helpers do # AST and enumeration helpers. @moduledoc false + @typedoc """ + Options for `expr_to_string/2`. + """ + @type expr_to_string_opts :: [ + collapse_structs: boolean() + ] + ## AST helpers @doc """ @@ -272,6 +279,7 @@ defmodule Module.Types.Helpers do We also undo some macro expressions done by the Kernel module. """ + @spec expr_to_string(Macro.t(), expr_to_string_opts) :: String.t() def expr_to_string(expr, opts \\ []) do string = prewalk_expr_to_string(expr, opts) diff --git a/lib/elixir/lib/path.ex b/lib/elixir/lib/path.ex index cf7b3ae75ab..46461f116c2 100644 --- a/lib/elixir/lib/path.ex +++ b/lib/elixir/lib/path.ex @@ -22,6 +22,8 @@ defmodule Path do """ @type t :: IO.chardata() + @type relative_to_opts :: [force: boolean()] + @doc """ Converts the given path to an absolute one. @@ -401,7 +403,7 @@ defmodule Path do Path.relative_to("../foo", "/usr/local") #=> "../foo" """ - @spec relative_to(t, t, keyword) :: binary + @spec relative_to(t, t, relative_to_opts) :: binary def relative_to(path, cwd, opts \\ []) when is_list(opts) do os_type = major_os_type() split_path = split(path) @@ -479,7 +481,7 @@ defmodule Path do Check `relative_to/3` for the supported options. """ - @spec relative_to_cwd(t, keyword) :: binary + @spec relative_to_cwd(t, relative_to_opts) :: binary def relative_to_cwd(path, opts \\ []) when is_list(opts) do case :file.get_cwd() do {:ok, base} -> relative_to(path, IO.chardata_to_string(base), opts) @@ -801,7 +803,7 @@ defmodule Path do Path.wildcard("projects/*/ebin/**/*.{beam,app}") """ - @spec wildcard(t, keyword) :: [binary] + @spec wildcard(t, match_dot: boolean()) :: [binary] def wildcard(glob, opts \\ []) when is_list(opts) do mod = if Keyword.get(opts, :match_dot), do: :file, else: Path.Wildcard diff --git a/lib/elixir/lib/record.ex b/lib/elixir/lib/record.ex index 2b1ce2f3559..15d378e27b2 100644 --- a/lib/elixir/lib/record.ex +++ b/lib/elixir/lib/record.ex @@ -45,6 +45,13 @@ defmodule Record do a module by calling `Code.fetch_docs/1`. """ + @type extract_opts :: [ + from: binary(), + from_lib: binary(), + includes: [binary()], + macros: keyword() + ] + @doc """ Extracts record information from an Erlang file. @@ -102,7 +109,7 @@ defmodule Record do ] """ - @spec extract(name :: atom, keyword) :: keyword + @spec extract(name :: atom, extract_opts) :: keyword def extract(name, opts) when is_atom(name) and is_list(opts) do Record.Extractor.extract(name, opts) end @@ -119,7 +126,7 @@ defmodule Record do Accepts the same options as listed for `Record.extract/2`. """ - @spec extract_all(keyword) :: [{name :: atom, keyword}] + @spec extract_all(extract_opts) :: [{name :: atom, keyword}] def extract_all(opts) when is_list(opts) do Record.Extractor.extract_all(opts) end diff --git a/lib/elixir/lib/regex.ex b/lib/elixir/lib/regex.ex index 2327f43ed99..a94f32057e6 100644 --- a/lib/elixir/lib/regex.ex +++ b/lib/elixir/lib/regex.ex @@ -164,6 +164,11 @@ defmodule Regex do @type t :: %__MODULE__{re_pattern: term, source: binary, opts: [term]} + @type named_captures_opts :: [ + return: :binary | :index, + offset: non_neg_integer() + ] + defmodule CompileError do @moduledoc """ An exception raised when a regular expression could not be compiled. @@ -321,7 +326,7 @@ defmodule Regex do ["d", ""] """ - @spec run(t, binary, [term]) :: nil | [binary] | [{integer, integer}] + @spec run(t, binary, capture_opts) :: nil | [binary] | [{integer, integer}] def run(regex, string, options \\ []) def run(%Regex{} = regex, string, options) when is_binary(string) do @@ -343,6 +348,8 @@ defmodule Regex do * `:return` - when set to `:index`, returns byte index and match length. Defaults to `:binary`. + * `:offset` - (since v1.12.0) specifies the starting offset to match in the given string. + Defaults to zero. ## Examples @@ -363,7 +370,7 @@ defmodule Regex do You can then use `binary_part/3` to fetch the relevant part from the given string. """ - @spec named_captures(t, String.t(), keyword) :: map | nil + @spec named_captures(t, String.t(), named_captures_opts) :: map | nil def named_captures(regex, string, options \\ []) when is_binary(string) do names = names(regex) options = Keyword.put(options, :capture, names) @@ -545,7 +552,7 @@ defmodule Regex do [["cd"], ["ce"]] """ - @spec scan(t(), String.t(), [term()]) :: [[String.t()]] | [[{integer(), integer()}]] + @spec scan(t(), String.t(), capture_opts) :: [[String.t()]] | [[{integer(), integer()}]] def scan(regex, string, options \\ []) def scan(%Regex{} = regex, string, options) when is_binary(string) do @@ -573,6 +580,25 @@ defmodule Regex do end end + @typedoc """ + Options for regex functions that capture matches. + """ + @type capture_opts :: [ + return: :binary | :index, + capture: :all | :first | :all_but_first | :none | :all_names | [binary() | atom()], + offset: non_neg_integer() + ] + + @typedoc """ + Options for `split/3`. + """ + @type split_opts :: [ + parts: pos_integer() | :infinity, + trim: boolean(), + on: :first | :all | :all_but_first | :none | :all_names | [atom() | integer()], + include_captures: boolean() + ] + @doc """ Splits the given target based on the given pattern and in the given number of parts. @@ -626,7 +652,7 @@ defmodule Regex do ["a", "b", "c"] """ - @spec split(t, String.t(), [term]) :: [String.t()] + @spec split(t, String.t(), split_opts) :: [String.t()] def split(regex, string, options \\ []) def split(%Regex{}, "", opts) do diff --git a/lib/elixir/lib/registry.ex b/lib/elixir/lib/registry.ex index 17370964701..d35f82bc12a 100644 --- a/lib/elixir/lib/registry.ex +++ b/lib/elixir/lib/registry.ex @@ -242,6 +242,11 @@ defmodule Registry do {:register, registry, key, registry_partition :: pid, value} | {:unregister, registry, key, registry_partition :: pid} + @typedoc """ + Options used for `dispatch/4`. + """ + @type dispatch_opts :: [parallel: boolean()] + ## Via callbacks @doc false @@ -483,9 +488,15 @@ defmodule Registry do See the module documentation for examples of using the `dispatch/3` function for building custom dispatching or a pubsub system. + + ## Options + + * `:parallel` - if `true`, the dispatching is done in parallel + across all partitions. Defaults to `false`. + """ @doc since: "1.4.0" - @spec dispatch(registry, key, dispatcher, keyword) :: :ok + @spec dispatch(registry, key, dispatcher, dispatch_opts) :: :ok when dispatcher: (entries :: [{pid, value}] -> term) | {module(), atom(), [term()]} def dispatch(registry, key, mfa_or_fun, opts \\ []) when is_atom(registry) and is_function(mfa_or_fun, 1) diff --git a/lib/elixir/lib/string.ex b/lib/elixir/lib/string.ex index 626e7d259b3..73d59f31901 100644 --- a/lib/elixir/lib/string.ex +++ b/lib/elixir/lib/string.ex @@ -298,6 +298,15 @@ defmodule String do | [nonempty_binary] | (compiled_search_pattern :: :binary.cp()) + @type split_opts :: [ + parts: pos_integer() | :infinity, + trim: boolean() + ] + + @type splitter_opts :: [trim: boolean()] + + @type replace_opts :: [global: boolean()] + @conditional_mappings [:greek, :turkic] @doc """ @@ -502,7 +511,8 @@ defmodule String do ["a", "b", " c "] """ - @spec split(t, pattern | Regex.t(), keyword) :: [t] + @spec split(t, pattern, split_opts()) :: [t] + @spec split(t, Regex.t(), Regex.split_opts()) :: [t] def split(string, pattern, options \\ []) def split(string, %Regex{} = pattern, options) when is_binary(string) and is_list(options) do @@ -607,7 +617,7 @@ defmodule String do ["1", "2", "3", "4"] """ - @spec splitter(t, pattern, keyword) :: Enumerable.t() + @spec splitter(t, pattern, splitter_opts) :: Enumerable.t() def splitter(string, pattern, options \\ []) def splitter(string, "", options) when is_binary(string) and is_list(options) do @@ -1616,7 +1626,7 @@ defmodule String do "é" """ - @spec replace(t, pattern | Regex.t(), t | (t -> t | iodata), keyword) :: t + @spec replace(t, pattern | Regex.t(), t | (t -> t | iodata), replace_opts) :: t def replace(subject, pattern, replacement, options \\ []) when is_binary(subject) and (is_binary(replacement) or is_function(replacement, 1)) and diff --git a/lib/elixir/lib/string_io.ex b/lib/elixir/lib/string_io.ex index 550633a7af8..d52ff1c238b 100644 --- a/lib/elixir/lib/string_io.ex +++ b/lib/elixir/lib/string_io.ex @@ -17,6 +17,11 @@ defmodule StringIO do """ + @type open_opts :: [ + capture_prompt: boolean(), + encoding: :unicode | :latin1 + ] + # We're implementing the GenServer behaviour instead of using the # `use GenServer` macro, because we don't want the `child_spec/1` # function as it doesn't make sense to be started under a supervisor. @@ -59,7 +64,7 @@ defmodule StringIO do """ @doc since: "1.7.0" - @spec open(binary, keyword, (pid -> res)) :: {:ok, res} when res: var + @spec open(binary, open_opts, (pid -> res)) :: {:ok, res} when res: var def open(string, options, function) when is_binary(string) and is_list(options) and is_function(function, 1) do {:ok, pid} = GenServer.start(__MODULE__, {self(), string, options}, []) @@ -83,7 +88,8 @@ defmodule StringIO do If options are provided, the result will be `{:ok, pid}`, returning the IO device created. The option `:capture_prompt`, when set to `true`, causes prompts (which are specified as arguments to `IO.get*` functions) to be - included in the device's output. + included in the device's output. See `options/3` for the list of supported + options. If a function is provided, the device will be created and sent to the function. When the function returns, the device will be closed. The final @@ -111,7 +117,7 @@ defmodule StringIO do {:ok, {"", "The input was foo"}} """ - @spec open(binary, keyword) :: {:ok, pid} + @spec open(binary, open_opts) :: {:ok, pid} @spec open(binary, (pid -> res)) :: {:ok, res} when res: var def open(string, options_or_function \\ []) diff --git a/lib/elixir/lib/supervisor.ex b/lib/elixir/lib/supervisor.ex index 6829ee90c21..a3cb0318545 100644 --- a/lib/elixir/lib/supervisor.ex +++ b/lib/elixir/lib/supervisor.ex @@ -659,6 +659,19 @@ defmodule Supervisor do @typedoc since: "1.16.0" @type module_spec :: {module(), args :: term()} | module() + @typedoc """ + Options for overriding child specification fields. + """ + @type child_spec_overrides :: [ + id: atom() | term(), + start: {module(), atom(), [term()]}, + restart: restart(), + shutdown: shutdown(), + type: type(), + modules: [module()] | :dynamic, + significant: boolean() + ] + @doc """ Starts a supervisor with the given children. @@ -896,7 +909,7 @@ defmodule Supervisor do #=> start: {Agent, :start_link, [fn -> :ok end]}} """ - @spec child_spec(child_spec() | module_spec(), keyword()) :: child_spec() + @spec child_spec(child_spec() | module_spec(), child_spec_overrides()) :: child_spec() def child_spec(module_or_map, overrides) def child_spec({_, _, _, _, _, _} = tuple, _overrides) do diff --git a/lib/elixir/lib/system.ex b/lib/elixir/lib/system.ex index d2881e5b0b5..13d7e93298f 100644 --- a/lib/elixir/lib/system.ex +++ b/lib/elixir/lib/system.ex @@ -113,6 +113,28 @@ defmodule System do | :sigusr1 | :sigusr2 + @type cmd_opts :: [ + into: Collectable.t(), + lines: pos_integer(), + cd: Path.t(), + env: [{binary(), binary() | nil}], + arg0: binary(), + stderr_to_stdout: boolean(), + use_stdio: boolean(), + parallelism: boolean() + ] + + @type shell_opts :: [ + into: Collectable.t(), + lines: pos_integer(), + cd: Path.t(), + env: [{binary(), binary() | nil}], + stderr_to_stdout: boolean(), + use_stdio: boolean(), + parallelism: boolean(), + close_stdin: boolean() + ] + @vm_signals [:sigquit, :sigterm, :sigusr1] @os_signals [:sighup, :sigabrt, :sigalrm, :sigusr2, :sigchld, :sigstop, :sigtstp] @signals @vm_signals ++ @os_signals @@ -941,7 +963,7 @@ defmodule System do immediately terminate. Defaults to false. """ @doc since: "1.12.0" - @spec shell(binary, keyword) :: {Collectable.t(), exit_status :: non_neg_integer} + @spec shell(binary, shell_opts) :: {Collectable.t(), exit_status :: non_neg_integer} def shell(command, opts \\ []) when is_binary(command) do command |> String.trim() |> do_shell(opts) end @@ -1101,7 +1123,7 @@ defmodule System do If you desire to execute a trusted command inside a shell, with pipes, redirecting and so on, please check `shell/2`. """ - @spec cmd(binary, [binary], keyword) :: {Collectable.t(), exit_status :: non_neg_integer} + @spec cmd(binary, [binary], cmd_opts) :: {Collectable.t(), exit_status :: non_neg_integer} def cmd(command, args, opts \\ []) when is_binary(command) and is_list(args) do assert_no_null_byte!(command, "System.cmd/3") diff --git a/lib/elixir/lib/task/supervisor.ex b/lib/elixir/lib/task/supervisor.ex index d8b3f5ac80a..59b63f680e0 100644 --- a/lib/elixir/lib/task/supervisor.ex +++ b/lib/elixir/lib/task/supervisor.ex @@ -87,6 +87,18 @@ defmodule Task.Supervisor do @typedoc since: "1.17.0" @type async_stream_option :: Task.async_stream_option() | {:shutdown, Supervisor.shutdown()} + @typedoc """ + Options for `async/3`, `async/5`, `async_nolink/3`, and `async_nolink/5` functions. + """ + @type async_opts :: [ + shutdown: :brutal_kill | timeout() + ] + + @type start_child_opts :: [ + restart: :temporary | :transient | :permanent, + shutdown: :brutal_kill | timeout() + ] + @doc false def child_spec(opts) when is_list(opts) do id = @@ -175,7 +187,7 @@ defmodule Task.Supervisor do The tasks must trap exits for the timeout to have an effect. """ - @spec async(Supervisor.supervisor(), (-> any), Keyword.t()) :: Task.t() + @spec async(Supervisor.supervisor(), (-> any), async_opts) :: Task.t() def async(supervisor, fun, options \\ []) do async(supervisor, :erlang, :apply, [fun, []], options) end @@ -197,7 +209,7 @@ defmodule Task.Supervisor do The tasks must trap exits for the timeout to have an effect. """ - @spec async(Supervisor.supervisor(), module, atom, [term], Keyword.t()) :: Task.t() + @spec async(Supervisor.supervisor(), module, atom, [term], async_opts) :: Task.t() def async(supervisor, module, fun, args, options \\ []) do async(supervisor, :link, module, fun, args, options) end @@ -284,7 +296,7 @@ defmodule Task.Supervisor do end """ - @spec async_nolink(Supervisor.supervisor(), (-> any), Keyword.t()) :: Task.t() + @spec async_nolink(Supervisor.supervisor(), (-> any), async_opts) :: Task.t() def async_nolink(supervisor, fun, options \\ []) do async_nolink(supervisor, :erlang, :apply, [fun, []], options) end @@ -303,7 +315,7 @@ defmodule Task.Supervisor do as the `:restart` option (the default), as `async_nolink/5` keeps a direct reference to the task which is lost if the task is restarted. """ - @spec async_nolink(Supervisor.supervisor(), module, atom, [term], Keyword.t()) :: Task.t() + @spec async_nolink(Supervisor.supervisor(), module, atom, [term], async_opts) :: Task.t() def async_nolink(supervisor, module, fun, args, options \\ []) do async(supervisor, :nolink, module, fun, args, options) end @@ -523,7 +535,7 @@ defmodule Task.Supervisor do The task must trap exits for the timeout to have an effect. """ - @spec start_child(Supervisor.supervisor(), (-> any), keyword) :: + @spec start_child(Supervisor.supervisor(), (-> any), start_child_opts) :: DynamicSupervisor.on_start_child() def start_child(supervisor, fun, options \\ []) do restart = options[:restart] @@ -538,7 +550,7 @@ defmodule Task.Supervisor do Similar to `start_child/3` except the task is specified by the given `module`, `fun` and `args`. """ - @spec start_child(Supervisor.supervisor(), module, atom, [term], keyword) :: + @spec start_child(Supervisor.supervisor(), module, atom, [term], start_child_opts) :: DynamicSupervisor.on_start_child() def start_child(supervisor, module, fun, args, options \\ []) when is_atom(fun) and is_list(args) do diff --git a/lib/elixir/lib/version.ex b/lib/elixir/lib/version.ex index 8d98380cee1..f96420bffef 100644 --- a/lib/elixir/lib/version.ex +++ b/lib/elixir/lib/version.ex @@ -118,6 +118,8 @@ defmodule Version do @type build :: String.t() | nil @type t :: %__MODULE__{major: major, minor: minor, patch: patch, pre: pre, build: build} + @type match_opts :: [allow_pre: boolean()] + defmodule Requirement do @moduledoc """ A struct that holds version requirement information. @@ -296,7 +298,7 @@ defmodule Version do ** (Version.InvalidRequirementError) invalid requirement: "== == 1.0.0" """ - @spec match?(version, requirement, keyword) :: boolean + @spec match?(version, requirement, match_opts) :: boolean def match?(version, requirement, opts \\ []) def match?(version, requirement, opts) when is_binary(requirement) do diff --git a/lib/ex_unit/lib/ex_unit.ex b/lib/ex_unit/lib/ex_unit.ex index bc2ea55fac1..4c1baecfef5 100644 --- a/lib/ex_unit/lib/ex_unit.ex +++ b/lib/ex_unit/lib/ex_unit.ex @@ -93,6 +93,50 @@ defmodule ExUnit do @type test_id :: {module, name :: atom} + @typedoc """ + Configuration options for ExUnit. + + See `configure/1` for detailed documentation of each option. + """ + @type configure_opts :: [ + {:assert_receive_timeout, non_neg_integer()} + | {:autorun, boolean()} + | {:capture_log, boolean() | [level: Logger.level()]} + | {:colors, + [ + enabled: boolean(), + success: atom(), + invalid: atom(), + skipped: atom(), + failure: atom(), + error_info: atom(), + extra_info: atom(), + location_info: [atom()], + diff_insert: atom(), + diff_insert_whitespace: IO.ANSI.ansidata(), + diff_delete: atom(), + diff_delete_whitespace: IO.ANSI.ansidata() + ]} + | {:exclude, keyword()} + | {:exit_status, non_neg_integer()} + | {:failures_manifest_path, String.t()} + | {:formatters, [module()]} + | {:include, keyword()} + | {:max_cases, pos_integer()} + | {:max_failures, pos_integer() | :infinity} + | {:only_test_ids, [test_id()]} + | {:rand_algorithm, atom()} + | {:refute_receive_timeout, non_neg_integer()} + | {:seed, non_neg_integer()} + | {:slowest, non_neg_integer()} + | {:slowest_modules, non_neg_integer()} + | {:stacktrace_depth, non_neg_integer()} + | {:timeout, pos_integer()} + | {:trace, boolean()} + | {:test_location_relative_path, String.t()} + | {atom(), term()} + ] + defmodule Test do @moduledoc """ A struct that keeps information about the test. @@ -218,7 +262,7 @@ defmodule ExUnit do If you want to run tests manually, you can set the `:autorun` option to `false` and use `run/0` to run tests. """ - @spec start(Keyword.t()) :: :ok + @spec start(configure_opts()) :: :ok def start(options \\ []) do {:ok, _} = Application.ensure_all_started(:ex_unit) @@ -357,7 +401,7 @@ defmodule ExUnit do and these options can then be used in places such as custom formatters. These other options will be ignored by ExUnit itself. """ - @spec configure(Keyword.t()) :: :ok + @spec configure(configure_opts()) :: :ok def configure(options) when is_list(options) do Enum.each(options, fn {k, v} -> Application.put_env(:ex_unit, k, v) diff --git a/lib/ex_unit/lib/ex_unit/callbacks.ex b/lib/ex_unit/lib/ex_unit/callbacks.ex index 622a02658cc..5fad529b552 100644 --- a/lib/ex_unit/lib/ex_unit/callbacks.ex +++ b/lib/ex_unit/lib/ex_unit/callbacks.ex @@ -169,6 +169,12 @@ defmodule ExUnit.Callbacks do end """ + @type child_spec_overrides :: [ + restart: :permanent | :transient | :temporary, + shutdown: :brutal_kill | timeout(), + type: :worker | :supervisor + ] + @doc false def __register__(module) do Module.put_attribute(module, :ex_unit_describe, nil) @@ -560,7 +566,7 @@ defmodule ExUnit.Callbacks do more about these keys in [the `Task` module](`Task#module-ancestor-and-caller-tracking`). """ @doc since: "1.5.0" - @spec start_supervised(Supervisor.child_spec() | module | {module, term}, keyword) :: + @spec start_supervised(Supervisor.child_spec() | module | {module, term}, child_spec_overrides) :: Supervisor.on_start_child() def start_supervised(child_spec_or_module, opts \\ []) do sup = @@ -581,7 +587,8 @@ defmodule ExUnit.Callbacks do not started properly. """ @doc since: "1.6.0" - @spec start_supervised!(Supervisor.child_spec() | module | {module, term}, keyword) :: pid + @spec start_supervised!(Supervisor.child_spec() | module | {module, term}, child_spec_overrides) :: + pid def start_supervised!(child_spec_or_module, opts \\ []) do case start_supervised(child_spec_or_module, opts) do {:ok, pid} -> @@ -627,7 +634,10 @@ defmodule ExUnit.Callbacks do > *by the test supervisor* in reverse order, ensuring graceful termination. """ @doc since: "1.14.0" - @spec start_link_supervised!(Supervisor.child_spec() | module | {module, term}, keyword) :: + @spec start_link_supervised!( + Supervisor.child_spec() | module | {module, term}, + child_spec_overrides + ) :: pid def start_link_supervised!(child_spec_or_module, opts \\ []) do pid = start_supervised!(child_spec_or_module, opts) diff --git a/lib/ex_unit/lib/ex_unit/capture_io.ex b/lib/ex_unit/lib/ex_unit/capture_io.ex index 30e437a43ff..f506b6a4bf5 100644 --- a/lib/ex_unit/lib/ex_unit/capture_io.ex +++ b/lib/ex_unit/lib/ex_unit/capture_io.ex @@ -28,6 +28,12 @@ defmodule ExUnit.CaptureIO do """ + @type capture_io_opts :: [ + input: String.t(), + capture_prompt: boolean(), + encoding: :unicode | :latin1 + ] + @doc """ Captures IO generated when evaluating `fun`. @@ -152,7 +158,7 @@ defmodule ExUnit.CaptureIO do See `capture_io/1` for more information. """ - @spec capture_io(atom() | pid() | String.t() | keyword(), (-> any())) :: String.t() + @spec capture_io(atom() | pid() | String.t() | capture_io_opts, (-> any())) :: String.t() def capture_io(device_pid_input_or_options, fun) def capture_io(device_or_pid, fun) @@ -172,7 +178,7 @@ defmodule ExUnit.CaptureIO do See `capture_io/1` for more information. """ - @spec capture_io(atom() | pid(), String.t() | keyword(), (-> any())) :: String.t() + @spec capture_io(atom() | pid(), String.t() | capture_io_opts, (-> any())) :: String.t() def capture_io(device_or_pid, input_or_options, fun) when (is_atom(device_or_pid) or is_pid(device_or_pid)) and (is_binary(input_or_options) or is_list(input_or_options)) and is_function(fun, 0) do @@ -209,7 +215,7 @@ defmodule ExUnit.CaptureIO do See `with_io/1` for more information. """ @doc since: "1.13.0" - @spec with_io(atom() | pid() | String.t() | keyword(), (-> any())) :: {any(), String.t()} + @spec with_io(atom() | pid() | String.t() | capture_io_opts, (-> any())) :: {any(), String.t()} def with_io(device_pid_input_or_options, fun) def with_io(device, fun) when is_atom(device) and is_function(fun, 0) do @@ -234,7 +240,7 @@ defmodule ExUnit.CaptureIO do See `with_io/1` for more information. """ @doc since: "1.13.0" - @spec with_io(atom() | pid(), String.t() | keyword(), (-> any())) :: {any(), String.t()} + @spec with_io(atom() | pid(), String.t() | capture_io_opts, (-> any())) :: {any(), String.t()} def with_io(device_or_pid, input_or_options, fun) def with_io(device, input, fun) diff --git a/lib/ex_unit/lib/ex_unit/capture_log.ex b/lib/ex_unit/lib/ex_unit/capture_log.ex index e885a10df91..e8178a9b7b4 100644 --- a/lib/ex_unit/lib/ex_unit/capture_log.ex +++ b/lib/ex_unit/lib/ex_unit/capture_log.ex @@ -43,6 +43,10 @@ defmodule ExUnit.CaptureLog do @compile {:no_warn_undefined, Logger} + @type capture_log_opts :: [ + level: Logger.level() | nil + ] + @doc """ Captures Logger messages generated when evaluating `fun`. @@ -74,7 +78,7 @@ defmodule ExUnit.CaptureLog do To get the result of the evaluation along with the captured log, use `with_log/2`. """ - @spec capture_log(keyword, (-> any)) :: String.t() + @spec capture_log(capture_log_opts, (-> any)) :: String.t() def capture_log(opts \\ [], fun) do {_, log} = with_log(opts, fun) log @@ -98,7 +102,7 @@ defmodule ExUnit.CaptureLog do """ @doc since: "1.13.0" - @spec with_log(keyword, (-> result)) :: {result, log :: String.t()} when result: any + @spec with_log(capture_log_opts, (-> result)) :: {result, log :: String.t()} when result: any def with_log(opts \\ [], fun) when is_list(opts) do opts = if opts[:level] == :warn do diff --git a/lib/ex_unit/lib/ex_unit/case.ex b/lib/ex_unit/lib/ex_unit/case.ex index 38b59cf2e5c..69103257b05 100644 --- a/lib/ex_unit/lib/ex_unit/case.ex +++ b/lib/ex_unit/lib/ex_unit/case.ex @@ -338,6 +338,11 @@ defmodule ExUnit.Case do """ @type env :: module() | Macro.Env.t() + + @type register_attribute_opts :: [ + accumulate: boolean(), + persist: boolean() + ] @compile {:no_warn_undefined, [IEx.Pry]} @reserved [:module, :file, :line, :test, :async, :registered, :describe] @@ -767,7 +772,7 @@ defmodule ExUnit.Case do """ @doc since: "1.3.0" - @spec register_attribute(env, atom, keyword) :: :ok + @spec register_attribute(env, atom, register_attribute_opts) :: :ok def register_attribute(env, name, opts \\ []) def register_attribute(%{module: mod}, name, opts), do: register_attribute(mod, name, opts) @@ -809,7 +814,7 @@ defmodule ExUnit.Case do """ @doc since: "1.10.0" - @spec register_describe_attribute(env, atom, keyword) :: :ok + @spec register_describe_attribute(env, atom, register_attribute_opts) :: :ok def register_describe_attribute(env, name, opts \\ []) def register_describe_attribute(%{module: mod}, name, opts) do @@ -849,7 +854,7 @@ defmodule ExUnit.Case do """ @doc since: "1.10.0" - @spec register_module_attribute(env, atom, keyword) :: :ok + @spec register_module_attribute(env, atom, register_attribute_opts) :: :ok def register_module_attribute(env, name, opts \\ []) def register_module_attribute(%{module: mod}, name, opts) do diff --git a/lib/iex/lib/iex.ex b/lib/iex/lib/iex.ex index 778f2325b05..7c091804c31 100644 --- a/lib/iex/lib/iex.ex +++ b/lib/iex/lib/iex.ex @@ -390,6 +390,78 @@ defmodule IEx do """ + @typedoc """ + Color settings used by the IEx shell. + """ + @type colors_opts :: [ + enabled: boolean(), + eval_result: IO.ANSI.ansidata(), + eval_info: IO.ANSI.ansidata(), + eval_error: IO.ANSI.ansidata(), + eval_interrupt: IO.ANSI.ansidata(), + stack_info: IO.ANSI.ansidata(), + blame_diff: IO.ANSI.ansidata(), + ls_directory: IO.ANSI.ansidata(), + ls_device: IO.ANSI.ansidata(), + doc_code: IO.ANSI.ansidata(), + doc_inline_code: IO.ANSI.ansidata(), + doc_headings: IO.ANSI.ansidata(), + doc_title: IO.ANSI.ansidata(), + doc_bold: IO.ANSI.ansidata(), + doc_underline: IO.ANSI.ansidata(), + syntax_colors: syntax_colors_opts() | false + ] + + @typedoc """ + Syntax coloring settings for inspected expressions. + """ + @type syntax_colors_opts :: [ + atom: IO.ANSI.ansidata(), + binary: IO.ANSI.ansidata(), + boolean: IO.ANSI.ansidata(), + charlist: IO.ANSI.ansidata(), + list: IO.ANSI.ansidata(), + map: IO.ANSI.ansidata(), + nil: IO.ANSI.ansidata(), + number: IO.ANSI.ansidata(), + string: IO.ANSI.ansidata(), + tuple: IO.ANSI.ansidata(), + variable: IO.ANSI.ansidata(), + call: IO.ANSI.ansidata(), + operator: IO.ANSI.ansidata(), + reset: IO.ANSI.ansidata() + ] + + @typedoc """ + Inspect settings used by the IEx shell. + """ + @type inspect_opts :: [ + base: :binary | :decimal | :octal | :hex, + binaries: :infer | :as_binaries | :as_strings, + charlists: :infer | :as_charlists | :as_lists, + custom_options: keyword(), + inspect_fun: (term(), Inspect.Opts.t() -> Inspect.Algebra.t()), + limit: pos_integer() | :infinity, + pretty: boolean(), + printable_limit: pos_integer() | :infinity, + safe: boolean(), + structs: boolean(), + syntax_colors: syntax_colors_opts(), + width: pos_integer() | :infinity + ] + + @type configure_opts :: [ + auto_reload: boolean(), + alive_prompt: String.t(), + colors: colors_opts(), + default_prompt: String.t(), + dot_iex: String.t() | nil, + history_size: integer(), + inspect: inspect_opts(), + parser: {module(), atom(), [any()]}, + width: pos_integer() + ] + @doc """ Configures IEx. @@ -521,7 +593,7 @@ defmodule IEx do in-memory modules when they get invalidated by a concurrent compilation happening in the Operating System. """ - @spec configure(keyword()) :: :ok + @spec configure(configure_opts) :: :ok def configure(options) do IEx.Config.configure(options) end diff --git a/lib/iex/lib/iex/broker.ex b/lib/iex/lib/iex/broker.ex index d2d9ed57140..c9b6d552d5c 100644 --- a/lib/iex/lib/iex/broker.ex +++ b/lib/iex/lib/iex/broker.ex @@ -9,6 +9,13 @@ defmodule IEx.Broker do @type take_ref :: {takeover_ref :: reference(), server_ref :: reference()} @type shell :: pid | nil + @typedoc """ + Options for `take_over/3`. + """ + @type take_over_opts :: [ + evaluator: pid() + ] + use GenServer ## Shell API @@ -58,7 +65,7 @@ defmodule IEx.Broker do @doc """ Client requests a takeover. """ - @spec take_over(binary, iodata, keyword) :: + @spec take_over(binary, iodata, take_over_opts) :: {:ok, server :: pid, group_leader :: pid, counter :: integer} | {:error, :no_iex | :refused | atom()} def take_over(location, whereami, opts) do diff --git a/lib/iex/lib/iex/helpers.ex b/lib/iex/lib/iex/helpers.ex index 80860f60bb4..9b9db7ad745 100644 --- a/lib/iex/lib/iex/helpers.ex +++ b/lib/iex/lib/iex/helpers.ex @@ -113,6 +113,7 @@ defmodule IEx.Helpers do * `:force` - when `true`, forces the application to recompile """ + @spec recompile(force: boolean()) :: :ok | :error | :noop def recompile(options \\ []) do cond do not mix_started?() -> diff --git a/lib/iex/lib/iex/server.ex b/lib/iex/lib/iex/server.ex index 5ab6d8eff04..fe0d938ee03 100644 --- a/lib/iex/lib/iex/server.ex +++ b/lib/iex/lib/iex/server.ex @@ -14,6 +14,14 @@ defmodule IEx.Server do """ + @type run_opts :: [ + prefix: String.t(), + env: Macro.Env.t(), + binding: keyword(), + on_eof: :stop_evaluator | :halt, + register: boolean() + ] + @doc false defstruct parser_state: [], counter: 1, @@ -35,7 +43,7 @@ defmodule IEx.Server do """ @doc since: "1.8.0" - @spec run(keyword) :: :ok + @spec run(run_opts) :: :ok def run(opts) when is_list(opts) do if Keyword.get(opts, :register, true) do IEx.Broker.register(self()) diff --git a/lib/iex/test/iex/autocomplete_test.exs b/lib/iex/test/iex/autocomplete_test.exs index 99b526f7efa..39d454f50ef 100644 --- a/lib/iex/test/iex/autocomplete_test.exs +++ b/lib/iex/test/iex/autocomplete_test.exs @@ -62,7 +62,16 @@ defmodule IEx.AutocompleteTest do assert expand(~c"t String") == {:yes, ~c"", [~c"String", ~c"StringIO"]} assert expand(~c"t String.") == - {:yes, ~c"", [~c"codepoint/0", ~c"grapheme/0", ~c"pattern/0", ~c"t/0"]} + {:yes, ~c"", + [ + ~c"codepoint/0", + ~c"grapheme/0", + ~c"pattern/0", + ~c"replace_opts/0", + ~c"split_opts/0", + ~c"splitter_opts/0", + ~c"t/0" + ]} assert expand(~c"t String.grap") == {:yes, ~c"heme", []} assert expand(~c"t String.grap") == {:yes, ~c"heme", []} @@ -85,7 +94,16 @@ defmodule IEx.AutocompleteTest do assert expand(~c"t(String") == {:yes, ~c"", [~c"String", ~c"StringIO"]} assert expand(~c"t(String.") == - {:yes, ~c"", [~c"codepoint/0", ~c"grapheme/0", ~c"pattern/0", ~c"t/0"]} + {:yes, ~c"", + [ + ~c"codepoint/0", + ~c"grapheme/0", + ~c"pattern/0", + ~c"replace_opts/0", + ~c"split_opts/0", + ~c"splitter_opts/0", + ~c"t/0" + ]} assert expand(~c"t(String.grap") == {:yes, ~c"heme", []} end diff --git a/lib/iex/test/iex/helpers_test.exs b/lib/iex/test/iex/helpers_test.exs index 10d9ecf5735..77f35f5529e 100644 --- a/lib/iex/test/iex/helpers_test.exs +++ b/lib/iex/test/iex/helpers_test.exs @@ -967,7 +967,7 @@ defmodule IEx.HelpersTest do describe "t" do test "prints when there is no type information or the type is private" do - assert capture_io(fn -> t(IEx) end) == "No type information for IEx was found\n" + assert capture_io(fn -> t(List) end) == "No type information for List was found\n" assert capture_io(fn -> t(Enum.doesnt_exist()) end) == "No type information for Enum.doesnt_exist was found or " <> diff --git a/lib/logger/lib/logger.ex b/lib/logger/lib/logger.ex index 107cc585c4e..3b6ec5aa784 100644 --- a/lib/logger/lib/logger.ex +++ b/lib/logger/lib/logger.ex @@ -507,6 +507,30 @@ defmodule Logger do @type report :: map() | keyword() @type message :: :unicode.chardata() | String.Chars.t() | report() @type metadata :: keyword() + + @type configure_opts :: [ + level: level(), + translator_inspect_opts: Inspect.Opts.t(), + sync_threshold: non_neg_integer(), + discard_threshold: non_neg_integer(), + truncate: non_neg_integer() | :infinity, + utc_log: boolean() + ] + + @type formatter_opts :: [ + colors: [ + enabled: boolean(), + debug: atom(), + info: atom(), + warning: atom(), + error: atom() + ], + format: String.t() | {module(), atom()}, + metadata: :all | [atom()], + truncate: pos_integer() | :infinity, + utc_log: boolean() + ] + @new_erlang_levels [:emergency, :alert, :critical, :warning, :notice] @levels [:error, :info, :debug] ++ @new_erlang_levels @metadata :logger_level @@ -552,7 +576,7 @@ defmodule Logger do instead. """ @doc since: "1.15.0" - @spec default_formatter(keyword) :: {module, :logger.formatter_config()} + @spec default_formatter(formatter_opts) :: {module, :logger.formatter_config()} def default_formatter(overrides \\ []) when is_list(overrides) do Application.get_env(:logger, :default_formatter, []) |> Keyword.merge(overrides) @@ -712,7 +736,7 @@ defmodule Logger do :translator_inspect_opts ] @backend_options [:sync_threshold, :discard_threshold, :truncate, :utc_log] - @spec configure(keyword) :: :ok + @spec configure(configure_opts) :: :ok def configure(options) do for {k, v} <- options do cond do @@ -973,7 +997,7 @@ defmodule Logger do anonymous functions to `bare_log/3` and they will only be evaluated if there is something to be logged. """ - @spec bare_log(level, message | (-> message | {message, keyword}), keyword) :: :ok + @spec bare_log(level, message | (-> message | {message, keyword}), metadata) :: :ok def bare_log(level, message_or_fun, metadata \\ []) do level = elixir_level_to_erlang_level(level) diff --git a/lib/logger/lib/logger/backends/internal.ex b/lib/logger/lib/logger/backends/internal.ex index afef580a6e5..4af6c2e117b 100644 --- a/lib/logger/lib/logger/backends/internal.ex +++ b/lib/logger/lib/logger/backends/internal.ex @@ -8,13 +8,32 @@ defmodule Logger.Backends.Internal do @name __MODULE__ @type backend :: :gen_event.handler() + @typedoc """ + Configuration options for Logger backends. + + These are the built-in backend options that can be configured at runtime. + """ + @type backend_config_opts :: [ + sync_threshold: pos_integer(), + discard_threshold: pos_integer() | :infinity, + truncate: pos_integer() | :infinity, + utc_log: boolean() + ] + + @typedoc """ + Options for `add/2` and `remove/2` operations. + """ + @type add_remove_opts :: [ + flush: boolean() + ] + @doc """ Apply runtime configuration to all backends. See the module doc for more information. """ @backend_options [:sync_threshold, :discard_threshold, :truncate, :utc_log] - @spec configure(keyword) :: :ok + @spec configure(backend_config_opts) :: :ok def configure(options) do ensure_started() Logger.Backends.Config.configure(Keyword.take(options, @backend_options)) @@ -65,7 +84,7 @@ defmodule Logger.Backends.Internal do {:ok, _pid} = Logger.add_backend(MyBackend, flush: true) """ - @spec add(backend, keyword) :: Supervisor.on_start_child() + @spec add(backend, add_remove_opts) :: Supervisor.on_start_child() def add(backend, opts \\ []) do ensure_started() _ = if opts[:flush], do: Logger.flush() @@ -91,7 +110,7 @@ defmodule Logger.Backends.Internal do to `Logger` are processed before the backend is removed """ - @spec remove(backend, keyword) :: :ok | {:error, term} + @spec remove(backend, add_remove_opts) :: :ok | {:error, term} def remove(backend, opts \\ []) do ensure_started() _ = if opts[:flush], do: Logger.flush() diff --git a/lib/logger/lib/logger/formatter.ex b/lib/logger/lib/logger/formatter.ex index 858449bc27c..1caed9da74d 100644 --- a/lib/logger/lib/logger/formatter.ex +++ b/lib/logger/lib/logger/formatter.ex @@ -99,6 +99,20 @@ defmodule Logger.Formatter do @type date_time_ms :: {date, time_ms} @type pattern :: :date | :level | :levelpad | :message | :metadata | :node | :time + + @type new_opts :: [ + colors: [ + enabled: boolean(), + debug: atom(), + info: atom(), + warning: atom(), + error: atom() + ], + format: String.t() | {module(), atom()}, + metadata: :all | [atom()], + truncate: pos_integer() | :infinity, + utc_log: boolean() + ] @valid_patterns [:time, :date, :message, :level, :node, :metadata, :levelpad] @default_pattern "\n$time $metadata[$level] $message\n" @replacement "�" @@ -152,7 +166,7 @@ defmodule Logger.Formatter do The color of the message can also be configured per message via the `:ansi_color` metadata. """ - @spec new(keyword) :: formatter when formatter: term + @spec new(new_opts) :: formatter when formatter: term def new(options \\ []) do template = compile(options[:format]) colors = colors(options[:colors] || []) diff --git a/lib/logger/lib/logger/utils.ex b/lib/logger/lib/logger/utils.ex index 5492740714d..302e4829e8c 100644 --- a/lib/logger/lib/logger/utils.ex +++ b/lib/logger/lib/logger/utils.ex @@ -127,6 +127,12 @@ defmodule Logger.Utils do For information about format scanning and how to consume them, check `:io_lib.scan_format/2`. """ + @spec scan_inspect( + atom() | binary() | charlist(), + list(), + non_neg_integer() | :infinity, + Inspect.Opts.t() + ) :: [:io_lib.format_spec()] def scan_inspect(format, args, truncate, opts \\ %Inspect.Opts{}) def scan_inspect(format, args, truncate, opts) when is_atom(format) do diff --git a/lib/mix/lib/mix.ex b/lib/mix/lib/mix.ex index 5197d890df5..ee3701f686f 100644 --- a/lib/mix/lib/mix.ex +++ b/lib/mix/lib/mix.ex @@ -424,6 +424,25 @@ defmodule Mix do """ + @typedoc """ + Options for `install/2`. + + See the `install/2` function documentation for detailed information about each option. + """ + @type install_opts :: [ + force: boolean(), + verbose: boolean(), + consolidate_protocols: boolean(), + elixir: String.t(), + system_env: Enum.t(), + config: keyword(), + config_path: String.t() | atom(), + lockfile: String.t() | atom(), + start_applications: boolean(), + compilers: [atom()], + runtime_config: keyword() + ] + @mix_install_project Mix.InstallProject @mix_install_app :mix_install @mix_install_app_string Atom.to_string(@mix_install_app) @@ -845,6 +864,10 @@ defmodule Mix do if `:force` is enabled. """ @doc since: "1.12.0" + @spec install( + [atom() | {atom(), String.t()} | {atom(), String.t(), keyword()} | {atom(), keyword()}], + install_opts() + ) :: :ok def install(deps, opts \\ []) def install(deps, opts) when is_list(deps) and is_list(opts) do diff --git a/lib/mix/lib/mix/compilers/erlang.ex b/lib/mix/lib/mix/compilers/erlang.ex index ab94b0a03de..1ffa1425770 100644 --- a/lib/mix/lib/mix/compilers/erlang.ex +++ b/lib/mix/lib/mix/compilers/erlang.ex @@ -7,6 +7,15 @@ defmodule Mix.Compilers.Erlang do @manifest_vsn 1 + @typedoc """ + Options for `compile/6`. + """ + @type compile_opts :: [ + force: boolean(), + parallel: MapSet.t(Path.t()), + preload: (-> term()) + ] + @doc """ Compiles the given `mappings`. @@ -58,6 +67,14 @@ defmodule Mix.Compilers.Erlang do `{:error, errors, warnings}` in case of error. This function returns `{status, diagnostics}` as specified in `Mix.Task.Compiler`. """ + @spec compile( + Path.t(), + [{Path.t(), Path.t()}], + atom(), + atom(), + compile_opts, + (Path.t(), Path.t() -> {:ok, term(), [term()]} | {:error, [term()], [term()]}) + ) :: {Mix.Task.Compiler.status(), [Mix.Task.Compiler.Diagnostic.t()]} def compile(manifest, mappings, src_ext, dest_ext, opts, callback) when is_atom(src_ext) and is_atom(dest_ext) and is_list(opts) do force = opts[:force] diff --git a/lib/mix/lib/mix/dep.ex b/lib/mix/lib/mix/dep.ex index 1dd953b4cd9..8887ec86f54 100644 --- a/lib/mix/lib/mix/dep.ex +++ b/lib/mix/lib/mix/dep.ex @@ -79,6 +79,13 @@ defmodule Mix.Dep do system_env: keyword } + @typedoc """ + Options for `filter_by_name/3`. + """ + @type filter_by_name_opts :: [ + include_children: boolean() + ] + @doc """ Returns loaded dependencies from the cache for the current environment. @@ -197,6 +204,7 @@ defmodule Mix.Dep do Raises if any of the names are missing. """ + @spec filter_by_name([atom() | String.t()], [t()], filter_by_name_opts) :: [t()] def filter_by_name(given, all_deps, opts \\ []) do # Ensure all apps are atoms apps = to_app_names(given) diff --git a/lib/mix/lib/mix/dep/converger.ex b/lib/mix/lib/mix/dep/converger.ex index 1d036c49822..c29073f53f7 100644 --- a/lib/mix/lib/mix/dep/converger.ex +++ b/lib/mix/lib/mix/dep/converger.ex @@ -8,6 +8,14 @@ defmodule Mix.Dep.Converger do @moduledoc false + @typedoc """ + Options for `converge/1` and `converge/4`. + """ + @type converge_opts :: [ + env: atom(), + target: atom() + ] + @doc """ Topologically sorts the given dependencies. """ @@ -72,6 +80,7 @@ defmodule Mix.Dep.Converger do See `Mix.Dep.Loader.children/1` for options. """ + @spec converge(converge_opts) :: [Mix.Dep.t()] def converge(opts \\ []) do converge(nil, nil, opts, &{&1, &2, &3}) |> elem(0) end @@ -89,6 +98,7 @@ defmodule Mix.Dep.Converger do See `Mix.Dep.Loader.children/1` for options. """ + @spec converge(term(), map() | nil, converge_opts, function()) :: {[Mix.Dep.t()], term(), map()} def converge(acc, lock, opts, callback) do {deps, acc, lock} = all(acc, lock, opts, callback) if remote = Mix.RemoteConverger.get(), do: remote.post_converge() diff --git a/lib/mix/lib/mix/dep/lock.ex b/lib/mix/lib/mix/dep/lock.ex index 9f031024782..a8ec7192f56 100644 --- a/lib/mix/lib/mix/dep/lock.ex +++ b/lib/mix/lib/mix/dep/lock.ex @@ -9,6 +9,14 @@ defmodule Mix.Dep.Lock do @moduledoc false + @typedoc """ + Options for `write/2`. + """ + @type write_opts :: [ + file: Path.t(), + check_locked: boolean() + ] + @doc """ Reads the lockfile, returns a map containing each app name and its current lock information. @@ -30,7 +38,7 @@ defmodule Mix.Dep.Lock do @doc """ Receives a map and writes it as the latest lock. """ - @spec write(map(), keyword) :: :ok + @spec write(map(), write_opts) :: :ok def write(map, opts \\ []) do lockfile = opts[:file] || lockfile() diff --git a/lib/mix/lib/mix/generator.ex b/lib/mix/lib/mix/generator.ex index 1e8af77ef60..e04e4ca30dd 100644 --- a/lib/mix/lib/mix/generator.ex +++ b/lib/mix/lib/mix/generator.ex @@ -7,6 +7,12 @@ defmodule Mix.Generator do Conveniences for working with paths and generating content. """ + @type generator_opts :: [ + force: boolean(), + quiet: boolean(), + format_elixir: boolean() + ] + @doc ~S""" Creates a file with the given contents. @@ -17,6 +23,7 @@ defmodule Mix.Generator do * `:force` - forces creation without a shell prompt * `:quiet` - does not log command output + * `:format_elixir` (since v1.18.0) - if `true`, apply formatter to the generated file ## Examples @@ -25,7 +32,7 @@ defmodule Mix.Generator do true """ - @spec create_file(Path.t(), iodata, keyword) :: boolean() + @spec create_file(Path.t(), iodata, generator_opts) :: boolean() def create_file(path, contents, opts \\ []) when is_binary(path) do log(:green, :creating, Path.relative_to_cwd(path), opts) @@ -62,7 +69,7 @@ defmodule Mix.Generator do true """ - @spec create_directory(Path.t(), keyword) :: true + @spec create_directory(Path.t(), quiet: boolean()) :: true def create_directory(path, options \\ []) when is_binary(path) do log(:green, "creating", Path.relative_to_cwd(path), options) File.mkdir_p!(path) @@ -79,6 +86,7 @@ defmodule Mix.Generator do * `:force` - forces copying without a shell prompt * `:quiet` - does not log command output + * `:format_elixir` (since v1.18.0) - if `true`, apply formatter to the generated file ## Examples @@ -88,7 +96,7 @@ defmodule Mix.Generator do """ @doc since: "1.9.0" - @spec copy_file(Path.t(), Path.t(), keyword) :: boolean() + @spec copy_file(Path.t(), Path.t(), generator_opts) :: boolean() def copy_file(source, target, options \\ []) do create_file(target, File.read!(source), options) end @@ -116,7 +124,7 @@ defmodule Mix.Generator do """ @doc since: "1.9.0" - @spec copy_template(Path.t(), Path.t(), keyword, keyword) :: boolean() + @spec copy_template(Path.t(), Path.t(), keyword, generator_opts) :: boolean() def copy_template(source, target, assigns, options \\ []) do create_file(target, EEx.eval_file(source, assigns: assigns), options) end diff --git a/lib/mix/lib/mix/local/installer.ex b/lib/mix/lib/mix/local/installer.ex index b5180d87168..10d8d0938ce 100644 --- a/lib/mix/lib/mix/local/installer.ex +++ b/lib/mix/lib/mix/local/installer.ex @@ -23,10 +23,27 @@ defmodule Mix.Local.Installer do | {:url, url :: binary} | {:fetcher, dep_spec :: tuple} + @typedoc """ + Options for installer functions. + + These options are used by various installer functions to control behavior + during installation, parsing, and uninstallation. + """ + @type installer_opts :: [ + app: String.t(), + submodules: boolean(), + sparse: String.t(), + force: boolean(), + sha512: String.t(), + organization: String.t(), + repo: String.t(), + timeout: pos_integer() + ] + @doc """ Checks that the `install_spec` and `opts` are supported by the respective module. """ - @callback check_install_spec(install_spec, opts :: keyword) :: :ok | {:error, String.t()} + @callback check_install_spec(install_spec, opts :: installer_opts) :: :ok | {:error, String.t()} @doc """ Returns a list of already installed version of the same artifact. @@ -37,7 +54,7 @@ defmodule Mix.Local.Installer do Builds a local artifact either from a remote dependency or for the current project. """ - @callback build(install_spec, opts :: Keyword.t()) :: Path.t() + @callback build(install_spec, opts :: installer_opts) :: Path.t() @doc """ The installation itself. @@ -175,7 +192,7 @@ defmodule Mix.Local.Installer do @doc """ Receives `argv` and `opts` from options parsing and returns an `install_spec`. """ - @spec parse_args([String.t()], keyword) :: install_spec | {:error, String.t()} + @spec parse_args([String.t()], installer_opts) :: install_spec | {:error, String.t()} def parse_args(argv, opts) def parse_args([], _opts) do diff --git a/lib/mix/lib/mix/project.ex b/lib/mix/lib/mix/project.ex index 72f648967ec..7c89b2ba35e 100644 --- a/lib/mix/lib/mix/project.ex +++ b/lib/mix/lib/mix/project.ex @@ -158,6 +158,22 @@ defmodule Mix.Project do """ + @type build_structure_opts :: [ + symlink_ebin: boolean(), + source: String.t() + ] + + @typedoc """ + Options for dependency traversal functions. + + These options control how dependency trees are traversed and filtered + in functions like `deps_scms/1`, `deps_paths/1`, and `deps_tree/1`. + """ + @type deps_traversal_opts :: [ + depth: pos_integer(), + parents: [atom()] + ] + @doc false defmacro __using__(_) do quote do @@ -540,7 +556,7 @@ defmodule Mix.Project do """ @doc since: "1.10.0" - @spec deps_scms(keyword) :: %{optional(atom) => Mix.SCM.t()} + @spec deps_scms(deps_traversal_opts) :: %{optional(atom) => Mix.SCM.t()} def deps_scms(opts \\ []) when is_list(opts) do traverse_deps(opts, fn %{scm: scm} -> scm end) end @@ -561,7 +577,7 @@ defmodule Mix.Project do #=> %{foo: "deps/foo", bar: "custom/path/dep"} """ - @spec deps_paths(keyword) :: %{optional(atom) => Path.t()} + @spec deps_paths(deps_traversal_opts) :: %{optional(atom) => Path.t()} def deps_paths(opts \\ []) when is_list(opts) do traverse_deps(opts, fn %{opts: opts} -> opts[:dest] end) end @@ -583,7 +599,7 @@ defmodule Mix.Project do """ @doc since: "1.15.0" - @spec deps_tree(keyword) :: %{optional(atom) => [atom]} + @spec deps_tree(deps_traversal_opts) :: %{optional(atom) => [atom]} def deps_tree(opts \\ []) when is_list(opts) do traverse_deps(opts, fn %{deps: deps} -> Enum.map(deps, & &1.app) end) end @@ -843,8 +859,11 @@ defmodule Mix.Project do * `:symlink_ebin` - symlink ebin instead of copying it + * `:source` - the source directory to copy from. + Defaults to the current working directory. + """ - @spec build_structure(keyword, keyword) :: :ok + @spec build_structure(keyword, build_structure_opts) :: :ok def build_structure(config \\ config(), opts \\ []) do source = opts[:source] || File.cwd!() target = app_path(config) @@ -878,7 +897,7 @@ defmodule Mix.Project do `opts` are the same options that can be passed to `build_structure/2`. """ - @spec ensure_structure(keyword, keyword) :: :ok + @spec ensure_structure(keyword, build_structure_opts) :: :ok def ensure_structure(config \\ config(), opts \\ []) do if File.exists?(app_path(config)) do :ok diff --git a/lib/mix/lib/mix/release.ex b/lib/mix/lib/mix/release.ex index dc066d64f89..ff74c480797 100644 --- a/lib/mix/lib/mix/release.ex +++ b/lib/mix/lib/mix/release.ex @@ -68,6 +68,23 @@ defmodule Mix.Release do steps: [(t -> t) | :assemble, ...] } + @typedoc """ + Options for stripping BEAM files. + """ + @type strip_beam_opts :: [ + keep: [String.t()], + compress: boolean() + ] + + @typedoc """ + Erlang/OTP sys.config structure. + + A list of tuples where each tuple contains an application name and its + configuration as a keyword list. This is the standard format for Erlang + application configuration. + """ + @type sys_config :: [{application(), keyword()}] + @default_apps [kernel: :permanent, stdlib: :permanent, elixir: :permanent, sasl: :permanent] @safe_modes [:permanent, :temporary, :transient] @unsafe_modes [:load, :none] @@ -445,7 +462,7 @@ defmodule Mix.Release do In case there are no config providers, it doesn't change `sys_config`. """ - @spec make_sys_config(t, keyword(), Config.Provider.config_path()) :: + @spec make_sys_config(t, sys_config(), Config.Provider.config_path()) :: :ok | {:error, String.t()} def make_sys_config(release, sys_config, config_provider_path) do {sys_config, runtime_config?} = @@ -900,8 +917,26 @@ defmodule Mix.Release do The exact chunks that are kept are not documented and may change in future versions. + + ## Options + + * `:keep` - a list of additional chunk names (as strings) to keep in the + stripped BEAM file. These will be added to the significant chunks + determined by `:beam_lib.significant_chunks/0` + + * `:compress` - when `true`, the resulting BEAM file will be compressed + using gzip. Defaults to `false` + + ## Examples + + # Strip with default options + Mix.Release.strip_beam(beam_binary) + + # Keep additional chunks and compress + Mix.Release.strip_beam(beam_binary, keep: ["Docs", "ChunkName"], compress: true) + """ - @spec strip_beam(binary(), keyword()) :: {:ok, binary()} | {:error, :beam_lib, term()} + @spec strip_beam(binary(), strip_beam_opts()) :: {:ok, binary()} | {:error, :beam_lib, term()} def strip_beam(binary, options \\ []) when is_list(options) do chunks_to_keep = options[:keep] |> List.wrap() |> Enum.map(&String.to_charlist/1) all_chunks = Enum.uniq(@additional_chunks ++ :beam_lib.significant_chunks() ++ chunks_to_keep) diff --git a/lib/mix/lib/mix/shell.ex b/lib/mix/lib/mix/shell.ex index 75770ef7a8a..03f7bf31b52 100644 --- a/lib/mix/lib/mix/shell.ex +++ b/lib/mix/lib/mix/shell.ex @@ -7,6 +7,29 @@ defmodule Mix.Shell do Defines `Mix.Shell` contract. """ + @type cmd_opts :: [ + {:print_app, boolean()} + | {:stderr_to_stdout, boolean()} + | {:quiet, boolean()} + | {:env, [{String.t(), String.t()}]} + | {:cd, String.t()} + | {atom(), term()} + ] + + @type yes_opts :: [ + {:default, :yes | :no} + | {atom(), term()} + ] + + @type stream_cmd_opts :: [ + {:cd, String.t()} + | {:stderr_to_stdout, boolean()} + | {:use_stdio, boolean()} + | {:env, [{String.t(), String.t()}]} + | {:quiet, boolean()} + | {atom(), term()} + ] + @doc false defstruct [:callback] @@ -48,7 +71,7 @@ defmodule Mix.Shell do All the built-in shells support these. """ - @callback cmd(command :: String.t(), options :: keyword) :: integer + @callback cmd(command :: String.t(), options :: cmd_opts) :: integer @doc """ Prompts the user for input. @@ -69,7 +92,7 @@ defmodule Mix.Shell do * `:default` - `:yes` or `:no` (the default is `:yes`) """ - @callback yes?(message :: binary, options :: keyword) :: boolean + @callback yes?(message :: binary, options :: yes_opts) :: boolean @doc """ Prints the current application to the shell if @@ -117,7 +140,7 @@ defmodule Mix.Shell do * `:quiet` - overrides the callback to no-op """ - @spec cmd(String.t() | {String.t(), [String.t()]}, keyword, (binary -> term)) :: + @spec cmd(String.t() | {String.t(), [String.t()]}, stream_cmd_opts, (binary -> term)) :: exit_status :: non_neg_integer def cmd(command, options \\ [], callback) when is_function(callback, 1) do callback = diff --git a/lib/mix/lib/mix/shell/io.ex b/lib/mix/lib/mix/shell/io.ex index 57aabfc5524..0988272954c 100644 --- a/lib/mix/lib/mix/shell/io.ex +++ b/lib/mix/lib/mix/shell/io.ex @@ -11,6 +11,11 @@ defmodule Mix.Shell.IO do @behaviour Mix.Shell + @typedoc """ + Options for `yes?/2`. + """ + @type yes_opts :: [default: :yes | :no] + @doc """ Prints the current application to the shell if it was not printed yet. @@ -72,6 +77,7 @@ defmodule Mix.Shell.IO do end """ + @spec yes?(String.t(), yes_opts) :: boolean() def yes?(message, options \\ []) do default = Keyword.get(options, :default, :yes) diff --git a/lib/mix/lib/mix/shell/quiet.ex b/lib/mix/lib/mix/shell/quiet.ex index 6c5f0c0dbbf..22fec04943f 100644 --- a/lib/mix/lib/mix/shell/quiet.ex +++ b/lib/mix/lib/mix/shell/quiet.ex @@ -52,11 +52,14 @@ defmodule Mix.Shell.Quiet do the prompt instead. Defaults to `:yes`. """ + @spec yes?(String.t(), Mix.Shell.IO.yes_opts()) :: boolean() defdelegate yes?(message, options \\ []), to: Mix.Shell.IO @doc """ Executes the given command quietly without outputting anything. """ + @spec cmd(String.t() | {String.t(), [String.t()]}, Mix.Shell.stream_cmd_opts()) :: + exit_status :: non_neg_integer def cmd(command, opts \\ []) do Mix.Shell.cmd(command, opts, fn data -> data end) end diff --git a/lib/mix/lib/mix/sync/lock.ex b/lib/mix/lib/mix/sync/lock.ex index 4705f7602e9..9f13b853435 100644 --- a/lib/mix/lib/mix/sync/lock.ex +++ b/lib/mix/lib/mix/sync/lock.ex @@ -73,6 +73,13 @@ defmodule Mix.Sync.Lock do @probe_data_size byte_size(@probe_data) @probe_timeout_ms 5_000 + @typedoc """ + Options for `with_lock/3`. + """ + @type with_lock_opts :: [ + on_taken: (String.t() -> any()) + ] + @doc """ Acquires a lock identified by the given key. @@ -95,7 +102,7 @@ defmodule Mix.Sync.Lock do is successfully acquired by this process. """ - @spec with_lock(iodata(), (-> term()), keyword()) :: term() + @spec with_lock(iodata(), (-> term()), with_lock_opts()) :: term() def with_lock(key, fun, opts \\ []) do opts = Keyword.validate!(opts, [:on_taken]) diff --git a/lib/mix/lib/mix/tasks/format.ex b/lib/mix/lib/mix/tasks/format.ex index db05232bcb3..2abd3feced3 100644 --- a/lib/mix/lib/mix/tasks/format.ex +++ b/lib/mix/lib/mix/tasks/format.ex @@ -223,15 +223,55 @@ defmodule Mix.Tasks.Format do ins: [text: :green, space: :green_background] ] + @typedoc """ + Options passed to plugin `features/1` callback. + + These are the same formatter options from `.formatter.exs` configuration, + allowing plugins to access formatting settings when determining their capabilities. + """ + @type features_opts :: [ + inputs: [String.t()], + plugins: [module()], + subdirectories: [String.t()], + import_deps: [atom()], + export: keyword(), + locals_without_parens: keyword(), + line_length: pos_integer(), + normalize_bitstring_modifiers: boolean(), + normalize_charlists_as_binaries: boolean() + ] + + @typedoc """ + Options passed to plugin `format/2` callback. + + These options include context-specific information about what is being formatted + (sigil, extension, file) along with all the standard formatter configuration options. + """ + @type format_opts :: [ + {:sigil, atom()} + | {:modifiers, charlist()} + | {:extension, String.t()} + | {:file, String.t()} + | {:inputs, [String.t()]} + | {:plugins, [module()]} + | {:subdirectories, [String.t()]} + | {:import_deps, [atom()]} + | {:export, keyword()} + | {:locals_without_parens, keyword()} + | {:line_length, pos_integer()} + | {:normalize_bitstring_modifiers, boolean()} + | {:normalize_charlists_as_binaries, boolean()} + ] + @doc """ Returns which features this plugin should plug into. """ - @callback features(Keyword.t()) :: [sigils: [atom()], extensions: [binary()]] + @callback features(features_opts) :: [sigils: [atom()], extensions: [binary()]] @doc """ Receives a string to be formatted with options and returns said string. """ - @callback format(String.t(), Keyword.t()) :: String.t() + @callback format(String.t(), format_opts) :: String.t() @impl true def run(all_args) do @@ -356,6 +396,16 @@ defmodule Mix.Tasks.Format do plugins end + @typedoc """ + Options for `formatter_for_file/2`. + """ + @type formatter_for_file_opts :: [ + deps_paths: %{atom() => String.t()}, + dot_formatter: String.t(), + plugin_loader: ([module()] -> [module()]), + root: String.t() + ] + @doc """ Returns a formatter function and the formatter options to be used for the given file. @@ -389,6 +439,8 @@ defmodule Mix.Tasks.Format do * `:root` - use the given root as the current working directory. """ @doc since: "1.13.0" + @spec formatter_for_file(String.t(), formatter_for_file_opts) :: + {(String.t() -> String.t()), keyword()} def formatter_for_file(file, opts \\ []) do cwd = Keyword.get_lazy(opts, :root, &File.cwd!/0) {dot_formatter, formatter_opts} = eval_dot_formatter(cwd, opts) @@ -809,8 +861,18 @@ defmodule Mix.Tasks.Format do end) end + @typedoc """ + Options for `text_diff_format/3`. + """ + @type text_diff_format_opts :: [ + after: non_neg_integer(), + before: non_neg_integer(), + color: boolean(), + line: pos_integer() + ] + @doc false - @spec text_diff_format(String.t(), String.t()) :: iolist() + @spec text_diff_format(String.t(), String.t(), text_diff_format_opts) :: iolist() def text_diff_format(old, new, opts \\ []) def text_diff_format(code, code, _opts), do: [] diff --git a/lib/mix/lib/mix/tasks/profile.cprof.ex b/lib/mix/lib/mix/tasks/profile.cprof.ex index 62abd3489a1..5eaf752d1c9 100644 --- a/lib/mix/lib/mix/tasks/profile.cprof.ex +++ b/lib/mix/lib/mix/tasks/profile.cprof.ex @@ -95,6 +95,15 @@ defmodule Mix.Tasks.Profile.Cprof do (for example, 2147483647 in a 32-bit host). """ + @typedoc """ + Options for the cprof profiler. + """ + @type profile_opts :: [ + matching: {module() | :_, atom() | :_, arity() | :_}, + limit: non_neg_integer(), + module: module() + ] + @switches [ parallel: :boolean, require: :keep, @@ -172,7 +181,7 @@ defmodule Mix.Tasks.Profile.Cprof do * `:module` - filters out any results not pertaining to the given module """ - @spec profile((-> result), keyword()) :: result when result: any() + @spec profile((-> result), profile_opts()) :: result when result: any() def profile(fun, opts \\ []) when is_function(fun, 0) do Mix.ensure_application!(:tools) {return_value, num_matched_functions, analysis_result} = profile_and_analyse(fun, opts) diff --git a/lib/mix/lib/mix/tasks/profile.eprof.ex b/lib/mix/lib/mix/tasks/profile.eprof.ex index 52099b7247a..e4c134a01de 100644 --- a/lib/mix/lib/mix/tasks/profile.eprof.ex +++ b/lib/mix/lib/mix/tasks/profile.eprof.ex @@ -101,6 +101,18 @@ defmodule Mix.Tasks.Profile.Eprof do `Mix.Tasks.Profile.Cprof` that uses [`:cprof`](`:cprof`) and has a low performance degradation effect. """ + @typedoc """ + Options for the eprof profiler. + """ + @type profile_opts :: [ + matching: {module() | :_, atom() | :_, arity() | :_}, + calls: non_neg_integer(), + time: non_neg_integer(), + sort: :time | :calls, + warmup: boolean(), + set_on_spawn: boolean() + ] + @switches [ parallel: :boolean, require: :keep, @@ -189,7 +201,7 @@ defmodule Mix.Tasks.Profile.Eprof do * `:set_on_spawn` - if newly spawned processes should be measured (default: `true`) """ - @spec profile((-> result), keyword()) :: result when result: any() + @spec profile((-> result), profile_opts()) :: result when result: any() def profile(fun, opts \\ []) when is_function(fun, 0) do Mix.ensure_application!(:tools) {return_value, results} = profile_and_analyse(fun, opts) diff --git a/lib/mix/lib/mix/tasks/profile.fprof.ex b/lib/mix/lib/mix/tasks/profile.fprof.ex index 8a7f4cd4cbc..5fdf1840ac1 100644 --- a/lib/mix/lib/mix/tasks/profile.fprof.ex +++ b/lib/mix/lib/mix/tasks/profile.fprof.ex @@ -114,6 +114,16 @@ defmodule Mix.Tasks.Profile.Fprof do this should provide more realistic insights into bottlenecks. """ + @typedoc """ + Options for the fprof profiler. + """ + @type profile_opts :: [ + callers: boolean(), + details: boolean(), + sort: :acc | :own, + trace_to_file: boolean() + ] + @switches [ parallel: :boolean, require: :keep, @@ -184,7 +194,7 @@ defmodule Mix.Tasks.Profile.Fprof do usage for larger workloads. """ - @spec profile((-> result), keyword()) :: result when result: any() + @spec profile((-> result), profile_opts()) :: result when result: any() def profile(fun, opts \\ []) when is_function(fun, 0) do Mix.ensure_application!(:runtime_tools) Mix.ensure_application!(:tools) diff --git a/lib/mix/lib/mix/tasks/profile.tprof.ex b/lib/mix/lib/mix/tasks/profile.tprof.ex index 163c9448cf1..1a266621589 100644 --- a/lib/mix/lib/mix/tasks/profile.tprof.ex +++ b/lib/mix/lib/mix/tasks/profile.tprof.ex @@ -144,6 +144,21 @@ defmodule Mix.Tasks.Profile.Tprof do which is more limited but has a lower footprint. """ + @typedoc """ + Options for the tprof profiler. + """ + @type profile_opts :: [ + matching: {module() | :_, atom() | :_, arity() | :_}, + type: :time | :memory | :calls, + calls: non_neg_integer(), + time: non_neg_integer(), + memory: non_neg_integer(), + sort: :calls | :per_call | :time | :memory, + report: :process | :total, + warmup: boolean(), + set_on_spawn: boolean() + ] + @switches [ parallel: :boolean, require: :keep, @@ -260,7 +275,7 @@ defmodule Mix.Tasks.Profile.Tprof do * `:set_on_spawn` - if newly spawned processes should be measured (default: `true`) """ - @spec profile((-> result), keyword()) :: result when result: any() + @spec profile((-> result), profile_opts()) :: result when result: any() def profile(fun, opts \\ []) when is_function(fun, 0) do Mix.ensure_application!(:tools) {type, return_value, results} = profile_and_analyse(fun, opts) diff --git a/lib/mix/lib/mix/tasks/run.ex b/lib/mix/lib/mix/tasks/run.ex index 101e6fca4be..043b18a74f9 100644 --- a/lib/mix/lib/mix/tasks/run.ex +++ b/lib/mix/lib/mix/tasks/run.ex @@ -65,6 +65,19 @@ defmodule Mix.Tasks.Run do """ + @typedoc """ + Options for the internal `run/5` function. + + These options are typically parsed from command-line arguments. + """ + @type run_opts :: [ + {:parallel_require, String.t()} + | {:eval, String.t()} + | {:require, String.t()} + | {:parallel, boolean()} + | {:config, String.t()} + ] + @impl true def run(args) do {opts, head} = @@ -99,7 +112,7 @@ defmodule Mix.Tasks.Run do @doc false @spec run( OptionParser.argv(), - keyword, + run_opts, OptionParser.argv(), (String.t() -> term()), (String.t() -> term()) diff --git a/lib/mix/lib/mix/utils.ex b/lib/mix/lib/mix/utils.ex index edf04d21221..3e95e7207c0 100644 --- a/lib/mix/lib/mix/utils.ex +++ b/lib/mix/lib/mix/utils.ex @@ -276,7 +276,7 @@ defmodule Mix.Utils do Returns the name of the file written to, or "-" if the output was to STDOUT. This function is made public mostly for testing. """ - @spec write_according_to_opts!(Path.t(), iodata(), keyword) :: Path.t() + @spec write_according_to_opts!(Path.t(), iodata(), write_opts) :: Path.t() def write_according_to_opts!(default_file_spec, contents, opts) do file_spec = Keyword.get(opts, :output, default_file_spec) @@ -305,7 +305,7 @@ defmodule Mix.Utils do If the `:output` option is `-` then prints to standard output, see write_according_to_opts!/3 for details. """ - @spec write_json_tree!(Path.t(), [node], (node -> {formatted_node, [node]}), keyword) :: + @spec write_json_tree!(Path.t(), [node], (node -> {formatted_node, [node]}), write_opts) :: Path.t() when node: term() def write_json_tree!(default_file_spec, nodes, callback, opts \\ []) do @@ -336,13 +336,36 @@ defmodule Mix.Utils do @type formatted_node :: {name :: String.Chars.t(), edge_info :: String.Chars.t()} + @typedoc """ + Options for `write_according_to_opts!/3`, `write_json_tree!/4`, and `write_dot_graph!/5`. + """ + @type write_opts :: [ + output: String.t() + ] + + @typedoc """ + Options for `print_tree/3`. + """ + @type print_tree_opts :: [ + format: String.t() + ] + + @typedoc """ + Options for `read_path/2`. + """ + @type read_path_opts :: [ + timeout: pos_integer(), + sha512: String.t() + ] + @doc """ Prints the given tree according to the callback. The callback will be invoked for each node and it must return a `{printed, children}` tuple. """ - @spec print_tree([node], (node -> {formatted_node, [node]}), keyword) :: :ok when node: term() + @spec print_tree([node], (node -> {formatted_node, [node]}), print_tree_opts) :: :ok + when node: term() def print_tree(nodes, callback, opts \\ []) do pretty? = case Keyword.get(opts, :format) do @@ -414,7 +437,7 @@ defmodule Mix.Utils do String.t(), [node], (node -> {formatted_node, [node]}), - keyword + write_opts ) :: Path.t() when node: term() def write_dot_graph!(default_file_spec, title, nodes, callback, opts \\ []) do @@ -632,7 +655,7 @@ defmodule Mix.Utils do seconds """ - @spec read_path(String.t(), keyword) :: + @spec read_path(String.t(), read_path_opts) :: {:ok, binary} | :badpath | {:remote, String.t()}