# # 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} 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} """ 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} 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)