Plugins/CheckerPlugins/CodeStyleChecker/DocStyleChecker.py

branch
Py2 comp.
changeset 3057
10516539f238
parent 3056
9986ec0e559a
parent 3004
c4bf32c791d0
child 3058
0a02c433f52d
equal deleted inserted replaced
3056:9986ec0e559a 3057:10516539f238
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2013 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a checker for documentation string conventions.
8 """
9
10 from __future__ import unicode_literals # __IGNORE_WARNING__
11
12 #
13 # The routines of the checker class are modeled after the ones found in
14 # pep257.py (version 0.2.4).
15 #
16
17 try:
18 # Python 2
19 from StringIO import StringIO # __IGNORE_EXCEPTION__
20 except ImportError:
21 # Python 3
22 from io import StringIO # __IGNORE_WARNING__
23 import tokenize
24 import ast
25 import sys
26
27 from PyQt4.QtCore import QT_TRANSLATE_NOOP, QCoreApplication
28
29
30 class DocStyleContext(object):
31 """
32 Class implementing the source context.
33 """
34 def __init__(self, source, startLine, contextType):
35 """
36 Constructor
37
38 @param source source code of the context (list of string or string)
39 @param startLine line number the context starts in the source (integer)
40 @param contextType type of the context object (string)
41 """
42 if isinstance(source, str):
43 self.__source = source.splitlines(True)
44 else:
45 self.__source = source[:]
46 self.__start = startLine
47 self.__indent = ""
48 self.__type = contextType
49
50 # ensure first line is left justified
51 if self.__source:
52 self.__indent = self.__source[0].replace(
53 self.__source[0].lstrip(), "")
54 self.__source[0] = self.__source[0].lstrip()
55
56 def source(self):
57 """
58 Public method to get the source.
59
60 @return source (list of string)
61 """
62 return self.__source
63
64 def ssource(self):
65 """
66 Public method to get the joined source lines.
67
68 @return source (string)
69 """
70 return "".join(self.__source)
71
72 def start(self):
73 """
74 Public method to get the start line number.
75
76 @return start line number (integer)
77 """
78 return self.__start
79
80 def end(self):
81 """
82 Public method to get the end line number.
83
84 @return end line number (integer)
85 """
86 return self.__start + len(self.__source) - 1
87
88 def indent(self):
89 """
90 Public method to get the indentation of the first line.
91
92 @return indentation string (string)
93 """
94 return self.__indent
95
96 def contextType(self):
97 """
98 Public method to get the context type.
99
100 @return context type (string)
101 """
102 return self.__type
103
104
105 class DocStyleChecker(object):
106 """
107 Class implementing a checker for documentation string conventions.
108 """
109 Codes = [
110 "D101", "D102", "D103", "D104", "D105",
111 "D111", "D112", "D113",
112 "D121", "D122",
113 "D131", "D132", "D133", "D134",
114 "D141", "D142", "D143", "D144", "D145",
115
116 "D203", "D205",
117 "D221", "D222",
118 "D231", "D234", "D235", "D236", "D237", "D238", "D239",
119 "D242", "D243", "D244", "D245", "D246", "D247",
120 "D250", "D251",
121 ]
122
123 Messages = {
124 "D101": QT_TRANSLATE_NOOP(
125 "DocStyleChecker", "module is missing a docstring"),
126 "D102": QT_TRANSLATE_NOOP(
127 "DocStyleChecker",
128 "public function/method is missing a docstring"),
129 "D103": QT_TRANSLATE_NOOP(
130 "DocStyleChecker",
131 "private function/method may be missing a docstring"),
132 "D104": QT_TRANSLATE_NOOP(
133 "DocStyleChecker", "public class is missing a docstring"),
134 "D105": QT_TRANSLATE_NOOP(
135 "DocStyleChecker", "private class may be missing a docstring"),
136 "D111": QT_TRANSLATE_NOOP(
137 "DocStyleChecker", 'docstring not surrounded by """'),
138 "D112": QT_TRANSLATE_NOOP(
139 "DocStyleChecker",
140 'docstring containing \\ not surrounded by r"""'),
141 "D113": QT_TRANSLATE_NOOP(
142 "DocStyleChecker",
143 'docstring containing unicode character not surrounded by u"""'),
144 "D121": QT_TRANSLATE_NOOP(
145 "DocStyleChecker", "one-liner docstring on multiple lines"),
146 "D122": QT_TRANSLATE_NOOP(
147 "DocStyleChecker", "docstring has wrong indentation"),
148 "D131": QT_TRANSLATE_NOOP(
149 "DocStyleChecker", "docstring summary does not end with a period"),
150 "D132": QT_TRANSLATE_NOOP(
151 "DocStyleChecker",
152 "docstring summary is not in imperative mood"
153 " (Does instead of Do)"),
154 "D133": QT_TRANSLATE_NOOP(
155 "DocStyleChecker",
156 "docstring summary looks like a function's/method's signature"),
157 "D134": QT_TRANSLATE_NOOP(
158 "DocStyleChecker",
159 "docstring does not mention the return value type"),
160 "D141": QT_TRANSLATE_NOOP(
161 "DocStyleChecker",
162 "function/method docstring is separated by a blank line"),
163 "D142": QT_TRANSLATE_NOOP(
164 "DocStyleChecker",
165 "class docstring is not preceded by a blank line"),
166 "D143": QT_TRANSLATE_NOOP(
167 "DocStyleChecker",
168 "class docstring is not followed by a blank line"),
169 "D144": QT_TRANSLATE_NOOP(
170 "DocStyleChecker",
171 "docstring summary is not followed by a blank line"),
172 "D145": QT_TRANSLATE_NOOP(
173 "DocStyleChecker",
174 "last paragraph of docstring is not followed by a blank line"),
175
176 "D203": QT_TRANSLATE_NOOP(
177 "DocStyleChecker",
178 "private function/method is missing a docstring"),
179 "D205": QT_TRANSLATE_NOOP(
180 "DocStyleChecker", "private class is missing a docstring"),
181 "D221": QT_TRANSLATE_NOOP(
182 "DocStyleChecker",
183 "leading quotes of docstring not on separate line"),
184 "D222": QT_TRANSLATE_NOOP(
185 "DocStyleChecker",
186 "trailing quotes of docstring not on separate line"),
187 "D231": QT_TRANSLATE_NOOP(
188 "DocStyleChecker", "docstring summary does not end with a period"),
189 "D234": QT_TRANSLATE_NOOP(
190 "DocStyleChecker",
191 "docstring does not contain a @return line but function/method"
192 " returns something"),
193 "D235": QT_TRANSLATE_NOOP(
194 "DocStyleChecker",
195 "docstring contains a @return line but function/method doesn't"
196 " return anything"),
197 "D236": QT_TRANSLATE_NOOP(
198 "DocStyleChecker",
199 "docstring does not contain enough @param/@keyparam lines"),
200 "D237": QT_TRANSLATE_NOOP(
201 "DocStyleChecker",
202 "docstring contains too many @param/@keyparam lines"),
203 "D238": QT_TRANSLATE_NOOP(
204 "DocStyleChecker",
205 "keyword only arguments must be documented with @keyparam lines"),
206 "D239": QT_TRANSLATE_NOOP(
207 "DocStyleChecker", "order of @param/@keyparam lines does"
208 " not match the function/method signature"),
209 "D242": QT_TRANSLATE_NOOP(
210 "DocStyleChecker", "class docstring is preceded by a blank line"),
211 "D243": QT_TRANSLATE_NOOP(
212 "DocStyleChecker", "class docstring is followed by a blank line"),
213 "D244": QT_TRANSLATE_NOOP(
214 "DocStyleChecker",
215 "function/method docstring is preceded by a blank line"),
216 "D245": QT_TRANSLATE_NOOP(
217 "DocStyleChecker",
218 "function/method docstring is followed by a blank line"),
219 "D246": QT_TRANSLATE_NOOP(
220 "DocStyleChecker",
221 "docstring summary is not followed by a blank line"),
222 "D247": QT_TRANSLATE_NOOP(
223 "DocStyleChecker",
224 "last paragraph of docstring is followed by a blank line"),
225 "D250": QT_TRANSLATE_NOOP(
226 "DocStyleChecker",
227 "docstring does not contain a @exception line but function/method"
228 " raises an exception"),
229 "D251": QT_TRANSLATE_NOOP(
230 "DocStyleChecker",
231 "docstring contains a @exception line but function/method doesn't"
232 " raise an exception"),
233 }
234
235 def __init__(self, source, filename, select, ignore, expected, repeat,
236 maxLineLength=79, docType="pep257"):
237 """
238 Constructor
239
240 @param source source code to be checked (list of string)
241 @param filename name of the source file (string)
242 @param select list of selected codes (list of string)
243 @param ignore list of codes to be ignored (list of string)
244 @param expected list of expected codes (list of string)
245 @param repeat flag indicating to report each occurrence of a code
246 (boolean)
247 @keyparam maxLineLength allowed line length (integer)
248 @keyparam docType type of the documentation strings
249 (string, one of 'eric' or 'pep257')
250 """
251 assert docType in ("eric", "pep257")
252
253 self.__select = tuple(select)
254 self.__ignore = tuple(ignore)
255 self.__expected = expected[:]
256 self.__repeat = repeat
257 self.__maxLineLength = maxLineLength
258 self.__docType = docType
259 self.__filename = filename
260 self.__source = source[:]
261
262 # statistics counters
263 self.counters = {}
264
265 # collection of detected errors
266 self.errors = []
267
268 self.__lineNumber = 0
269
270 # caches
271 self.__functionsCache = None
272 self.__classesCache = None
273 self.__methodsCache = None
274
275 self.__keywords = [
276 'moduleDocstring', 'functionDocstring',
277 'classDocstring', 'methodDocstring',
278 'defDocstring', 'docstring'
279 ]
280 if self.__docType == "pep257":
281 checkersWithCodes = {
282 "moduleDocstring": [
283 (self.__checkModulesDocstrings, ("D101",)),
284 ],
285 "functionDocstring": [
286 ],
287 "classDocstring": [
288 (self.__checkClassDocstring, ("D104", "D105")),
289 (self.__checkBlankBeforeAndAfterClass, ("D142", "D143")),
290 ],
291 "methodDocstring": [
292 ],
293 "defDocstring": [
294 (self.__checkFunctionDocstring, ("D102", "D103")),
295 (self.__checkImperativeMood, ("D132",)),
296 (self.__checkNoSignature, ("D133",)),
297 (self.__checkReturnType, ("D134",)),
298 (self.__checkNoBlankLineBefore, ("D141",)),
299 ],
300 "docstring": [
301 (self.__checkTripleDoubleQuotes, ("D111",)),
302 (self.__checkBackslashes, ("D112",)),
303 (self.__checkUnicode, ("D113",)),
304 (self.__checkOneLiner, ("D121",)),
305 (self.__checkIndent, ("D122",)),
306 (self.__checkEndsWithPeriod, ("D131",)),
307 (self.__checkBlankAfterSummary, ("D144",)),
308 (self.__checkBlankAfterLastParagraph, ("D145",)),
309 ],
310 }
311 elif self.__docType == "eric":
312 checkersWithCodes = {
313 "moduleDocstring": [
314 (self.__checkModulesDocstrings, ("D101",)),
315 ],
316 "functionDocstring": [
317 ],
318 "classDocstring": [
319 (self.__checkClassDocstring, ("D104", "D205")),
320 (self.__checkEricNoBlankBeforeAndAfterClassOrFunction,
321 ("D242", "D243")),
322 ],
323 "methodDocstring": [
324 ],
325 "defDocstring": [
326 (self.__checkFunctionDocstring, ("D102", "D203")),
327 (self.__checkImperativeMood, ("D132",)),
328 (self.__checkNoSignature, ("D133",)),
329 (self.__checkEricReturn, ("D234", "D235")),
330 (self.__checkEricFunctionArguments,
331 ("D236", "D237", "D238", "D239")),
332 (self.__checkEricNoBlankBeforeAndAfterClassOrFunction,
333 ("D244", "D245")),
334 (self.__checkEricException, ("D250", "D251")),
335 ],
336 "docstring": [
337 (self.__checkTripleDoubleQuotes, ("D111",)),
338 (self.__checkBackslashes, ("D112",)),
339 (self.__checkUnicode, ("D113",)),
340 (self.__checkIndent, ("D122",)),
341 (self.__checkEricEndsWithPeriod, ("D231",)),
342 (self.__checkEricBlankAfterSummary, ("D246",)),
343 (self.__checkEricNBlankAfterLastParagraph, ("D247",)),
344 (self.__checkEricQuotesOnSeparateLines, ("D222", "D223"))
345 ],
346 }
347
348 self.__checkers = {}
349 for key, checkers in checkersWithCodes.items():
350 for checker, codes in checkers:
351 if any(not (code and self.__ignoreCode(code))
352 for code in codes):
353 if key not in self.__checkers:
354 self.__checkers[key] = []
355 self.__checkers[key].append(checker)
356
357 def __ignoreCode(self, code):
358 """
359 Private method to check if the error code should be ignored.
360
361 @param code message code to check for (string)
362 @return flag indicating to ignore the given code (boolean)
363 """
364 return (code.startswith(self.__ignore) and
365 not code.startswith(self.__select))
366
367 def __error(self, lineNumber, offset, code, *args):
368 """
369 Private method to record an issue.
370
371 @param lineNumber line number of the issue (integer)
372 @param offset position within line of the issue (integer)
373 @param code message code (string)
374 @param args arguments for the message (list)
375 """
376 if self.__ignoreCode(code):
377 return
378
379 if code in self.counters:
380 self.counters[code] += 1
381 else:
382 self.counters[code] = 1
383
384 # Don't care about expected codes
385 if code in self.__expected:
386 return
387
388 if code and (self.counters[code] == 1 or self.__repeat):
389 if code in DocStyleChecker.Codes:
390 text = self.getMessage(code, *args)
391 else:
392 text = code + " " + QCoreApplication.translate(
393 "DocStyleChecker", "no message for this code defined")
394 # record the issue with one based line number
395 self.errors.append((self.__filename, lineNumber + 1, offset, text))
396
397 @classmethod
398 def getMessage(cls, code, *args):
399 """
400 Class method to get a translated and formatted message for a
401 given code.
402
403 @param code message code (string)
404 @param args arguments for a formatted message (list)
405 @return translated and formatted message (string)
406 """
407 if code in DocStyleChecker.Messages:
408 return code + " " + QCoreApplication.translate(
409 "DocStyleChecker",
410 DocStyleChecker.Messages[code]).format(*args)
411 else:
412 return code + " " + QCoreApplication.translate(
413 "DocStyleChecker", "no message for this code defined")
414
415 def __resetReadline(self):
416 """
417 Private method to reset the internal readline function.
418 """
419 self.__lineNumber = 0
420
421 def __readline(self):
422 """
423 Private method to get the next line from the source.
424
425 @return next line of source (string)
426 """
427 self.__lineNumber += 1
428 if self.__lineNumber > len(self.__source):
429 return ''
430 return self.__source[self.__lineNumber - 1]
431
432 def run(self):
433 """
434 Public method to check the given source for violations of doc string
435 conventions.
436 """
437 if not self.__source or not self.__filename:
438 # don't do anything, if essential data is missing
439 return
440
441 if not self.__checkers:
442 # don't do anything, if no codes were selected
443 return
444
445 for keyword in self.__keywords:
446 if keyword in self.__checkers:
447 for check in self.__checkers[keyword]:
448 for context in self.__parseContexts(keyword):
449 docstring = self.__parseDocstring(context, keyword)
450 check(docstring, context)
451
452 def __getSummaryLine(self, docstringContext):
453 """
454 Private method to extract the summary line.
455
456 @param docstringContext docstring context (DocStyleContext)
457 @return summary line (string) and the line it was found on (integer)
458 """
459 lines = docstringContext.source()
460
461 line = (lines[0]
462 .replace('r"""', "", 1)
463 .replace('u"""', "", 1)
464 .replace('"""', "")
465 .replace("r'''", "", 1)
466 .replace("u'''", "", 1)
467 .replace("'''", "")
468 .strip())
469
470 if len(lines) == 1 or len(line) > 0:
471 return line, 0
472 return lines[1].strip().replace('"""', "").replace("'''", ""), 1
473
474 def __getSummaryLines(self, docstringContext):
475 """
476 Private method to extract the summary lines.
477
478 @param docstringContext docstring context (DocStyleContext)
479 @return summary lines (list of string) and the line it was found on
480 (integer)
481 """
482 summaries = []
483 lines = docstringContext.source()
484
485 line0 = (lines[0]
486 .replace('r"""', "", 1)
487 .replace('u"""', "", 1)
488 .replace('"""', "")
489 .replace("r'''", "", 1)
490 .replace("u'''", "", 1)
491 .replace("'''", "")
492 .strip())
493 if len(lines) > 1:
494 line1 = lines[1].strip().replace('"""', "").replace("'''", "")
495 else:
496 line1 = ""
497 if len(lines) > 2:
498 line2 = lines[2].strip().replace('"""', "").replace("'''", "")
499 else:
500 line2 = ""
501 if line0:
502 lineno = 0
503 summaries.append(line0)
504 if not line0.endswith(".") and line1:
505 # two line summary
506 summaries.append(line1)
507 elif line1:
508 lineno = 1
509 summaries.append(line1)
510 if not line1.endswith(".") and line2:
511 # two line summary
512 summaries.append(line2)
513 else:
514 lineno = 2
515 summaries.append(line2)
516 return summaries, lineno
517
518 if sys.version_info[0] < 3:
519 def __getArgNames(self, node):
520 """
521 Private method to get the argument names of a function node.
522
523 @param node AST node to extract arguments names from
524 @return tuple of two list of argument names, one for arguments
525 and one for keyword arguments (tuple of list of string)
526 """
527 def unpackArgs(args):
528 """
529 Local helper function to unpack function argument names.
530
531 @param args list of AST node arguments
532 @return list of argument names (list of string)
533 """
534 ret = []
535 for arg in args:
536 if isinstance(arg, ast.Tuple):
537 ret.extend(unpackArgs(arg.elts))
538 else:
539 ret.append(arg.id)
540 return ret
541
542 arguments = unpackArgs(node.args.args)
543 if node.args.vararg is not None:
544 arguments.append(node.args.vararg)
545 kwarguments = []
546 if node.args.kwarg is not None:
547 kwarguments.append(node.args.kwarg)
548 return arguments, kwarguments
549 else:
550 def __getArgNames(self, node): # __IGNORE_WARNING__
551 """
552 Private method to get the argument names of a function node.
553
554 @param node AST node to extract arguments names from
555 @return tuple of two list of argument names, one for arguments
556 and one for keyword arguments (tuple of list of string)
557 """
558 arguments = []
559 arguments.extend([arg.arg for arg in node.args.args])
560 if node.args.vararg is not None:
561 arguments.append(node.args.vararg)
562
563 kwarguments = []
564 kwarguments.extend([arg.arg for arg in node.args.kwonlyargs])
565 if node.args.kwarg is not None:
566 kwarguments.append(node.args.kwarg)
567 return arguments, kwarguments
568
569 ##################################################################
570 ## Parsing functionality below
571 ##################################################################
572
573 def __parseModuleDocstring(self, source):
574 """
575 Private method to extract a docstring given a module source.
576
577 @param source source to parse (list of string)
578 @return context of extracted docstring (DocStyleContext)
579 """
580 for kind, value, (line, char), _, _ in tokenize.generate_tokens(
581 StringIO("".join(source)).readline):
582 if kind in [tokenize.COMMENT, tokenize.NEWLINE, tokenize.NL]:
583 continue
584 elif kind == tokenize.STRING: # first STRING should be docstring
585 return DocStyleContext(value, line - 1, "docstring")
586 else:
587 return None
588
589 def __parseDocstring(self, context, what=''):
590 """
591 Private method to extract a docstring given `def` or `class` source.
592
593 @param context context data to get the docstring from (DocStyleContext)
594 @param what string denoting what is being parsed (string)
595 @return context of extracted docstring (DocStyleContext)
596 """
597 moduleDocstring = self.__parseModuleDocstring(context.source())
598 if what.startswith('module') or context.contextType() == "module":
599 return moduleDocstring
600 if moduleDocstring:
601 return moduleDocstring
602
603 tokenGenerator = tokenize.generate_tokens(
604 StringIO(context.ssource()).readline)
605 try:
606 kind = None
607 while kind != tokenize.INDENT:
608 kind, _, _, _, _ = next(tokenGenerator)
609 kind, value, (line, char), _, _ = next(tokenGenerator)
610 if kind == tokenize.STRING: # STRING after INDENT is a docstring
611 return DocStyleContext(
612 value, context.start() + line - 1, "docstring")
613 except StopIteration:
614 pass
615
616 return None
617
618 def __parseTopLevel(self, keyword):
619 """
620 Private method to extract top-level functions or classes.
621
622 @param keyword keyword signaling what to extract (string)
623 @return extracted function or class contexts (list of DocStyleContext)
624 """
625 self.__resetReadline()
626 tokenGenerator = tokenize.generate_tokens(self.__readline)
627 kind, value, char = None, None, None
628 contexts = []
629 try:
630 while True:
631 start, end = None, None
632 while not (kind == tokenize.NAME and
633 value == keyword and
634 char == 0):
635 kind, value, (line, char), _, _ = next(tokenGenerator)
636 start = line - 1, char
637 while not (kind == tokenize.DEDENT and
638 value == '' and
639 char == 0):
640 kind, value, (line, char), _, _ = next(tokenGenerator)
641 end = line - 1, char
642 contexts.append(DocStyleContext(
643 self.__source[start[0]:end[0]], start[0], keyword))
644 except StopIteration:
645 return contexts
646
647 def __parseFunctions(self):
648 """
649 Private method to extract top-level functions.
650
651 @return extracted function contexts (list of DocStyleContext)
652 """
653 if not self.__functionsCache:
654 self.__functionsCache = self.__parseTopLevel('def')
655 return self.__functionsCache
656
657 def __parseClasses(self):
658 """
659 Private method to extract top-level classes.
660
661 @return extracted class contexts (list of DocStyleContext)
662 """
663 if not self.__classesCache:
664 self.__classesCache = self.__parseTopLevel('class')
665 return self.__classesCache
666
667 def __skipIndentedBlock(self, tokenGenerator):
668 """
669 Private method to skip over an indented block of source code.
670
671 @param tokenGenerator token generator
672 @return last token of the indented block
673 """
674 kind, value, start, end, raw = next(tokenGenerator)
675 while kind != tokenize.INDENT:
676 kind, value, start, end, raw = next(tokenGenerator)
677 indent = 1
678 for kind, value, start, end, raw in tokenGenerator:
679 if kind == tokenize.INDENT:
680 indent += 1
681 elif kind == tokenize.DEDENT:
682 indent -= 1
683 if indent == 0:
684 return kind, value, start, end, raw
685
686 def __parseMethods(self):
687 """
688 Private method to extract methods of all classes.
689
690 @return extracted method contexts (list of DocStyleContext)
691 """
692 if not self.__methodsCache:
693 contexts = []
694 for classContext in self.__parseClasses():
695 tokenGenerator = tokenize.generate_tokens(
696 StringIO(classContext.ssource()).readline)
697 kind, value, char = None, None, None
698 try:
699 while True:
700 start, end = None, None
701 while not (kind == tokenize.NAME and value == 'def'):
702 kind, value, (line, char), _, _ = \
703 next(tokenGenerator)
704 start = line - 1, char
705 kind, value, (line, char), _, _ = \
706 self.__skipIndentedBlock(tokenGenerator)
707 end = line - 1, char
708 startLine = classContext.start() + start[0]
709 endLine = classContext.start() + end[0]
710 contexts.append(
711 DocStyleContext(self.__source[startLine:endLine],
712 startLine, "def"))
713 except StopIteration:
714 pass
715 self.__methodsCache = contexts
716
717 return self.__methodsCache
718
719 def __parseContexts(self, kind):
720 """
721 Private method to extract a context from the source.
722
723 @param kind kind of context to extract (string)
724 @return requested contexts (list of DocStyleContext)
725 """
726 if kind == 'moduleDocstring':
727 return [DocStyleContext(self.__source, 0, "module")]
728 if kind == 'functionDocstring':
729 return self.__parseFunctions()
730 if kind == 'classDocstring':
731 return self.__parseClasses()
732 if kind == 'methodDocstring':
733 return self.__parseMethods()
734 if kind == 'defDocstring':
735 return self.__parseFunctions() + self.__parseMethods()
736 if kind == 'docstring':
737 return ([DocStyleContext(self.__source, 0, "module")] +
738 self.__parseFunctions() +
739 self.__parseClasses() +
740 self.__parseMethods())
741 return [] # fall back
742
743 ##################################################################
744 ## Checking functionality below (PEP-257)
745 ##################################################################
746
747 def __checkModulesDocstrings(self, docstringContext, context):
748 """
749 Private method to check, if the module has a docstring.
750
751 @param docstringContext docstring context (DocStyleContext)
752 @param context context of the docstring (DocStyleContext)
753 """
754 if docstringContext is None:
755 self.__error(context.start(), 0, "D101")
756 return
757
758 docstring = docstringContext.ssource()
759 if (not docstring or not docstring.strip() or
760 not docstring.strip('\'"')):
761 self.__error(context.start(), 0, "D101")
762
763 def __checkFunctionDocstring(self, docstringContext, context):
764 """
765 Private method to check, that all public functions and methods
766 have a docstring.
767
768 @param docstringContext docstring context (DocStyleContext)
769 @param context context of the docstring (DocStyleContext)
770 """
771 functionName = context.source()[0].lstrip().split()[1].split("(")[0]
772 if functionName.startswith('_') and not functionName.endswith('__'):
773 if self.__docType == "eric":
774 code = "D203"
775 else:
776 code = "D103"
777 else:
778 code = "D102"
779
780 if docstringContext is None:
781 self.__error(context.start(), 0, code)
782 return
783
784 docstring = docstringContext.ssource()
785 if (not docstring or not docstring.strip() or
786 not docstring.strip('\'"')):
787 self.__error(context.start(), 0, code)
788
789 def __checkClassDocstring(self, docstringContext, context):
790 """
791 Private method to check, that all public functions and methods
792 have a docstring.
793
794 @param docstringContext docstring context (DocStyleContext)
795 @param context context of the docstring (DocStyleContext)
796 """
797 className = context.source()[0].lstrip().split()[1].split("(")[0]
798 if className.startswith('_'):
799 if self.__docType == "eric":
800 code = "D205"
801 else:
802 code = "D105"
803 else:
804 code = "D104"
805
806 if docstringContext is None:
807 self.__error(context.start(), 0, code)
808 return
809
810 docstring = docstringContext.ssource()
811 if (not docstring or not docstring.strip() or
812 not docstring.strip('\'"')):
813 self.__error(context.start(), 0, code)
814
815 def __checkTripleDoubleQuotes(self, docstringContext, context):
816 """
817 Private method to check, that all docstrings are surrounded
818 by triple double quotes.
819
820 @param docstringContext docstring context (DocStyleContext)
821 @param context context of the docstring (DocStyleContext)
822 """
823 if docstringContext is None:
824 return
825
826 docstring = docstringContext.ssource().strip()
827 if not docstring.startswith(('"""', 'r"""', 'u"""')):
828 self.__error(docstringContext.start(), 0, "D111")
829
830 def __checkBackslashes(self, docstringContext, context):
831 """
832 Private method to check, that all docstrings containing
833 backslashes are surrounded by raw triple double quotes.
834
835 @param docstringContext docstring context (DocStyleContext)
836 @param context context of the docstring (DocStyleContext)
837 """
838 if docstringContext is None:
839 return
840
841 docstring = docstringContext.ssource().strip()
842 if "\\" in docstring and not docstring.startswith('r"""'):
843 self.__error(docstringContext.start(), 0, "D112")
844
845 def __checkUnicode(self, docstringContext, context):
846 """
847 Private method to check, that all docstrings containing unicode
848 characters are surrounded by unicode triple double quotes.
849
850 @param docstringContext docstring context (DocStyleContext)
851 @param context context of the docstring (DocStyleContext)
852 """
853 if docstringContext is None:
854 return
855
856 docstring = docstringContext.ssource().strip()
857 if not docstring.startswith('u"""') and \
858 any(ord(char) > 127 for char in docstring):
859 self.__error(docstringContext.start(), 0, "D113")
860
861 def __checkOneLiner(self, docstringContext, context):
862 """
863 Private method to check, that one-liner docstrings fit on
864 one line with quotes.
865
866 @param docstringContext docstring context (DocStyleContext)
867 @param context context of the docstring (DocStyleContext)
868 """
869 if docstringContext is None:
870 return
871
872 lines = docstringContext.source()
873 if len(lines) > 1:
874 nonEmptyLines = [l for l in lines if l.strip().strip('\'"')]
875 if len(nonEmptyLines) == 1:
876 modLen = len(context.indent() + '"""' +
877 nonEmptyLines[0].strip() + '"""')
878 if context.contextType() != "module":
879 modLen += 4
880 if not nonEmptyLines[0].strip().endswith("."):
881 # account for a trailing dot
882 modLen += 1
883 if modLen <= self.__maxLineLength:
884 self.__error(docstringContext.start(), 0, "D121")
885
886 def __checkIndent(self, docstringContext, context):
887 """
888 Private method to check, that docstrings are properly indented.
889
890 @param docstringContext docstring context (DocStyleContext)
891 @param context context of the docstring (DocStyleContext)
892 """
893 if docstringContext is None:
894 return
895
896 lines = docstringContext.source()
897 if len(lines) == 1:
898 return
899
900 nonEmptyLines = [l.rstrip() for l in lines[1:] if l.strip()]
901 if not nonEmptyLines:
902 return
903
904 indent = min([len(l) - len(l.strip()) for l in nonEmptyLines])
905 if context.contextType() == "module":
906 expectedIndent = 0
907 else:
908 expectedIndent = len(context.indent()) + 4
909 if indent != expectedIndent:
910 self.__error(docstringContext.start(), 0, "D122")
911
912 def __checkEndsWithPeriod(self, docstringContext, context):
913 """
914 Private method to check, that docstring summaries end with a period.
915
916 @param docstringContext docstring context (DocStyleContext)
917 @param context context of the docstring (DocStyleContext)
918 """
919 if docstringContext is None:
920 return
921
922 summary, lineNumber = self.__getSummaryLine(docstringContext)
923 if not summary.endswith("."):
924 self.__error(docstringContext.start() + lineNumber, 0, "D131")
925
926 def __checkImperativeMood(self, docstringContext, context):
927 """
928 Private method to check, that docstring summaries are in
929 imperative mood.
930
931 @param docstringContext docstring context (DocStyleContext)
932 @param context context of the docstring (DocStyleContext)
933 """
934 if docstringContext is None:
935 return
936
937 summary, lineNumber = self.__getSummaryLine(docstringContext)
938 firstWord = summary.strip().split()[0]
939 if firstWord.endswith("s") and not firstWord.endswith("ss"):
940 self.__error(docstringContext.start() + lineNumber, 0, "D132")
941
942 def __checkNoSignature(self, docstringContext, context):
943 """
944 Private method to check, that docstring summaries don't repeat
945 the function's signature.
946
947 @param docstringContext docstring context (DocStyleContext)
948 @param context context of the docstring (DocStyleContext)
949 """
950 if docstringContext is None:
951 return
952
953 functionName = context.source()[0].lstrip().split()[1].split("(")[0]
954 summary, lineNumber = self.__getSummaryLine(docstringContext)
955 if functionName + "(" in summary.replace(" ", "") and \
956 not functionName + "()" in summary.replace(" ", ""):
957 # report only, if it is not an abbreviated form (i.e. function() )
958 self.__error(docstringContext.start() + lineNumber, 0, "D133")
959
960 def __checkReturnType(self, docstringContext, context):
961 """
962 Private method to check, that docstrings mention the return value type.
963
964 @param docstringContext docstring context (DocStyleContext)
965 @param context context of the docstring (DocStyleContext)
966 """
967 if docstringContext is None:
968 return
969
970 if "return" not in docstringContext.ssource().lower():
971 tokens = list(
972 tokenize.generate_tokens(StringIO(context.ssource()).readline))
973 return_ = [tokens[i + 1][0] for i, token in enumerate(tokens)
974 if token[1] == "return"]
975 if (set(return_) -
976 set([tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE]) !=
977 set([])):
978 self.__error(docstringContext.end(), 0, "D134")
979
980 def __checkNoBlankLineBefore(self, docstringContext, context):
981 """
982 Private method to check, that function/method docstrings are not
983 preceded by a blank line.
984
985 @param docstringContext docstring context (DocStyleContext)
986 @param context context of the docstring (DocStyleContext)
987 """
988 if docstringContext is None:
989 return
990
991 contextLines = context.source()
992 cti = 0
993 while cti < len(contextLines) and \
994 not contextLines[cti].strip().startswith(
995 ('"""', 'r"""', 'u"""', "'''", "r'''", "u'''")):
996 cti += 1
997 if cti == len(contextLines):
998 return
999
1000 if not contextLines[cti - 1].strip():
1001 self.__error(docstringContext.start(), 0, "D141")
1002
1003 def __checkBlankBeforeAndAfterClass(self, docstringContext, context):
1004 """
1005 Private method to check, that class docstrings have one
1006 blank line around them.
1007
1008 @param docstringContext docstring context (DocStyleContext)
1009 @param context context of the docstring (DocStyleContext)
1010 """
1011 if docstringContext is None:
1012 return
1013
1014 contextLines = context.source()
1015 cti = 0
1016 while cti < len(contextLines) and \
1017 not contextLines[cti].strip().startswith(
1018 ('"""', 'r"""', 'u"""', "'''", "r'''", "u'''")):
1019 cti += 1
1020 if cti == len(contextLines):
1021 return
1022
1023 start = cti
1024 if contextLines[cti].strip() in (
1025 '"""', 'r"""', 'u"""', "'''", "r'''", "u'''"):
1026 # it is a multi line docstring
1027 cti += 1
1028
1029 while cti < len(contextLines) and \
1030 not contextLines[cti].strip().endswith(('"""', "'''")):
1031 cti += 1
1032 end = cti
1033 if cti >= len(contextLines) - 1:
1034 return
1035
1036 if contextLines[start - 1].strip():
1037 self.__error(docstringContext.start(), 0, "D142")
1038 if contextLines[end + 1].strip():
1039 self.__error(docstringContext.end(), 0, "D143")
1040
1041 def __checkBlankAfterSummary(self, docstringContext, context):
1042 """
1043 Private method to check, that docstring summaries are followed
1044 by a blank line.
1045
1046 @param docstringContext docstring context (DocStyleContext)
1047 @param context context of the docstring (DocStyleContext)
1048 """
1049 if docstringContext is None:
1050 return
1051
1052 docstrings = docstringContext.source()
1053 if len(docstrings) <= 3:
1054 # correct/invalid one-liner
1055 return
1056
1057 summary, lineNumber = self.__getSummaryLine(docstringContext)
1058 if len(docstrings) > 2:
1059 if docstrings[lineNumber + 1].strip():
1060 self.__error(docstringContext.start() + lineNumber, 0, "D144")
1061
1062 def __checkBlankAfterLastParagraph(self, docstringContext, context):
1063 """
1064 Private method to check, that the last paragraph of docstrings is
1065 followed by a blank line.
1066
1067 @param docstringContext docstring context (DocStyleContext)
1068 @param context context of the docstring (DocStyleContext)
1069 """
1070 if docstringContext is None:
1071 return
1072
1073 docstrings = docstringContext.source()
1074 if len(docstrings) <= 3:
1075 # correct/invalid one-liner
1076 return
1077
1078 if docstrings[-2].strip():
1079 self.__error(docstringContext.end(), 0, "D145")
1080
1081 ##################################################################
1082 ## Checking functionality below (eric specific ones)
1083 ##################################################################
1084
1085 def __checkEricQuotesOnSeparateLines(self, docstringContext, context):
1086 """
1087 Private method to check, that leading and trailing quotes are on
1088 a line by themselves.
1089
1090 @param docstringContext docstring context (DocStyleContext)
1091 @param context context of the docstring (DocStyleContext)
1092 """
1093 if docstringContext is None:
1094 return
1095
1096 lines = docstringContext.source()
1097 if lines[0].strip().strip('ru"\''):
1098 self.__error(docstringContext.start(), 0, "D221")
1099 if lines[-1].strip().strip('"\''):
1100 self.__error(docstringContext.end(), 0, "D222")
1101
1102 def __checkEricEndsWithPeriod(self, docstringContext, context):
1103 """
1104 Private method to check, that docstring summaries end with a period.
1105
1106 @param docstringContext docstring context (DocStyleContext)
1107 @param context context of the docstring (DocStyleContext)
1108 """
1109 if docstringContext is None:
1110 return
1111
1112 summaryLines, lineNumber = self.__getSummaryLines(docstringContext)
1113 if summaryLines[-1].lstrip().startswith("@"):
1114 summaryLines.pop(-1)
1115 summary = " ".join([s.strip() for s in summaryLines if s])
1116 if not summary.endswith(".") and \
1117 not summary.split(None, 1)[0].lower() == "constructor":
1118 self.__error(
1119 docstringContext.start() + lineNumber + len(summaryLines) - 1,
1120 0, "D231")
1121
1122 def __checkEricReturn(self, docstringContext, context):
1123 """
1124 Private method to check, that docstrings contain an &#64;return line
1125 if they return anything and don't otherwise.
1126
1127 @param docstringContext docstring context (DocStyleContext)
1128 @param context context of the docstring (DocStyleContext)
1129 """
1130 if docstringContext is None:
1131 return
1132
1133 tokens = list(
1134 tokenize.generate_tokens(StringIO(context.ssource()).readline))
1135 return_ = [tokens[i + 1][0] for i, token in enumerate(tokens)
1136 if token[1] in ("return", "yield")]
1137 if "@return" not in docstringContext.ssource():
1138 if (set(return_) -
1139 set([tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE]) !=
1140 set([])):
1141 self.__error(docstringContext.end(), 0, "D234")
1142 else:
1143 if (set(return_) -
1144 set([tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE]) ==
1145 set([])):
1146 self.__error(docstringContext.end(), 0, "D235")
1147
1148 def __checkEricFunctionArguments(self, docstringContext, context):
1149 """
1150 Private method to check, that docstrings contain an &#64;param and/or
1151 &#64;keyparam line for each argument.
1152
1153 @param docstringContext docstring context (DocStyleContext)
1154 @param context context of the docstring (DocStyleContext)
1155 """
1156 if docstringContext is None:
1157 return
1158
1159 try:
1160 tree = ast.parse(context.ssource())
1161 except (SyntaxError, TypeError):
1162 return
1163 if (isinstance(tree, ast.Module) and len(tree.body) == 1 and
1164 isinstance(tree.body[0], ast.FunctionDef)):
1165 functionDef = tree.body[0]
1166 argNames, kwNames = self.__getArgNames(functionDef)
1167 if "self" in argNames:
1168 argNames.remove("self")
1169 if "cls" in argNames:
1170 argNames.remove("cls")
1171
1172 docstring = docstringContext.ssource()
1173 if (docstring.count("@param") + docstring.count("@keyparam") <
1174 len(argNames + kwNames)):
1175 self.__error(docstringContext.end(), 0, "D236")
1176 elif (docstring.count("@param") + docstring.count("@keyparam") >
1177 len(argNames + kwNames)):
1178 self.__error(docstringContext.end(), 0, "D237")
1179 else:
1180 # extract @param and @keyparam from docstring
1181 args = []
1182 kwargs = []
1183 for line in docstringContext.source():
1184 if line.strip().startswith(("@param", "@keyparam")):
1185 at, name = line.strip().split(None, 2)[:2]
1186 if at == "@keyparam":
1187 kwargs.append(name.lstrip("*"))
1188 args.append(name.lstrip("*"))
1189
1190 # do the checks
1191 for name in kwNames:
1192 if name not in kwargs:
1193 self.__error(docstringContext.end(), 0, "D238")
1194 return
1195 if argNames + kwNames != args:
1196 self.__error(docstringContext.end(), 0, "D239")
1197
1198 def __checkEricException(self, docstringContext, context):
1199 """
1200 Private method to check, that docstrings contain an &#64;exception line
1201 if they raise an exception and don't otherwise.
1202
1203 @param docstringContext docstring context (DocStyleContext)
1204 @param context context of the docstring (DocStyleContext)
1205 """
1206 if docstringContext is None:
1207 return
1208
1209 tokens = list(
1210 tokenize.generate_tokens(StringIO(context.ssource()).readline))
1211 exception = [tokens[i + 1][0] for i, token in enumerate(tokens)
1212 if token[1] == "raise"]
1213 if "@exception" not in docstringContext.ssource() and \
1214 "@throws" not in docstringContext.ssource() and \
1215 "@raise" not in docstringContext.ssource():
1216 if (set(exception) -
1217 set([tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE]) !=
1218 set([])):
1219 self.__error(docstringContext.end(), 0, "D250")
1220 else:
1221 if (set(exception) -
1222 set([tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE]) ==
1223 set([])):
1224 self.__error(docstringContext.end(), 0, "D251")
1225
1226 def __checkEricBlankAfterSummary(self, docstringContext, context):
1227 """
1228 Private method to check, that docstring summaries are followed
1229 by a blank line.
1230
1231 @param docstringContext docstring context (DocStyleContext)
1232 @param context context of the docstring (DocStyleContext)
1233 """
1234 if docstringContext is None:
1235 return
1236
1237 docstrings = docstringContext.source()
1238 if len(docstrings) <= 3:
1239 # correct/invalid one-liner
1240 return
1241
1242 summaryLines, lineNumber = self.__getSummaryLines(docstringContext)
1243 if len(docstrings) - 2 > lineNumber + len(summaryLines) - 1:
1244 if docstrings[lineNumber + len(summaryLines)].strip():
1245 self.__error(docstringContext.start() + lineNumber, 0, "D246")
1246
1247 def __checkEricNoBlankBeforeAndAfterClassOrFunction(
1248 self, docstringContext, context):
1249 """
1250 Private method to check, that class and function/method docstrings
1251 have no blank line around them.
1252
1253 @param docstringContext docstring context (DocStyleContext)
1254 @param context context of the docstring (DocStyleContext)
1255 """
1256 if docstringContext is None:
1257 return
1258
1259 contextLines = context.source()
1260 isClassContext = contextLines[0].lstrip().startswith("class ")
1261 cti = 0
1262 while cti < len(contextLines) and \
1263 not contextLines[cti].strip().startswith(
1264 ('"""', 'r"""', 'u"""', "'''", "r'''", "u'''")):
1265 cti += 1
1266 if cti == len(contextLines):
1267 return
1268
1269 start = cti
1270 if contextLines[cti].strip() in (
1271 '"""', 'r"""', 'u"""', "'''", "r'''", "u'''"):
1272 # it is a multi line docstring
1273 cti += 1
1274
1275 while cti < len(contextLines) and \
1276 not contextLines[cti].strip().endswith(('"""', "'''")):
1277 cti += 1
1278 end = cti
1279 if cti >= len(contextLines) - 1:
1280 return
1281
1282 if isClassContext:
1283 if not contextLines[start - 1].strip():
1284 self.__error(docstringContext.start(), 0, "D242")
1285 if not contextLines[end + 1].strip():
1286 self.__error(docstringContext.end(), 0, "D243")
1287 else:
1288 if not contextLines[start - 1].strip():
1289 self.__error(docstringContext.start(), 0, "D244")
1290 if not contextLines[end + 1].strip():
1291 self.__error(docstringContext.end(), 0, "D245")
1292
1293 def __checkEricNBlankAfterLastParagraph(self, docstringContext, context):
1294 """
1295 Private method to check, that the last paragraph of docstrings is
1296 not followed by a blank line.
1297
1298 @param docstringContext docstring context (DocStyleContext)
1299 @param context context of the docstring (DocStyleContext)
1300 """
1301 if docstringContext is None:
1302 return
1303
1304 docstrings = docstringContext.source()
1305 if len(docstrings) <= 3:
1306 # correct/invalid one-liner
1307 return
1308
1309 if not docstrings[-2].strip():
1310 self.__error(docstringContext.end(), 0, "D247")

eric ide

mercurial