summaryrefslogtreecommitdiff
path: root/lib/edoc
diff options
context:
space:
mode:
Diffstat (limited to 'lib/edoc')
-rw-r--r--lib/edoc/include/edoc_doclet.hrl33
-rw-r--r--lib/edoc/src/edoc.erl163
-rw-r--r--lib/edoc/src/edoc.hrl41
-rw-r--r--lib/edoc/src/edoc_data.erl5
-rw-r--r--lib/edoc/src/edoc_doclet.erl54
-rw-r--r--lib/edoc/src/edoc_extract.erl128
-rw-r--r--lib/edoc/src/edoc_layout.erl9
-rw-r--r--lib/edoc/src/edoc_lib.erl70
-rw-r--r--lib/edoc/src/edoc_macros.erl15
-rw-r--r--lib/edoc/src/edoc_parser.yrl4
-rw-r--r--lib/edoc/src/edoc_refs.erl9
-rw-r--r--lib/edoc/src/edoc_report.erl6
-rw-r--r--lib/edoc/src/edoc_run.erl18
-rw-r--r--lib/edoc/src/edoc_specs.erl22
-rw-r--r--lib/edoc/src/edoc_tags.erl33
-rw-r--r--lib/edoc/src/edoc_types.erl107
-rw-r--r--lib/edoc/src/edoc_types.hrl75
-rw-r--r--lib/edoc/test/edoc_SUITE.erl31
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.