[gi-docgen/check-command] Add a "check" command
- From: Emmanuele Bassi <ebassi src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gi-docgen/check-command] Add a "check" command
- Date: Thu, 3 Jun 2021 08:50:28 +0000 (UTC)
commit c1d272cd07217c600f0fdc71243d3ae4d5736484
Author: Emmanuele Bassi <ebassi gnome org>
Date: Thu Jun 3 09:45:41 2021 +0100
Add a "check" command
We want to catch undocumented API before we merge it. The introspection
scanner can fill in the defaults, and this might not cause warnings; but
the documentation is another matter entirely.
Right now, we check only for missing documentation. In the future, we
should add more checks, like:
- property and signal annotations
- invalid links
- legacy gtk-doc content
- `|[ ... ]|` code blocks
- `[][]` internal links
- `%` and `#` sigils
Fixes: #51
docs/gi-docgen.1 | 18 ++++
docs/tools/check.rst | 32 ++++++
docs/tools/index.rst | 6 +-
gidocgen/gdcheck.py | 280 ++++++++++++++++++++++++++++++++++++++++++++++++++
gidocgen/gidocmain.py | 6 +-
5 files changed, 340 insertions(+), 2 deletions(-)
---
diff --git a/docs/gi-docgen.1 b/docs/gi-docgen.1
index 36fc14e..1d64cbd 100644
--- a/docs/gi-docgen.1
+++ b/docs/gi-docgen.1
@@ -120,6 +120,24 @@ be used multiple times to specify multiple directories
.B \-\-dry\-run
parse the \fIGIR_FILE\fR without generating the documentation
+.SH The check command
+.sp
+The \fBcheck\fR command runs a series of checks on the introspection
+file, to verify that public API is properly documented. It can be used
+as part of a test suite.
+
+.B gi-docgen check [
+.I OPTIONS
+.B ] [
+.I GIR_FILE
+.B ]
+
+.SS "options:"
+.TP
+.BI \-\-add\-include\-path\fB= PATH
+add a directory to the path which the scanner uses to find GIR files. Can
+be used multiple times to specify multiple directories
+
.SH The help command
.sp
The \fBhelp\fR command prints out the command line help. If you don't
diff --git a/docs/tools/check.rst b/docs/tools/check.rst
new file mode 100644
index 0000000..f1e7bc4
--- /dev/null
+++ b/docs/tools/check.rst
@@ -0,0 +1,32 @@
+.. SPDX-FileCopyrightText: 2021 GNOME Foundation
+..
+.. SPDX-License-Identifier: Apache-2.0 OR GPL-3.0-or-later
+
+===============
+gi-docgen check
+===============
+
+Check the documentation in the introspection data
+-------------------------------------------------
+
+SYNOPSIS
+========
+
+**gi-docgen check** [OPTIONS...] [GIRFILE]
+
+DESCRIPTION
+===========
+
+The **check** command runs a series of checks on the introspection
+file, to verify that public API is properly documented. It can be used
+as part of a test suite.
+
+OPTIONS
+=======
+
+``--add-include--path DIR``
+ Adds ``DIR`` to the list of paths used to find introspection data
+ files included in the given ``GIRFILE``. The default search path
+ for GIR files is ``$XDG_DATA_DIRS/gir-1.0`` and ``$XDG_DATA_HOME/gir-1.0``;
+ this option is typically used to include uninstalled GIR files, or
+ non-standard locations.
diff --git a/docs/tools/index.rst b/docs/tools/index.rst
index e52da63..5508dc2 100644
--- a/docs/tools/index.rst
+++ b/docs/tools/index.rst
@@ -13,6 +13,7 @@ Commands
generate
gen-index
+ check
SYNOPSIS
========
@@ -30,7 +31,10 @@ COMMANDS
:doc:`gen-index`
Generates the symbol indices for search
-
+
+:doc:`check`
+ Checks the documentation
+
OPTIONS
=======
diff --git a/gidocgen/gdcheck.py b/gidocgen/gdcheck.py
new file mode 100644
index 0000000..0a5fce7
--- /dev/null
+++ b/gidocgen/gdcheck.py
@@ -0,0 +1,280 @@
+# SPDX-FileCopyrightText: 2021 GNOME Foundation
+# SPDX-License-Identifier: Apache-2.0 OR GPL-3.0-or-later
+
+import argparse
+import sys
+
+from . import gir, log, utils
+
+
+HELP_MSG = "Checks introspection data for valid documentation"
+
+
+def _check_doc_element(path, symbol, results):
+ name = '.'.join(path + [symbol.name])
+
+ if symbol.source_position is not None:
+ filename = symbol.source_position[0]
+ line = symbol.source_position[1]
+ else:
+ filename = "<unknown>"
+ line = 0
+
+ if symbol.doc is None:
+ results.append(f"Symbol '{name}' at {filename}:{line} is not documented")
+
+
+def _check_arg_docs(path, arguments, results):
+ symbol = '.'.join(path)
+ for arg in arguments:
+ if arg.doc is None:
+ results.append(f"Parameter '{arg.name}' of symbol '{symbol}' is not documented")
+
+
+def _check_retval_docs(path, retval, results):
+ if retval is None:
+ return
+
+ if isinstance(retval.target, gir.VoidType):
+ return
+
+ symbol = '.'.join(path)
+ if retval.doc is None:
+ results.append(f"Return value for symbol '{symbol}' is not documented")
+
+
+def _check_aliases(repository, symbols, results):
+ for alias in symbols:
+ _check_doc_element([repository.namespace.name], alias, results)
+
+
+def _check_bitfields(repository, symbols, results):
+ for bitfield in symbols:
+ _check_doc_element([repository.namespace.name], bitfield, results)
+
+ for member in bitfield.members:
+ _check_doc_element([repository.namespace.name, bitfield.name], member, results)
+
+ for func in bitfield.functions:
+ _check_doc_element([repository.namespace.name, bitfield.name], func, results)
+ _check_arg_docs([repository.namespace.name, bitfield.name, func.name], func.parameters, results)
+ _check_retval_docs([repository.namespace.name, bitfield.name, func.name], func.return_value,
results)
+
+
+def _check_callbacks(repository, symbols, results):
+ for cb in symbols:
+ _check_doc_element([repository.namespace.name], cb, results)
+ _check_arg_docs([repository.namespace.name, cb.name], cb.parameters, results)
+ _check_retval_docs([repository.namespace.name, cb.name], cb.return_value, results)
+
+
+def _check_classes(repository, symbols, results):
+ for cls in symbols:
+ _check_doc_element([repository.namespace.name], cls, results)
+
+ for ctor in cls.constructors:
+ _check_doc_element([repository.namespace.name, cls.name], ctor, results)
+ _check_arg_docs([repository.namespace.name, cls.name, ctor.name], ctor.parameters, results)
+ _check_retval_docs([repository.namespace.name, cls.name, ctor.name], ctor.return_value, results)
+
+ for method in cls.methods:
+ _check_doc_element([repository.namespace.name, cls.name], method, results)
+ _check_arg_docs([repository.namespace.name, cls.name, method.name], method.parameters, results)
+ _check_retval_docs([repository.namespace.name, cls.name, method.name], method.return_value,
results)
+
+ for func in cls.functions:
+ _check_doc_element([repository.namespace.name, cls.name], func, results)
+ _check_arg_docs([repository.namespace.name, cls.name, func.name], func.parameters, results)
+ _check_retval_docs([repository.namespace.name, cls.name, func.name], func.return_value, results)
+
+ for prop in cls.properties.values():
+ _check_doc_element([repository.namespace.name, cls.name], prop, results)
+
+ for signal in cls.signals.values():
+ _check_doc_element([repository.namespace.name, cls.name], signal, results)
+ _check_arg_docs([repository.namespace.name, cls.name, signal.name], signal.parameters, results)
+ _check_retval_docs([repository.namespace.name, cls.name, signal.name], signal.return_value,
results)
+
+
+def _check_constants(repository, symbols, results):
+ for constant in symbols:
+ _check_doc_element([repository.namespace.name], constant, results)
+
+
+def _check_domains(repository, symbols, results):
+ for domain in symbols:
+ _check_doc_element([repository.namespace.name], domain, results)
+
+ for member in domain.members:
+ _check_doc_element([repository.namespace.name, domain.name], member, results)
+
+ for func in domain.functions:
+ _check_doc_element([repository.namespace.name, domain.name], func, results)
+ _check_arg_docs([repository.namespace.name, domain.name, func.name], func.parameters, results)
+ _check_retval_docs([repository.namespace.name, domain.name, func.name], func.return_value,
results)
+
+
+def _check_enums(repository, symbols, results):
+ for enum in symbols:
+ _check_doc_element([repository.namespace.name], enum, results)
+
+ for member in enum.members:
+ _check_doc_element([repository.namespace.name, enum.name], member, results)
+
+ for func in enum.functions:
+ _check_doc_element([repository.namespace.name, enum.name], func, results)
+ _check_arg_docs([repository.namespace.name, enum.name, func.name], func.parameters, results)
+ _check_retval_docs([repository.namespace.name, enum.name, func.name], func.return_value, results)
+
+
+def _check_functions(repository, symbols, results):
+ for func in symbols:
+ _check_doc_element([repository.namespace.name], func, results)
+ _check_arg_docs([repository.namespace.name, func.name], func.parameters, results)
+ _check_retval_docs([repository.namespace.name, func.name], func.return_value, results)
+
+
+def _check_function_macros(repository, symbols, results):
+ for func in symbols:
+ _check_doc_element([repository.namespace.name], func, results)
+ _check_arg_docs([repository.namespace.name, func.name], func.parameters, results)
+ _check_retval_docs([repository.namespace.name, func.name], func.return_value, results)
+
+
+def _check_interfaces(repository, symbols, results):
+ for iface in symbols:
+ _check_doc_element([repository.namespace.name], iface, results)
+
+ for method in iface.methods:
+ _check_doc_element([repository.namespace.name, iface.name], method, results)
+ _check_arg_docs([repository.namespace.name, iface.name, method.name], method.parameters, results)
+ _check_retval_docs([repository.namespace.name, iface.name, method.name], method.return_value,
results)
+
+ for func in iface.functions:
+ _check_doc_element([repository.namespace.name, iface.name], func, results)
+ _check_arg_docs([repository.namespace.name, iface.name, func.name], func.parameters, results)
+ _check_retval_docs([repository.namespace.name, iface.name, func.name], func.return_value,
results)
+
+ for prop in iface.properties.values():
+ _check_doc_element([repository.namespace.name, iface.name], prop, results)
+
+ for signal in iface.signals.values():
+ _check_doc_element([repository.namespace.name, iface.name], signal, results)
+ _check_arg_docs([repository.namespace.name, iface.name, signal.name], signal.parameters, results)
+ _check_retval_docs([repository.namespace.name, iface.name, signal.name], signal.return_value,
results)
+
+
+def _check_records(repository, symbols, results):
+ for struct in symbols:
+ _check_doc_element([repository.namespace.name], struct, results)
+
+ for ctor in struct.constructors:
+ _check_doc_element([repository.namespace.name, struct.name], ctor, results)
+ _check_arg_docs([repository.namespace.name, struct.name, ctor.name], ctor.parameters, results)
+ _check_retval_docs([repository.namespace.name, struct.name, ctor.name], ctor.return_value,
results)
+
+ for method in struct.methods:
+ _check_doc_element([repository.namespace.name, struct.name], method, results)
+ _check_arg_docs([repository.namespace.name, struct.name, method.name], method.parameters,
results)
+ _check_retval_docs([repository.namespace.name, struct.name, method.name], method.return_value,
results)
+
+ for func in struct.functions:
+ _check_doc_element([repository.namespace.name, struct.name], func, results)
+ _check_arg_docs([repository.namespace.name, struct.name, func.name], func.parameters, results)
+ _check_retval_docs([repository.namespace.name, struct.name, func.name], func.return_value,
results)
+
+
+def _check_unions(repository, symbols, results):
+ for union in symbols:
+ _check_doc_element([repository.namespace.name], union, results)
+
+ for ctor in union.constructors:
+ _check_doc_element([repository.namespace.name, union.name], ctor, results)
+ _check_arg_docs([repository.namespace.name, union.name, ctor.name], ctor.parameters, results)
+ _check_retval_docs([repository.namespace.name, union.name, ctor.name], ctor.return_value,
results)
+
+ for method in union.methods:
+ _check_doc_element([repository.namespace.name, union.name], method, results)
+ _check_arg_docs([repository.namespace.name, union.name, method.name], method.parameters, results)
+ _check_retval_docs([repository.namespace.name, union.name, method.name], method.return_value,
results)
+
+ for func in union.functions:
+ _check_doc_element([repository.namespace.name, union.name], func, results)
+ _check_arg_docs([repository.namespace.name, union.name, func.name], func.parameters, results)
+ _check_retval_docs([repository.namespace.name, union.name, func.name], func.return_value,
results)
+
+
+def check(repository):
+ namespace = repository.namespace
+
+ symbols = {
+ "aliases": sorted(namespace.get_aliases(), key=lambda alias: alias.name.lower()),
+ "bitfields": sorted(namespace.get_bitfields(), key=lambda bitfield: bitfield.name.lower()),
+ "callbacks": sorted(namespace.get_callbacks(), key=lambda callback: callback.name.lower()),
+ "classes": sorted(namespace.get_classes(), key=lambda cls: cls.name.lower()),
+ "constants": sorted(namespace.get_constants(), key=lambda const: const.name.lower()),
+ "domains": sorted(namespace.get_error_domains(), key=lambda domain: domain.name.lower()),
+ "enums": sorted(namespace.get_enumerations(), key=lambda enum: enum.name.lower()),
+ "functions": sorted(namespace.get_functions(), key=lambda func: func.name.lower()),
+ "function_macros": sorted(namespace.get_effective_function_macros(), key=lambda func:
func.name.lower()),
+ "interfaces": sorted(namespace.get_interfaces(), key=lambda interface: interface.name.lower()),
+ "structs": sorted(namespace.get_effective_records(), key=lambda record: record.name.lower()),
+ "unions": sorted(namespace.get_unions(), key=lambda union: union.name.lower()),
+ }
+
+ all_indices = {
+ "aliases": _check_aliases,
+ "bitfields": _check_bitfields,
+ "callbacks": _check_callbacks,
+ "classes": _check_classes,
+ "constants": _check_constants,
+ "domains": _check_domains,
+ "enums": _check_enums,
+ "functions": _check_functions,
+ "function_macros": _check_function_macros,
+ "interfaces": _check_interfaces,
+ "structs": _check_records,
+ "unions": _check_unions,
+ }
+
+ results = []
+
+ # Each section is isolated, so we run it into a thread pool
+ for section in all_indices:
+ checker = all_indices.get(section, None)
+ if checker is None:
+ log.error(f"No checker for section {section}")
+ continue
+
+ s = symbols.get(section, None)
+ if s is None:
+ log.debug(f"No symbols for section {section}")
+ continue
+
+ log.debug(f"Checking symbols for section {section}")
+ checker(repository, s, results)
+
+ for res in results:
+ log.warning(res)
+
+ return len(results) == 0
+
+
+def add_args(parser):
+ parser.add_argument("--add-include-path", action="append", dest="include_paths", default=[],
+ help="include paths for other GIR files")
+ parser.add_argument("infile", metavar="GIRFILE", type=argparse.FileType('r', encoding='UTF-8'),
+ default=sys.stdin, help="the GIR file to parse")
+
+
+def run(options):
+ paths = []
+ paths.extend(options.include_paths)
+ paths.extend(utils.default_search_paths())
+ log.info(f"Search paths: {paths}")
+
+ parser = gir.GirParser(search_paths=paths)
+ parser.parse(options.infile)
+
+ log.checkpoint()
+ return check(parser.get_repository())
diff --git a/gidocgen/gidocmain.py b/gidocgen/gidocmain.py
index 679efa6..a3c97f2 100644
--- a/gidocgen/gidocmain.py
+++ b/gidocgen/gidocmain.py
@@ -7,7 +7,7 @@ import sys
import traceback
from . import core, log
-from . import gdindex, gdgenerate, gdgenindices, gdgendeps, gdsearch
+from . import gdindex, gdgenerate, gdgenindices, gdgendeps, gdsearch, gdcheck
class GIDocGenApp:
@@ -50,6 +50,10 @@ class GIDocGenApp:
add_args_func=gdsearch.add_args,
run_func=gdsearch.run,
help_msg=gdsearch.HELP_MSG)
+ self.add_command('check',
+ add_args_func=gdcheck.add_args,
+ run_func=gdcheck.run,
+ help_msg=gdcheck.HELP_MSG)
def run(self, args):
"""
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]