Plugins/CheckerPlugins/CodeStyleChecker/DocStyleChecker.py

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

eric ide

mercurial