summaryrefslogtreecommitdiff
path: root/lib/edoc
diff options
context:
space:
mode:
authorRadek Szymczyszyn <radoslaw.szymczyszyn@erlang-solutions.com>2020-12-07 15:38:30 +0100
committerRadek Szymczyszyn <radoslaw.szymczyszyn@erlang-solutions.com>2020-12-21 10:23:02 +0100
commit07d71c6532b916600c0598758f8684c18418307d (patch)
tree9a23f4c573404afc21004ce0296a2f9e61520678 /lib/edoc
parent0b8d5c550dbb82ad635e54b3bf30d5b026c8ca97 (diff)
downloaderlang-07d71c6532b916600c0598758f8684c18418307d.tar.gz
Replace @spec/@type tags with corresponding attributes
This also moves types from header files to modules, so that they're namespaced. @spec/@type syntax allows to specify an application that is referrred to (e.g. `//syntax_tools/erl_syntax:forms()`), whereas the -spec/-type attribute syntax doesn't. In order to keep links generated by EDoc valid, an app name inference mechanism is introduced, which works based on the modules currently available in the code path. This allows to use specs/types which refer to types defined outside the current application and make sure EDoc will generate proper external links.
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.