Skip to content

Commit fe2dec2

Browse files
committed
if there is a duplicate parameter then issue an error message
fix ChicagoBoss/ChicagoBoss#169
1 parent 33e3fd7 commit fe2dec2

File tree

2 files changed

+130
-40
lines changed

2 files changed

+130
-40
lines changed

src/boss_record_compiler.erl

Lines changed: 79 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
-module(boss_record_compiler).
22
-author('emmiller@gmail.com').
3+
-author('zkessin@gmail.com').
34
-define(DATABASE_MODULE, boss_db).
45
-define(PREFIX, "BOSSRECORDINTERNAL").
56
-ifdef(TEST).
67
-compile(export_all).
78
-endif.
8-
9+
-compile(export_all).
910
%-type limit() :: pos_integer()|all.
11+
-type error(T) :: {ok, T} | {error, string()}.
1012
-type syntaxTree() :: erl_syntax:syntaxTree().
1113
-type name() :: atom()|[byte(),...].
12-
-type fctn_n() :: {atom(), non_neg_integer()}.
13-
-type fctn() :: {function, atom(), atom(), non_neg_integer(), _}.
14+
-type fctn_n() :: {atom(), non_neg_integer()}.
15+
-type fctn() :: {function, atom(), atom(), non_neg_integer(), _}.
16+
-type pair() :: {atom(),atom()}.
17+
-type assoc() :: {has, {atom(), integer()}} |
18+
{has, {atom(), integer(), [any()]}} |
19+
{belongs_to, atom()}.
1420

1521

1622
-export([compile/1, compile/2, edoc_module/1, edoc_module/2, process_tokens/1, trick_out_forms/2]).
@@ -20,6 +26,19 @@
2026
-spec edoc_module(string(),_) -> {module(),_}.
2127
-spec process_tokens(nonempty_maybe_improper_list()) -> {nonempty_maybe_improper_list(),[{_,_}]}.
2228
-spec process_tokens(nonempty_maybe_improper_list(),[any()],[{_,_}]) -> {nonempty_maybe_improper_list(),[{_,_}]}.
29+
-spec make_counters([{counter, atom()}| {atom(), any()}]) -> [atom()].
30+
-spec make_generated_forms(atom(),
31+
[atom(),...],
32+
[pair(),...],
33+
[pair(),...],
34+
[ atom()],
35+
boolean()) -> error([syntaxTree()]).
36+
-spec make_generated_forms(atom(),
37+
[atom(),...],
38+
[pair(),...],
39+
[pair(),...],
40+
[ atom()]) -> error([syntaxTree()]).
41+
-spec has_duplicates([any()]) ->any().
2342
-spec trick_out_forms([any(),...],[any()]) -> [any(),...].
2443
-spec trick_out_forms([any(),...],[any()],[any()]) -> [any(),...].
2544
-spec trick_out_forms([any(),...],[any()],atom(),[any()],[any()]) -> [any(),...].
@@ -29,8 +48,8 @@
2948
-spec override_functions([syntaxTree()|fctn()],[syntaxTree()],[fctn_n()]) -> [any()].
3049
-spec export_forms([{atom(), pos_integer()}]) -> syntaxTree().
3150
-spec export_forms([{atom(), pos_integer()}],[syntaxTree()]) -> syntaxTree().
32-
-spec database_columns_forms(atom() ,[atom()],[{atom(),atom()}]) -> syntaxTree().
33-
-spec database_table_forms(atom(),[{atom(),atom()}]) -> syntaxTree().
51+
-spec database_columns_forms(atom() ,[atom()],[pair()]) -> syntaxTree().
52+
-spec database_table_forms(atom(),[pair()]) -> syntaxTree().
3453
-spec attribute_types_forms(atom() ,[{atom(), atom()}]) -> syntaxTree().
3554
-spec validate_types_forms(atom()) -> syntaxTree().
3655
-spec validate_forms(atom()) -> syntaxTree().
@@ -41,17 +60,14 @@
4160
-spec deep_get_forms() -> syntaxTree().
4261
-spec get_attributes_forms(atom(),[atom()]) -> syntaxTree().
4362
-spec set_attributes_forms(atom(),[atom()]) -> syntaxTree().
44-
-type assoc() :: {has, {atom(), integer()}} |
45-
{has, {atom(), integer(), [any()]}} |
46-
{belongs_to, atom()}.
4763
-spec association_forms(atom(),[assoc()]) -> [any(),...].
4864

4965
-spec belongs_to_list_forms([{atom(),any()}]) -> syntaxTree().
5066

51-
-spec belongs_to_list_make_list([{atom(),atom()}]) -> syntaxTree().
67+
-spec belongs_to_list_make_list([pair()]) -> syntaxTree().
5268
-spec attribute_names_forms(name(),[atom()]) -> syntaxTree().
5369
-spec has_one_forms(name(),atom(),[any()]) -> syntaxTree().
54-
-spec has_many_forms(atom(),atom(), pos_integer()|all|many, [any()]) -> syntaxTree().
70+
-spec has_many_forms(atom(),atom(), pos_integer(), [any()]) -> syntaxTree().
5571
-spec first_or_undefined_forms( syntaxTree()) -> syntaxTree().
5672
-spec has_many_application_forms(name(),{'tree',atom(),{'attr',_,[any()],'none' | {_,_,_}},_} | {'wrapper',atom(),{'attr',_,[any()],'none' | {_,_,_}},_},
5773
pos_integer(),
@@ -93,8 +109,10 @@ edoc_module(File) ->
93109

94110
edoc_module(File, Options) ->
95111
{ok, Forms, TokenInfo} = boss_compiler:parse(File, fun ?MODULE:process_tokens/1, []),
96-
edoc_extract:source(trick_out_forms(Forms, TokenInfo), edoc:read_comments(File),
97-
File, edoc_lib:get_doc_env([]), Options).
112+
edoc_extract:source(trick_out_forms(Forms, TokenInfo),
113+
edoc:read_comments(File),
114+
File, edoc_lib:get_doc_env([]),
115+
Options).
98116

99117
process_tokens(Tokens) ->
100118
process_tokens(Tokens, [], []).
@@ -116,26 +134,32 @@ trick_out_forms(Forms, TokenInfo) ->
116134
trick_out_forms(Forms, [], TokenInfo).
117135

118136
trick_out_forms([
119-
{attribute, _Pos, module, {ModuleName, Parameters}} = H
120-
| Forms], LeadingForms, TokenInfo) ->
137+
{attribute, _Pos, module, {ModuleName, Parameters}} = H
138+
| Forms],
139+
LeadingForms,
140+
TokenInfo) ->
121141
trick_out_forms(lists:reverse([H|LeadingForms]), Forms, ModuleName, Parameters, TokenInfo);
122142
trick_out_forms([H|T], LeadingForms, TokenInfo) ->
123143
trick_out_forms(T, [H|LeadingForms], TokenInfo).
124144

145+
has_duplicates(List) ->
146+
erlang:length(List) =/= sets:size(sets:from_list(List)).
147+
148+
125149
trick_out_forms(LeadingForms, Forms, ModuleName, Parameters, TokenInfo) ->
126-
Attributes = proplists:get_value(attributes, erl_syntax_lib:analyze_forms(LeadingForms ++ Forms), []),
150+
Attributes = proplists:get_value(attributes, erl_syntax_lib:analyze_forms(LeadingForms ++ Forms), []),
127151
[{eof, _Line}|ReversedOtherForms] = lists:reverse(Forms),
128-
UserForms = lists:reverse(ReversedOtherForms),
129-
Counters = make_counters(Attributes),
130-
131-
GeneratedForms =
152+
UserForms = lists:reverse(ReversedOtherForms),
153+
Counters = make_counters(Attributes),
154+
155+
{ok,GeneratedForms} =
132156
make_generated_forms(ModuleName, Parameters, TokenInfo, Attributes,
133157
Counters),
134158

135-
UserFunctionList = list_functions(UserForms),
136-
GeneratedFunctionList = list_functions(GeneratedForms),
159+
UserFunctionList = list_functions(UserForms),
160+
GeneratedFunctionList = list_functions(GeneratedForms),
137161

138-
GeneratedExportForms = export_forms(GeneratedFunctionList),
162+
GeneratedExportForms = export_forms(GeneratedFunctionList),
139163

140164
LeadingForms ++ GeneratedExportForms ++ UserForms ++
141165
override_functions(GeneratedForms, UserFunctionList).
@@ -147,23 +171,38 @@ make_counters(Attributes) ->
147171
(_, Acc) -> Acc
148172
end, [], Attributes).
149173

174+
make_generated_forms(ModuleName, Parameters, _TokenInfo, _Attributes,
175+
_Counters) ->
176+
make_generated_forms(ModuleName,
177+
Parameters, _TokenInfo, _Attributes,
178+
_Counters, has_duplicates(Parameters)).
179+
180+
make_generated_forms(ModuleName, Parameters, _TokenInfo, _Attributes,
181+
_Counters, _Dup = true) ->
182+
DupFields = Parameters -- sets:to_list(sets:from_list(Parameters)),
183+
lager:error("Unable to compile module ~p due to duplicate field(s) ~p",
184+
[ModuleName, DupFields]),
185+
{error, "Duplicate Fields"};
186+
150187
make_generated_forms(ModuleName, Parameters, TokenInfo, Attributes,
151-
Counters) ->
152-
lists:concat([attribute_names_forms(ModuleName, Parameters) ,
153-
attribute_types_forms(ModuleName, TokenInfo) ,
154-
database_columns_forms(ModuleName, Parameters, Attributes) ,
155-
database_table_forms(ModuleName, Attributes) ,
156-
validate_types_forms(ModuleName) ,
157-
validate_forms(ModuleName) ,
158-
save_forms(ModuleName) ,
159-
set_attributes_forms(ModuleName, Parameters) ,
160-
get_attributes_forms(ModuleName, Parameters) ,
161-
counter_getter_forms(Counters) ,
162-
counter_reset_forms(Counters) ,
163-
counter_incr_forms(Counters) ,
164-
association_forms(ModuleName, Attributes) ,
165-
parameter_getter_forms(Parameters)
166-
|deep_get_forms()]).
188+
Counters, _Dup = false) ->
189+
lager:notice("Module \"~p\" Parameters ~p Attributes~p", [ModuleName,Parameters, Attributes]),
190+
GF = attribute_names_forms(ModuleName, Parameters) ++
191+
attribute_types_forms(ModuleName, TokenInfo) ++
192+
database_columns_forms(ModuleName, Parameters, Attributes) ++
193+
database_table_forms(ModuleName, Attributes) ++
194+
validate_types_forms(ModuleName) ++
195+
validate_forms(ModuleName) ++
196+
save_forms(ModuleName) ++
197+
set_attributes_forms(ModuleName, Parameters) ++
198+
get_attributes_forms(ModuleName, Parameters) ++
199+
counter_getter_forms(Counters) ++
200+
counter_reset_forms(Counters) ++
201+
counter_incr_forms(Counters) ++
202+
association_forms(ModuleName, Attributes) ++
203+
parameter_getter_forms(Parameters) ++
204+
deep_get_forms(),
205+
{ok, GF}.
167206

168207
list_functions(Forms) ->
169208
list_functions(Forms, []).
@@ -490,8 +529,8 @@ has_one_forms(HasOne, ModuleName, Opts) ->
490529
])]))
491530
].
492531

493-
has_many_forms(HasMany, ModuleName, many, Opts) ->
494-
has_many_forms(HasMany, ModuleName, all, Opts);
532+
%% has_many_forms(HasMany, ModuleName, many, Opts) ->
533+
%% has_many_forms(HasMany, ModuleName, all, Opts);
495534
has_many_forms(HasMany, ModuleName, Limit, Opts) ->
496535
Sort = proplists:get_value(order_by, Opts, 'id'),
497536
IsDescending = proplists:get_value(descending, Opts, false),

test/boss_record_compiler_test.erl

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,57 @@
22
-include_lib("proper/include/proper.hrl").
33
-include_lib("eunit/include/eunit.hrl").
44

5+
-type error(T) :: {ok, T} | {error, string()}.
6+
-type syntaxTree() :: erl_syntax:syntaxTree().
7+
-type name() :: atom()|[byte(),...].
8+
-type fctn_n() :: {atom(), non_neg_integer()}.
9+
-type fctn() :: {function, atom(), atom(), non_neg_integer(), _}.
10+
-type pair() :: {atom(),atom()}.
11+
-type assoc() :: {has, {atom(), integer()}} |
12+
{has, {atom(), integer(), [any()]}} |
13+
{belongs_to, atom()}.
14+
15+
16+
17+
make_counters_test() ->
18+
?assert(proper:check_spec({boss_record_compiler,make_counters, 1},
19+
[{to_file, user}])),
20+
ok.
21+
22+
23+
prop_duplicated_forms() ->
24+
?FORALL({Parameters},
25+
{
26+
list(atom())
27+
},
28+
begin
29+
{TokenInfo, Attributes,Counters} = {[], [], [c]},
30+
ModuleName = 'test',
31+
case boss_record_compiler:make_generated_forms(ModuleName,Parameters, TokenInfo, Attributes,Counters) of
32+
{ok, GF} ->
33+
not(boss_record_compiler:has_duplicates(Parameters));
34+
{error, _} ->
35+
DupFields = Parameters -- sets:to_list(sets:from_list(Parameters)),
36+
boss_record_compiler:has_duplicates(Parameters)
37+
end
38+
end).
39+
40+
41+
make_generated_forms_test() ->
42+
?assert(proper:check_spec({boss_record_compiler,make_generated_forms, 5},
43+
[{to_file, user}])),
44+
?assert(proper:quickcheck(prop_duplicated_forms(),
45+
[{to_file, user}])),
46+
47+
ok.
48+
49+
has_duplicates_test() ->
50+
?assert( boss_record_compiler:has_duplicates([1,1])),
51+
?assertNot(boss_record_compiler:has_duplicates([1,2])),
52+
?assert(proper:check_spec({boss_record_compiler,has_duplicates, 1},
53+
[{to_file, user}])),
54+
ok.
55+
556

657
list_functions_test() ->
758
?assert(proper:check_spec({boss_record_compiler,list_functions, 1},

0 commit comments

Comments
 (0)