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