blob: 3bc41d05fa7d7771c6c2684f720fa160080b951b [file] [log] [blame]
#
# objdoc: epydoc documentation completeness checker
# Edward Loper
#
# Created [01/30/01 05:18 PM]
# $Id: checker.py 1366 2006-09-07 15:54:59Z edloper $
#
"""
Documentation completeness checker. This module defines a single
class, C{DocChecker}, which can be used to check the that specified
classes of objects are documented.
"""
__docformat__ = 'epytext en'
##################################################
## Imports
##################################################
import re, sys, os.path, string
from xml.dom.minidom import Text as _Text
from epydoc.apidoc import *
# The following methods may be undocumented:
_NO_DOCS = ['__hash__', '__repr__', '__str__', '__cmp__']
# The following methods never need descriptions, authors, or
# versions:
_NO_BASIC = ['__hash__', '__repr__', '__str__', '__cmp__']
# The following methods never need return value descriptions.
_NO_RETURN = ['__init__', '__hash__', '__repr__', '__str__', '__cmp__']
# The following methods don't need parameters documented:
_NO_PARAM = ['__cmp__']
class DocChecker:
"""
Documentation completeness checker. C{DocChecker} can be used to
check that specified classes of objects are documented. To check
the documentation for a group of objects, you should create a
C{DocChecker} from a L{DocIndex<apidoc.DocIndex>} that documents
those objects; and then use the L{check} method to run specified
checks on the objects' documentation.
What checks are run, and what objects they are run on, are
specified by the constants defined by C{DocChecker}. These
constants are divided into three groups.
- Type specifiers indicate what type of objects should be
checked: L{MODULE}; L{CLASS}; L{FUNC}; L{VAR}; L{IVAR};
L{CVAR}; L{PARAM}; and L{RETURN}.
- Public/private specifiers indicate whether public or private
objects should be checked: L{PRIVATE}.
- Check specifiers indicate what checks should be run on the
objects: L{TYPE}; L{DESCR}; L{AUTHOR};
and L{VERSION}.
The L{check} method is used to perform a check on the
documentation. Its parameter is formed by or-ing together at
least one value from each specifier group:
>>> checker.check(DocChecker.MODULE | DocChecker.DESCR)
To specify multiple values from a single group, simply or their
values together:
>>> checker.check(DocChecker.MODULE | DocChecker.CLASS |
... DocChecker.FUNC )
@group Types: MODULE, CLASS, FUNC, VAR, IVAR, CVAR, PARAM,
RETURN, ALL_T
@type MODULE: C{int}
@cvar MODULE: Type specifier that indicates that the documentation
of modules should be checked.
@type CLASS: C{int}
@cvar CLASS: Type specifier that indicates that the documentation
of classes should be checked.
@type FUNC: C{int}
@cvar FUNC: Type specifier that indicates that the documentation
of functions should be checked.
@type VAR: C{int}
@cvar VAR: Type specifier that indicates that the documentation
of module variables should be checked.
@type IVAR: C{int}
@cvar IVAR: Type specifier that indicates that the documentation
of instance variables should be checked.
@type CVAR: C{int}
@cvar CVAR: Type specifier that indicates that the documentation
of class variables should be checked.
@type PARAM: C{int}
@cvar PARAM: Type specifier that indicates that the documentation
of function and method parameters should be checked.
@type RETURN: C{int}
@cvar RETURN: Type specifier that indicates that the documentation
of return values should be checked.
@type ALL_T: C{int}
@cvar ALL_T: Type specifier that indicates that the documentation
of all objects should be checked.
@group Checks: TYPE, AUTHOR, VERSION, DESCR, ALL_C
@type TYPE: C{int}
@cvar TYPE: Check specifier that indicates that every variable and
parameter should have a C{@type} field.
@type AUTHOR: C{int}
@cvar AUTHOR: Check specifier that indicates that every object
should have an C{author} field.
@type VERSION: C{int}
@cvar VERSION: Check specifier that indicates that every object
should have a C{version} field.
@type DESCR: C{int}
@cvar DESCR: Check specifier that indicates that every object
should have a description.
@type ALL_C: C{int}
@cvar ALL_C: Check specifier that indicates that all checks
should be run.
@group Publicity: PRIVATE
@type PRIVATE: C{int}
@cvar PRIVATE: Specifier that indicates that private objects should
be checked.
"""
# Types
MODULE = 1
CLASS = 2
FUNC = 4
VAR = 8
#IVAR = 16
#CVAR = 32
PARAM = 64
RETURN = 128
PROPERTY = 256
ALL_T = 1+2+4+8+16+32+64+128+256
# Checks
TYPE = 256
AUTHOR = 1024
VERSION = 2048
DESCR = 4096
ALL_C = 256+512+1024+2048+4096
# Private/public
PRIVATE = 16384
ALL = ALL_T + ALL_C + PRIVATE
def __init__(self, docindex):
"""
Create a new C{DocChecker} that can be used to run checks on
the documentation of the objects documented by C{docindex}
@param docindex: A documentation map containing the
documentation for the objects to be checked.
@type docindex: L{Docindex<apidoc.DocIndex>}
"""
self._docindex = docindex
# Initialize instance variables
self._checks = 0
self._last_warn = None
self._out = sys.stdout
self._num_warnings = 0
def check(self, *check_sets):
"""
Run the specified checks on the documentation of the objects
contained by this C{DocChecker}'s C{DocIndex}. Any errors found
are printed to standard out.
@param check_sets: The checks that should be run on the
documentation. This value is constructed by or-ing
together the specifiers that indicate which objects should
be checked, and which checks should be run. See the
L{module description<checker>} for more information.
If no checks are specified, then a default set of checks
will be run.
@type check_sets: C{int}
@return: True if no problems were found.
@rtype: C{boolean}
"""
if not check_sets:
check_sets = (DocChecker.MODULE | DocChecker.CLASS |
DocChecker.FUNC | DocChecker.VAR |
DocChecker.DESCR,)
self._warnings = {}
log.start_progress('Checking docs')
for j, checks in enumerate(check_sets):
self._check(checks)
log.end_progress()
for (warning, docs) in self._warnings.items():
docs = sorted(docs)
docnames = '\n'.join([' - %s' % self._name(d) for d in docs])
log.warning('%s:\n%s' % (warning, docnames))
def _check(self, checks):
self._checks = checks
# Get the list of objects to check.
valdocs = sorted(self._docindex.reachable_valdocs(
imports=False, packages=False, bases=False, submodules=False,
subclasses=False, private = (checks & DocChecker.PRIVATE)))
docs = set()
for d in valdocs:
if not isinstance(d, GenericValueDoc): docs.add(d)
for doc in valdocs:
if isinstance(doc, NamespaceDoc):
for d in doc.variables.values():
if isinstance(d.value, GenericValueDoc): docs.add(d)
for i, doc in enumerate(sorted(docs)):
if isinstance(doc, ModuleDoc):
self._check_module(doc)
elif isinstance(doc, ClassDoc):
self._check_class(doc)
elif isinstance(doc, RoutineDoc):
self._check_func(doc)
elif isinstance(doc, PropertyDoc):
self._check_property(doc)
elif isinstance(doc, VariableDoc):
self._check_var(doc)
else:
log.error("Don't know how to check %r" % doc)
def _name(self, doc):
name = str(doc.canonical_name)
if isinstance(doc, RoutineDoc): name += '()'
return name
def _check_basic(self, doc):
"""
Check the description, author, version, and see-also fields of
C{doc}. This is used as a helper function by L{_check_module},
L{_check_class}, and L{_check_func}.
@param doc: The documentation that should be checked.
@type doc: L{APIDoc}
@rtype: C{None}
"""
if ((self._checks & DocChecker.DESCR) and
(doc.descr in (None, UNKNOWN))):
if doc.docstring in (None, UNKNOWN):
self.warning('Undocumented', doc)
else:
self.warning('No description', doc)
if self._checks & DocChecker.AUTHOR:
for tag, arg, descr in doc.metadata:
if 'author' == tag: break
else:
self.warning('No authors', doc)
if self._checks & DocChecker.VERSION:
for tag, arg, descr in doc.metadata:
if 'version' == tag: break
else:
self.warning('No version', doc)
def _check_module(self, doc):
"""
Run checks on the module whose APIDoc is C{doc}.
@param doc: The APIDoc of the module to check.
@type doc: L{APIDoc}
@rtype: C{None}
"""
if self._checks & DocChecker.MODULE:
self._check_basic(doc)
def _check_class(self, doc):
"""
Run checks on the class whose APIDoc is C{doc}.
@param doc: The APIDoc of the class to check.
@type doc: L{APIDoc}
@rtype: C{None}
"""
if self._checks & DocChecker.CLASS:
self._check_basic(doc)
def _check_property(self, doc):
if self._checks & DocChecker.PROPERTY:
self._check_basic(doc)
def _check_var(self, doc):
"""
Run checks on the variable whose documentation is C{var} and
whose name is C{name}.
@param doc: The documentation for the variable to check.
@type doc: L{APIDoc}
@rtype: C{None}
"""
if self._checks & DocChecker.VAR:
if (self._checks & (DocChecker.DESCR|DocChecker.TYPE) and
doc.descr in (None, UNKNOWN) and
doc.type_descr in (None, UNKNOWN) and
doc.docstring in (None, UNKNOWN)):
self.warning('Undocumented', doc)
else:
if (self._checks & DocChecker.DESCR and
doc.descr in (None, UNKNOWN)):
self.warning('No description', doc)
if (self._checks & DocChecker.TYPE and
doc.type_descr in (None, UNKNOWN)):
self.warning('No type information', doc)
def _check_func(self, doc):
"""
Run checks on the function whose APIDoc is C{doc}.
@param doc: The APIDoc of the function to check.
@type doc: L{APIDoc}
@rtype: C{None}
"""
name = doc.canonical_name
if (self._checks & DocChecker.FUNC and
doc.docstring in (None, UNKNOWN) and
doc.canonical_name[-1] not in _NO_DOCS):
self.warning('Undocumented', doc)
return
if (self._checks & DocChecker.FUNC and
doc.canonical_name[-1] not in _NO_BASIC):
self._check_basic(doc)
if (self._checks & DocChecker.RETURN and
doc.canonical_name[-1] not in _NO_RETURN):
if (doc.return_type in (None, UNKNOWN) and
doc.return_descr in (None, UNKNOWN)):
self.warning('No return descr', doc)
if (self._checks & DocChecker.PARAM and
doc.canonical_name[-1] not in _NO_PARAM):
if doc.arg_descrs in (None, UNKNOWN):
self.warning('No argument info', doc)
else:
args_with_descr = []
for arg, descr in doc.arg_descrs:
if isinstance(arg, basestring):
args_with_descr.append(arg)
else:
args_with_descr += arg
for posarg in doc.posargs:
if (self._checks & DocChecker.DESCR and
posarg not in args_with_descr):
self.warning('Argument(s) not described', doc)
if (self._checks & DocChecker.TYPE and
posarg not in doc.arg_types):
self.warning('Argument type(s) not described', doc)
def warning(self, msg, doc):
self._warnings.setdefault(msg,set()).add(doc)