aboutsummaryrefslogtreecommitdiffstats
path: root/lib/edoc/src/edoc_tags.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/edoc/src/edoc_tags.erl')
-rw-r--r--lib/edoc/src/edoc_tags.erl133
1 files changed, 127 insertions, 6 deletions
diff --git a/lib/edoc/src/edoc_tags.erl b/lib/edoc/src/edoc_tags.erl
index c0b861e08a..def39ee34c 100644
--- a/lib/edoc/src/edoc_tags.erl
+++ b/lib/edoc/src/edoc_tags.erl
@@ -31,7 +31,8 @@
-module(edoc_tags).
-export([tags/0, tags/1, tag_names/0, tag_parsers/0, scan_lines/2,
- filter_tags/3, check_tags/4, parse_tags/4]).
+ filter_tags/2, filter_tags/3, check_tags/4, parse_tags/4,
+ check_types/3]).
-import(edoc_report, [report/4, warning/4, error/3]).
@@ -201,6 +202,9 @@ append_lines([]) -> [].
%% Filtering out unknown tags.
+filter_tags(Ts, Tags) ->
+ filter_tags(Ts, Tags, no).
+
filter_tags(Ts, Tags, Where) ->
filter_tags(Ts, Tags, Where, []).
@@ -211,7 +215,8 @@ filter_tags([#tag{name = N, line = L} = T | Ts], Tags, Where, Ts1) ->
true ->
filter_tags(Ts, Tags, Where, [T | Ts1]);
false ->
- warning(L, Where, "tag @~s not recognized.", [N]),
+ [warning(L, Where, "tag @~s not recognized.", [N]) ||
+ Where =/= no],
filter_tags(Ts, Tags, Where, Ts1)
end;
filter_tags([], _, _, Ts) ->
@@ -320,12 +325,24 @@ parse_contact(Data, Line, _Env, _Where) ->
Info
end.
-parse_typedef(Data, Line, _Env, _Where) ->
+parse_typedef(Data, Line, _Env, Where) ->
Def = edoc_parser:parse_typedef(Data, Line),
- {#t_typedef{name = #t_name{name = T}}, _} = Def,
- case edoc_types:is_predefined(T) of
+ {#t_typedef{name = #t_name{name = T}, args = As}, _} = Def,
+ NAs = length(As),
+ case edoc_types:is_predefined(T, NAs) of
true ->
- throw_error(Line, {"redefining built-in type '~w'.", [T]});
+ case
+ edoc_types:is_new_predefined(T, NAs)
+ orelse edoc_types:is_predefined_otp_type(T, NAs)
+ of
+ false ->
+ throw_error(Line, {"redefining built-in type '~w'.",
+ [T]});
+ true ->
+ warning(Line, Where, "redefining built-in type '~w'.",
+ [T]),
+ Def
+ end;
false ->
Def
end.
@@ -384,3 +401,107 @@ throw_error(L, file_not_string) ->
throw_error(L, "expected file name as a string");
throw_error(L, D) ->
throw({error, L, D}).
+
+%% Checks local types.
+
+-record(parms, {tab, warn, file, line}).
+
+check_types(Entries0, Opts, File) ->
+ Entries = edoc_data:hidden_filter(Entries0, Opts),
+ Tags = edoc_data:get_all_tags(Entries),
+ DT = ets:new(types, [bag]),
+ _ = [add_type(DT, Name, As, File, Line) ||
+ #tag{line = Line,
+ data = {#t_typedef{name = Name, args = As},_}} <- Tags],
+ Warn = proplists:get_value(report_missing_type, Opts,
+ ?REPORT_MISSING_TYPE) =:= true,
+ P = #parms{tab = DT, warn = Warn, file = File, line = 0},
+ try check_types(Tags, P)
+ after true = ets:delete(DT)
+ end.
+
+add_type(DT, Name, Args, File, Line) ->
+ NArgs = length(Args),
+ TypeName = {Name, NArgs},
+ case lists:member(TypeName, ets:lookup(DT, Name)) of
+ true ->
+ #t_name{name = N} = Name,
+ type_warning(Line, File, "duplicated type", N, NArgs);
+ false ->
+ ets:insert(DT, {Name, NArgs})
+ end.
+
+check_types([], _P)->
+ ok;
+check_types([Tag | Tags], P) ->
+ check_type(Tag, P, Tags).
+
+check_type(#tag{line = L, data = Data}, P0, Ts) ->
+ P = P0#parms{line = L},
+ case Data of
+ {#t_typedef{type = Type, defs = Defs},_} ->
+ check_type(Type, P, Defs++Ts);
+ #t_spec{type = Type, defs = Defs} ->
+ check_type(Type, P, Defs++Ts);
+ _->
+ check_types(Ts, P0)
+ end;
+check_type(#t_def{type = Type}, P, Ts) ->
+ check_type(Type, P, Ts);
+check_type(#t_type{name = Name, args = Args}, P, Ts) ->
+ check_used_type(Name, Args, P),
+ check_types(Args++Ts, P);
+check_type(#t_var{}, P, Ts) ->
+ check_types(Ts, P);
+check_type(#t_fun{args = Args, range = Range}, P, Ts) ->
+ check_type(Range, P, Args++Ts);
+check_type(#t_tuple{types = Types}, P, Ts) ->
+ check_types(Types ++Ts, P);
+check_type(#t_list{type = Type}, P, Ts) ->
+ check_type(Type, P, Ts);
+check_type(#t_nil{}, P, Ts) ->
+ check_types(Ts, P);
+check_type(#t_paren{type = Type}, P, Ts) ->
+ check_type(Type, P, Ts);
+check_type(#t_nonempty_list{type = Type}, P, Ts) ->
+ check_type(Type, P, Ts);
+check_type(#t_atom{}, P, Ts) ->
+ check_types(Ts, P);
+check_type(#t_integer{}, P, Ts) ->
+ check_types(Ts, P);
+check_type(#t_integer_range{}, P, Ts) ->
+ check_types(Ts, P);
+check_type(#t_binary{}, P, Ts) ->
+ check_types(Ts, P);
+check_type(#t_float{}, P, Ts) ->
+ check_types(Ts, P);
+check_type(#t_union{types = Types}, P, Ts) ->
+ check_types(Types++Ts, P);
+check_type(#t_record{fields = Fields}, P, Ts) ->
+ check_types(Fields++Ts, P);
+check_type(#t_field{type = Type}, P, Ts) ->
+ check_type(Type, P, Ts);
+check_type(undefined, P, Ts) ->
+ check_types(Ts, P).
+
+check_used_type(#t_name{name = N, module = Mod}=Name, Args, P) ->
+ NArgs = length(Args),
+ TypeName = {Name, NArgs},
+ DT = P#parms.tab,
+ case
+ Mod =/= []
+ orelse lists:member(TypeName, ets:lookup(DT, Name))
+ orelse edoc_types:is_predefined(N, NArgs)
+ orelse edoc_types:is_predefined_otp_type(N, NArgs)
+ of
+ true ->
+ ok;
+ false ->
+ #parms{warn = W, line = L, file = File} = P,
+ %% true = ets:insert(DT, TypeName),
+ [type_warning(L, File, "missing type", N, NArgs) || W]
+ end.
+
+type_warning(Line, File, S, N, NArgs) ->
+ AS = ["/"++integer_to_list(NArgs) || NArgs > 0],
+ warning(Line, File, S++" ~w~s", [N, AS]).