| Tor Norbye | 3a2425a | 2013-11-04 10:16:08 -0800 | [diff] [blame^] | 1 | # |
| 2 | # objdoc: epydoc documentation completeness checker |
| 3 | # Edward Loper |
| 4 | # |
| 5 | # Created [01/30/01 05:18 PM] |
| 6 | # $Id: checker.py 1366 2006-09-07 15:54:59Z edloper $ |
| 7 | # |
| 8 | |
| 9 | """ |
| 10 | Documentation completeness checker. This module defines a single |
| 11 | class, C{DocChecker}, which can be used to check the that specified |
| 12 | classes of objects are documented. |
| 13 | """ |
| 14 | __docformat__ = 'epytext en' |
| 15 | |
| 16 | ################################################## |
| 17 | ## Imports |
| 18 | ################################################## |
| 19 | |
| 20 | import re, sys, os.path, string |
| 21 | from xml.dom.minidom import Text as _Text |
| 22 | from epydoc.apidoc import * |
| 23 | |
| 24 | # The following methods may be undocumented: |
| 25 | _NO_DOCS = ['__hash__', '__repr__', '__str__', '__cmp__'] |
| 26 | |
| 27 | # The following methods never need descriptions, authors, or |
| 28 | # versions: |
| 29 | _NO_BASIC = ['__hash__', '__repr__', '__str__', '__cmp__'] |
| 30 | |
| 31 | # The following methods never need return value descriptions. |
| 32 | _NO_RETURN = ['__init__', '__hash__', '__repr__', '__str__', '__cmp__'] |
| 33 | |
| 34 | # The following methods don't need parameters documented: |
| 35 | _NO_PARAM = ['__cmp__'] |
| 36 | |
| 37 | class DocChecker: |
| 38 | """ |
| 39 | Documentation completeness checker. C{DocChecker} can be used to |
| 40 | check that specified classes of objects are documented. To check |
| 41 | the documentation for a group of objects, you should create a |
| 42 | C{DocChecker} from a L{DocIndex<apidoc.DocIndex>} that documents |
| 43 | those objects; and then use the L{check} method to run specified |
| 44 | checks on the objects' documentation. |
| 45 | |
| 46 | What checks are run, and what objects they are run on, are |
| 47 | specified by the constants defined by C{DocChecker}. These |
| 48 | constants are divided into three groups. |
| 49 | |
| 50 | - Type specifiers indicate what type of objects should be |
| 51 | checked: L{MODULE}; L{CLASS}; L{FUNC}; L{VAR}; L{IVAR}; |
| 52 | L{CVAR}; L{PARAM}; and L{RETURN}. |
| 53 | - Public/private specifiers indicate whether public or private |
| 54 | objects should be checked: L{PRIVATE}. |
| 55 | - Check specifiers indicate what checks should be run on the |
| 56 | objects: L{TYPE}; L{DESCR}; L{AUTHOR}; |
| 57 | and L{VERSION}. |
| 58 | |
| 59 | The L{check} method is used to perform a check on the |
| 60 | documentation. Its parameter is formed by or-ing together at |
| 61 | least one value from each specifier group: |
| 62 | |
| 63 | >>> checker.check(DocChecker.MODULE | DocChecker.DESCR) |
| 64 | |
| 65 | To specify multiple values from a single group, simply or their |
| 66 | values together: |
| 67 | |
| 68 | >>> checker.check(DocChecker.MODULE | DocChecker.CLASS | |
| 69 | ... DocChecker.FUNC ) |
| 70 | |
| 71 | @group Types: MODULE, CLASS, FUNC, VAR, IVAR, CVAR, PARAM, |
| 72 | RETURN, ALL_T |
| 73 | @type MODULE: C{int} |
| 74 | @cvar MODULE: Type specifier that indicates that the documentation |
| 75 | of modules should be checked. |
| 76 | @type CLASS: C{int} |
| 77 | @cvar CLASS: Type specifier that indicates that the documentation |
| 78 | of classes should be checked. |
| 79 | @type FUNC: C{int} |
| 80 | @cvar FUNC: Type specifier that indicates that the documentation |
| 81 | of functions should be checked. |
| 82 | @type VAR: C{int} |
| 83 | @cvar VAR: Type specifier that indicates that the documentation |
| 84 | of module variables should be checked. |
| 85 | @type IVAR: C{int} |
| 86 | @cvar IVAR: Type specifier that indicates that the documentation |
| 87 | of instance variables should be checked. |
| 88 | @type CVAR: C{int} |
| 89 | @cvar CVAR: Type specifier that indicates that the documentation |
| 90 | of class variables should be checked. |
| 91 | @type PARAM: C{int} |
| 92 | @cvar PARAM: Type specifier that indicates that the documentation |
| 93 | of function and method parameters should be checked. |
| 94 | @type RETURN: C{int} |
| 95 | @cvar RETURN: Type specifier that indicates that the documentation |
| 96 | of return values should be checked. |
| 97 | @type ALL_T: C{int} |
| 98 | @cvar ALL_T: Type specifier that indicates that the documentation |
| 99 | of all objects should be checked. |
| 100 | |
| 101 | @group Checks: TYPE, AUTHOR, VERSION, DESCR, ALL_C |
| 102 | @type TYPE: C{int} |
| 103 | @cvar TYPE: Check specifier that indicates that every variable and |
| 104 | parameter should have a C{@type} field. |
| 105 | @type AUTHOR: C{int} |
| 106 | @cvar AUTHOR: Check specifier that indicates that every object |
| 107 | should have an C{author} field. |
| 108 | @type VERSION: C{int} |
| 109 | @cvar VERSION: Check specifier that indicates that every object |
| 110 | should have a C{version} field. |
| 111 | @type DESCR: C{int} |
| 112 | @cvar DESCR: Check specifier that indicates that every object |
| 113 | should have a description. |
| 114 | @type ALL_C: C{int} |
| 115 | @cvar ALL_C: Check specifier that indicates that all checks |
| 116 | should be run. |
| 117 | |
| 118 | @group Publicity: PRIVATE |
| 119 | @type PRIVATE: C{int} |
| 120 | @cvar PRIVATE: Specifier that indicates that private objects should |
| 121 | be checked. |
| 122 | """ |
| 123 | # Types |
| 124 | MODULE = 1 |
| 125 | CLASS = 2 |
| 126 | FUNC = 4 |
| 127 | VAR = 8 |
| 128 | #IVAR = 16 |
| 129 | #CVAR = 32 |
| 130 | PARAM = 64 |
| 131 | RETURN = 128 |
| 132 | PROPERTY = 256 |
| 133 | ALL_T = 1+2+4+8+16+32+64+128+256 |
| 134 | |
| 135 | # Checks |
| 136 | TYPE = 256 |
| 137 | AUTHOR = 1024 |
| 138 | VERSION = 2048 |
| 139 | DESCR = 4096 |
| 140 | ALL_C = 256+512+1024+2048+4096 |
| 141 | |
| 142 | # Private/public |
| 143 | PRIVATE = 16384 |
| 144 | |
| 145 | ALL = ALL_T + ALL_C + PRIVATE |
| 146 | |
| 147 | def __init__(self, docindex): |
| 148 | """ |
| 149 | Create a new C{DocChecker} that can be used to run checks on |
| 150 | the documentation of the objects documented by C{docindex} |
| 151 | |
| 152 | @param docindex: A documentation map containing the |
| 153 | documentation for the objects to be checked. |
| 154 | @type docindex: L{Docindex<apidoc.DocIndex>} |
| 155 | """ |
| 156 | self._docindex = docindex |
| 157 | |
| 158 | # Initialize instance variables |
| 159 | self._checks = 0 |
| 160 | self._last_warn = None |
| 161 | self._out = sys.stdout |
| 162 | self._num_warnings = 0 |
| 163 | |
| 164 | def check(self, *check_sets): |
| 165 | """ |
| 166 | Run the specified checks on the documentation of the objects |
| 167 | contained by this C{DocChecker}'s C{DocIndex}. Any errors found |
| 168 | are printed to standard out. |
| 169 | |
| 170 | @param check_sets: The checks that should be run on the |
| 171 | documentation. This value is constructed by or-ing |
| 172 | together the specifiers that indicate which objects should |
| 173 | be checked, and which checks should be run. See the |
| 174 | L{module description<checker>} for more information. |
| 175 | If no checks are specified, then a default set of checks |
| 176 | will be run. |
| 177 | @type check_sets: C{int} |
| 178 | @return: True if no problems were found. |
| 179 | @rtype: C{boolean} |
| 180 | """ |
| 181 | if not check_sets: |
| 182 | check_sets = (DocChecker.MODULE | DocChecker.CLASS | |
| 183 | DocChecker.FUNC | DocChecker.VAR | |
| 184 | DocChecker.DESCR,) |
| 185 | |
| 186 | self._warnings = {} |
| 187 | log.start_progress('Checking docs') |
| 188 | for j, checks in enumerate(check_sets): |
| 189 | self._check(checks) |
| 190 | log.end_progress() |
| 191 | |
| 192 | for (warning, docs) in self._warnings.items(): |
| 193 | docs = sorted(docs) |
| 194 | docnames = '\n'.join([' - %s' % self._name(d) for d in docs]) |
| 195 | log.warning('%s:\n%s' % (warning, docnames)) |
| 196 | |
| 197 | def _check(self, checks): |
| 198 | self._checks = checks |
| 199 | |
| 200 | # Get the list of objects to check. |
| 201 | valdocs = sorted(self._docindex.reachable_valdocs( |
| 202 | imports=False, packages=False, bases=False, submodules=False, |
| 203 | subclasses=False, private = (checks & DocChecker.PRIVATE))) |
| 204 | docs = set() |
| 205 | for d in valdocs: |
| 206 | if not isinstance(d, GenericValueDoc): docs.add(d) |
| 207 | for doc in valdocs: |
| 208 | if isinstance(doc, NamespaceDoc): |
| 209 | for d in doc.variables.values(): |
| 210 | if isinstance(d.value, GenericValueDoc): docs.add(d) |
| 211 | |
| 212 | for i, doc in enumerate(sorted(docs)): |
| 213 | if isinstance(doc, ModuleDoc): |
| 214 | self._check_module(doc) |
| 215 | elif isinstance(doc, ClassDoc): |
| 216 | self._check_class(doc) |
| 217 | elif isinstance(doc, RoutineDoc): |
| 218 | self._check_func(doc) |
| 219 | elif isinstance(doc, PropertyDoc): |
| 220 | self._check_property(doc) |
| 221 | elif isinstance(doc, VariableDoc): |
| 222 | self._check_var(doc) |
| 223 | else: |
| 224 | log.error("Don't know how to check %r" % doc) |
| 225 | |
| 226 | def _name(self, doc): |
| 227 | name = str(doc.canonical_name) |
| 228 | if isinstance(doc, RoutineDoc): name += '()' |
| 229 | return name |
| 230 | |
| 231 | def _check_basic(self, doc): |
| 232 | """ |
| 233 | Check the description, author, version, and see-also fields of |
| 234 | C{doc}. This is used as a helper function by L{_check_module}, |
| 235 | L{_check_class}, and L{_check_func}. |
| 236 | |
| 237 | @param doc: The documentation that should be checked. |
| 238 | @type doc: L{APIDoc} |
| 239 | @rtype: C{None} |
| 240 | """ |
| 241 | if ((self._checks & DocChecker.DESCR) and |
| 242 | (doc.descr in (None, UNKNOWN))): |
| 243 | if doc.docstring in (None, UNKNOWN): |
| 244 | self.warning('Undocumented', doc) |
| 245 | else: |
| 246 | self.warning('No description', doc) |
| 247 | if self._checks & DocChecker.AUTHOR: |
| 248 | for tag, arg, descr in doc.metadata: |
| 249 | if 'author' == tag: break |
| 250 | else: |
| 251 | self.warning('No authors', doc) |
| 252 | if self._checks & DocChecker.VERSION: |
| 253 | for tag, arg, descr in doc.metadata: |
| 254 | if 'version' == tag: break |
| 255 | else: |
| 256 | self.warning('No version', doc) |
| 257 | |
| 258 | def _check_module(self, doc): |
| 259 | """ |
| 260 | Run checks on the module whose APIDoc is C{doc}. |
| 261 | |
| 262 | @param doc: The APIDoc of the module to check. |
| 263 | @type doc: L{APIDoc} |
| 264 | @rtype: C{None} |
| 265 | """ |
| 266 | if self._checks & DocChecker.MODULE: |
| 267 | self._check_basic(doc) |
| 268 | |
| 269 | def _check_class(self, doc): |
| 270 | """ |
| 271 | Run checks on the class whose APIDoc is C{doc}. |
| 272 | |
| 273 | @param doc: The APIDoc of the class to check. |
| 274 | @type doc: L{APIDoc} |
| 275 | @rtype: C{None} |
| 276 | """ |
| 277 | if self._checks & DocChecker.CLASS: |
| 278 | self._check_basic(doc) |
| 279 | |
| 280 | def _check_property(self, doc): |
| 281 | if self._checks & DocChecker.PROPERTY: |
| 282 | self._check_basic(doc) |
| 283 | |
| 284 | def _check_var(self, doc): |
| 285 | """ |
| 286 | Run checks on the variable whose documentation is C{var} and |
| 287 | whose name is C{name}. |
| 288 | |
| 289 | @param doc: The documentation for the variable to check. |
| 290 | @type doc: L{APIDoc} |
| 291 | @rtype: C{None} |
| 292 | """ |
| 293 | if self._checks & DocChecker.VAR: |
| 294 | if (self._checks & (DocChecker.DESCR|DocChecker.TYPE) and |
| 295 | doc.descr in (None, UNKNOWN) and |
| 296 | doc.type_descr in (None, UNKNOWN) and |
| 297 | doc.docstring in (None, UNKNOWN)): |
| 298 | self.warning('Undocumented', doc) |
| 299 | else: |
| 300 | if (self._checks & DocChecker.DESCR and |
| 301 | doc.descr in (None, UNKNOWN)): |
| 302 | self.warning('No description', doc) |
| 303 | if (self._checks & DocChecker.TYPE and |
| 304 | doc.type_descr in (None, UNKNOWN)): |
| 305 | self.warning('No type information', doc) |
| 306 | |
| 307 | def _check_func(self, doc): |
| 308 | """ |
| 309 | Run checks on the function whose APIDoc is C{doc}. |
| 310 | |
| 311 | @param doc: The APIDoc of the function to check. |
| 312 | @type doc: L{APIDoc} |
| 313 | @rtype: C{None} |
| 314 | """ |
| 315 | name = doc.canonical_name |
| 316 | if (self._checks & DocChecker.FUNC and |
| 317 | doc.docstring in (None, UNKNOWN) and |
| 318 | doc.canonical_name[-1] not in _NO_DOCS): |
| 319 | self.warning('Undocumented', doc) |
| 320 | return |
| 321 | if (self._checks & DocChecker.FUNC and |
| 322 | doc.canonical_name[-1] not in _NO_BASIC): |
| 323 | self._check_basic(doc) |
| 324 | if (self._checks & DocChecker.RETURN and |
| 325 | doc.canonical_name[-1] not in _NO_RETURN): |
| 326 | if (doc.return_type in (None, UNKNOWN) and |
| 327 | doc.return_descr in (None, UNKNOWN)): |
| 328 | self.warning('No return descr', doc) |
| 329 | if (self._checks & DocChecker.PARAM and |
| 330 | doc.canonical_name[-1] not in _NO_PARAM): |
| 331 | if doc.arg_descrs in (None, UNKNOWN): |
| 332 | self.warning('No argument info', doc) |
| 333 | else: |
| 334 | args_with_descr = [] |
| 335 | for arg, descr in doc.arg_descrs: |
| 336 | if isinstance(arg, basestring): |
| 337 | args_with_descr.append(arg) |
| 338 | else: |
| 339 | args_with_descr += arg |
| 340 | for posarg in doc.posargs: |
| 341 | if (self._checks & DocChecker.DESCR and |
| 342 | posarg not in args_with_descr): |
| 343 | self.warning('Argument(s) not described', doc) |
| 344 | if (self._checks & DocChecker.TYPE and |
| 345 | posarg not in doc.arg_types): |
| 346 | self.warning('Argument type(s) not described', doc) |
| 347 | |
| 348 | def warning(self, msg, doc): |
| 349 | self._warnings.setdefault(msg,set()).add(doc) |