diff --git a/.formatter.exs b/.formatter.exs index 048ad18..1360534 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -1,4 +1,4 @@ -locals_without_parens = [field: 2, field: 3, parameter: 1, plugin: 1, plugin: 2] +locals_without_parens = [field: 2, field: 3, parameter: 1, parameter: 2, plugin: 1, plugin: 2] [ inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"], diff --git a/guides/plugins/reflection.md b/guides/plugins/reflection.md index a51d30a..6f72f90 100644 --- a/guides/plugins/reflection.md +++ b/guides/plugins/reflection.md @@ -21,7 +21,7 @@ defmodule Guides.Plugins.Reflection do |> Enum.to_list() def __typed_structor__(:fields), do: unquote(fields) - def __typed_structor__(:parameters), do: unquote(definition.parameters) + def __typed_structor__(:parameters), do: Enum.map(unquote(definition.parameters), &Keyword.fetch!(&1, :name)) def __typed_structor__(:enforced_fields), do: unquote(enforced_fields) for field <- definition.fields do diff --git a/lib/typed_structor.ex b/lib/typed_structor.ex index c2959a4..8db43f8 100644 --- a/lib/typed_structor.ex +++ b/lib/typed_structor.ex @@ -91,7 +91,8 @@ defmodule TypedStructor do # create a lexical scope try do - import TypedStructor, only: [field: 2, field: 3, parameter: 1, plugin: 1, plugin: 2] + import TypedStructor, + only: [field: 2, field: 3, parameter: 1, parameter: 2, plugin: 1, plugin: 2] unquote(register_global_plugins()) @@ -136,6 +137,7 @@ defmodule TypedStructor do defp register_global_plugins do :typed_structor |> Application.get_env(:plugins, []) + |> List.wrap() |> Enum.map(fn {plugin, opts} when is_atom(plugin) and is_list(opts) -> {plugin, opts} @@ -206,14 +208,17 @@ defmodule TypedStructor do fied :number, int # not int() """ - defmacro parameter(name) when is_atom(name) do + defmacro parameter(name, opts \\ []) + + defmacro parameter(name, opts) when is_atom(name) and is_list(opts) do quote do - @__ts_struct_parameters__ unquote(name) + @__ts_struct_parameters__ Keyword.merge(unquote(opts), name: unquote(name)) end end - defmacro parameter(name) do - raise ArgumentError, "expected an atom, got: #{inspect(name)}" + defmacro parameter(name, opts) do + raise ArgumentError, + "name must be an atom and opts must be a list, got: #{inspect(name)} and #{inspect(opts)}" end @doc """ @@ -285,7 +290,15 @@ defmodule TypedStructor do type_name = Keyword.get(@__ts_options__, :type_name, :t) - parameters = Enum.map(@__ts_definition__.parameters, &Macro.var(&1, __MODULE__)) + parameters = + Enum.map( + @__ts_definition__.parameters, + fn parameter -> + parameter + |> Keyword.fetch!(:name) + |> Macro.var(__MODULE__) + end + ) case Keyword.get(@__ts_options__, :type_kind, :type) do :type -> diff --git a/lib/typed_structor/definition.ex b/lib/typed_structor/definition.ex index 389e614..7911142 100644 --- a/lib/typed_structor/definition.ex +++ b/lib/typed_structor/definition.ex @@ -7,7 +7,7 @@ defmodule TypedStructor.Definition do @type t() :: %__MODULE__{ options: Keyword.t(), fields: [Keyword.t()], - parameters: [atom()] + parameters: [Keyword.t()] } defstruct [:options, :fields, :parameters] diff --git a/test/config_test.exs b/test/config_test.exs index e877976..d8534be 100644 --- a/test/config_test.exs +++ b/test/config_test.exs @@ -84,7 +84,7 @@ defmodule ConfigTest do end defp set_plugins_config(plugins) do - previous_value = Application.get_env(:typed_structor, :plugins) + previous_value = Application.get_env(:typed_structor, :plugins, []) Application.put_env(:typed_structor, :plugins, plugins) on_exit(fn -> Application.put_env(:typed_structor, :plugins, previous_value) end) end diff --git a/test/support/test_case.ex b/test/support/test_case.ex index 4d5347c..d462b38 100644 --- a/test/support/test_case.ex +++ b/test/support/test_case.ex @@ -30,7 +30,7 @@ defmodule TypedStructor.TestCase do |> Macro.expand(__CALLER__) |> then(&Module.concat(__CALLER__.module, &1)) - code = Keyword.fetch(options, :do) + code = Keyword.fetch!(options, :do) content = """ @@ -60,14 +60,14 @@ defmodule TypedStructor.TestCase do def __with_file__(%{tmp_dir: dir}, {module_name, content}, fun) when is_function(fun, 0) do path = Path.join([dir, Atom.to_string(module_name)]) - try do - File.write!(path, content) - compile_file!(path, dir) + File.write!(path, content) + mods = compile_file!(path, dir) + try do fun.() after File.rm!(path) - cleanup_modules([module_name], dir) + cleanup_modules(mods, dir) end end @@ -139,7 +139,7 @@ defmodule TypedStructor.TestCase do module |> Code.Typespec.fetch_types() |> case do - :error -> refute "Failed to fetch types for module #{module}" + :error -> flunk("Failed to fetch types for module #{module}") {:ok, types} -> types end end @@ -150,18 +150,26 @@ defmodule TypedStructor.TestCase do def fetch_doc!(module, :moduledoc) when is_atom(module) do case Code.fetch_docs(module) do {:docs_v1, _, :elixir, _, %{"en" => doc}, _, _} -> doc - _ -> refute "Failed to fetch moduledoc for #{module}" + _ -> flunk("Failed to fetch moduledoc for #{module}") end end def fetch_doc!(module, {type, name, arity}) when is_atom(module) do - with( - {:docs_v1, _, :elixir, _, _, _, docs} <- Code.fetch_docs(module), - {_, _, _, %{"en" => doc}, _} <- List.keyfind(docs, {type, name, arity}, 0) - ) do - doc - else - _other -> refute "Failed to fetch doc for #{inspect({type, name, arity})} at #{module}" + docs = + case Code.fetch_docs(module) do + {:docs_v1, _, :elixir, _, _, _, docs} -> docs + {:error, reason} -> flunk("Failed to fetch doc for #{module}: #{inspect(reason)}") + end + + case List.keyfind(docs, {type, name, arity}, 0) do + nil -> + flunk(""" + Failed to fetch doc for #{inspect({type, name, arity})} at #{module}, docs: + #{Enum.map_join(docs, " \n", fn doc -> inspect(elem(doc, 0)) end)} + """) + + {_, _, _, %{"en" => doc}, _} -> + doc end end @@ -174,7 +182,7 @@ defmodule TypedStructor.TestCase do expected_types = format_types(expected) if String.length(String.trim(expected_types)) === 0 do - refute "Expected types are empty: #{inspect(expected)}" + flunk("Expected types are empty: #{inspect(expected)}") end assert expected_types == format_types(actual) diff --git a/test/typed_structor_test.exs b/test/typed_structor_test.exs index 1751942..3a31f26 100644 --- a/test/typed_structor_test.exs +++ b/test/typed_structor_test.exs @@ -379,15 +379,17 @@ defmodule TypedStructorTest do end test "raises an error when the parameter is not a atom" do - assert_raise ArgumentError, ~r[expected an atom, got: "age"], fn -> - defmodule Struct do - use TypedStructor - - typed_structor do - parameter "age" - end - end - end + assert_raise ArgumentError, + ~r|name must be an atom and opts must be a list|, + fn -> + defmodule Struct do + use TypedStructor + + typed_structor do + parameter "age" + end + end + end end end