Skip to content

Commit

Permalink
Tests, changelog, docs
Browse files Browse the repository at this point in the history
  • Loading branch information
altjohndev committed Oct 29, 2024
1 parent f626923 commit 5457c60
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 31 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)

## [0.0.3] - 2024-XX-XX

### Added

- `UnionTypespec.union_typep/1` macro.
65 changes: 35 additions & 30 deletions lib/union_typespec.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ defmodule UnionTypespec do
A simple, tiny, compile time-only library for defining an Elixir `@type` whose values are one of a fixed set of options.
"""

@moduledoc since: "0.0.1"

@doc """
Transforms an enumerable (like a list of atoms) into an AST for use in a typespec.
Expand All @@ -12,56 +14,59 @@ defmodule UnionTypespec do
Example:
defmodule MyModule do
import UnionTypespec, only: [union_type: 1]
defmodule MyModule do
import UnionTypespec, only: [union_type: 1]
@permissions [:view, :edit, :admin]
union_type permission :: @permissions
@permissions [:view, :edit, :admin]
union_type permission :: @permissions
@spec random_permission() :: permission()
def random_permission, do: Enum.random(@permissions)
end
@spec random_permission() :: permission()
def random_permission, do: Enum.random(@permissions)
end
"""
@doc since: "0.0.1"
defmacro union_type({:"::", _, [{name, _, _}, data]}) do
quote bind_quoted: [data: data, name: name] do
@type unquote({name, [], Elixir}) :: unquote(UnionTypespec.union_type_ast(data))
end
end

@doc """
Same as `union_type/1` but for a private type (`@typep`).
Unquote on the right-hand side of `@type` if you prefer the explicitness of that syntax over the `union_type` macro.
Example:
defmodule MyModule do
import UnionTypespec, only: [union_typep: 1]
defmodule MyModule do
@permissions [:view, :edit, :admin]
@type permission :: unquote(UnionTypespec.union_type_ast(@permissions))
@permissions [:view, :edit, :admin]
union_typep permission :: @permissions
@spec random_permission() :: permission()
defp random_permission, do: Enum.random(@permissions)
end
@spec random_permission() :: permission()
def random_permission, do: Enum.random(@permissions)
end
"""
defmacro union_typep({:"::", _, [{name, _, _}, data]}) do
quote bind_quoted: [data: data, name: name] do
@typep unquote({name, [], Elixir}) :: unquote(UnionTypespec.union_type_ast(data))
end
end
@doc since: "0.0.1"
def union_type_ast([item]), do: item
def union_type_ast([head | tail]), do: {:|, [], [head, union_type_ast(tail)]}

@doc """
Unquote on the right-hand side of `@type` if you prefer the explicitness of that syntax over the `union_type` macro.
Same as `#{inspect(__MODULE__)}.union_type/1` but for a private type (`@typep`).
Example:
defmodule MyModule do
@permissions [:view, :edit, :admin]
@type permission :: unquote(UnionTypespec.union_type_ast(@permissions))
defmodule MyModule do
import UnionTypespec, only: [union_typep: 1]
@spec random_permission() :: permission()
def random_permission, do: Enum.random(@permissions)
end
@permissions [:view, :edit, :admin]
union_typep permission :: @permissions
@spec random_permission() :: permission()
defp random_permission, do: Enum.random(@permissions)
end
"""
def union_type_ast([item]), do: item
def union_type_ast([head | tail]), do: {:|, [], [head, union_type_ast(tail)]}
@doc since: "0.0.3"
defmacro union_typep({:"::", _, [{name, _, _}, data]}) do
quote bind_quoted: [data: data, name: name] do
@typep unquote({name, [], Elixir}) :: unquote(UnionTypespec.union_type_ast(data))
end
end
end
8 changes: 8 additions & 0 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ defmodule UnionTypespec.MixProject do
aliases: aliases(),
test_coverage: [tool: ExCoveralls],
elixirc_paths: elixirc_paths(Mix.env()),
docs: docs(),
preferred_cli_env: [
check: :test,
coveralls: :test,
Expand Down Expand Up @@ -73,4 +74,11 @@ defmodule UnionTypespec.MixProject do
]
]
end

defp docs do
[
extras: ~w(CHANGELOG.md README.md),
main: "readme"
]
end
end
3 changes: 2 additions & 1 deletion test/support/some_module.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
defmodule SomeModule do
defmodule UnionTypespec.Test.SampleModule do
@moduledoc false

import UnionTypespec, only: [union_type: 1, union_typep: 1]

@statuses [:read, :unread, :deleted]
Expand Down
32 changes: 32 additions & 0 deletions test/union_typespec_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
defmodule UnionTypespecTest do
use ExUnit.Case, async: true

alias UnionTypespec.Test.SampleModule

describe "union_type/1" do
test "creates a valid @type from list" do
assert fetch!(:type, :status) == "status() :: :read | :unread | :deleted"
end
end

describe "union_type_ast/1" do
test "when unquoted in a @type, creates valid value of type" do
assert fetch!(:type, :permission) == "permission() :: :view | :edit | :admin"
end
end

describe "union_typep/1" do
test "creates a valid @typep from list" do
assert fetch!(:typep, :role) == "role() :: :user | :admin"
end
end

defp fetch!(type_of_type, type_name) do
assert {:ok, types} = Code.Typespec.fetch_types(SampleModule)
assert {^type_of_type, type} = Enum.find(types, fn {_, {name, _, _}} -> name == type_name end)

type
|> Code.Typespec.type_to_quoted()
|> Macro.to_string()
end
end

0 comments on commit 5457c60

Please sign in to comment.