UtilitiesPython2/DocStyleCheckerPy2.py

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

eric ide

mercurial