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