From 4b6e7387fb931b0ee0b6da457b91d7c2a84c7c49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Zi=C4=99tkiewicz?= Date: Mon, 19 Jun 2023 14:18:55 +0200 Subject: [PATCH 01/17] Don't add specs unless recompilation_enabled is set --- lib/contexted/delegator.ex | 5 ++++- lib/contexted/mix/tasks/compile/contexted.ex | 9 ++------- lib/contexted/utils.ex | 13 +++++++++++++ 3 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 lib/contexted/utils.ex diff --git a/lib/contexted/delegator.ex b/lib/contexted/delegator.ex index 54a3a2c..d49cad0 100644 --- a/lib/contexted/delegator.ex +++ b/lib/contexted/delegator.ex @@ -29,6 +29,7 @@ defmodule Contexted.Delegator do enable_recompilation: true """ + alias Contexted.Utils alias Contexted.ModuleAnalyzer @doc """ @@ -75,7 +76,9 @@ defmodule Contexted.Delegator do Enum.map(functions, fn {name, _arity, args, doc, spec} -> quote do if unquote(doc), do: unquote(Code.string_to_quoted!(doc)) - if unquote(spec), do: unquote(Code.string_to_quoted!(spec)) + + if unquote(spec) && Utils.recompilation_enabled?(), + do: unquote(Code.string_to_quoted!(spec)) defdelegate unquote(name)(unquote_splicing(args)), to: unquote(module), diff --git a/lib/contexted/mix/tasks/compile/contexted.ex b/lib/contexted/mix/tasks/compile/contexted.ex index e1108ca..1dcda6a 100644 --- a/lib/contexted/mix/tasks/compile/contexted.ex +++ b/lib/contexted/mix/tasks/compile/contexted.ex @@ -1,7 +1,7 @@ defmodule Mix.Tasks.Compile.Contexted do use Mix.Task.Compiler - alias Contexted.Tracer + alias Contexted.{Tracer, Utils} alias Mix.Task.Compiler @moduledoc """ @@ -16,7 +16,7 @@ defmodule Mix.Tasks.Compile.Contexted do @spec run(any()) :: :ok def run(_argv) do if Enum.count(@contexts) > 0 do - if recompilation_enabled?() do + if Utils.recompilation_enabled?() do Compiler.after_compiler(:app, &Tracer.after_compiler/1) end @@ -26,9 +26,4 @@ defmodule Mix.Tasks.Compile.Contexted do :ok end end - - @spec recompilation_enabled? :: boolean() - defp recompilation_enabled? do - Application.get_env(:contexted, :enable_recompilation) - end end diff --git a/lib/contexted/utils.ex b/lib/contexted/utils.ex new file mode 100644 index 0000000..7021a7b --- /dev/null +++ b/lib/contexted/utils.ex @@ -0,0 +1,13 @@ +defmodule Contexted.Utils do + @moduledoc """ + The `Contexted.Utils` defines utils functions for other modules. + """ + + @doc """ + Checks is `enable_recompilation` option is set. + """ + @spec recompilation_enabled? :: boolean() + def recompilation_enabled? do + Application.get_env(:contexted, :enable_recompilation) + end +end From b5b45fbb6875db6ec0e59a202ddf5552d4aa9bf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Zi=C4=99tkiewicz?= Date: Mon, 19 Jun 2023 14:21:28 +0200 Subject: [PATCH 02/17] Sort aliases alphabetically --- lib/contexted/delegator.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/contexted/delegator.ex b/lib/contexted/delegator.ex index d49cad0..55ec405 100644 --- a/lib/contexted/delegator.ex +++ b/lib/contexted/delegator.ex @@ -29,8 +29,7 @@ defmodule Contexted.Delegator do enable_recompilation: true """ - alias Contexted.Utils - alias Contexted.ModuleAnalyzer + alias Contexted.{ModuleAnalyzer, Utils} @doc """ Delegates all public functions of the given module. From d27e7ccf6ad9080ff409485c4329a723a14089e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Zi=C4=99tkiewicz?= Date: Mon, 19 Jun 2023 14:22:56 +0200 Subject: [PATCH 03/17] Increase version --- README.md | 2 +- mix.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f158887..b6405ff 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ Add the following to your `mix.exs` file: ```elixir defp deps do [ - {:contexted, "~> 0.1.4"} + {:contexted, "~> 0.1.5"} ] end ``` diff --git a/mix.exs b/mix.exs index 8151acd..63a930b 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule Contexted.MixProject do app: :contexted, description: "Contexted is an Elixir library designed to streamline the management of complex Phoenix contexts in your projects, offering tools for module separation, subcontext creation, and auto-generating CRUD operations for improved code maintainability.", - version: "0.1.4", + version: "0.1.5", elixir: "~> 1.14", start_permanent: Mix.env() == :prod, deps: deps(), From c6e0c00b1c8e8c27697ce09c2272cea9f1a4b104 Mon Sep 17 00:00:00 2001 From: olafbado Date: Mon, 24 Jul 2023 15:42:42 +0200 Subject: [PATCH 04/17] fix: :adhesive_bandage: Add missing function clause for `format_type` function --- lib/contexted/module_analyzer.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/contexted/module_analyzer.ex b/lib/contexted/module_analyzer.ex index c17a0ca..286562a 100644 --- a/lib/contexted/module_analyzer.ex +++ b/lib/contexted/module_analyzer.ex @@ -113,6 +113,7 @@ defmodule Contexted.ModuleAnalyzer do defp format_type({:atom, _, atom}), do: ":#{Atom.to_string(atom)}" defp format_type({:type, _, type_name, _}), do: "#{type_name}()" + defp format_type({:user_type, _, atom, _}), do: ":#{Atom.to_string(atom)}" defp format_type({:remote_type, _, [{:atom, _, module}, {:atom, _, type}, _list]}) do if module == :elixir do From b80a563faef4d89f2e7cd020c6ce9771fcf82ff5 Mon Sep 17 00:00:00 2001 From: Szymon Soppa Date: Tue, 25 Jul 2023 09:58:07 +0200 Subject: [PATCH 05/17] Upgrades version to 0.1.6 --- README.md | 2 +- mix.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b6405ff..3628795 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ Add the following to your `mix.exs` file: ```elixir defp deps do [ - {:contexted, "~> 0.1.5"} + {:contexted, "~> 0.1.6"} ] end ``` diff --git a/mix.exs b/mix.exs index 63a930b..1dd81c8 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule Contexted.MixProject do app: :contexted, description: "Contexted is an Elixir library designed to streamline the management of complex Phoenix contexts in your projects, offering tools for module separation, subcontext creation, and auto-generating CRUD operations for improved code maintainability.", - version: "0.1.5", + version: "0.1.6", elixir: "~> 1.14", start_permanent: Mix.env() == :prod, deps: deps(), From 12ef2af1df8958f49a59f4251cc1082b229cff3a Mon Sep 17 00:00:00 2001 From: olafbado Date: Wed, 26 Jul 2023 09:17:04 +0200 Subject: [PATCH 06/17] Fix tracer bug --- lib/contexted/tracer.ex | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/contexted/tracer.ex b/lib/contexted/tracer.ex index 67127ce..c43d907 100644 --- a/lib/contexted/tracer.ex +++ b/lib/contexted/tracer.ex @@ -92,11 +92,17 @@ defmodule Contexted.Tracer do @spec map_module_to_context_module(module()) :: module() | nil defp map_module_to_context_module(module) do Enum.find(@contexts, fn context -> - stringified_context = Atom.to_string(context) + regex = build_regex(context) stringified_module = Atom.to_string(module) - String.contains?(stringified_module, stringified_context) || - stringified_context == stringified_module + Regex.match?(regex, stringified_module) end) end + + @spec build_regex(module()) :: Regex.t() + defp build_regex(context) do + context + |> Atom.to_string() + |> then(&~r/\b#{&1}\b/) + end end From a8836957da4bb8fe2da095a56350bf54ada36d03 Mon Sep 17 00:00:00 2001 From: Szymon Soppa Date: Wed, 26 Jul 2023 14:21:59 +0200 Subject: [PATCH 07/17] Release 0.1.7 --- README.md | 2 +- mix.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3628795..282e848 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ Add the following to your `mix.exs` file: ```elixir defp deps do [ - {:contexted, "~> 0.1.6"} + {:contexted, "~> 0.1.7"} ] end ``` diff --git a/mix.exs b/mix.exs index 1dd81c8..525aced 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule Contexted.MixProject do app: :contexted, description: "Contexted is an Elixir library designed to streamline the management of complex Phoenix contexts in your projects, offering tools for module separation, subcontext creation, and auto-generating CRUD operations for improved code maintainability.", - version: "0.1.6", + version: "0.1.7", elixir: "~> 1.14", start_permanent: Mix.env() == :prod, deps: deps(), From 1561e3502e332299b351c34bf1b4d29a9342c87b Mon Sep 17 00:00:00 2001 From: olafbado Date: Tue, 1 Aug 2023 12:06:20 +0200 Subject: [PATCH 08/17] fix: :bug: Check if a given atom in a type is a module Do not prefix an atom with `:` if the atom is a module --- lib/contexted/module_analyzer.ex | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/contexted/module_analyzer.ex b/lib/contexted/module_analyzer.ex index 286562a..a342817 100644 --- a/lib/contexted/module_analyzer.ex +++ b/lib/contexted/module_analyzer.ex @@ -3,6 +3,8 @@ defmodule Contexted.ModuleAnalyzer do The `Contexted.ModuleAnalyzer` defines utils functions that analyze and extract information from other modules. """ + @module_prefix "Elixir" + @doc """ Fetches the `@doc` definitions for all functions within the given module. """ @@ -111,7 +113,16 @@ defmodule Contexted.ModuleAnalyzer do Enum.map_join(types, " | ", &format_type/1) end - defp format_type({:atom, _, atom}), do: ":#{Atom.to_string(atom)}" + defp format_type({:atom, _, atom}) do + stringed_atom = Atom.to_string(atom) + + if is_module(stringed_atom) do + stringed_atom + else + ":#{stringed_atom}" + end + end + defp format_type({:type, _, type_name, _}), do: "#{type_name}()" defp format_type({:user_type, _, atom, _}), do: ":#{Atom.to_string(atom)}" @@ -122,4 +133,6 @@ defmodule Contexted.ModuleAnalyzer do "#{module}.#{type}()" end end + + defp is_module(stringed_atom), do: String.starts_with?(stringed_atom, @module_prefix) end From 22c256b27d1d5f69161f27d6dff01d7b13a4f45a Mon Sep 17 00:00:00 2001 From: olafbado Date: Tue, 1 Aug 2023 12:07:16 +0200 Subject: [PATCH 09/17] refactor: :label: Add typespec --- lib/contexted/module_analyzer.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/contexted/module_analyzer.ex b/lib/contexted/module_analyzer.ex index a342817..f528e08 100644 --- a/lib/contexted/module_analyzer.ex +++ b/lib/contexted/module_analyzer.ex @@ -134,5 +134,6 @@ defmodule Contexted.ModuleAnalyzer do end end + @spec is_module(String.t()) :: boolean() defp is_module(stringed_atom), do: String.starts_with?(stringed_atom, @module_prefix) end From 5cc09c364189ff213a2e000bdb244083b6c136f0 Mon Sep 17 00:00:00 2001 From: olafbado Date: Wed, 2 Aug 2023 10:32:13 +0200 Subject: [PATCH 10/17] fix: :adhesive_bandage: Add small fix --- lib/contexted/module_analyzer.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/contexted/module_analyzer.ex b/lib/contexted/module_analyzer.ex index f528e08..84aad7b 100644 --- a/lib/contexted/module_analyzer.ex +++ b/lib/contexted/module_analyzer.ex @@ -3,7 +3,7 @@ defmodule Contexted.ModuleAnalyzer do The `Contexted.ModuleAnalyzer` defines utils functions that analyze and extract information from other modules. """ - @module_prefix "Elixir" + @module_prefix "Elixir." @doc """ Fetches the `@doc` definitions for all functions within the given module. From dd4210402950504e68a38c2d789be6047e6fb21f Mon Sep 17 00:00:00 2001 From: Szymon Soppa Date: Wed, 2 Aug 2023 13:55:57 +0200 Subject: [PATCH 11/17] Release 0.1.8 --- README.md | 2 +- mix.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 282e848..4efa160 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ Add the following to your `mix.exs` file: ```elixir defp deps do [ - {:contexted, "~> 0.1.7"} + {:contexted, "~> 0.1.8"} ] end ``` diff --git a/mix.exs b/mix.exs index 525aced..be6887b 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule Contexted.MixProject do app: :contexted, description: "Contexted is an Elixir library designed to streamline the management of complex Phoenix contexts in your projects, offering tools for module separation, subcontext creation, and auto-generating CRUD operations for improved code maintainability.", - version: "0.1.7", + version: "0.1.8", elixir: "~> 1.14", start_permanent: Mix.env() == :prod, deps: deps(), From c73cc0854af5390bc2c6b757b065141eca62950c Mon Sep 17 00:00:00 2001 From: olafbado Date: Tue, 1 Aug 2023 07:12:31 +0200 Subject: [PATCH 12/17] fix: :bug: Fix spec formatting --- lib/contexted/delegator.ex | 2 +- lib/contexted/module_analyzer.ex | 37 +++++++++++++++++--------------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/lib/contexted/delegator.ex b/lib/contexted/delegator.ex index 55ec405..12ccca8 100644 --- a/lib/contexted/delegator.ex +++ b/lib/contexted/delegator.ex @@ -65,7 +65,7 @@ defmodule Contexted.Delegator do |> Enum.map(fn {name, arity} -> args = ModuleAnalyzer.generate_random_function_arguments(arity) doc = ModuleAnalyzer.get_function_doc(functions_docs, name, arity) - spec = ModuleAnalyzer.get_function_spec(functions_specs, name, arity) + spec = ModuleAnalyzer.get_function_spec(functions_specs, name, arity, module) {name, arity, args, doc, spec} end) diff --git a/lib/contexted/module_analyzer.ex b/lib/contexted/module_analyzer.ex index 84aad7b..5bc50f0 100644 --- a/lib/contexted/module_analyzer.ex +++ b/lib/contexted/module_analyzer.ex @@ -37,14 +37,14 @@ defmodule Contexted.ModuleAnalyzer do Finds and returns the `@spec` definition in string format for the specified function name and arity. Returns `nil` if the function is not found in the specs. """ - @spec get_function_spec([tuple()], atom(), non_neg_integer()) :: String.t() | nil - def get_function_spec(specs, function_name, arity) do + @spec get_function_spec([tuple()], atom(), non_neg_integer(), module()) :: String.t() | nil + def get_function_spec(specs, function_name, arity, module) do # Find the spec tuple in the specs spec = find_spec(specs, function_name, arity) # If spec is found, build the spec expression if spec do - build_spec(spec) + build_spec(spec, module) else nil end @@ -89,11 +89,11 @@ defmodule Contexted.ModuleAnalyzer do end) end - @spec build_spec(tuple()) :: String.t() - defp build_spec({{function_name, _arity}, spec}) do + @spec build_spec(tuple(), module()) :: String.t() + defp build_spec({{function_name, _arity}, spec}, module) do {:type, _, :fun, [arg_types, return_type]} = hd(spec) - arg_types_string = format_arg_types(arg_types) - return_type_string = format_type(return_type) + arg_types_string = format_arg_types(arg_types, module) + return_type_string = format_type(return_type, module) function_with_args = "#{function_name}(#{arg_types_string})" return_value = return_type_string @@ -101,16 +101,16 @@ defmodule Contexted.ModuleAnalyzer do "@spec #{function_with_args} :: #{return_value}" end - @spec format_arg_types(tuple()) :: String.t() - defp format_arg_types({:type, _, :product, []}), do: "" + @spec format_arg_types(tuple(), module()) :: String.t() + defp format_arg_types({:type, _, :product, []}, _module), do: "" - defp format_arg_types({:type, _, :product, arg_types}) do - Enum.map_join(arg_types, ",", &format_type/1) + defp format_arg_types({:type, _, :product, arg_types}, module) do + Enum.map_join(arg_types, ",", &format_type(&1, module)) end - @spec format_type(tuple()) :: String.t() - defp format_type({:type, _, :union, types}) do - Enum.map_join(types, " | ", &format_type/1) + @spec format_type(tuple(), module()) :: String.t() + defp format_type({:type, _, :union, types}, module) do + Enum.map_join(types, " | ", &format_type(&1, module)) end defp format_type({:atom, _, atom}) do @@ -123,10 +123,13 @@ defmodule Contexted.ModuleAnalyzer do end end - defp format_type({:type, _, type_name, _}), do: "#{type_name}()" - defp format_type({:user_type, _, atom, _}), do: ":#{Atom.to_string(atom)}" + defp format_type({:type, _, type_name, _}, _module), do: "#{type_name}()" - defp format_type({:remote_type, _, [{:atom, _, module}, {:atom, _, type}, _list]}) do + defp format_type({:user_type, _, atom, _}, module) do + "#{Atom.to_string(module)}.#{Atom.to_string(atom)}()" + end + + defp format_type({:remote_type, _, [{:atom, _, module}, {:atom, _, type}, _list]}, _module) do if module == :elixir do "#{type}()" else From 8c131f7786366d21aa62b4a7e40234f4aa20ce02 Mon Sep 17 00:00:00 2001 From: olafbado Date: Wed, 2 Aug 2023 14:05:56 +0200 Subject: [PATCH 13/17] fix: :adhesive_bandage: Fix after rebase --- lib/contexted/module_analyzer.ex | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/contexted/module_analyzer.ex b/lib/contexted/module_analyzer.ex index 5bc50f0..06b15d0 100644 --- a/lib/contexted/module_analyzer.ex +++ b/lib/contexted/module_analyzer.ex @@ -113,7 +113,13 @@ defmodule Contexted.ModuleAnalyzer do Enum.map_join(types, " | ", &format_type(&1, module)) end - defp format_type({:atom, _, atom}) do + defp format_type({:type, _, type_name, _}, _module), do: "#{type_name}()" + + defp format_type({:user_type, _, atom, _}, module) do + "#{Atom.to_string(module)}.#{Atom.to_string(atom)}()" + end + + defp format_type({:atom, _, atom}, _module) do stringed_atom = Atom.to_string(atom) if is_module(stringed_atom) do @@ -123,12 +129,6 @@ defmodule Contexted.ModuleAnalyzer do end end - defp format_type({:type, _, type_name, _}, _module), do: "#{type_name}()" - - defp format_type({:user_type, _, atom, _}, module) do - "#{Atom.to_string(module)}.#{Atom.to_string(atom)}()" - end - defp format_type({:remote_type, _, [{:atom, _, module}, {:atom, _, type}, _list]}, _module) do if module == :elixir do "#{type}()" From f6d81588bb17a3942df03594ccb00f903f382456 Mon Sep 17 00:00:00 2001 From: Szymon Soppa Date: Wed, 2 Aug 2023 16:04:15 +0200 Subject: [PATCH 14/17] Release 0.1.9 --- README.md | 2 +- mix.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4efa160..7e31716 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ Add the following to your `mix.exs` file: ```elixir defp deps do [ - {:contexted, "~> 0.1.8"} + {:contexted, "~> 0.1.9"} ] end ``` diff --git a/mix.exs b/mix.exs index be6887b..8c9e3f1 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule Contexted.MixProject do app: :contexted, description: "Contexted is an Elixir library designed to streamline the management of complex Phoenix contexts in your projects, offering tools for module separation, subcontext creation, and auto-generating CRUD operations for improved code maintainability.", - version: "0.1.8", + version: "0.1.9", elixir: "~> 1.14", start_permanent: Mix.env() == :prod, deps: deps(), From f239cd3d695f5655335ac9eae50016fcd27fed13 Mon Sep 17 00:00:00 2001 From: Szymon Soppa Date: Wed, 13 Sep 2023 17:13:16 +0200 Subject: [PATCH 15/17] Sets enable_recompilation config to false as default --- lib/contexted/utils.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/contexted/utils.ex b/lib/contexted/utils.ex index 7021a7b..08ddce6 100644 --- a/lib/contexted/utils.ex +++ b/lib/contexted/utils.ex @@ -8,6 +8,6 @@ defmodule Contexted.Utils do """ @spec recompilation_enabled? :: boolean() def recompilation_enabled? do - Application.get_env(:contexted, :enable_recompilation) + Application.get_env(:contexted, :enable_recompilation, false) end end From 65260399dc136a8e472fd3e2e088f626abaa56ef Mon Sep 17 00:00:00 2001 From: Szymon Soppa Date: Wed, 13 Sep 2023 17:15:15 +0200 Subject: [PATCH 16/17] Release 0.1.10 --- README.md | 2 +- mix.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7e31716..48b12b7 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ Add the following to your `mix.exs` file: ```elixir defp deps do [ - {:contexted, "~> 0.1.9"} + {:contexted, "~> 0.1.10"} ] end ``` diff --git a/mix.exs b/mix.exs index 8c9e3f1..91eb032 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule Contexted.MixProject do app: :contexted, description: "Contexted is an Elixir library designed to streamline the management of complex Phoenix contexts in your projects, offering tools for module separation, subcontext creation, and auto-generating CRUD operations for improved code maintainability.", - version: "0.1.9", + version: "0.1.10", elixir: "~> 1.14", start_permanent: Mix.env() == :prod, deps: deps(), From 1310f05da6841736c07dff10640f8fd0b30d7f20 Mon Sep 17 00:00:00 2001 From: Jakub Melkowski <9402720+Blatts12@users.noreply.github.com> Date: Tue, 17 Oct 2023 14:13:37 +0200 Subject: [PATCH 17/17] Use `Macro.to_string/1` instead of parsing specs by "hand" (#34) --- README.md | 2 +- lib/contexted/delegator.ex | 30 +++++++++++++- lib/contexted/module_analyzer.ex | 71 +++++++++----------------------- mix.exs | 2 +- 4 files changed, 50 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 48b12b7..8f41fad 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ Add the following to your `mix.exs` file: ```elixir defp deps do [ - {:contexted, "~> 0.1.10"} + {:contexted, "~> 0.1.11"} ] end ``` diff --git a/lib/contexted/delegator.ex b/lib/contexted/delegator.ex index 12ccca8..0efffba 100644 --- a/lib/contexted/delegator.ex +++ b/lib/contexted/delegator.ex @@ -65,7 +65,7 @@ defmodule Contexted.Delegator do |> Enum.map(fn {name, arity} -> args = ModuleAnalyzer.generate_random_function_arguments(arity) doc = ModuleAnalyzer.get_function_doc(functions_docs, name, arity) - spec = ModuleAnalyzer.get_function_spec(functions_specs, name, arity, module) + spec = ModuleAnalyzer.get_function_spec(functions_specs, name, arity) {name, arity, args, doc, spec} end) @@ -85,9 +85,37 @@ defmodule Contexted.Delegator do end end) + types = + case Code.Typespec.fetch_types(module) do + {:ok, types} -> + Enum.map(types, fn + {type_of_type, type} -> + Code.Typespec.type_to_quoted(type) + |> add_ast_for_type(type_of_type) + + _ -> + nil + end) + + _ -> + [] + end + # Combine the generated delegates into a single AST quote do + (unquote_splicing(types)) (unquote_splicing(delegates)) end end + + @spec add_ast_for_type(tuple(), atom()) :: tuple() + defp add_ast_for_type(ast, type_of_type) do + {:@, [context: Elixir, imports: [{1, Kernel}]], + [ + {type_of_type, [context: Elixir], + [ + ast + ]} + ]} + end end diff --git a/lib/contexted/module_analyzer.ex b/lib/contexted/module_analyzer.ex index 06b15d0..0591028 100644 --- a/lib/contexted/module_analyzer.ex +++ b/lib/contexted/module_analyzer.ex @@ -3,8 +3,6 @@ defmodule Contexted.ModuleAnalyzer do The `Contexted.ModuleAnalyzer` defines utils functions that analyze and extract information from other modules. """ - @module_prefix "Elixir." - @doc """ Fetches the `@doc` definitions for all functions within the given module. """ @@ -37,14 +35,14 @@ defmodule Contexted.ModuleAnalyzer do Finds and returns the `@spec` definition in string format for the specified function name and arity. Returns `nil` if the function is not found in the specs. """ - @spec get_function_spec([tuple()], atom(), non_neg_integer(), module()) :: String.t() | nil - def get_function_spec(specs, function_name, arity, module) do + @spec get_function_spec([tuple()], atom(), non_neg_integer()) :: String.t() | nil + def get_function_spec(specs, function_name, arity) do # Find the spec tuple in the specs spec = find_spec(specs, function_name, arity) # If spec is found, build the spec expression if spec do - build_spec(spec, module) + build_spec(spec) else nil end @@ -89,54 +87,23 @@ defmodule Contexted.ModuleAnalyzer do end) end - @spec build_spec(tuple(), module()) :: String.t() - defp build_spec({{function_name, _arity}, spec}, module) do - {:type, _, :fun, [arg_types, return_type]} = hd(spec) - arg_types_string = format_arg_types(arg_types, module) - return_type_string = format_type(return_type, module) - - function_with_args = "#{function_name}(#{arg_types_string})" - return_value = return_type_string - - "@spec #{function_with_args} :: #{return_value}" - end - - @spec format_arg_types(tuple(), module()) :: String.t() - defp format_arg_types({:type, _, :product, []}, _module), do: "" - - defp format_arg_types({:type, _, :product, arg_types}, module) do - Enum.map_join(arg_types, ",", &format_type(&1, module)) - end - - @spec format_type(tuple(), module()) :: String.t() - defp format_type({:type, _, :union, types}, module) do - Enum.map_join(types, " | ", &format_type(&1, module)) - end - - defp format_type({:type, _, type_name, _}, _module), do: "#{type_name}()" - - defp format_type({:user_type, _, atom, _}, module) do - "#{Atom.to_string(module)}.#{Atom.to_string(atom)}()" - end - - defp format_type({:atom, _, atom}, _module) do - stringed_atom = Atom.to_string(atom) - - if is_module(stringed_atom) do - stringed_atom - else - ":#{stringed_atom}" - end + @spec build_spec(tuple()) :: String.t() + defp build_spec({{function_name, _arity}, specs}) do + Enum.map_join(specs, "\n", fn spec -> + Code.Typespec.spec_to_quoted(function_name, spec) + |> add_spec_ast() + |> Macro.to_string() + end) end - defp format_type({:remote_type, _, [{:atom, _, module}, {:atom, _, type}, _list]}, _module) do - if module == :elixir do - "#{type}()" - else - "#{module}.#{type}()" - end + @spec add_spec_ast(tuple()) :: tuple() + defp add_spec_ast(ast) do + {:@, [context: Elixir, imports: [{1, Kernel}]], + [ + {:spec, [context: Elixir], + [ + ast + ]} + ]} end - - @spec is_module(String.t()) :: boolean() - defp is_module(stringed_atom), do: String.starts_with?(stringed_atom, @module_prefix) end diff --git a/mix.exs b/mix.exs index 91eb032..4d4ba4f 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule Contexted.MixProject do app: :contexted, description: "Contexted is an Elixir library designed to streamline the management of complex Phoenix contexts in your projects, offering tools for module separation, subcontext creation, and auto-generating CRUD operations for improved code maintainability.", - version: "0.1.10", + version: "0.1.11", elixir: "~> 1.14", start_permanent: Mix.env() == :prod, deps: deps(),