|
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 |
|
23 from PyQt4.QtCore import QT_TRANSLATE_NOOP, QCoreApplication |
|
24 |
|
25 |
|
26 class Pep257Context(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 Pep257Checker(object): |
|
102 """ |
|
103 Class implementing a checker for PEP-257 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" |
|
111 ] |
|
112 |
|
113 Messages = { |
|
114 "D101": QT_TRANSLATE_NOOP( |
|
115 "Pep257Checker", "module is missing a docstring"), |
|
116 "D102": QT_TRANSLATE_NOOP( |
|
117 "Pep257Checker", "public function/method is missing a docstring"), |
|
118 "D103": QT_TRANSLATE_NOOP( |
|
119 "Pep257Checker", |
|
120 "private function/method may be missing a docstring"), |
|
121 "D104": QT_TRANSLATE_NOOP( |
|
122 "Pep257Checker", "public class is missing a docstring"), |
|
123 "D105": QT_TRANSLATE_NOOP( |
|
124 "Pep257Checker", "private class may be missing a docstring"), |
|
125 "D111": QT_TRANSLATE_NOOP( |
|
126 "Pep257Checker", 'docstring not surrounded by """'), |
|
127 "D112": QT_TRANSLATE_NOOP( |
|
128 "Pep257Checker", 'docstring containing \\ not surrounded by r"""'), |
|
129 "D113": QT_TRANSLATE_NOOP( |
|
130 "Pep257Checker", |
|
131 'docstring containing unicode character not surrounded by u"""'), |
|
132 "D121": QT_TRANSLATE_NOOP( |
|
133 "Pep257Checker", "one-liner docstring on multiple lines"), |
|
134 "D122": QT_TRANSLATE_NOOP( |
|
135 "Pep257Checker", "docstring has wrong indentation"), |
|
136 "D131": QT_TRANSLATE_NOOP( |
|
137 "Pep257Checker", "docstring summary does not end with a period"), |
|
138 "D132": QT_TRANSLATE_NOOP( |
|
139 "Pep257Checker", |
|
140 "docstring summary is not in imperative mood" |
|
141 " (Does instead of Do)"), |
|
142 "D133": QT_TRANSLATE_NOOP( |
|
143 "Pep257Checker", |
|
144 "docstring summary looks like a function's/method's signature"), |
|
145 "D134": QT_TRANSLATE_NOOP( |
|
146 "Pep257Checker", |
|
147 "docstring does not mention the return value type"), |
|
148 "D141": QT_TRANSLATE_NOOP( |
|
149 "Pep257Checker", "docstring is separated by a blank line"), |
|
150 } |
|
151 |
|
152 def __init__(self, source, filename, select, ignore, expected, repeat): |
|
153 """ |
|
154 Constructor (according to 'extended' pep8.py API) |
|
155 |
|
156 @param source source code to be checked (list of string) |
|
157 @param filename name of the source file (string) |
|
158 @param select list of selected codes (list of string) |
|
159 @param ignore list of codes to be ignored (list of string) |
|
160 @param expected list of expected codes (list of string) |
|
161 @param repeat flag indicating to report each occurrence of a code |
|
162 (boolean) |
|
163 """ |
|
164 self.__select = tuple(select) |
|
165 self.__ignore = tuple(ignore) |
|
166 self.__expected = expected[:] |
|
167 self.__repeat = repeat |
|
168 self.__filename = filename |
|
169 self.__source = source[:] |
|
170 self.__isScript = self.__source[0].startswith('#!') |
|
171 |
|
172 # statistics counters |
|
173 self.counters = {} |
|
174 |
|
175 # collection of detected errors |
|
176 self.errors = [] |
|
177 |
|
178 self.__lineNumber = 0 |
|
179 |
|
180 # caches |
|
181 self.__functionsCache = None |
|
182 self.__classesCache = None |
|
183 self.__methodsCache = None |
|
184 |
|
185 self.__keywords = [ |
|
186 'moduleDocstring', 'functionDocstring', |
|
187 'classDocstring', 'methodDocstring', |
|
188 'defDocstring', 'docstring' |
|
189 ] |
|
190 self.__checkersWithCodes = { |
|
191 "moduleDocstring": [ |
|
192 (self.__checkModulesDocstrings, ("D101",)), |
|
193 ], |
|
194 "functionDocstring": [ |
|
195 ], |
|
196 "classDocstring": [ |
|
197 (self.__checkClassDocstring, ("D104", "D105")), |
|
198 ], |
|
199 "methodDocstring": [ |
|
200 ], |
|
201 "defDocstring": [ |
|
202 (self.__checkFunctionDocstring, ("D102", "D103")), |
|
203 (self.__checkImperativeMood, ("D132",)), |
|
204 (self.__checkNoSignature, ("D133",)), |
|
205 (self.__checkReturnType, ("D134",)), |
|
206 (self.__checkNoBlankLineBefore, ("D141",)), |
|
207 ], |
|
208 "docstring": [ |
|
209 (self.__checkTripleDoubleQuotes, ("D111",)), |
|
210 (self.__checkBackslashes, ("D112",)), |
|
211 (self.__checkUnicode, ("D113",)), |
|
212 (self.__checkOneLiner, ("D121",)), |
|
213 (self.__checkIndent, ("D122",)), |
|
214 (self.__checkEndsWithPeriod, ("D131",)), |
|
215 ], |
|
216 } |
|
217 |
|
218 self.__checkers = {} |
|
219 for key, checkers in self.__checkersWithCodes.items(): |
|
220 for checker, codes in checkers: |
|
221 if any(not (code and self.__ignoreCode(code)) |
|
222 for code in codes): |
|
223 if key not in self.__checkers: |
|
224 self.__checkers[key] = [] |
|
225 self.__checkers[key].append(checker) |
|
226 |
|
227 def __ignoreCode(self, code): |
|
228 """ |
|
229 Private method to check if the error code should be ignored. |
|
230 |
|
231 @param code message code to check for (string) |
|
232 @return flag indicating to ignore the given code (boolean) |
|
233 """ |
|
234 return (code.startswith(self.__ignore) and |
|
235 not code.startswith(self.__select)) |
|
236 |
|
237 def __error(self, lineNumber, offset, code, *args): |
|
238 """ |
|
239 Private method to record an issue. |
|
240 |
|
241 @param lineNumber line number of the issue (integer) |
|
242 @param offset position within line of the issue (integer) |
|
243 @param code message code (string) |
|
244 @param args arguments for the message (list) |
|
245 """ |
|
246 if self.__ignoreCode(code): |
|
247 return |
|
248 |
|
249 if code in self.counters: |
|
250 self.counters[code] += 1 |
|
251 else: |
|
252 self.counters[code] = 1 |
|
253 |
|
254 # Don't care about expected codes |
|
255 if code in self.__expected: |
|
256 return |
|
257 |
|
258 if code and (self.counters[code] == 1 or self.__repeat): |
|
259 if code in Pep257Checker.Codes: |
|
260 text = self.__getMessage(code, *args) |
|
261 # record the issue with one based line number |
|
262 self.errors.append((self.__filename, lineNumber + 1, offset, text)) |
|
263 |
|
264 def __getMessage(self, code, *args): |
|
265 """ |
|
266 Private method to get a translated and formatted message for a |
|
267 given code. |
|
268 |
|
269 @param code message code (string) |
|
270 @param args arguments for a formatted message (list) |
|
271 @return translated and formatted message (string) |
|
272 """ |
|
273 if code in Pep257Checker.Messages: |
|
274 return code + " " + QCoreApplication.translate( |
|
275 "Pep257Checker", Pep257Checker.Messages[code]).format(*args) |
|
276 else: |
|
277 return code + " " + QCoreApplication.translate( |
|
278 "Pep257Checker", "no message for this code defined") |
|
279 |
|
280 def __resetReadline(self): |
|
281 """ |
|
282 Private method to reset the internal readline function. |
|
283 """ |
|
284 self.__lineNumber = 0 |
|
285 |
|
286 def __readline(self): |
|
287 """ |
|
288 Private method to get the next line from the source. |
|
289 |
|
290 @return next line of source (string) |
|
291 """ |
|
292 self.__lineNumber += 1 |
|
293 if self.__lineNumber > len(self.__source): |
|
294 return '' |
|
295 return self.__source[self.__lineNumber - 1] |
|
296 |
|
297 def run(self): |
|
298 """ |
|
299 Public method to check the given source for violations of doc string |
|
300 conventions according to PEP-257. |
|
301 """ |
|
302 if not self.__source or not self.__filename: |
|
303 # don't do anything, if essential data is missing |
|
304 return |
|
305 |
|
306 for keyword in self.__keywords: |
|
307 if keyword in self.__checkers: |
|
308 for check in self.__checkers[keyword]: |
|
309 for context in self.__parseContexts(keyword): |
|
310 docstring = self.__parseDocstring(context, keyword) |
|
311 check(docstring, context) |
|
312 |
|
313 def __getSummaryLine(self, docstringContext): |
|
314 """ |
|
315 Private method to extract the summary line. |
|
316 |
|
317 @param docstringContext docstring context (Pep257Context) |
|
318 @return summary line (string) and the line it was found on (integer) |
|
319 """ |
|
320 lines = docstringContext.source() |
|
321 |
|
322 line = (lines[0] |
|
323 .replace('r"""', "", 1) |
|
324 .replace('u"""', "", 1) |
|
325 .replace('"""', "") |
|
326 .strip()) |
|
327 |
|
328 if len(lines) == 1 or len(line) > 0: |
|
329 return line, 0 |
|
330 return lines[1].strip(), 1 |
|
331 |
|
332 first_line = lines[0].strip() |
|
333 if len(lines) == 1 or len(first_line) > 0: |
|
334 return first_line, 0 |
|
335 return lines[1].strip(), 1 |
|
336 |
|
337 ################################################################## |
|
338 ## Parsing functionality below |
|
339 ################################################################## |
|
340 |
|
341 def __parseModuleDocstring(self, source): |
|
342 """ |
|
343 Private method to extract a docstring given a module source. |
|
344 |
|
345 @param source source to parse (list of string) |
|
346 @return context of extracted docstring (Pep257Context) |
|
347 """ |
|
348 for kind, value, (line, char), _, _ in tokenize.generate_tokens( |
|
349 StringIO("".join(source)).readline): |
|
350 if kind in [tokenize.COMMENT, tokenize.NEWLINE, tokenize.NL]: |
|
351 continue |
|
352 elif kind == tokenize.STRING: # first STRING should be docstring |
|
353 return Pep257Context(value, line - 1, "docstring") |
|
354 else: |
|
355 return None |
|
356 |
|
357 def __parseDocstring(self, context, what=''): |
|
358 """ |
|
359 Private method to extract a docstring given `def` or `class` source. |
|
360 |
|
361 @param context context data to get the docstring from (Pep257Context) |
|
362 @return context of extracted docstring (Pep257Context) |
|
363 """ |
|
364 moduleDocstring = self.__parseModuleDocstring(context.source()) |
|
365 if what.startswith('module'): |
|
366 return moduleDocstring |
|
367 if moduleDocstring: |
|
368 return moduleDocstring |
|
369 |
|
370 tokenGenerator = tokenize.generate_tokens( |
|
371 StringIO(context.ssource()).readline) |
|
372 try: |
|
373 kind = None |
|
374 while kind != tokenize.INDENT: |
|
375 kind, _, _, _, _ = next(tokenGenerator) |
|
376 kind, value, (line, char), _, _ = next(tokenGenerator) |
|
377 if kind == tokenize.STRING: # STRING after INDENT is a docstring |
|
378 return Pep257Context( |
|
379 value, context.start() + line - 1, "docstring") |
|
380 except StopIteration: |
|
381 pass |
|
382 |
|
383 return None |
|
384 |
|
385 def __parseTopLevel(self, keyword): |
|
386 """ |
|
387 Private method to extract top-level functions or classes. |
|
388 |
|
389 @param keyword keyword signaling what to extract (string) |
|
390 @return extracted function or class contexts (list of Pep257Context) |
|
391 """ |
|
392 self.__resetReadline() |
|
393 tokenGenerator = tokenize.generate_tokens(self.__readline) |
|
394 kind, value, char = None, None, None |
|
395 contexts = [] |
|
396 try: |
|
397 while True: |
|
398 start, end = None, None |
|
399 while not (kind == tokenize.NAME and |
|
400 value == keyword and |
|
401 char == 0): |
|
402 kind, value, (line, char), _, _ = next(tokenGenerator) |
|
403 start = line - 1, char |
|
404 while not (kind == tokenize.DEDENT and |
|
405 value == '' and |
|
406 char == 0): |
|
407 kind, value, (line, char), _, _ = next(tokenGenerator) |
|
408 end = line - 1, char |
|
409 contexts.append(Pep257Context( |
|
410 self.__source[start[0]:end[0]], start[0], keyword)) |
|
411 except StopIteration: |
|
412 return contexts |
|
413 |
|
414 def __parseFunctions(self): |
|
415 """ |
|
416 Private method to extract top-level functions. |
|
417 |
|
418 @return extracted function contexts (list of Pep257Context) |
|
419 """ |
|
420 if not self.__functionsCache: |
|
421 self.__functionsCache = self.__parseTopLevel('def') |
|
422 return self.__functionsCache |
|
423 |
|
424 def __parseClasses(self): |
|
425 """ |
|
426 Private method to extract top-level classes. |
|
427 |
|
428 @return extracted class contexts (list of Pep257Context) |
|
429 """ |
|
430 if not self.__classesCache: |
|
431 self.__classesCache = self.__parseTopLevel('class') |
|
432 return self.__classesCache |
|
433 |
|
434 def __skipIndentedBlock(self, tokenGenerator): |
|
435 """ |
|
436 Private method to skip over an indented block of source code. |
|
437 |
|
438 @param tokenGenerator token generator |
|
439 @return last token of the indented block |
|
440 """ |
|
441 kind, value, start, end, raw = next(tokenGenerator) |
|
442 while kind != tokenize.INDENT: |
|
443 kind, value, start, end, raw = next(tokenGenerator) |
|
444 indent = 1 |
|
445 for kind, value, start, end, raw in tokenGenerator: |
|
446 if kind == tokenize.INDENT: |
|
447 indent += 1 |
|
448 elif kind == tokenize.DEDENT: |
|
449 indent -= 1 |
|
450 if indent == 0: |
|
451 return kind, value, start, end, raw |
|
452 |
|
453 def __parseMethods(self): |
|
454 """ |
|
455 Private method to extract methods of all classes. |
|
456 |
|
457 @return extracted method contexts (list of Pep257Context) |
|
458 """ |
|
459 if not self.__methodsCache: |
|
460 contexts = [] |
|
461 for classContext in self.__parseClasses(): |
|
462 tokenGenerator = tokenize.generate_tokens( |
|
463 StringIO(classContext.ssource()).readline) |
|
464 kind, value, char = None, None, None |
|
465 try: |
|
466 while True: |
|
467 start, end = None, None |
|
468 while not (kind == tokenize.NAME and value == 'def'): |
|
469 kind, value, (line, char), _, _ = \ |
|
470 next(tokenGenerator) |
|
471 start = line - 1, char |
|
472 kind, value, (line, char), _, _ = \ |
|
473 self.__skipIndentedBlock(tokenGenerator) |
|
474 end = line - 1, char |
|
475 startLine = classContext.start() + start[0] |
|
476 endLine = classContext.start() + end[0] |
|
477 contexts.append( |
|
478 Pep257Context(self.__source[startLine:endLine], |
|
479 startLine, "def")) |
|
480 except StopIteration: |
|
481 pass |
|
482 self.__methodsCache = contexts |
|
483 |
|
484 return self.__methodsCache |
|
485 |
|
486 def __parseContexts(self, kind): |
|
487 """ |
|
488 Private method to extract a context from the source. |
|
489 |
|
490 @param kind kind of context to extract (string) |
|
491 @return requested contexts (list of Pep257Context) |
|
492 """ |
|
493 if kind == 'moduleDocstring': |
|
494 return [Pep257Context(self.__source, 0, "module")] |
|
495 if kind == 'functionDocstring': |
|
496 return self.__parseFunctions() |
|
497 if kind == 'classDocstring': |
|
498 return self.__parseClasses() |
|
499 if kind == 'methodDocstring': |
|
500 return self.__parseMethods() |
|
501 if kind == 'defDocstring': |
|
502 return self.__parseFunctions() + self.__parseMethods() |
|
503 if kind == 'docstring': |
|
504 return ([Pep257Context(self.__source, 0, "module")] + |
|
505 self.__parseFunctions() + |
|
506 self.__parseClasses() + |
|
507 self.__parseMethods()) |
|
508 return [] # fall back |
|
509 |
|
510 ################################################################## |
|
511 ## Checking functionality below |
|
512 ################################################################## |
|
513 |
|
514 def __checkModulesDocstrings(self, docstringContext, context): |
|
515 """ |
|
516 Private method to check, if the module has a docstring. |
|
517 |
|
518 @param docstringContext docstring context (Pep257Context) |
|
519 @param context context of the docstring (Pep257Context) |
|
520 """ |
|
521 if docstringContext is None: |
|
522 self.__error(context.start(), 0, "D101") |
|
523 return |
|
524 |
|
525 docstring = docstringContext.ssource() |
|
526 if (not docstring or not docstring.strip() or |
|
527 not docstring.strip('\'"')): |
|
528 self.__error(context.start(), 0, "D101") |
|
529 |
|
530 def __checkFunctionDocstring(self, docstringContext, context): |
|
531 """ |
|
532 Private method to check, that all public functions and methods |
|
533 have a docstring. |
|
534 |
|
535 @param docstringContext docstring context (Pep257Context) |
|
536 @param context context of the docstring (Pep257Context) |
|
537 """ |
|
538 if self.__isScript: |
|
539 # assume nothing is exported |
|
540 return |
|
541 |
|
542 functionName = context.source()[0].lstrip().split()[1].split("(")[0] |
|
543 if functionName.startswith('_') and not functionName.endswith('__'): |
|
544 code = "D103" |
|
545 else: |
|
546 code = "D102" |
|
547 |
|
548 if docstringContext is None: |
|
549 self.__error(context.start(), 0, code) |
|
550 return |
|
551 |
|
552 docstring = docstringContext.ssource() |
|
553 if (not docstring or not docstring.strip() or |
|
554 not docstring.strip('\'"')): |
|
555 self.__error(context.start(), 0, code) |
|
556 |
|
557 def __checkClassDocstring(self, docstringContext, context): |
|
558 """ |
|
559 Private method to check, that all public functions and methods |
|
560 have a docstring. |
|
561 |
|
562 @param docstringContext docstring context (Pep257Context) |
|
563 @param context context of the docstring (Pep257Context) |
|
564 """ |
|
565 if self.__isScript: |
|
566 # assume nothing is exported |
|
567 return |
|
568 |
|
569 className = context.source()[0].lstrip().split()[1].split("(")[0] |
|
570 if className.startswith('_'): |
|
571 code = "D105" |
|
572 else: |
|
573 code = "D104" |
|
574 |
|
575 if docstringContext is None: |
|
576 self.__error(context.start(), 0, code) |
|
577 return |
|
578 |
|
579 docstring = docstringContext.ssource() |
|
580 if (not docstring or not docstring.strip() or |
|
581 not docstring.strip('\'"')): |
|
582 self.__error(context.start(), 0, code) |
|
583 |
|
584 def __checkTripleDoubleQuotes(self, docstringContext, context): |
|
585 """ |
|
586 Private method to check, that all docstrings are surrounded |
|
587 by triple double quotes. |
|
588 |
|
589 @param docstringContext docstring context (Pep257Context) |
|
590 @param context context of the docstring (Pep257Context) |
|
591 """ |
|
592 if docstringContext is None: |
|
593 return |
|
594 |
|
595 docstring = docstringContext.ssource().strip() |
|
596 if not docstring.startswith(('"""', 'r"""', 'u"""')): |
|
597 self.__error(docstringContext.start(), 0, "D111") |
|
598 |
|
599 def __checkBackslashes(self, docstringContext, context): |
|
600 """ |
|
601 Private method to check, that all docstrings containing |
|
602 backslashes are surrounded by raw triple double quotes. |
|
603 |
|
604 @param docstringContext docstring context (Pep257Context) |
|
605 @param context context of the docstring (Pep257Context) |
|
606 """ |
|
607 if docstringContext is None: |
|
608 return |
|
609 |
|
610 docstring = docstringContext.ssource().strip() |
|
611 if "\\" in docstring and not docstring.startswith('r"""'): |
|
612 self.__error(docstringContext.start(), 0, "D112") |
|
613 |
|
614 def __checkUnicode(self, docstringContext, context): |
|
615 """ |
|
616 Private method to check, that all docstrings containing unicode |
|
617 characters are surrounded by unicode triple double quotes. |
|
618 |
|
619 @param docstringContext docstring context (Pep257Context) |
|
620 @param context context of the docstring (Pep257Context) |
|
621 """ |
|
622 if docstringContext is None: |
|
623 return |
|
624 |
|
625 docstring = docstringContext.ssource().strip() |
|
626 if not docstring.startswith('u"""') and \ |
|
627 any(ord(char) > 127 for char in docstring): |
|
628 self.__error(docstringContext.start(), 0, "D113") |
|
629 |
|
630 def __checkOneLiner(self, docstringContext, context): |
|
631 """ |
|
632 Private method to check, that one-liner docstrings fit on |
|
633 one line with quotes. |
|
634 |
|
635 @param docstringContext docstring context (Pep257Context) |
|
636 @param context context of the docstring (Pep257Context) |
|
637 """ |
|
638 if docstringContext is None: |
|
639 return |
|
640 |
|
641 lines = docstringContext.source() |
|
642 if len(lines) > 1: |
|
643 nonEmptyLines = [l for l in lines if l.strip().strip('\'"')] |
|
644 if len(nonEmptyLines) == 1: |
|
645 self.__error(docstringContext.start(), 0, "D121") |
|
646 |
|
647 def __checkIndent(self, docstringContext, context): |
|
648 """ |
|
649 Private method to check, that docstrings are properly indented. |
|
650 |
|
651 @param docstringContext docstring context (Pep257Context) |
|
652 @param context context of the docstring (Pep257Context) |
|
653 """ |
|
654 if docstringContext is None: |
|
655 return |
|
656 |
|
657 lines = docstringContext.source() |
|
658 if len(lines) == 1: |
|
659 return |
|
660 |
|
661 nonEmptyLines = [l.rstrip() for l in lines[1:] if l.strip()] |
|
662 if not nonEmptyLines: |
|
663 return |
|
664 |
|
665 indent = min([len(l) - len(l.strip()) for l in nonEmptyLines]) |
|
666 if context.contextType() == "module": |
|
667 expectedIndent = 0 |
|
668 else: |
|
669 expectedIndent = len(context.indent()) + 4 |
|
670 if indent != expectedIndent: |
|
671 self.__error(docstringContext.start(), 0, "D122") |
|
672 |
|
673 def __checkEndsWithPeriod(self, docstringContext, context): |
|
674 """ |
|
675 Private method to check, that docstring summaries end with a period. |
|
676 |
|
677 @param docstringContext docstring context (Pep257Context) |
|
678 @param context context of the docstring (Pep257Context) |
|
679 """ |
|
680 if docstringContext is None: |
|
681 return |
|
682 |
|
683 summary, lineNumber = self.__getSummaryLine(docstringContext) |
|
684 if not summary.endswith("."): |
|
685 self.__error(docstringContext.start() + lineNumber, 0, "D131") |
|
686 |
|
687 def __checkImperativeMood(self, docstringContext, context): |
|
688 """ |
|
689 Private method to check, that docstring summaries are in |
|
690 imperative mood. |
|
691 |
|
692 @param docstringContext docstring context (Pep257Context) |
|
693 @param context context of the docstring (Pep257Context) |
|
694 """ |
|
695 if docstringContext is None: |
|
696 return |
|
697 |
|
698 summary, lineNumber = self.__getSummaryLine(docstringContext) |
|
699 firstWord = summary.strip().split()[0] |
|
700 if firstWord.endswith("s") and not firstWord.endswith("ss"): |
|
701 self.__error(docstringContext.start() + lineNumber, 0, "D132") |
|
702 |
|
703 def __checkNoSignature(self, docstringContext, context): |
|
704 """ |
|
705 Private method to check, that docstring summaries don't repeat |
|
706 the function's signature. |
|
707 |
|
708 @param docstringContext docstring context (Pep257Context) |
|
709 @param context context of the docstring (Pep257Context) |
|
710 """ |
|
711 if docstringContext is None: |
|
712 return |
|
713 |
|
714 functionName = context.source()[0].lstrip().split()[1].split("(")[0] |
|
715 summary, lineNumber = self.__getSummaryLine(docstringContext) |
|
716 if functionName + "(" in summary.replace(" ", ""): |
|
717 self.__error(docstringContext.start() + lineNumber, 0, "D133") |
|
718 |
|
719 def __checkReturnType(self, docstringContext, context): |
|
720 """ |
|
721 Private method to check, that docstrings mention the return value type. |
|
722 |
|
723 @param docstringContext docstring context (Pep257Context) |
|
724 @param context context of the docstring (Pep257Context) |
|
725 """ |
|
726 if docstringContext is None or self.__isScript: |
|
727 return |
|
728 |
|
729 if "return" not in docstringContext.ssource().lower(): |
|
730 tokens = list( |
|
731 tokenize.generate_tokens(StringIO(context.ssource()).readline)) |
|
732 return_ = [tokens[i + 1][0] for i, token in enumerate(tokens) |
|
733 if token[1] == "return"] |
|
734 if (set(return_) - |
|
735 set([tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE]) != |
|
736 set([])): |
|
737 self.__error(docstringContext.end(), 0, "D134") |
|
738 |
|
739 def __checkNoBlankLineBefore(self, docstringContext, context): |
|
740 """ |
|
741 Private method to check, that function/method docstrings are not |
|
742 preceded by a blank line. |
|
743 |
|
744 @param docstringContext docstring context (Pep257Context) |
|
745 @param context context of the docstring (Pep257Context) |
|
746 """ |
|
747 if docstringContext is None or self.__isScript: |
|
748 return |
|
749 |
|
750 contextLines = context.source() |
|
751 cti = 0 |
|
752 while not contextLines[cti].strip().startswith( |
|
753 ('"""', 'r"""', 'u"""')): |
|
754 cti += 1 |
|
755 if not contextLines[cti - 1].strip(): |
|
756 self.__error(docstringContext.start(), 0, "D141") |
|
757 |
|
758 # D142: check_blank_before_after_class |
|
759 ##def check_blank_before_after_class(class_docstring, context, is_script): |
|
760 ## """Class docstring should have 1 blank line around them. |
|
761 ## |
|
762 ## Insert a blank line before and after all docstrings (one-line or |
|
763 ## multi-line) that document a class -- generally speaking, the class's |
|
764 ## methods are separated from each other by a single blank line, and the |
|
765 ## docstring needs to be offset from the first method by a blank line; |
|
766 ## for symmetry, put a blank line between the class header and the |
|
767 ## docstring. |
|
768 ## |
|
769 ## """ |
|
770 ## if not class_docstring: |
|
771 ## return |
|
772 ## before, after = context.split(class_docstring)[:2] |
|
773 ## before_blanks = [not line.strip() for line in before.split('\n')] |
|
774 ## after_blanks = [not line.strip() for line in after.split('\n')] |
|
775 ## if before_blanks[-3:] != [False, True, True]: |
|
776 ## return True |
|
777 ## if not all(after_blanks) and after_blanks[:3] != [True, True, False]: |
|
778 ## return True |
|
779 |
|
780 # D143: check_blank_after_summary |
|
781 ##def check_blank_after_summary(docstring, context, is_script): |
|
782 ## """Blank line missing after one-line summary. |
|
783 ## |
|
784 ## Multi-line docstrings consist of a summary line just like a one-line |
|
785 ## docstring, followed by a blank line, followed by a more elaborate |
|
786 ## description. The summary line may be used by automatic indexing tools; |
|
787 ## it is important that it fits on one line and is separated from the |
|
788 ## rest of the docstring by a blank line. |
|
789 ## |
|
790 ## """ |
|
791 ## if not docstring: |
|
792 ## return |
|
793 ## lines = eval(docstring).split('\n') |
|
794 ## if len(lines) > 1: |
|
795 ## (summary_line, line_number) = get_summary_line_info(docstring) |
|
796 ## if len(lines) <= (line_number+1) or lines[line_number+1].strip() != '': |
|
797 ## return True |
|
798 |
|
799 # D144: check_blank_after_last_paragraph |
|
800 ##def check_blank_after_last_paragraph(docstring, context, is_script): |
|
801 ## """Multiline docstring should end with 1 blank line. |
|
802 ## |
|
803 ## The BDFL recommends inserting a blank line between the last |
|
804 ## paragraph in a multi-line docstring and its closing quotes, |
|
805 ## placing the closing quotes on a line by themselves. |
|
806 ## |
|
807 ## """ |
|
808 ## if (not docstring) or len(eval(docstring).split('\n')) == 1: |
|
809 ## return |
|
810 ## blanks = [not line.strip() for line in eval(docstring).split('\n')] |
|
811 ## if blanks[-3:] != [False, True, True]: |
|
812 ## return True |