diff options
-rw-r--r-- | lib/edoc/include/edoc_doclet.hrl | 33 | ||||
-rw-r--r-- | lib/edoc/src/edoc.erl | 163 | ||||
-rw-r--r-- | lib/edoc/src/edoc.hrl | 41 | ||||
-rw-r--r-- | lib/edoc/src/edoc_data.erl | 5 | ||||
-rw-r--r-- | lib/edoc/src/edoc_doclet.erl | 54 | ||||
-rw-r--r-- | lib/edoc/src/edoc_extract.erl | 128 | ||||
-rw-r--r-- | lib/edoc/src/edoc_layout.erl | 9 | ||||
-rw-r--r-- | lib/edoc/src/edoc_lib.erl | 70 | ||||
-rw-r--r-- | lib/edoc/src/edoc_macros.erl | 15 | ||||
-rw-r--r-- | lib/edoc/src/edoc_parser.yrl | 4 | ||||
-rw-r--r-- | lib/edoc/src/edoc_refs.erl | 9 | ||||
-rw-r--r-- | lib/edoc/src/edoc_report.erl | 6 | ||||
-rw-r--r-- | lib/edoc/src/edoc_run.erl | 18 | ||||
-rw-r--r-- | lib/edoc/src/edoc_specs.erl | 22 | ||||
-rw-r--r-- | lib/edoc/src/edoc_tags.erl | 33 | ||||
-rw-r--r-- | lib/edoc/src/edoc_types.erl | 107 | ||||
-rw-r--r-- | lib/edoc/src/edoc_types.hrl | 75 | ||||
-rw-r--r-- | lib/edoc/test/edoc_SUITE.erl | 31 |
18 files changed, 460 insertions, 363 deletions
diff --git a/lib/edoc/include/edoc_doclet.hrl b/lib/edoc/include/edoc_doclet.hrl index a05a9cb2bc..c15ba13aad 100644 --- a/lib/edoc/include/edoc_doclet.hrl +++ b/lib/edoc/include/edoc_doclet.hrl @@ -28,36 +28,13 @@ -define(NO_APP, []). -%% Context for doclets - -%% @type edoc_context() = #context{dir = string(), -%% env = edoc_lib:edoc_env(), -%% opts = [term()]} - --record(context, {dir = "", - env, - opts = []}). - -%% Doclet commands - -%% @type no_app(). -%% A value used to mark absence of an Erlang application -%% context. Use the macro `NO_APP' defined in -%% <a href="edoc_doclet.hrl">`edoc_doclet.hrl'</a> -%% to produce this value. - -%% @type doclet_gen() = #doclet_gen{sources = [string()], -%% app = no_app() | atom(), -%% modules = [atom()]} +-record(doclet_context, {dir = "", + env, + opts = []}). -record(doclet_gen, {sources = [], app = ?NO_APP, - modules = [] - }). - -%% @type doclet_toc() = #doclet_gen{paths = [string()], -%% indir = string()} + modules = []}). -record(doclet_toc, {paths, - indir - }). + indir}). diff --git a/lib/edoc/src/edoc.erl b/lib/edoc/src/edoc.erl index 0fdc818fae..24740942e2 100644 --- a/lib/edoc/src/edoc.erl +++ b/lib/edoc/src/edoc.erl @@ -60,23 +60,71 @@ read_comments/1, read_comments/2, read_source/1, read_source/2]). --compile({no_auto_import,[error/1]}). +-export_type([module_meta/0, + env/0, + comment/0, + entry/0, + entry_data/0, + tag/0, + xmerl_module/0]). -include("edoc.hrl"). +-type module_meta() :: #module{name :: [] | atom(), + parameters :: none | [atom()], + functions :: ordset(function_name()), + exports :: ordset(function_name()), + attributes :: ordset({atom(), term()}), + records :: [{atom(), [{atom(), term()}]}], + encoding :: epp:source_encoding()}. +%% Module information. + +-type env() :: #env{}. +%% Environment information needed by EDoc for generating references. + +-type comment() :: #comment{line :: integer(), + text :: string()}. +%% Simplified comment data. + + +-type entry() :: #entry{name :: function_name() | atom(), + args :: [atom()], + line :: integer(), + export :: boolean(), + data :: entry_data()}. +%% Module Entries (one per function, plus module header and footer). + +-type entry_data() :: term(). + +-type tag() :: #tag{name :: atom(), + line :: integer(), + origin :: comment, + data :: term()}. +%% Generic tag information. + +-type xmerl_module() :: any(). +%% The EDoc documentation data for a module, +%% expressed as an XML document in {@link //xmerl. XMerL} format. See +%% the file <a href="edoc.dtd">`edoc.dtd'</a> for details. + +-type ordset(T) :: ordsets:ordset(T). +-type function_name() :: {atom(), integer()}. +-type filename() :: file:filename(). +-type proplist() :: proplists:proplist(). +-type comment2() :: { Line :: integer(), + Column :: integer(), + Indentation :: integer(), + Text :: [string()] }. +-type syntaxTree() :: erl_syntax:syntaxTree(). + +-compile({no_auto_import, [error/1]}). -%% @spec (Name::filename()) -> ok %% @equiv file(Name, []) %% @deprecated See {@link file/2} for details. - +-spec file(filename()) -> ok. file(Name) -> file(Name, []). -%% @spec file(filename(), proplist()) -> ok -%% -%% @type filename() = //kernel/file:filename() -%% @type proplist() = [term()] -%% %% @deprecated This is part of the old interface to EDoc and is mainly %% kept for backwards compatibility. The preferred way of generating %% documentation is through one of the functions {@link application/2} @@ -116,6 +164,9 @@ file(Name) -> %% NEW-OPTIONS: source_suffix, file_suffix, dir %% INHERIT-OPTIONS: read/2 +-spec file(Name, Options) -> ok when + Name :: filename(), + Options :: proplist(). file(Name, Options) -> Text = read(Name, Options), SrcSuffix = proplists:get_value(source_suffix, Options, @@ -130,31 +181,32 @@ file(Name, Options) -> %% TODO: better documentation of files/1/2, application/1/2/3 -%% @spec (Files::[filename()]) -> ok - +-spec files([filename()]) -> ok. files(Files) -> files(Files, []). -%% @spec (Files::[filename()], -%% Options::proplist()) -> ok %% @doc Runs EDoc on a given set of source files. See {@link run/2} for %% details, including options. %% @equiv run([], Files, Options) +-spec files(Files, Options) -> ok when + Files :: [filename()], + Options :: proplist(). files(Files, Options) -> run(Files, Options). -%% @spec (Application::atom()) -> ok %% @equiv application(Application, []) - +-spec application(atom()) -> ok. application(App) -> application(App, []). -%% @spec (Application::atom(), Options::proplist()) -> ok %% @doc Run EDoc on an application in its default app-directory. See %% {@link application/3} for details. %% @see application/1 +-spec application(App, Options) -> ok when + App :: atom(), + Options :: proplist(). application(App, Options) when is_atom(App) -> case code:lib_dir(App) of Dir when is_list(Dir) -> @@ -165,8 +217,6 @@ application(App, Options) when is_atom(App) -> exit(error) end. -%% @spec (Application::atom(), Dir::filename(), Options::proplist()) -%% -> ok %% @doc Run EDoc on an application located in the specified directory. %% Tries to automatically set up good defaults. Unless the user %% specifies otherwise: @@ -191,6 +241,10 @@ application(App, Options) when is_atom(App) -> %% %% @see application/2 +-spec application(App, Dir, Options) -> ok when + App :: atom(), + Dir :: filename(), + Options :: proplist(). application(App, Dir, Options) when is_atom(App) -> Src = edoc_lib:try_subdir(Dir, ?SOURCE_DIR), Overview = filename:join(edoc_lib:try_subdir(Dir, ?EDOC_DIR), @@ -232,8 +286,6 @@ opt_negations() -> {no_subpackages, subpackages}, {no_report_missing_types, report_missing_types}]. -%% @spec run(Files::[filename()], -%% Options::proplist()) -> ok %% @doc Runs EDoc on a given set of source files. Note %% that the doclet plugin module has its own particular options; see the %% `doclet' option below. @@ -319,10 +371,13 @@ opt_negations() -> %% INHERIT-OPTIONS: edoc_lib:run_doclet/2 %% INHERIT-OPTIONS: edoc_lib:get_doc_env/3 +-spec run(Files, Opts) -> ok when + Files :: [filename()], + Opts :: proplist(). run(Files, Opts0) -> Opts = expand_opts(Opts0), Ctxt = init_context(Opts), - Dir = Ctxt#context.dir, + Dir = Ctxt#doclet_context.dir, Path = proplists:append_values(source_path, Opts), Ss = sources(Path, Opts), {Ss1, Ms} = expand_sources(expand_files(Files) ++ Ss, Opts), @@ -330,7 +385,7 @@ run(Files, Opts0) -> {App1, Ms1} = target_dir_info(Dir, App, Ms, Opts), Ms2 = edoc_lib:unique(lists:sort(Ms1)), Env = edoc_lib:get_doc_env(App1, Ms2, Opts), - Ctxt1 = Ctxt#context{env = Env}, + Ctxt1 = Ctxt#doclet_context{env = Env}, Cmd = #doclet_gen{sources = Ss1, app = App1, modules = Ms2 @@ -348,7 +403,7 @@ expand_opts(Opts0) -> %% DEFER-OPTIONS: run/2 init_context(Opts) -> - #context{dir = proplists:get_value(dir, Opts, ?CURRENT_DIR), + #doclet_context{dir = proplists:get_value(dir, Opts, ?CURRENT_DIR), opts = Opts }. @@ -431,21 +486,19 @@ toc(Dir, Paths, Opts0) -> Opts = expand_opts(Opts0 ++ [{dir, Dir}]), Ctxt = init_context(Opts), Env = edoc_lib:get_doc_env('', [], Opts), - Ctxt1 = Ctxt#context{env = Env}, + Ctxt1 = Ctxt#doclet_context{env = Env}, F = fun (M) -> M:run(#doclet_toc{paths=Paths}, Ctxt1) end, edoc_lib:run_doclet(F, Opts). -%% @spec read(File::filename()) -> string() %% @equiv read(File, []) +-spec read(filename()) -> string(). read(File) -> read(File, []). -%% @spec read(File::filename(), Options::proplist()) -> string() -%% %% @doc Reads and processes a source file and returns the resulting %% EDoc-text as a string. See {@link get_doc/2} and {@link layout/2} for %% options. @@ -454,19 +507,20 @@ read(File) -> %% INHERIT-OPTIONS: get_doc/2, layout/2 +-spec read(File, Opts) -> string() when + File :: filename(), + Opts :: proplist(). read(File, Opts) -> {_ModuleName, Doc} = get_doc(File, Opts), layout(Doc, Opts). -%% @spec layout(Doc::edoc_module()) -> string() %% @equiv layout(Doc, []) +-spec layout(xmerl_module()) -> string(). layout(Doc) -> layout(Doc, []). -%% @spec layout(Doc::edoc_module(), Options::proplist()) -> string() -%% %% @doc Transforms EDoc module documentation data to text. The default %% layout creates an HTML document. %% @@ -488,47 +542,38 @@ layout(Doc) -> %% INHERIT-OPTIONS: edoc_lib:run_layout/2 +-spec layout(Doc, Opts) -> string() when + Doc :: xmerl_module(), + Opts :: proplist(). layout(Doc, Opts) -> F = fun (M) -> M:module(Doc, Opts) end, edoc_lib:run_layout(F, Opts). - -%% @spec (File) -> [comment()] -%% @type comment() = {Line, Column, Indentation, Text} -%% where -%% Line = integer(), -%% Column = integer(), -%% Indentation = integer(), -%% Text = [string()] %% @equiv read_comments(File, []) +-spec read_comments(filename()) -> [comment2()]. read_comments(File) -> read_comments(File, []). -%% @spec read_comments(File::filename(), Options::proplist()) -> -%% [comment()] -%% %% @doc Extracts comments from an Erlang source code file. See the %% module {@link //syntax_tools/erl_comment_scan} for details on the %% representation of comments. Currently, no options are avaliable. +-spec read_comments(File, Opts) -> [comment2()] when + File :: filename(), + Opts :: proplist(). read_comments(File, _Opts) -> erl_comment_scan:file(File). -%% @spec (File) -> [syntaxTree()] %% @equiv read_source(File, []) +-spec read_source(filename()) -> [syntaxTree()]. read_source(Name) -> read_source(Name, []). -%% @spec read_source(File::filename(), Options::proplist()) -> -%% [syntaxTree()] -%% -%% @type syntaxTree() = //syntax_tools/erl_syntax:syntaxTree() -%% %% @doc Reads an Erlang source file and returns the list of "source code %% form" syntax trees. %% @@ -573,6 +618,9 @@ read_source(Name) -> %% NEW-OPTIONS: [no_]preprocess (preprocess -> includes, macros) +-spec read_source(File, Opts) -> [syntaxTree()] when + File :: filename(), + Opts :: proplist(). read_source(Name, Opts0) -> Opts = expand_opts(Opts0), case read_source_1(Name, Opts) of @@ -721,20 +769,12 @@ helpful_message(Name) -> "{preprocess, true} can help. See also edoc(3)."], lists:foreach(fun(M) -> edoc_report:report(Name, M, []) end, Ms). -%% @spec get_doc(File::filename()) -> {ModuleName, edoc_module()} %% @equiv get_doc(File, []) +-spec get_doc(filename()) -> {module(), xmerl_module()}. get_doc(File) -> get_doc(File, []). -%% @spec get_doc(File::filename(), Options::proplist()) -> -%% {ModuleName, edoc_module()} -%% ModuleName = atom() -%% -%% @type edoc_module(). The EDoc documentation data for a module, -%% expressed as an XML document in {@link //xmerl. XMerL} format. See -%% the file <a href="edoc.dtd">`edoc.dtd'</a> for details. -%% %% @doc Reads a source code file and extracts EDoc documentation data. %% Note that without an environment parameter (see {@link get_doc/3}), %% hypertext links may not be correct. @@ -781,14 +821,13 @@ get_doc(File) -> %% INHERIT-OPTIONS: get_doc/3 %% INHERIT-OPTIONS: edoc_lib:get_doc_env/3 +-spec get_doc(File, Options) -> {module(), xmerl_module()} when + File :: filename(), + Options :: proplist(). get_doc(File, Opts) -> Env = edoc_lib:get_doc_env(Opts), get_doc(File, Env, Opts). -%% @spec get_doc(File::filename(), Env::edoc_lib:edoc_env(), -%% Options::proplist()) -> {ModuleName, edoc_module()} -%% ModuleName = atom() -%% %% @doc Like {@link get_doc/2}, but for a given environment %% parameter. `Env' is an environment created by {@link %% edoc_lib:get_doc_env/3}. @@ -796,5 +835,11 @@ get_doc(File, Opts) -> %% INHERIT-OPTIONS: read_source/2, read_comments/2, edoc_extract:source/5 %% DEFER-OPTIONS: get_doc/2 +-spec get_doc(File, Env, Options) -> R when + File :: filename(), + Env :: env(), + Options :: proplist(), + R :: {module(), xmerl_module()} + | {module(), xmerl_module(), [entry()]}. get_doc(File, Env, Opts) -> edoc_extract:source(File, Env, Opts). diff --git a/lib/edoc/src/edoc.hrl b/lib/edoc/src/edoc.hrl index 1ec64561a1..0d9a57ea6f 100644 --- a/lib/edoc/src/edoc.hrl +++ b/lib/edoc/src/edoc.hrl @@ -44,28 +44,13 @@ -include("edoc_doclet.hrl"). -%% Module information - -%% @type module() = #module{name = [] | atom(), -%% parameters = none | [atom()], -%% functions = ordset(function_name()), -%% exports = ordset(function_name()), -%% attributes = ordset({atom(), term()}), -%% records = [{atom(), [{atom(), term()}]}], -%% encoding = epp:source_encoding()} -%% ordset(T) = sets:ordset(T) -%% function_name(T) = {atom(), integer()} - -record(module, {name = [], parameters = none, functions = [], exports = [], attributes = [], records = [], - encoding = latin1 - }). - -%% Environment for generating documentation data + encoding = latin1}). -record(env, {module = [], root = "", @@ -74,32 +59,10 @@ modules, app_default, macros = [], - includes = [] - }). - -%% Simplified comment data - -%% @type comment() = #comment{line = integer(), -%% text = string()} + includes = []}). -record(comment, {line = 0, text}). -%% Module Entries (one per function, plus module header and footer) - -%% @type entry() = #entry{{atom(), integer()} % function -%% | name = atom(), % other -%% args = [atom()], -%% line = integer(), -%% export = boolean(), -%% data = term()} - -record(entry, {name, args = [], line = 0, export, data}). -%% Generic tag information - -%% @type tag() = #tag{name = atom(), -%% line = integer(), -%% origin = comment | code, -%% data = term()} - -record(tag, {name, line = 0, origin = comment, data}). diff --git a/lib/edoc/src/edoc_data.erl b/lib/edoc/src/edoc_data.erl index 3075e47942..f9db181c24 100644 --- a/lib/edoc/src/edoc_data.erl +++ b/lib/edoc/src/edoc_data.erl @@ -81,6 +81,11 @@ %% NEW-OPTIONS: private, hidden, todo %% DEFER-OPTIONS: edoc_extract:source/4 +-spec module(Module, Entries, Env, Opts) -> edoc:xmerl_module() when + Module :: edoc:module_meta(), + Entries :: [edoc:entry()], + Env :: edoc:env(), + Opts :: proplists:proplist(). module(Module, Entries, Env, Opts) -> Name = atom_to_list(Module#module.name), HeaderEntry = get_entry(module, Entries), diff --git a/lib/edoc/src/edoc_doclet.erl b/lib/edoc/src/edoc_doclet.erl index 604291374a..ffeead89a3 100644 --- a/lib/edoc/src/edoc_doclet.erl +++ b/lib/edoc/src/edoc_doclet.erl @@ -55,12 +55,39 @@ -include_lib("xmerl/include/xmerl.hrl"). -%% Sources is the list of inputs in the order they were found. -%% Modules are sorted lists of atoms without duplicates. (They -%% usually include the data from the edoc-info file in the target -%% directory, if it exists.) +-export_type([command/0, + context/0, + doclet_gen/0, + doclet_toc/0, + no_app/0]). + +-type command() :: doclet_gen() + | doclet_toc(). +%% Doclet commands. + +-type context() :: #doclet_context{dir :: string(), + env :: edoc:env(), + opts :: [term()]}. +%% Context for doclets. + +-type no_app() :: ?NO_APP. +%% A value used to mark absence of an Erlang application +%% context. Use the macro `NO_APP' defined in +%% <a href="edoc_doclet.hrl">`edoc_doclet.hrl'</a> +%% to produce this value. + +-type doclet_gen() :: #doclet_gen{sources :: [string()], + app :: no_app() | atom(), + modules :: [module()]}. +%% Doclet command. + +-type doclet_toc() :: #doclet_toc{paths :: [string()], + indir :: string()}. +%% Doclet command. + +-callback run(command(), context()) -> ok. +%% Doclet entrypoint. -%% @spec (Command::doclet_gen() | doclet_toc(), edoc_context()) -> ok %% @doc Main doclet entry point. See the file <a %% href="edoc_doclet.hrl">`edoc_doclet.hrl'</a> for the data %% structures used for passing parameters. @@ -116,6 +143,7 @@ %% INHERIT-OPTIONS: copy_stylesheet/2 %% INHERIT-OPTIONS: stylesheet/1 +-spec run(edoc_doclet:command(), edoc_doclet:context()) -> ok. run(#doclet_gen{}=Cmd, Ctxt) -> gen(Cmd#doclet_gen.sources, Cmd#doclet_gen.app, @@ -124,10 +152,14 @@ run(#doclet_gen{}=Cmd, Ctxt) -> run(#doclet_toc{}=Cmd, Ctxt) -> toc(Cmd#doclet_toc.paths, Ctxt). +%% @doc `Sources' is the list of inputs in the order they were found. +%% Modules are sorted lists of atoms without duplicates. (They +%% usually include the data from the edoc-info file in the target +%% directory, if it exists.) gen(Sources, App, Modules, Ctxt) -> - Dir = Ctxt#context.dir, - Env = Ctxt#context.env, - Options = Ctxt#context.opts, + Dir = Ctxt#doclet_context.dir, + Env = Ctxt#doclet_context.env, + Options = Ctxt#doclet_context.opts, Title = title(App, Options), CSS = stylesheet(Options), {Modules1, Error} = sources(Sources, Dir, Modules, Env, Options), @@ -421,9 +453,9 @@ read_file(File, Context, Env, Opts) -> -define(CURRENT_DIR, "."). toc(Paths, Ctxt) -> - Opts = Ctxt#context.opts, - Dir = Ctxt#context.dir, - Env = Ctxt#context.env, + Opts = Ctxt#doclet_context.opts, + Dir = Ctxt#doclet_context.dir, + Env = Ctxt#doclet_context.env, app_index_file(Paths, Dir, Env, Opts). %% TODO: FIXME: it's unclear how much of this is working at all diff --git a/lib/edoc/src/edoc_extract.erl b/lib/edoc/src/edoc_extract.erl index f7e2c28b6f..bab1ef6fc7 100644 --- a/lib/edoc/src/edoc_extract.erl +++ b/lib/edoc/src/edoc_extract.erl @@ -37,15 +37,9 @@ %% %% @headerfile "edoc.hrl" (disabled until it can be made private) -include("edoc.hrl"). -%% @type filename() = //kernel/file:filename(). -%% @type proplist() = //stdlib/proplists:property(). -%% @type syntaxTree() = //syntax_tools/erl_syntax:syntaxTree(). - -%% @spec source(File::filename(), Env::edoc_env(), Options::proplist()) -%% -> {ModuleName, edoc:edoc_module()} -%% ModuleName = atom() -%% proplist() = [term()] -%% +-type filename() :: file:filename(). +-type proplist() :: proplists:proplist(). + %% @doc Like {@link source/5}, but reads the syntax tree and the %% comments from the specified file. %% @@ -53,18 +47,16 @@ %% @see edoc:read_source/2 %% @see source/4 +-spec source(File, Env, Opts) -> R when + File :: filename(), + Env :: edoc:env(), + Opts :: proplist(), + R :: {module(), edoc:xmerl_module()}. source(File, Env, Opts) -> Forms = edoc:read_source(File, Opts), Comments = edoc:read_comments(File, Opts), source(Forms, Comments, File, Env, Opts). -%% @spec source(Forms, Comments::[edoc:comment()], File::filename(), -%% Env::edoc_env(), Options::proplist()) -> -%% {ModuleName, edoc:edoc_module()} -%% -%% Forms = syntaxTree() | [syntaxTree()] -%% ModuleName = atom() -%% %% @doc Like {@link source/4}, but first inserts the given comments in %% the syntax trees. The syntax trees must contain valid position %% information. (Cf. {@link edoc:read_comments/2}.) @@ -75,6 +67,13 @@ source(File, Env, Opts) -> %% @see source/4 %% @see //syntax_tools/erl_recomment +-spec source(Forms, Comments, File, Env, Opts) -> R when + Forms :: erl_syntax:forms(), + Comments :: [edoc:comment()], + File :: filename(), + Env :: edoc:env(), + Opts :: proplist(), + R :: {module(), edoc:xmerl_module()}. source(Forms, Comments, File, Env, Opts) when is_list(Forms) -> Forms1 = erl_syntax:form_list(Forms), source(Forms1, Comments, File, Env, Opts); @@ -83,14 +82,6 @@ source(Forms, Comments, File, Env, Opts) -> TypeDocs = find_type_docs(Forms, Comments, Env, File), source1(Tree, File, Env, Opts, TypeDocs). -%% @spec source(Forms, File::filename(), Env::edoc_env(), -%% Options::proplist()) -> -%% {ModuleName, edoc:edoc_module()} -%% -%% Forms = syntaxTree() | [syntaxTree()] -%% ModuleName = atom() -%% @type edoc_env() = edoc_lib:edoc_env() -%% %% @doc Extracts EDoc documentation from commented source code syntax %% trees. The given `Forms' must be a single syntax tree of %% type `form_list', or a list of syntax trees representing @@ -113,6 +104,12 @@ source(Forms, Comments, File, Env, Opts) -> %% INHERIT-OPTIONS: add_macro_defs/3 %% INHERIT-OPTIONS: edoc_data:module/4 +-spec source(Forms, File, Env, Opts) -> R when + Forms :: erl_syntax:forms(), + File :: filename(), + Env :: edoc:env(), + Opts :: proplist(), + R :: {module(), edoc:xmerl_module()}. source(Forms, File, Env, Opts) when is_list(Forms) -> source(erl_syntax:form_list(Forms), File, Env, Opts); source(Tree, File0, Env, Opts) -> @@ -130,16 +127,11 @@ source1(Tree, File0, Env, Opts, TypeDocs) -> root = ""}, Env2 = add_macro_defs(module_macros(Env1), Opts, Env1), Entries1 = get_tags([Header, Footer | Entries], Env2, File, TypeDocs), - Entries2 = edoc_specs:add_data(Entries1, Opts, File, Module), + Entries2 = edoc_specs:add_type_data(Entries1, Opts, File, Module), edoc_tags:check_types(Entries2, Opts, File), Data = edoc_data:module(Module, Entries2, Env2, Opts), {Name, Data}. -%% @spec header(File::filename(), Env::edoc_env(), Options::proplist()) -%% -> {ok, Tags} | {error, Reason} -%% Tags = [term()] -%% Reason = term() -%% %% @doc Similar to {@link header/5}, but reads the syntax tree and the %% comments from the specified file. %% @@ -147,18 +139,15 @@ source1(Tree, File0, Env, Opts, TypeDocs) -> %% @see edoc:read_source/2 %% @see header/4 +-spec header(File, Env, Opts) -> edoc:entry_data() when + File :: filename(), + Env :: edoc:env(), + Opts :: proplist(). header(File, Env, Opts) -> Forms = edoc:read_source(File), Comments = edoc:read_comments(File), header(Forms, Comments, File, Env, Opts). -%% @spec header(Forms, Comments::[edoc:comment()], File::filename(), -%% Env::edoc_env(), Options::proplist()) -> -%% {ok, Tags} | {error, Reason} -%% Forms = syntaxTree() | [syntaxTree()] -%% Tags = [term()] -%% Reason = term() -%% %% @doc Similar to {@link header/4}, but first inserts the given %% comments in the syntax trees. The syntax trees must contain valid %% position information. (Cf. {@link edoc:read_comments/2}.) @@ -167,6 +156,12 @@ header(File, Env, Opts) -> %% @see header/4 %% @see //syntax_tools/erl_recomment +-spec header(Forms, Comments, File, Env, Opts) -> edoc:entry_data() when + Forms :: erl_syntax:forms(), + Comments :: [edoc:comment()], + File :: filename(), + Env :: edoc:env(), + Opts :: proplist(). header(Forms, Comments, File, Env, Opts) when is_list(Forms) -> Forms1 = erl_syntax:form_list(Forms), header(Forms1, Comments, File, Env, Opts); @@ -174,13 +169,6 @@ header(Forms, Comments, File, Env, Opts) -> Tree = erl_recomment:quick_recomment_forms(Forms, Comments), header(Tree, File, Env, Opts). -%% @spec header(Forms, File::filename(), Env::edoc_env(), -%% Options::proplist()) -> -%% {ok, Tags} | {error, Reason} -%% Forms = syntaxTree() | [syntaxTree()] -%% Tags = [term()] -%% Reason = term() -%% %% @doc Extracts EDoc documentation from commented header file syntax %% trees. Similar to {@link source/5}, but ignores any documentation %% that occurs before a module declaration or a function definition. @@ -190,6 +178,11 @@ header(Forms, Comments, File, Env, Opts) -> %% @see header/5 %% @see //syntax_tools/erl_recomment +-spec header(Forms, File, Env, Opts) -> edoc:entry_data() when + Forms :: erl_syntax:forms(), + File :: filename(), + Env :: edoc:env(), + Opts :: proplist(). header(Forms, File, Env, Opts) when is_list(Forms) -> header(erl_syntax:form_list(Forms), File, Env, Opts); header(Tree, File0, Env, _Opts) -> @@ -219,12 +212,6 @@ add_macro_defs(Defs0, Opts, Env) -> edoc_macros:check_defs(Defs), Env#env{macros = Defs ++ Defs0 ++ Env#env.macros}. -%% @spec file(File::filename(), Context, Env::edoc_env(), -%% Options::proplist()) -> {ok, Tags} | {error, Reason} -%% Context = overview -%% Tags = [term()] -%% Reason = term() -%% %% @doc Reads a text file and returns the list of tags in the file. Any %% lines of text before the first tag are ignored. `Env' is an %% environment created by {@link edoc_lib:get_doc_env/3}. Upon error, @@ -235,6 +222,13 @@ add_macro_defs(Defs0, Opts, Env) -> %% INHERIT-OPTIONS: text/4 +-spec file(File, Context, Env, Opts) -> {ok, Tags} | {error, Reason} when + File :: filename(), + Context :: edoc_tags:tag_flag(), + Env :: edoc:env(), + Opts :: proplist(), + Tags :: [term()], + Reason :: term(). file(File, Context, Env, Opts) -> case file:read_file(File) of {ok, Bin} -> @@ -250,11 +244,6 @@ file(File, Context, Env, Opts) -> end. -%% @spec (Text::string(), Context, Env::edoc_env(), -%% Options::proplist()) -> Tags -%% Context = overview -%% Tags = [term()] -%% %% @doc Returns the list of tags in the text. Any lines of text before %% the first tag are ignored. `Env' is an environment created by {@link %% edoc_lib:get_doc_env/3}. @@ -264,6 +253,12 @@ file(File, Context, Env, Opts) -> %% INHERIT-OPTIONS: add_macro_defs/3 %% DEFER-OPTIONS: source/4 +-spec text(Text, Context, Env, Opts) -> Tags when + Text :: string(), + Context :: overview, + Env :: edoc:env(), + Opts :: proplist(), + Tags :: [term()]. text(Text, Context, Env, Opts) -> text(Text, Context, Env, Opts, ""). @@ -285,11 +280,13 @@ text(Text, Context, Env, Opts, Where) -> end. -%% @spec (Forms::[syntaxTree()], File::filename()) -> module() %% @doc Initialises a module-info record with data about the module %% represented by the list of forms. Exports are guaranteed to exist in %% the set of defined names. +-spec get_module_info(Forms, File) -> edoc:module_meta() when + Forms :: erl_syntax:forms(), + File :: filename(). get_module_info(Forms, File) -> L = case catch {ok, erl_syntax_lib:analyze_forms(Forms)} of {ok, L1} -> @@ -332,10 +329,11 @@ get_list_keyval(Key, L) -> [] end. -%% @spec (Forms::[syntaxTree()]) -> [syntaxTree()] %% @doc Preprocessing: copies any precomments on forms to standalone %% comments, and removes "invisible" forms from the list. +-spec preprocess_forms(Forms) -> Forms when + Forms :: erl_syntax:forms(). preprocess_forms(Tree) -> preprocess_forms_1(erl_syntax:form_list_elements( erl_syntax:flatten_form_list(Tree))). @@ -451,12 +449,11 @@ comment_text([C | Cs], Ss) -> comment_text([], Ss) -> Ss. -%% @spec (string()) -> string() -%% %% @doc Replaces leading `%' characters by spaces. For example, `"%%% %% foo" -> "\s\s\s foo"', but `"% % foo" -> "\s % foo"', since the %% second `%' is preceded by whitespace. +-spec remove_percent_chars(string()) -> string(). remove_percent_chars([$% | Cs]) -> [$\s | remove_percent_chars(Cs)]; remove_percent_chars(Cs) -> Cs. @@ -553,15 +550,12 @@ capitalize(Cs) -> Cs. %% Collects the tags belonging to each entry, checks them, expands %% macros and parses the content. -%% %This is commented out until it can be made private -%% %@type tags() = #tags{names = set(atom()), -%% % single = set(atom()), -%% % module = set(atom()), -%% % footer = set(atom()), -%% % function = set(atom())} -%% % set(T) = sets:set(T) - -record(tags, {names,single,module,function,footer}). +%-type tags() :: #tags{names :: sets:set(atom()), +% single :: sets:set(atom()), +% module :: sets:set(atom()), +% footer :: sets:set(atom()), +% function :: sets:set(atom())}. get_tags(Es, Env, File) -> get_tags(Es, Env, File, dict:new()). diff --git a/lib/edoc/src/edoc_layout.erl b/lib/edoc/src/edoc_layout.erl index 3643419ba1..e314b1279c 100644 --- a/lib/edoc/src/edoc_layout.erl +++ b/lib/edoc/src/edoc_layout.erl @@ -100,11 +100,6 @@ module(Element, Options) -> % Put layout options in a data structure for easier access. -%% %Commented out until it can be made private -%% %@type opts() = #opts{root = string(), -%% % stylesheet = string(), -%% % index_columns = integer()} - -record(opts, {root, stylesheet, index_columns, @@ -112,6 +107,10 @@ module(Element, Options) -> encoding, pretty_printer}). +%-type opts() :: #opts{root :: string(), +% stylesheet :: string(), +% index_columns :: integer()}. + init_opts(Element, Options) -> Encoding = case get_attrval(encoding, Element) of "latin1" -> latin1; diff --git a/lib/edoc/src/edoc_lib.erl b/lib/edoc/src/edoc_lib.erl index 5959fa6f08..9c894b3e0e 100644 --- a/lib/edoc/src/edoc_lib.erl +++ b/lib/edoc/src/edoc_lib.erl @@ -38,13 +38,17 @@ write_file/3, write_file/4, write_info_file/3, read_info_file/1, get_doc_env/1, get_doc_env/3, copy_file/2, run_doclet/2, run_layout/2, - simplify_path/1, timestr/1, datestr/1, read_encoding/2]). + simplify_path/1, timestr/1, datestr/1, read_encoding/2, + infer_module_app/1]). -import(edoc_report, [report/2, warning/2]). -include("edoc.hrl"). -include_lib("xmerl/include/xmerl.hrl"). +-type filename() :: file:filename(). +-type proplist() :: proplists:proplist(). + -define(FILE_BASE, "/"). @@ -68,6 +72,29 @@ read_encoding(File, Options) -> Encoding -> Encoding end. +%% @doc Infer application containing the given module. +%% +%% It's expected that modules which are not preloaded +%% and don't match the `<app>/ebin/<mod>.beam' path pattern +%% will NOT have an app name inferred properly. +%% `no_app' is returned in such cases. +-spec infer_module_app(module()) -> no_app | {app, atom()}. +infer_module_app(Mod) -> + case code:which(Mod) of + ModPath when is_list(ModPath) -> + case lists:reverse(string:tokens(ModPath, "/")) of + [_BeamFile, "ebin", AppVer | _] -> + [App | _] = string:tokens(AppVer, "-"), + {app, list_to_atom(App)}; + _ -> + no_app + end; + preloaded -> + {app, erts}; + _ -> + no_app + end. + %% @private count(X, Xs) -> count(X, Xs, 0). @@ -305,19 +332,19 @@ parse_expr(S, L) -> end. +-record(info, {name = "", + email = "", + uri = ""}). + +%-type info() :: #info{name :: string(), +% email :: string(), +% uri :: string()}. + %% @doc EDoc "contact information" parsing. This is the type of the %% content in e.g. %% <a href="overview-summary.html#mtag-author">`@author'</a> tags. %% @private -%% % @type info() = #info{name = string(), -%% % email = string(), -%% % uri = string()} - --record(info, {name = "" :: string(), - email = "" :: string(), - uri = "" :: string()}). - parse_contact(S, L) -> I = scan_name(S, L, #info{}, []), {I#info.name, I#info.email, I#info.uri}. @@ -554,13 +581,14 @@ try_subdir(Dir, Subdir) -> false -> Dir end. -%% @spec (Text::deep_string(), Dir::edoc:filename(), -%% Name::edoc:filename()) -> ok -%% %% @doc Write the given `Text' to the file named by `Name' in directory %% `Dir'. If the target directory does not exist, it will be created. %% @private +-spec write_file(Text, Dir, Name) -> ok when + Text :: unicode:chardata(), + Dir :: filename(), + Name :: filename(). write_file(Text, Dir, Name) -> write_file(Text, Dir, Name, [{encoding,latin1}]). @@ -587,10 +615,9 @@ write_info_file(App, Modules, Dir) -> S = ["%% encoding: UTF-8\n" | S0], write_file(S, Dir, ?INFO_FILE, [{encoding,unicode}]). -%% @spec (Name::edoc:filename()) -> {ok, string()} | {error, Reason} -%% %% @doc Reads text from the file named by `Name'. +-spec read_file(filename()) -> {ok, string()} | {error, term()}. read_file(File) -> case file:read_file(File) of {ok, Bin} -> @@ -794,22 +821,13 @@ add_new(K, V, D) -> dict:store(K, V, D) end. -%% @spec (Options::proplist()) -> edoc_env() %% @equiv get_doc_env([], [], Opts) %% @private +-spec get_doc_env(proplist()) -> edoc:env(). get_doc_env(Opts) -> get_doc_env([], [], Opts). -%% @spec (App, Modules, Options::proplist()) -> edoc_env() -%% App = [] | atom() -%% Modules = [atom()] -%% proplist() = [term()] -%% -%% @type proplist() = //stdlib/proplists:property(). -%% @type edoc_env(). Environment information needed by EDoc for -%% generating references. The data representation is not documented. -%% %% @doc Creates an environment data structure used by parts of EDoc for %% generating references, etc. See {@link edoc:run/2} for a description %% of the options `file_suffix', `app_default' and `doc_path'. @@ -821,6 +839,10 @@ get_doc_env(Opts) -> %% INHERIT-OPTIONS: get_doc_links/4 %% DEFER-OPTIONS: edoc:run/2 +-spec get_doc_env(App, Modules, Options) -> edoc:env() when + App :: atom(), + Modules :: [module()], + Options :: proplist(). get_doc_env(App, Modules, Opts) -> Suffix = proplists:get_value(file_suffix, Opts, ?DEFAULT_FILE_SUFFIX), diff --git a/lib/edoc/src/edoc_macros.erl b/lib/edoc/src/edoc_macros.erl index 9bfa41efe0..3085ac1313 100644 --- a/lib/edoc/src/edoc_macros.erl +++ b/lib/edoc/src/edoc_macros.erl @@ -32,11 +32,6 @@ -export([expand_tags/3, std_macros/1, check_defs/1]). -%% Avoid warning for imported function 3 clashing with autoimported BIF. --compile({no_auto_import,[error/3]}). - --import(edoc_report, [report/2, error/3, warning/4]). - -include("edoc.hrl"). -include("edoc_types.hrl"). @@ -62,7 +57,7 @@ std_macros(Env) -> check_defs([{K, D} | Ds]) when is_atom(K), is_list(D) -> check_defs(Ds); check_defs([X | _Ds]) -> - report("bad macro definition: ~P.", [X, 10]), + edoc_report:report("bad macro definition: ~P.", [X, 10]), exit(error); check_defs([]) -> ok. @@ -72,7 +67,7 @@ check_defs([]) -> %% together with the file name etc. The expanded text must be flat! date_macro(_S, _Line, _Env) -> - edoc_lib:datestr(date()). + edoc_lib:datestr(date()). time_macro(_S, _Line, _Env) -> edoc_lib:timestr(time()). @@ -130,7 +125,7 @@ expand_tag(Cs, L, Defs, Env, Where) -> {'EXIT', R} -> exit(R); {error, L1, Error} -> - error(L1, Where, Error), + edoc_report:error(L1, Where, Error), exit(error); Other -> throw(Other) @@ -205,8 +200,8 @@ expand_macro_def(M, Arg, L, Defs, St, As) -> end, expand(Txt, L, Defs1, St1, As); error -> - warning(L, St1#state.where, - "undefined macro {@~s}.", [M]), + edoc_report:warning(L, St1#state.where, + "undefined macro {@~s}.", [M]), "??" end end. diff --git a/lib/edoc/src/edoc_parser.yrl b/lib/edoc/src/edoc_parser.yrl index 30e09444b0..a336edd7e5 100644 --- a/lib/edoc/src/edoc_parser.yrl +++ b/lib/edoc/src/edoc_parser.yrl @@ -337,7 +337,7 @@ all_vars(As) -> %% --------------------------------------------------------------------- -%% @doc EDoc type specification parsing. Parses the content of +%% EDoc type specification parsing. Parses the content of %% <a href="overview-summary.html#ftag-spec">`@spec'</a> declarations. parse_spec(S, L) -> @@ -355,7 +355,7 @@ parse_spec(S, L) -> %% --------------------------------------------------------------------- -%% @doc EDoc type definition parsing. Parses the content of +%% EDoc type definition parsing. Parses the content of %% <a href="overview-summary.html#gtag-type">`@type'</a> declarations. parse_typedef(S, L) -> diff --git a/lib/edoc/src/edoc_refs.erl b/lib/edoc/src/edoc_refs.erl index 6ff0da0b75..c6c118eeda 100644 --- a/lib/edoc/src/edoc_refs.erl +++ b/lib/edoc/src/edoc_refs.erl @@ -36,12 +36,21 @@ function/2, function/3, function/4, type/1, type/2, type/3, to_string/1, to_label/1, get_uri/2, is_top/2]). +-export_type([t/0]). + -import(edoc_lib, [join_uri/2, escape_uri/1]). -include("edoc.hrl"). -define(INDEX_FILE, "index.html"). +-type t() :: {app, atom()} + | {app, atom(), t()} + | {module, atom()} + | {module, atom(), t()} + | {function, atom(), arity()} + | {type, atom()} + | {type, atom(), arity()}. %% Creating references: diff --git a/lib/edoc/src/edoc_report.erl b/lib/edoc/src/edoc_report.erl index 8d705d466e..bd02e61843 100644 --- a/lib/edoc/src/edoc_report.erl +++ b/lib/edoc/src/edoc_report.erl @@ -23,15 +23,15 @@ %% @copyright 2001-2003 Richard Carlsson %% @author Richard Carlsson <carlsson.richard@gmail.com> %% @see edoc -%% @end +%% @end %% ===================================================================== %% @doc EDoc verbosity/error reporting. -module(edoc_report). -%% Avoid warning for local functions error/2,3 clashing with autoimported BIFs. --compile({no_auto_import,[error/2,error/3]}). +%% Avoid warning for local functions error/{1,2,3} clashing with autoimported BIF. +-compile({no_auto_import, [error/1, error/2, error/3]}). -export([error/1, error/2, error/3, diff --git a/lib/edoc/src/edoc_run.erl b/lib/edoc/src/edoc_run.erl index 50aba0a930..097f7e44e0 100644 --- a/lib/edoc/src/edoc_run.erl +++ b/lib/edoc/src/edoc_run.erl @@ -28,10 +28,12 @@ %% @doc Interface for calling EDoc from Erlang startup options. %% %% The following is an example of typical usage in a Makefile: +%% %% ```docs: %% erl -noshell -run edoc_run application "'$(APP_NAME)'" \ %% '"."' '[{def,{vsn,"$(VSN)"}}]' %% ''' +%% %% (note the single-quotes to avoid shell expansion, and the %% double-quotes enclosing the strings). %% @@ -51,15 +53,11 @@ -type args() :: [string()]. - -%% @spec application([string()]) -> none() -%% %% @doc Calls {@link edoc:application/3} with the corresponding %% arguments. The strings in the list are parsed as Erlang constant -%% terms. The list can be either `[App]', `[App, Options]' or `[App, -%% Dir, Options]'. In the first case {@link edoc:application/1} is -%% called instead; in the second case, {@link edoc:application/2} is -%% called. +%% terms. The list can be either `[App]', `[App, Options]' +%% or `[App, Dir, Options]'. In the first case {@link edoc:application/1} is +%% called instead; in the second case, {@link edoc:application/2} is called. %% %% The function call never returns; instead, the emulator is %% automatically terminated when the call has completed, signalling @@ -78,8 +76,6 @@ application(Args) -> end, run(F). -%% @spec files([string()]) -> none() -%% %% @doc Calls {@link edoc:files/2} with the corresponding arguments. The %% strings in the list are parsed as Erlang constant terms. The list can %% be either `[Files]' or `[Files, Options]'. In the first case, {@link @@ -101,7 +97,7 @@ files(Args) -> end, run(F). -%% @hidden Not official yet +%% @hidden Not official yet -spec toc(args()) -> no_return(). toc(Args) -> F = fun () -> @@ -115,8 +111,6 @@ toc(Args) -> run(F). -%% @spec file([string()]) -> none() -%% %% @deprecated This is part of the old interface to EDoc and is mainly %% kept for backwards compatibility. The preferred way of generating %% documentation is through one of the functions {@link application/1} diff --git a/lib/edoc/src/edoc_specs.erl b/lib/edoc/src/edoc_specs.erl index 19f890ed8b..9b5050a7c2 100644 --- a/lib/edoc/src/edoc_specs.erl +++ b/lib/edoc/src/edoc_specs.erl @@ -23,7 +23,7 @@ -export([type/2, spec/1, dummy_spec/1, docs/2]). --export([add_data/4, tag/1, is_tag/1]). +-export([add_type_data/4, tag/1, is_tag/1]). -include("edoc.hrl"). -include("edoc_types.hrl"). @@ -96,16 +96,16 @@ dummy_spec(Form) -> docs(Forms, CommentFun) -> find_type_docs(Forms, [], CommentFun). --type entry() :: #entry{}. --type module_info() :: #module{}. --type entries() :: [entry()]. --spec add_data(Entries::entries(), Options::proplists:proplist(), - File::file:filename(), Module::module_info()) -> entries(). - %% @doc Create tags a la EDoc for Erlang specifications and types. %% Exported types and types used (indirectly) by Erlang specs are %% added to the entries. -add_data(Entries, Opts, File, Module) -> + +-spec add_type_data(Entries, Opts, File, Module) -> [edoc:entry()] when + Entries :: [edoc:entry()], + Opts :: proplists:proplist(), + File :: file:filename(), + Module :: edoc:module_meta(). +add_type_data(Entries, Opts, File, Module) -> TypeDefs0 = espec_types(Entries), TypeTable = ets:new(etypes, [ordered_set]), Es1 = expand_records(Entries, TypeDefs0, TypeTable, Opts, File, Module), @@ -634,7 +634,7 @@ analyze_type_attribute(Form) -> %% @doc Return `true' if `Tag' is one of the specification and type %% attribute tags recognized by the Erlang compiler. --spec is_tag(Tag::atom()) -> boolean(). +-spec is_tag(Tag :: tag_kind() | term()) -> boolean(). is_tag(opaque) -> true; is_tag(spec) -> true; @@ -643,8 +643,8 @@ is_tag(_) -> false. %% @doc Return the kind of the attribute tag. --type tag_kind() :: 'type' | 'spec' | 'unknown'. --spec tag(Tag::atom()) -> tag_kind(). +-type tag_kind() :: 'spec' | 'type'. +-spec tag(Tag :: atom()) -> tag_kind() | unknown. tag(opaque) -> type; tag(spec) -> spec; diff --git a/lib/edoc/src/edoc_tags.erl b/lib/edoc/src/edoc_tags.erl index 3b117f252a..9f05894ec0 100644 --- a/lib/edoc/src/edoc_tags.erl +++ b/lib/edoc/src/edoc_tags.erl @@ -35,12 +35,9 @@ -export([tags/0, tags/1, tag_names/0, tag_parsers/0, scan_lines/2, filter_tags/2, filter_tags/3, check_tags/4, parse_tags/4, - check_types/3]). + check_types/3]). -%% Avoid warning for imported function 3 clashing with autoimported BIF. --compile({no_auto_import,[error/3]}). - --import(edoc_report, [report/4, warning/4, error/3]). +-export_type([tag_flag/0]). -include("edoc.hrl"). -include("edoc_types.hrl"). @@ -64,6 +61,13 @@ %% - @history (never properly updated; use version control etc.) %% - @category (useless; superseded by keywords or free text search) +-type tag() :: author | copyright | deprecated | doc | docfile | 'end' | equiv | headerfile + | hidden | param | private | reference | returns | see | since | spec | throws + | title | 'TODO' | todo | type | version. +-type parser() :: text | xml | fun(). +-type tag_flag() :: module | footer | function | overview | single. + +-spec tags() -> [{tag(), parser(), [tag_flag()]}]. tags() -> All = [module,footer,function,overview], [{author, fun parse_contact/4, [module,overview]}, @@ -223,7 +227,7 @@ filter_tags([#tag{name = N, line = L} = T | Ts], Tags, Where, Ts1) -> false -> case Where of no -> ok; - _ -> warning(L, Where, "tag @~s not recognized.", [N]) + _ -> edoc_report:warning(L, Where, "tag @~s not recognized.", [N]) end, filter_tags(Ts, Tags, Where, Ts1) end; @@ -242,7 +246,7 @@ check_tags([#tag{name = T, line = L} | Ts], Allow, Single, Where, Error, Seen) - false -> check_tags(Ts, Allow, Single, Where, Error, Seen); true -> - report(L, Where, "multiple @~s tag.", [T]), + edoc_report:report(L, Where, "multiple @~s tag.", [T]), check_tags(Ts, Allow, Single, Where, true, Seen) end; false -> @@ -251,7 +255,7 @@ check_tags([#tag{name = T, line = L} | Ts], Allow, Single, Where, Error, Seen) - true -> check_tags(Ts, Allow, Single, Where, Error, Seen1); false -> - report(L, Where, "tag @~s not allowed here.", [T]), + edoc_report:report(L, Where, "tag @~s not allowed here.", [T]), check_tags(Ts, Allow, Single, Where, true, Seen1) end end; @@ -285,7 +289,7 @@ parse_tag(T, F, Env, Where) -> {expand, Ts} -> Ts; {error, L, Error} -> - error(L, Where, Error), + edoc_report:error(L, Where, Error), exit(error); {'EXIT', R} -> exit(R); Other -> throw(Other) @@ -344,8 +348,8 @@ parse_typedef(Data, Line, _Env, Where) -> throw_error(Line, {"redefining built-in type '~w'.", [T]}); true -> - warning(Line, Where, "redefining built-in type '~w'.", - [T]), + edoc_report:warning(Line, Where, "redefining built-in type '~w'.", + [T]), Def end; false -> @@ -354,7 +358,7 @@ parse_typedef(Data, Line, _Env, Where) -> -type line() :: erl_anno:line(). --spec parse_file(_, line(), _, _) -> no_return(). +-spec parse_file(_, line(), edoc:env(), _) -> no_return(). parse_file(Data, Line, Env, _Where) -> case edoc_lib:parse_expr(Data, Line) of @@ -370,7 +374,7 @@ parse_file(Data, Line, Env, _Where) -> throw_error(Line, file_not_string) end. --spec parse_header(_, line(), _, _) -> no_return(). +-spec parse_header(_, line(), edoc:env(), _) -> no_return(). parse_header(Data, Line, Env, {Where, _}) -> parse_header(Data, Line, Env, Where); @@ -393,6 +397,7 @@ parse_header(Data, Line, Env, Where) when is_list(Where) -> -type err() :: 'file_not_string' | {'file_not_found', file:filename()} | {'read_file', file:filename(), term()} + | {string(), [term()]} | string(). -spec throw_error(line(), err()) -> no_return(). @@ -519,4 +524,4 @@ check_used_type(#t_name{name = N, module = Mod}=Name, Args, P, LocalTypes) -> type_warning(Line, File, S, N, NArgs) -> AS = ["/"++integer_to_list(NArgs) || NArgs > 0], - warning(Line, File, S++" ~w~s", [N, AS]). + edoc_report:warning(Line, File, S++" ~w~s", [N, AS]). diff --git a/lib/edoc/src/edoc_types.erl b/lib/edoc/src/edoc_types.erl index 510f9513b2..51219b5143 100644 --- a/lib/edoc/src/edoc_types.erl +++ b/lib/edoc/src/edoc_types.erl @@ -39,6 +39,100 @@ -include("edoc_types.hrl"). -include_lib("xmerl/include/xmerl.hrl"). +%-type t_spec() :: #t_spec{name :: t_name(), +% type :: t_type(), +% defs :: [t_def()]}. +%% Function specification. + +-type type() :: t_atom() | t_binary() | t_float() | t_fun() | t_integer() + | t_integer_range() | t_list() | t_nil()| t_nonempty_list() + | t_record() | t_tuple() | t_type() | t_union() | t_var() + | t_paren(). + +%-type t_typedef() :: #t_typedef{name :: t_name(), +% args :: [type()], +% type :: type() | undefined, +% defs :: [t_def()]}. +%% Type declaration/definition. + +%-type t_throws() :: #t_throws{type :: type(), +% defs :: [t_def()]}. +%% Exception declaration. + +%-type t_def() :: #t_def{name :: t_type() | t_var(), +% type :: type()}. +%% Local definition `name = type'. + +-type t_name() :: #t_name{app :: [] | atom(), + module :: [] | atom(), + name :: [] | atom()}. + +-type t_var() :: #t_var{a :: list(), + name :: [] | atom()}. +%% Type variable. + +-type t_type() :: #t_type{a :: list(), + name :: t_name(), + args :: [type()]}. +%% Abstract type `name(...)'. + +-type t_union() :: #t_union{a :: list(), + types :: [type()]}. +%% Union type `t1 | ... | tN'. + +-type t_fun() :: #t_fun{a :: list(), + args :: [type()], + range :: type()}. +%% Function `(t1, ..., tN) -> range'. + +-type t_tuple() :: #t_tuple{a :: list(), + types :: [type()]}. +%% Tuple type `{t1,...,tN}'. + +-type t_list() :: #t_list{a :: list(), + type :: type()}. +%% List type `[type]'. + +-type t_nil() :: #t_nil{a :: list()}. +%% Empty-list constant `[]'. + +-type t_nonempty_list() :: #t_nonempty_list{a :: list(), + type :: type()}. +%% List type `[type, ...]'. + +-type t_atom() :: #t_atom{a :: list(), + val :: atom()}. +%% Atom constant. + +-type t_integer() :: #t_integer{a :: list(), + val :: integer()}. +%% Integer constant. + +-type t_integer_range() :: #t_integer_range{a :: list(), + from :: integer(), + to :: integer()}. + +-type t_binary() :: #t_binary{a :: list(), + base_size :: integer(), + unit_size :: integer()}. + +-type t_float() :: #t_float{a :: list(), + val :: float()}. +%% Floating-point constant. + +-type t_record() :: #t_record{a :: list(), + name :: t_atom(), + fields :: [t_field()]}. +%% Record "type" `#r{f1, ..., fN}'. + +-type t_field() :: #t_field{a :: list(), + name :: type(), + type :: type()}. +%% Named field `n1 = t1'. + +-type t_paren() :: #t_paren{a :: list(), type :: type()}. +%% Parentheses. + is_predefined(cons, 2) -> true; is_predefined(deep_string, 0) -> true; is_predefined(F, A) -> erl_internal:is_type(F, A). @@ -63,7 +157,18 @@ to_label(N) -> edoc_refs:to_label(to_ref(N)). get_uri(Name, Env) -> - edoc_refs:get_uri(to_ref(Name), Env). + NewName = infer_module_app(Name), + edoc_refs:get_uri(to_ref(NewName), Env). + +infer_module_app(#t_name{app = [], module = M} = TName) when is_atom(M) -> + case edoc_lib:infer_module_app(M) of + no_app -> + TName; + {app, App} when is_atom(App) -> + TName#t_name{app = App} + end; +infer_module_app(Other) -> + Other. to_xml(#t_var{name = N}, _Env) -> {typevar, [{name, atom_to_list(N)}], []}; diff --git a/lib/edoc/src/edoc_types.hrl b/lib/edoc/src/edoc_types.hrl index 99719793d0..0a40c8aa87 100644 --- a/lib/edoc/src/edoc_types.hrl +++ b/lib/edoc/src/edoc_types.hrl @@ -28,37 +28,14 @@ %% Type specification data structures -%% @type t_spec() = #t_spec{name = t_name(), -%% type = t_type(), -%% defs = [t_def()]} - -record(t_spec, {name, type, defs=[]}). % function specification -%% @type type() = t_atom() | t_binary() | t_float() | t_fun() | t_integer() -%% | t_integer_range() | t_list() | t_nil()| t_nonempty_list() -%% | t_record() | t_tuple() | t_type() | t_union() | t_var() -%% | t_paren() - -%% @type t_typedef() = #t_typedef{name = t_name(), -%% args = [type()], -%% type = type() | undefined, -%% defs = [t_def()]}. - -record(t_typedef, {name, args, type, defs=[]}). % type declaration/definition -%% @type t_throws() = #t_throws{type = type(), -%% defs = [t_def()]} - -record(t_throws, {type, defs=[]}). % exception declaration -%% @type t_def() = #t_def{name = t_type() | t_var(), -%% type = type()} - -record(t_def, {name, type}). % local definition 'name = type' -%% @type t_name() = #t_name{app = [] | atom(), -%% module = [] | atom(), -%% name = [] | atom()} -record(t_name, {app = [], % app = [] if module = [] module=[], % unqualified if module = [] @@ -74,93 +51,43 @@ -define(set_t_ann(X, Y), setelement(2, X, Y)). -define(add_t_ann(X, Y), ?set_t_ann(X, [Y | ?t_ann(X)])). -%% @type t_var() = #t_var{a = list(), name = [] | atom()} - -record(t_var, {a=[], name=[]}). % type variable -%% @type t_type() = #t_type{a = list(), -%% name = t_name(), -%% args = [type()]} - -record(t_type, {a=[], % abstract type 'name(...)' name, args = []}). -%% @type t_union() = #t_union{a = list(), -%% types = [type()]} - -record(t_union, {a=[], types = []}). % union type 't1|...|tN' -%% @type t_fun() = #t_fun{a = list(), -%% args = [type()], -%% range = type()} - -record(t_fun, {a=[], args, range}). % function '(t1,...,tN) -> range' -%% @type t_tuple() = #t_tuple{a = list(), -%% types = [type()]} - -record(t_tuple, {a=[], types = []}). % tuple type '{t1,...,tN}' -%% @type t_list() = #t_list{a = list(), -%% type = type()} - -record(t_list, {a=[], type}). % list type '[type]' -%% @type t_nil() = #t_nil{a = list()} - -record(t_nil, {a=[]}). % empty-list constant '[]' -%% @type t_nonempty_list() = #t_nonempty_list{a = list(), -%% type = type()} - -record(t_nonempty_list, {a=[], type}). % list type '[type, ...]' -%% @type t_atom() = #t_atom{a = list(), -%% val = atom()} - -record(t_atom, {a=[], val}). % atom constant -%% @type t_integer() = #t_integer{a = list(), -%% val = integer()} - -record(t_integer, {a=[], val}). % integer constant -%% @type t_integer_range() = #t_integer_range{a = list(), -%% from = integer(), -%% to = integer()} - -record(t_integer_range, {a=[], from, to}). -%% @type t_binary() = #t_binary{a = list(), -%% base_size = integer(), -%% unit_size = integer()} - -record(t_binary, {a=[], base_size = 0, unit_size = 0}). -%% @type t_float() = #t_float{a = list(), -%% val = float()} - -record(t_float, {a=[], val}). % floating-point constant -%% @type t_record() = #t_list{a = list(), -%% name = t_atom(), -%% fields = [field()]} - -record(t_record, {a=[], % record "type" '#r{f1,...,fN}' name, fields = []}). -%% @type t_field() = #t_field{a = list(), -%% name = type(), -%% type = type()} - -record(t_field, {a=[], name, type}). % named field 'n1=t1' -%% @type t_paren() = #t_paren{a = list(), type = type()} - -record(t_paren, {a=[], type}). % parentheses -record(t_map, {a=[], types=[]}). + -record(t_map_field, {a=[], assoc_type, k_type, v_type}). diff --git a/lib/edoc/test/edoc_SUITE.erl b/lib/edoc/test/edoc_SUITE.erl index 29ca9d1203..386699272b 100644 --- a/lib/edoc/test/edoc_SUITE.erl +++ b/lib/edoc/test/edoc_SUITE.erl @@ -24,12 +24,13 @@ %% Test cases -export([app/1,appup/1,build_std/1,build_map_module/1,otp_12008/1, - build_app/1, otp_14285/1]). + build_app/1, otp_14285/1, infer_module_app_test/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. -all() -> - [app,appup,build_std,build_map_module,otp_12008, build_app, otp_14285]. +all() -> + [app,appup,build_std,build_map_module,otp_12008, build_app, otp_14285, + infer_module_app_test]. groups() -> []. @@ -129,3 +130,27 @@ otp_14285(Config) -> ok = edoc:files([Un1], Opts2), ok = edoc:files([Un2], Opts2), ok. + +infer_module_app_test(Config) -> + Modules = lists:map(fun ({M, _, _}) -> + {list_to_atom(M), M ++ ".beam"} + end, code:all_available()), + true = lists:all(fun infer_module_app_test_/1, Modules). + +infer_module_app_test_({M, Beam}) -> + case edoc_lib:infer_module_app(M) of + no_app -> + true; + {app, App} when is_atom(App) -> + %% When `App' is actually returned, the corresponding + %% BEAM file is expected to be found on disk in the app's + %% ebin dir. + %% `preloaded' modules should be found under `erts/ebin' + %% or under `erts/preloaded/ebin' in case of running tests + %% from the source tree. + BeamPath1 = filename:join([code:lib_dir(App), "ebin", Beam]), + BeamPath2 = filename:join([code:lib_dir(App), "preloaded", "ebin", Beam]), + R1 = filelib:is_regular(BeamPath1), + R2 = filelib:is_regular(BeamPath2), + R1 orelse R2 + end. |