Plugins/CheckerPlugins/Pep8/Pep257Checker.py

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

eric ide

mercurial