UtilitiesPython2/DocStyleCheckerPy2.py

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

eric ide

mercurial