Skip to content

Document structs, maps, keyword lists via testing & fix accidental key replacement bug #73

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Sep 10, 2022
53 changes: 44 additions & 9 deletions lib/representer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,11 @@ defmodule Representer do

conditions = Macro.prewalk(conditions, &remove_type_parentheses/1)
# typespecs may receive variable types as arguments if they are constrained by :when
vars = Enum.map(conditions, fn {var, _type} -> var end)
{:ok, represented, _} = Mapping.get_placeholder(represented, vars)
{conditions, represented} =
Enum.map_reduce(conditions, represented, fn {var, type}, represented ->
{:ok, represented, var} = Mapping.get_placeholder(represented, var)
{{var, type}, represented}
end)

definition = Macro.prewalk(definition, &remove_type_parentheses/1)
meta = Keyword.put(meta, :visited?, true)
Expand Down Expand Up @@ -277,6 +280,43 @@ defmodule Representer do
do_use_existing_placeholders(node, represented)
end

# module attributes that hold module or function names inside of key-value pairs.
# Note that module attributes that hold module or function names outside of key-value pairs are excluded from this list.
@attributes_with_key_value_pairs_that_hold_module_or_function_names ~w(compile optional_callbacks dialyzer)a
defp do_use_existing_placeholders({:@, meta, [{name, meta2, value}]}, represented)
when name in @attributes_with_key_value_pairs_that_hold_module_or_function_names do
handle_list = fn list, represented ->
Enum.map_reduce(list, represented, fn elem, represented ->
case elem do
{key, value} ->
key = Mapping.get_existing_placeholder(represented, key) || key
{{key, value}, represented}

_ ->
{elem, represented}
end
end)
end

{value, represented} =
case value do
# e.g. @dialyzer {:nowarn_function, function_name: 0}
[{elem1, elem2}] when is_list(elem2) ->
{elem2, represented} = handle_list.(elem2, represented)
{[{elem1, elem2}], represented}

# e.g. @optional_callbacks [function_name1: 0]
[elem1] when is_list(elem1) ->
{elem1, represented} = handle_list.(elem1, represented)
{[elem1], represented}

value ->
{value, represented}
end

{{:@, meta, [{name, meta2, value}]}, represented}
end

# module names
defp do_use_existing_placeholders({:__aliases__, meta, module_name}, represented)
when is_list(module_name) do
Expand Down Expand Up @@ -336,13 +376,8 @@ defmodule Representer do
{{{:., meta2, [{:__MODULE__, meta3, args3}, function_name]}, meta, context}, represented}
end

# replace keys in key value pairs
defp do_use_existing_placeholders({key, value}, represented) when is_atom(key) do
key = Mapping.get_existing_placeholder(represented, key) || key
{{key, value}, represented}
end

defp do_use_existing_placeholders(node, represented), do: {node, represented}
defp do_use_existing_placeholders(node, represented),
do: {node, represented}

defp drop_docstring({:__block__, meta, children}) do
children =
Expand Down
18 changes: 17 additions & 1 deletion test/representer_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,22 @@ defmodule RepresenterTest do
test "module_attributes" do
test_directory("module_attributes")
end

test "maps" do
test_directory("maps")
end

test "keyword_lists" do
test_directory("keyword_lists")
end

test "user_defined_structs" do
test_directory("user_defined_structs")
end

test "standard_lib_structs" do
test_directory("standard_lib_structs")
end
end

defp test_directory(dir) do
Expand All @@ -46,7 +62,7 @@ defmodule RepresenterTest do
input = Path.join(["./test_data", dir, "input.ex"])
assert {:ok, _} = input |> File.read!() |> Code.string_to_quoted()

expected_representation = Path.join(["./test_data", dir, "expected_representation.txt"])
expected_representation = Path.join(["./test_data", dir, "expected_representation.ex"])
expected_mapping = Path.join(["./test_data", dir, "expected_mapping.json"])

output_representation = Path.join(["./test_output", dir, "representation.txt"])
Expand Down
6 changes: 6 additions & 0 deletions test_data/keyword_lists/expected_mapping.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"Placeholder_1": "BitStuff",
"placeholder_2": "powers_of_two",
"placeholder_3": "only",
"placeholder_4": "zero"
}
9 changes: 9 additions & 0 deletions test_data/keyword_lists/expected_representation.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
defmodule Placeholder_1 do
import Bitwise, only: [:bsl]

def placeholder_2 do
placeholder_3 = "something"
placeholder_4 = 1
[zero: placeholder_4, one: bsl(1, 1), two: bsl(1, 2), three: bsl(1, 3)]
end
end
15 changes: 15 additions & 0 deletions test_data/keyword_lists/input.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
defmodule BitStuff do
import Bitwise, only: [:bsl]

def powers_of_two do
only = "something"
zero = 1

[
zero: zero,
one: bsl(1, 1),
two: bsl(1, 2),
three: bsl(1, 3),
]
end
end
8 changes: 8 additions & 0 deletions test_data/maps/expected_mapping.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Placeholder_1": "Clock",
"placeholder_2": "midnight",
"placeholder_3": "hour",
"placeholder_4": "tick",
"placeholder_5": "clock",
"placeholder_6": "minute"
}
11 changes: 11 additions & 0 deletions test_data/maps/expected_representation.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
defmodule Placeholder_1 do
def placeholder_2() do
placeholder_3 = 0
%{hour: placeholder_3, minute: 0, second: 0}
end

def placeholder_4(placeholder_5) do
placeholder_6 = placeholder_5.minute + 1
%{minute: placeholder_6 | placeholder_5}
end
end
16 changes: 16 additions & 0 deletions test_data/maps/input.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
defmodule Clock do
def midnight() do
hour = 0

%{
hour: hour,
minute: 0,
second: 0
}
end

def tick(clock) do
minute = clock.minute + 1
%{minute: minute | clock}
end
end
6 changes: 6 additions & 0 deletions test_data/standard_lib_structs/expected_mapping.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"Placeholder_1": "Clock",
"placeholder_2": "midnight",
"placeholder_3": "hour",
"placeholder_4": "hour_regex"
}
19 changes: 19 additions & 0 deletions test_data/standard_lib_structs/expected_representation.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
defmodule Placeholder_1 do
def placeholder_2() do
placeholder_3 = 0
%Time{calendar: Calendar.ISO, hour: placeholder_3, microsecond: {0, 0}, minute: 0, second: 0}
end

def placeholder_4() do
%Regex{
opts: "",
re_pattern:
{:re_pattern, 0, 0, 0,
<<69, 82, 67, 80, 81, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 58, 0, 0,
0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
131, 0, 13, 7, 7, 29, 58, 7, 7, 29, 58, 7, 7, 120, 0, 13, 0>>},
re_version: {"8.44 2020-02-12", :little},
source: "\\d\\d:\\d\\d:\\d\\d"
}
end
end
26 changes: 26 additions & 0 deletions test_data/standard_lib_structs/input.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
defmodule Clock do
def midnight() do
hour = 0

%Time{
calendar: Calendar.ISO,
hour: hour,
microsecond: {0, 0},
minute: 0,
second: 0
}
end

def hour_regex() do
%Regex{
opts: "",
re_pattern: {:re_pattern, 0, 0, 0,
<<69, 82, 67, 80, 81, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 255, 255, 255, 255,
255, 255, 255, 255, 0, 0, 58, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 131, 0, 13,
7, 7, 29, 58, 7, 7, 29, 58, 7, 7, 120, 0, 13, 0>>},
re_version: {"8.44 2020-02-12", :little},
source: "\\d\\d:\\d\\d:\\d\\d"
}
end
end
11 changes: 11 additions & 0 deletions test_data/user_defined_structs/expected_mapping.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"Placeholder_1": "Car",
"Placeholder_2": "VroomVroom",
"Placeholder_3": "Automobile",
"placeholder_4": "create",
"placeholder_5": "color",
"placeholder_6": "repaint",
"placeholder_7": "car",
"placeholder_8": "fake_create",
"placeholder_9": "authorize_driver"
}
24 changes: 24 additions & 0 deletions test_data/user_defined_structs/expected_representation.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
defmodule Placeholder_1 do
defstruct [:wheels, :doors, :color]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eventually, we will want these user defined keys to be replaced as mentionned in #37, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

end

defmodule Placeholder_2 do
defstruct [:cars, :driving_licence]
alias Placeholder_1, as: Placeholder_3

def placeholder_4(placeholder_5) do
%Placeholder_1{color: placeholder_5, wheels: 4, doors: 2}
end

def placeholder_6(placeholder_7, placeholder_5) do
%Placeholder_3{color: placeholder_5 | placeholder_7}
end

def placeholder_8(placeholder_5) do
%{color: placeholder_5, wheels: 4, doors: 2}
end

def placeholder_9() do
%__MODULE__{driving_licence: "I can totally drive a car"}
end
end
27 changes: 27 additions & 0 deletions test_data/user_defined_structs/input.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
defmodule Car do
defstruct [:wheels, :doors, :color]
end

defmodule VroomVroom do
defstruct [:cars, :driving_licence]
alias Car, as: Automobile

def create(color) do
%Car{color: color, wheels: 4, doors: 2}
end

def repaint(car, color) do
%Automobile{color: color | car}
end

def fake_create(color) do
# map, not a struct, hence "fake"
%{color: color, wheels: 4, doors: 2}
end

def authorize_driver() do
%__MODULE__{
driving_licence: "I can totally drive a car"
}
end
end