1%% =====================================================================
2%% Licensed under the Apache License, Version 2.0 (the "License"); you may
3%% not use this file except in compliance with the License. You may obtain
4%% a copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>
5%%
6%% Unless required by applicable law or agreed to in writing, software
7%% distributed under the License is distributed on an "AS IS" BASIS,
8%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9%% See the License for the specific language governing permissions and
10%% limitations under the License.
11%%
12%% Alternatively, you may use this file under the terms of the GNU Lesser
13%% General Public License (the "LGPL") as published by the Free Software
14%% Foundation; either version 2.1, or (at your option) any later version.
15%% If you wish to allow use of your version of this file only under the
16%% terms of the LGPL, you should delete the provisions above and replace
17%% them with the notice and other provisions required by the LGPL; see
18%% <http://www.gnu.org/licenses/>. If you do not delete the provisions
19%% above, a recipient may use your version of this file under the terms of
20%% either the Apache License or the LGPL.
21%%
22%% @private
23%% @copyright 2001-2003 Richard Carlsson
24%% @author Richard Carlsson <carlsson.richard@gmail.com>
25%% @see edoc
26%% @end
27%% =====================================================================
28
29%% @doc EDoc tag scanning.
30
31%% TODO: tag/macro for including the code of a function as `<pre>'-text.
32%% TODO: consider new tag: @license text
33
34-module(edoc_tags).
35
36-export([tags/0, tags/1, tag_names/0, tag_parsers/0, scan_lines/2,
37	 filter_tags/2, filter_tags/3, check_tags/4, parse_tags/4,
38	 check_types/3]).
39
40-export_type([tag_flag/0]).
41
42-include("edoc.hrl").
43-include("edoc_types.hrl").
44
45
46%% Tags are described by {Name, Parser, Flags}.
47%%   Name = atom()
48%%   Parser = text | xml | (Text,Line,Where) -> term()
49%%   Flags = [Flag]
50%%   Flag = module | function | overview | single
51%%
52%% Note that the pseudo-tag '@clear' is not listed here.
53%% (Cf. the function 'filter_tags'.)
54%%
55%% Rejected tag suggestions:
56%% - @keywords (never up to date; free text search is better)
57%% - @uses [modules] (never up to date; false dependencies)
58%% - @maintainer (never up to date; duplicates author info)
59%% - @contributor (unnecessary; mention in normal documentation)
60%% - @creator (unnecessary; already have copyright/author)
61%% - @history (never properly updated; use version control etc.)
62%% - @category (useless; superseded by keywords or free text search)
63
64-type tag() :: author | copyright | deprecated | doc | docfile | 'end' | equiv | headerfile
65	     | hidden | param | private | reference | returns | see | since | spec | throws
66	     | title | 'TODO' | todo | type | version.
67-type parser() :: text | xml | fun().
68-type tag_flag() :: module | footer | function | overview | single.
69
70-spec tags() -> [{tag(), parser(), [tag_flag()]}].
71tags() ->
72    All = [module,footer,function,overview],
73    [{author, fun parse_contact/4, [module,overview]},
74     {copyright, text, [module,overview,single]},
75     {deprecated, xml, [module,function,single]},
76     {doc, xml,	[module,function,overview,single]},
77     {docfile, fun parse_file/4, All},
78     {'end', text, All},
79     {equiv, fun parse_expr/4, [function,single]},
80     {headerfile, fun parse_header/4, All},
81     {hidden, text, [module,function,single]},
82     {param, fun parse_param/4, [function]},
83     {private, text, [module,function,single]},
84     {reference, xml, [module,footer,overview]},
85     {returns, xml, [function,single]},
86     {see, fun parse_see/4, [module,function,overview]},
87     {since, text, [module,function,overview,single]},
88     {spec, fun parse_spec/4, [function,single]},
89     {throws, fun parse_throws/4, [function,single]},
90     {title, text, [overview,single]},
91     {'TODO', xml, All},
92     {todo, xml, All},
93     {type, fun parse_typedef/4, [module,footer,function]},
94     {version, text, [module,overview,single]}].
95
96aliases('TODO') -> todo;
97aliases(return) -> returns;
98aliases(T) -> T.
99
100%% Selecting tags based on flags.
101tags(Flag) ->
102    [T || {T,_,Fs} <- tags(), lists:member(Flag, Fs)].
103
104%% The set of known tags.
105tag_names() ->
106    [T || {T,_,_} <- tags()].
107
108%% The pairs of tags and their parsers.
109tag_parsers() ->
110    [{T,F} || {T,F,_} <- tags()].
111
112
113%% Scanning lines of comment text.
114
115scan_lines(Ss, L) ->
116    lists:reverse(scan_lines(Ss, L, [])).
117
118scan_lines([S | Ss], L, As) ->
119    scan_lines(S, Ss, L, As);
120scan_lines([], _L, As) ->
121    As.
122
123%% Looking for a leading '@', skipping whitespace.
124%% Also accept "TODO:" at start of line as equivalent to "@TODO".
125
126scan_lines([$\s | Cs], Ss, L, As) -> scan_lines(Cs, Ss, L, As);
127scan_lines([$\t | Cs], Ss, L, As) -> scan_lines(Cs, Ss, L, As);
128scan_lines([$@ | Cs], Ss, L, As) -> scan_tag(Cs, Ss, L, As, []);
129scan_lines(("TODO:"++_)=Cs, Ss, L, As) -> scan_tag(Cs, Ss, L, As, []);
130scan_lines(_, Ss, L, As) -> scan_lines(Ss, L + 1, As).
131
132%% Scanning chars following '@', accepting only nonempty valid names.
133%% See edoc_lib:is_name/1 for details on what is a valid name. In tags
134%% we also allow the initial letter to be uppercase or underscore.
135
136scan_tag([C | Cs], Ss, L, As, Ts) when C >= $a, C =< $z ->
137    scan_tag_1(Cs, Ss, L, As, [C | Ts]);
138scan_tag([C | Cs], Ss, L, As, Ts) when C >= $A, C =< $Z ->
139    scan_tag_1(Cs, Ss, L, As, [C | Ts]);
140scan_tag([C | Cs], Ss, L, As, Ts) when C >= $\300, C =< $\377,
141					 C =/= $\327, C =/= $\367 ->
142    scan_tag_1(Cs, Ss, L, As, [C | Ts]);
143scan_tag([$_ | Cs], Ss, L, As, Ts) ->
144    scan_tag_1(Cs, Ss, L, As, [$_ | Ts]);
145scan_tag(_Cs, Ss, L, As, _Ts) ->
146    scan_lines(Ss, L + 1, As).    % not a valid name
147
148scan_tag_1([C | Cs], Ss, L, As, Ts) when C >= $a, C =< $z ->
149    scan_tag_1(Cs, Ss, L, As, [C | Ts]);
150scan_tag_1([C | Cs], Ss, L, As, Ts) when C >= $A, C =< $Z ->
151    scan_tag_1(Cs, Ss, L, As, [C | Ts]);
152scan_tag_1([C | Cs], Ss, L, As, Ts) when C >= $0, C =< $9 ->
153    scan_tag_1(Cs, Ss, L, As, [C | Ts]);
154scan_tag_1([C | Cs], Ss, L, As, Ts) when C >= $\300, C =< $\377,
155					 C =/= $\327, C =/= $\367 ->
156    scan_tag_1(Cs, Ss, L, As, [C | Ts]);
157scan_tag_1([$_ | Cs], Ss, L, As, Ts) ->
158    scan_tag_1(Cs, Ss, L, As, [$_ | Ts]);
159scan_tag_1(Cs, Ss, L, As, Ts) ->
160    scan_tag_2(Cs, Ss, L, As, {Ts, L}).
161
162%% Check that the tag is followed by whitespace, linebreak, or colon.
163
164scan_tag_2([$\s | Cs], Ss, L, As, T) ->
165    scan_tag_lines(Ss, T, [Cs], L + 1, As);
166scan_tag_2([$\t | Cs], Ss, L, As, T) ->
167    scan_tag_lines(Ss, T, [Cs], L + 1, As);
168scan_tag_2([$: | Cs], Ss, L, As, T) ->
169    scan_tag_lines(Ss, T, [Cs], L + 1, As);
170scan_tag_2([], Ss, L, As, T) ->
171    scan_tag_lines(Ss, T, [[]], L + 1, As);
172scan_tag_2(_, Ss, L, As, _T) ->
173    scan_lines(Ss, L + 1, As).
174
175%% Scanning lines after a tag is found.
176
177scan_tag_lines([S | Ss], T, Ss1, L, As) ->
178    scan_tag_lines(S, S, Ss, T, Ss1, L, As);
179scan_tag_lines([], {Ts, L1}, Ss1, _L, As) ->
180    [make_tag(Ts, L1, Ss1) | As].
181
182%% Collecting tag text lines until end of comment or next tagged line.
183
184scan_tag_lines([$\s | Cs], S, Ss, T, Ss1, L, As) ->
185    scan_tag_lines(Cs, S, Ss, T, Ss1, L, As);
186scan_tag_lines([$\t | Cs], S, Ss, T, Ss1, L, As) ->
187    scan_tag_lines(Cs, S, Ss, T, Ss1, L, As);
188scan_tag_lines([$@, C | _Cs], S, Ss, {Ts, L1}, Ss1, L, As)
189  when C >= $a, C =< $z ->
190    scan_lines(S, Ss, L, [make_tag(Ts, L1, Ss1) | As]);
191scan_tag_lines([$@, C | _Cs], S, Ss, {Ts, L1}, Ss1, L, As)
192  when C >= $A, C =< $Z ->
193    scan_lines(S, Ss, L, [make_tag(Ts, L1, Ss1) | As]);
194scan_tag_lines([$@, C | _Cs], S, Ss, {Ts, L1}, Ss1, L, As)
195  when C >= $\300, C =< $\377, C =/= $\327, C =/= $\367 ->
196    scan_lines(S, Ss, L, [make_tag(Ts, L1, Ss1) | As]);
197scan_tag_lines("TODO:"++_, S, Ss, {Ts, L1}, Ss1, L, As) ->
198    scan_lines(S, Ss, L, [make_tag(Ts, L1, Ss1) | As]);
199scan_tag_lines(_Cs, S, Ss, T, Ss1, L, As) ->
200    scan_tag_lines(Ss, T, [S | Ss1], L + 1, As).
201
202make_tag(Cs, L, Ss) ->
203    #tag{name = aliases(list_to_atom(lists:reverse(Cs))),
204	 line = L,
205	 data = append_lines(lists:reverse(Ss))}.
206
207%% Flattening lines of text and inserting line breaks.
208
209append_lines([L]) -> L;
210append_lines([L | Ls]) -> L ++ [$\n | append_lines(Ls)];
211append_lines([]) -> [].
212
213%% Filtering out unknown tags.
214
215filter_tags(Ts, Tags) ->
216    filter_tags(Ts, Tags, no).
217
218filter_tags(Ts, Tags, Where) ->
219    filter_tags(Ts, Tags, Where, []).
220
221filter_tags([#tag{name = clear} | Ts], Tags, Where, _Ts1) ->
222    filter_tags(Ts, Tags, Where);
223filter_tags([#tag{name = N, line = L} = T | Ts], Tags, Where, Ts1) ->
224    case sets:is_element(N, Tags) of
225	true ->
226	    filter_tags(Ts, Tags, Where, [T | Ts1]);
227	false ->
228	    case Where of
229		no -> ok;
230		_ -> edoc_report:warning(L, Where, "tag @~s not recognized.", [N])
231	    end,
232	    filter_tags(Ts, Tags, Where, Ts1)
233    end;
234filter_tags([], _, _, Ts) ->
235    lists:reverse(Ts).
236
237%% Check occurrences of tags.
238
239check_tags(Ts, Allow, Single, Where) ->
240    check_tags(Ts, Allow, Single, Where, false, sets:new()).
241
242check_tags([#tag{name = T, line = L} | Ts], Allow, Single, Where, Error, Seen) ->
243    case sets:is_element(T, Seen) of
244	true ->
245	    case sets:is_element(T, Single) of
246		false ->
247		    check_tags(Ts, Allow, Single, Where, Error, Seen);
248		true ->
249		    edoc_report:report(L, Where, "multiple @~s tag.", [T]),
250		    check_tags(Ts, Allow, Single, Where, true, Seen)
251	    end;
252	false ->
253	    Seen1 = sets:add_element(T, Seen),
254	    case sets:is_element(T, Allow) of
255		true ->
256		    check_tags(Ts, Allow, Single, Where, Error, Seen1);
257		false ->
258		    edoc_report:report(L, Where, "tag @~s not allowed here.", [T]),
259		    check_tags(Ts, Allow, Single, Where, true, Seen1)
260	    end
261    end;
262check_tags([], _, _, _, Error, _) ->
263    Error.
264
265
266%% Parses tag contents for specific tags.
267
268parse_tags(Ts, How, Env, Where) ->
269    parse_tags(Ts, How, Env, Where, []).
270
271parse_tags([#tag{name = Name} = T | Ts], How, Env, Where, Ts1) ->
272    case dict:fetch(Name, How) of
273	text ->
274	    parse_tags(Ts, How, Env, Where, [T | Ts1]);
275	xml ->
276	    [T1] = parse_tag(T, fun parse_xml/4, Env, Where),
277	    parse_tags(Ts, How, Env, Where, [T1 | Ts1]);
278	F when is_function(F) ->
279	    Ts2 = parse_tag(T, F, Env, Where),
280	    parse_tags(Ts, How, Env, Where, lists:reverse(Ts2, Ts1))
281    end;
282parse_tags([], _How, _Env, _Where, Ts) ->
283    lists:reverse(Ts).
284
285parse_tag(T, F, Env, Where) ->
286    case catch {ok, F(T#tag.data, T#tag.line, Env, Where)} of
287	{ok, Data} ->
288	    [T#tag{data = Data}];
289	{expand, Ts} ->
290	    Ts;
291	{error, L, Error} ->
292	    edoc_report:error(L, Where, Error),
293	    exit(error);
294	{'EXIT', R} -> exit(R);
295	Other -> throw(Other)
296    end.
297
298%% parser functions for the built-in content types. They also perform
299%% some sanity checks on the results.
300
301parse_xml(Data, Line, _Env, _Where) ->
302    edoc_wiki:parse_xml(Data, Line).
303
304parse_see(Data, Line, _Env, _Where) ->
305    edoc_parser:parse_see(Data, Line).
306
307parse_expr(Data, Line, _Env, _Where) ->
308    edoc_lib:parse_expr(Data, Line).
309
310parse_spec(Data, Line, _Env, {_, {F, A}} = Where) ->
311    edoc_report:warning(Line, Where,
312			"EDoc @spec tags are deprecated. "
313			"Please use -spec attributes instead.", []),
314    Spec = edoc_parser:parse_spec(Data, Line),
315    #t_spec{name = N, type = #t_fun{args = As}} = Spec,
316    if length(As) /= A ->
317	    throw_error(Line, "@spec arity does not match.");
318       true ->
319	    case N of
320		undefined ->
321		    Spec#t_spec{name = #t_name{module = [], name = F}};
322		#t_name{module = [], name = F} ->
323		    Spec;
324		_ ->
325		    throw_error(Line, "@spec name does not match.")
326	    end
327    end.
328
329parse_param(Data, Line, _Env, {_, {_F, _A}} = _Where) ->
330    edoc_parser:parse_param(Data, Line).
331
332parse_throws(Data, Line, _Env, {_, {_F, _A}} = _Where) ->
333    edoc_parser:parse_throws(Data, Line).
334
335parse_contact(Data, Line, _Env, _Where) ->
336    case edoc_lib:parse_contact(Data, Line) of
337	{"", "", _URI} ->
338	    throw_error(Line, "must specify name or e-mail.");
339	Info ->
340	    Info
341    end.
342
343parse_typedef(Data, Line, _Env, Where) ->
344    edoc_report:warning(Line, Where,
345			"EDoc @type tags are deprecated. "
346			"Please use -type attributes instead.", []),
347    Def = edoc_parser:parse_typedef(Data, Line),
348    {#t_typedef{name = #t_name{name = T}, args = As}, _} = Def,
349    NAs = length(As),
350    case edoc_types:is_predefined(T, NAs) of
351	true ->
352            case edoc_types:is_new_predefined(T, NAs) of
353                false ->
354                    throw_error(Line, {"redefining built-in type '~w'.",
355                                       [T]});
356                true ->
357		    edoc_report:warning(Line, Where, "redefining built-in type '~w'.",
358					[T]),
359                    Def
360            end;
361	false ->
362	    Def
363    end.
364
365-type line() :: erl_anno:line().
366
367-spec parse_file(_, line(), edoc:env(), _) -> no_return().
368
369parse_file(Data, Line, Env, _Where) ->
370    case edoc_lib:parse_expr(Data, Line) of
371	{string, _, File0} ->
372	    File = edoc_lib:strip_space(File0),
373	    case edoc_extract:file(File, module, Env, []) of
374		{ok, Ts} ->
375		    throw({expand, Ts});
376		{error, R} ->
377		    throw_error(Line, {read_file, File, R})
378	    end;
379	_ ->
380	    throw_error(Line, file_not_string)
381    end.
382
383-spec parse_header(_, line(), edoc:env(), _) -> no_return().
384
385parse_header(Data, Line, Env, {Where, _}) ->
386    parse_header(Data, Line, Env, Where);
387parse_header(Data, Line, Env, Where) when is_list(Where) ->
388    case edoc_lib:parse_expr(Data, Line) of
389	{string, _, File} ->
390	    Dir = filename:dirname(Where),
391	    Path = Env#env.includes ++ [Dir],
392	    case edoc_lib:find_file(Path, File) of
393		"" ->
394		    throw_error(Line, {file_not_found, File});
395		File1 ->
396		    Ts = edoc_extract:header(File1, Env, []),
397		    throw({expand, Ts})
398	    end;
399	_ ->
400	    throw_error(Line, file_not_string)
401    end.
402
403-type err() :: 'file_not_string'
404             | {'file_not_found', file:filename()}
405             | {'read_file', file:filename(), term()}
406             | {string(), [term()]}
407             | string().
408
409-spec throw_error(line(), err()) -> no_return().
410
411throw_error(L, {read_file, File, R}) ->
412    throw_error(L, {"error reading file '~ts': ~w", [edoc_lib:filename(File), R]});
413throw_error(L, {file_not_found, F}) ->
414    throw_error(L, {"file not found: ~ts", [F]});
415throw_error(L, file_not_string) ->
416    throw_error(L, "expected file name as a string");
417throw_error(L, D) ->
418    throw({error, L, D}).
419
420%% Checks local types.
421
422-record(parms, {tab, warn, file, line}).
423
424check_types(Entries, Opts, File) ->
425    Tags = edoc_data:get_all_tags(Entries),
426    TypeTags = [Tag || #tag{data = {#t_typedef{},_}}=Tag <- Tags],
427    Entries2 = edoc_data:hidden_filter(Entries, Opts),
428    Tags2 = edoc_data:get_all_tags(Entries2),
429    SpecTags = [Tag || #tag{data = #t_spec{}}=Tag <- Tags2],
430    DT = ets:new(types, [bag]),
431    _ = [add_type(DT, Name, As, File, Line) ||
432            #tag{line = Line,
433                 data = {#t_typedef{name = Name, args = As},_}} <- TypeTags],
434    Warn = proplists:get_value(report_missing_types, Opts,
435                               ?REPORT_MISSING_TYPES) =:= true,
436    P = #parms{tab = DT, warn = Warn, file = File, line = 0},
437    try check_types3(TypeTags++SpecTags, P, [])
438    after true = ets:delete(DT)
439    end.
440
441add_type(DT, Name, Args, File, Line) ->
442    NArgs = length(Args),
443    TypeName = {Name, NArgs},
444    case lists:member(TypeName, ets:lookup(DT, Name)) of
445        true ->
446            #t_name{name = N} = Name,
447            type_warning(Line, File, "duplicated type", N, NArgs);
448        false ->
449            ets:insert(DT, {Name, NArgs})
450    end.
451
452check_types3([], _P, _Ls)->
453    ok;
454check_types3([Tag | Tags], P, Ls) ->
455    check_type(Tag, P, Ls, Tags).
456
457check_type(#tag{line = L, data = Data}, P0, Ls, Ts) ->
458    P = P0#parms{line = L},
459    case Data of
460        {#t_typedef{type = Type, defs = Defs},_} ->
461            check_type(Type, P, Ls, Defs++Ts);
462        #t_spec{type = Type, defs = Defs} ->
463            LocalTypes =
464                [{N,length(Args)} ||
465                    #t_def{name = #t_type{name = N, args = Args}} <- Defs],
466            check_type(Type, P, LocalTypes, Defs),
467            check_types3(Ts, P, Ls);
468        _->
469            check_types3(Ts, P0, Ls)
470    end;
471check_type(#t_def{type = Type}, P, Ls, Ts) ->
472    check_type(Type, P, Ls, Ts);
473check_type(#t_type{name = Name, args = Args}, P, Ls, Ts) ->
474    check_used_type(Name, Args, P, Ls),
475    check_types3(Args++Ts, P, Ls);
476check_type(#t_var{}, P, Ls, Ts) ->
477    check_types3(Ts, P, Ls);
478check_type(#t_fun{args = Args, range = Range}, P, Ls, Ts) ->
479    check_type(Range, P, Ls, Args++Ts);
480check_type(#t_map{}, P, Ls, Ts) ->
481    check_types3(Ts, P, Ls);
482check_type(#t_tuple{types = Types}, P, Ls, Ts) ->
483    check_types3(Types ++Ts, P, Ls);
484check_type(#t_list{type = Type}, P, Ls, Ts) ->
485    check_type(Type, P, Ls, Ts);
486check_type(#t_nil{}, P, Ls, Ts) ->
487    check_types3(Ts, P, Ls);
488check_type(#t_paren{type = Type}, P, Ls, Ts) ->
489    check_type(Type, P, Ls, Ts);
490check_type(#t_nonempty_list{type = Type}, P, Ls, Ts) ->
491    check_type(Type, P, Ls, Ts);
492check_type(#t_atom{}, P, Ls, Ts) ->
493    check_types3(Ts, P, Ls);
494check_type(#t_integer{}, P, Ls, Ts) ->
495    check_types3(Ts, P, Ls);
496check_type(#t_integer_range{}, P, Ls, Ts) ->
497    check_types3(Ts, P, Ls);
498check_type(#t_binary{}, P, Ls, Ts) ->
499    check_types3(Ts, P, Ls);
500check_type(#t_float{}, P, Ls, Ts) ->
501    check_types3(Ts, P, Ls);
502check_type(#t_union{types = Types}, P, Ls, Ts) ->
503    check_types3(Types++Ts, P, Ls);
504check_type(#t_record{fields = Fields}, P, Ls, Ts) ->
505    check_types3(Fields++Ts, P, Ls);
506check_type(#t_field{type = Type}, P, Ls, Ts) ->
507    check_type(Type, P, Ls, Ts);
508check_type(undefined, P, Ls, Ts) ->
509    check_types3(Ts, P, Ls).
510
511check_used_type(#t_name{name = N, module = Mod}=Name, Args, P, LocalTypes) ->
512    NArgs = length(Args),
513    TypeName = {Name, NArgs},
514    DT = P#parms.tab,
515    case
516        Mod =/= []
517        orelse lists:member(TypeName, ets:lookup(DT, Name))
518        orelse edoc_types:is_predefined(N, NArgs)
519        orelse lists:member(TypeName, LocalTypes)
520    of
521        true ->
522            ok;
523        false ->
524            #parms{warn = W, line = L, file = File} = P,
525            %% true = ets:insert(DT, TypeName),
526            _ = [type_warning(L, File, "missing type", N, NArgs) || W],
527	    ok
528    end.
529
530type_warning(Line, File, S, N, NArgs) ->
531    AS = ["/"++integer_to_list(NArgs) || NArgs > 0],
532    edoc_report:warning(Line, File, S++" ~w~s", [N, AS]).
533