|
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 @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 @param and/or |
|
1152 @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 @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") |