12 # pep257.py (version 0.2.4). |
12 # pep257.py (version 0.2.4). |
13 # |
13 # |
14 |
14 |
15 import tokenize |
15 import tokenize |
16 import ast |
16 import ast |
17 import sys |
|
18 from io import StringIO |
17 from io import StringIO |
|
18 import contextlib |
19 |
19 |
20 try: |
20 try: |
21 ast.AsyncFunctionDef # __IGNORE_EXCEPTION__ |
21 ast.AsyncFunctionDef # __IGNORE_EXCEPTION__ |
22 except AttributeError: |
22 except AttributeError: |
23 ast.AsyncFunctionDef = ast.FunctionDef |
23 ast.AsyncFunctionDef = ast.FunctionDef |
24 |
24 |
25 |
25 |
26 class DocStyleContext(object): |
26 class DocStyleContext: |
27 """ |
27 """ |
28 Class implementing the source context. |
28 Class implementing the source context. |
29 """ |
29 """ |
30 def __init__(self, source, startLine, contextType): |
30 def __init__(self, source, startLine, contextType): |
31 """ |
31 """ |
115 @rtype str |
115 @rtype str |
116 """ |
116 """ |
117 return self.__special |
117 return self.__special |
118 |
118 |
119 |
119 |
120 class DocStyleChecker(object): |
120 class DocStyleChecker: |
121 """ |
121 """ |
122 Class implementing a checker for documentation string conventions. |
122 Class implementing a checker for documentation string conventions. |
123 """ |
123 """ |
124 Codes = [ |
124 Codes = [ |
125 "D101", "D102", "D103", "D104", "D105", |
125 "D101", "D102", "D103", "D104", "D105", |
133 "D231", "D232", "D234r", "D234y", "D235r", "D235y", "D236", "D237", |
133 "D231", "D232", "D234r", "D234y", "D235r", "D235y", "D236", "D237", |
134 "D238", "D239", |
134 "D238", "D239", |
135 "D242", "D243", "D244", "D245", "D246", "D247", |
135 "D242", "D243", "D244", "D245", "D246", "D247", |
136 "D250", "D251", "D252", "D253", |
136 "D250", "D251", "D252", "D253", |
137 "D260", "D261", "D262", "D263", |
137 "D260", "D261", "D262", "D263", |
138 |
|
139 "D901", |
|
140 ] |
138 ] |
141 |
139 |
142 def __init__(self, source, filename, select, ignore, expected, repeat, |
140 def __init__(self, source, filename, select, ignore, expected, repeat, |
143 maxLineLength=79, docType="pep257"): |
141 maxLineLength=79, docType="pep257"): |
144 """ |
142 """ |
305 "code": code, |
303 "code": code, |
306 "args": args, |
304 "args": args, |
307 } |
305 } |
308 ) |
306 ) |
309 |
307 |
310 def __reportInvalidSyntax(self): |
|
311 """ |
|
312 Private method to report a syntax error. |
|
313 """ |
|
314 exc_type, exc = sys.exc_info()[:2] |
|
315 if len(exc.args) > 1: |
|
316 offset = exc.args[1] |
|
317 if len(offset) > 2: |
|
318 offset = offset[1:3] |
|
319 else: |
|
320 offset = (1, 0) |
|
321 self.__error(offset[0] - 1, offset[1] or 0, |
|
322 'D901', exc_type.__name__, exc.args[0]) |
|
323 |
|
324 def __resetReadline(self): |
308 def __resetReadline(self): |
325 """ |
309 """ |
326 Private method to reset the internal readline function. |
310 Private method to reset the internal readline function. |
327 """ |
311 """ |
328 self.__lineNumber = 0 |
312 self.__lineNumber = 0 |
347 # don't do anything, if essential data is missing |
331 # don't do anything, if essential data is missing |
348 return |
332 return |
349 |
333 |
350 if not self.__checkers: |
334 if not self.__checkers: |
351 # don't do anything, if no codes were selected |
335 # don't do anything, if no codes were selected |
352 return |
|
353 |
|
354 try: |
|
355 ast.parse("".join(self.__source), self.__filename) |
|
356 except (SyntaxError, TypeError): |
|
357 self.__reportInvalidSyntax() |
|
358 return |
336 return |
359 |
337 |
360 for keyword in self.__keywords: |
338 for keyword in self.__keywords: |
361 if keyword in self.__checkers: |
339 if keyword in self.__checkers: |
362 for check in self.__checkers[keyword]: |
340 for check in self.__checkers[keyword]: |
403 .replace('"""', "") |
381 .replace('"""', "") |
404 .replace("r'''", "", 1) |
382 .replace("r'''", "", 1) |
405 .replace("u'''", "", 1) |
383 .replace("u'''", "", 1) |
406 .replace("'''", "") |
384 .replace("'''", "") |
407 .strip()) |
385 .strip()) |
408 if len(lines) > 1: |
386 line1 = ( |
409 line1 = lines[1].strip().replace('"""', "").replace("'''", "") |
387 lines[1].strip().replace('"""', "").replace("'''", "") |
410 else: |
388 if len(lines) > 1 else |
411 line1 = "" |
389 "" |
412 if len(lines) > 2: |
390 ) |
413 line2 = lines[2].strip().replace('"""', "").replace("'''", "") |
391 line2 = ( |
414 else: |
392 lines[2].strip().replace('"""', "").replace("'''", "") |
415 line2 = "" |
393 if len(lines) > 2 else |
|
394 "" |
|
395 ) |
416 if line0: |
396 if line0: |
417 lineno = 0 |
397 lineno = 0 |
418 summaries.append(line0) |
398 summaries.append(line0) |
419 if not line0.endswith(".") and line1: |
399 if not line0.endswith(".") and line1: |
420 # two line summary |
400 # two line summary |
485 if moduleDocstring: |
465 if moduleDocstring: |
486 return moduleDocstring |
466 return moduleDocstring |
487 |
467 |
488 tokenGenerator = tokenize.generate_tokens( |
468 tokenGenerator = tokenize.generate_tokens( |
489 StringIO(context.ssource()).readline) |
469 StringIO(context.ssource()).readline) |
490 try: |
470 with contextlib.suppress(StopIteration): |
491 kind = None |
471 kind = None |
492 while kind != tokenize.INDENT: |
472 while kind != tokenize.INDENT: |
493 kind, _, _, _, _ = next(tokenGenerator) |
473 kind, _, _, _, _ = next(tokenGenerator) |
494 kind, value, (line, char), _, _ = next(tokenGenerator) |
474 kind, value, (line, char), _, _ = next(tokenGenerator) |
495 if kind == tokenize.STRING: # STRING after INDENT is a docstring |
475 if kind == tokenize.STRING: # STRING after INDENT is a docstring |
496 return DocStyleContext( |
476 return DocStyleContext( |
497 value, context.start() + line - 1, "docstring") |
477 value, context.start() + line - 1, "docstring") |
498 except StopIteration: |
|
499 pass |
|
500 |
478 |
501 return None |
479 return None |
502 |
480 |
503 def __parseTopLevel(self, keyword): |
481 def __parseTopLevel(self, keyword): |
504 """ |
482 """ |
580 contexts = [] |
558 contexts = [] |
581 for classContext in self.__parseClasses(): |
559 for classContext in self.__parseClasses(): |
582 tokenGenerator = tokenize.generate_tokens( |
560 tokenGenerator = tokenize.generate_tokens( |
583 StringIO(classContext.ssource()).readline) |
561 StringIO(classContext.ssource()).readline) |
584 kind, value, char = None, None, None |
562 kind, value, char = None, None, None |
585 try: |
563 with contextlib.suppress(StopIteration): |
586 while True: |
564 while True: |
587 start, end = None, None |
565 start, end = None, None |
588 while not (kind == tokenize.NAME and value == 'def'): |
566 while not (kind == tokenize.NAME and value == 'def'): |
589 kind, value, (line, char), _, _ = ( |
567 kind, value, (line, char), _, _ = ( |
590 next(tokenGenerator) |
568 next(tokenGenerator) |
609 self.__source[startLine - 1].strip() == |
587 self.__source[startLine - 1].strip() == |
610 "@classmethod" |
588 "@classmethod" |
611 ): |
589 ): |
612 context.setSpecial("classmethod") |
590 context.setSpecial("classmethod") |
613 contexts.append(context) |
591 contexts.append(context) |
614 except StopIteration: |
|
615 pass |
|
616 self.__methodsCache = contexts |
592 self.__methodsCache = contexts |
617 |
593 |
618 return self.__methodsCache |
594 return self.__methodsCache |
619 |
595 |
620 def __parseContexts(self, kind): |
596 def __parseContexts(self, kind): |
817 nonEmptyLines = [line.rstrip() for line in lines[1:] if line.strip()] |
793 nonEmptyLines = [line.rstrip() for line in lines[1:] if line.strip()] |
818 if not nonEmptyLines: |
794 if not nonEmptyLines: |
819 return |
795 return |
820 |
796 |
821 indent = min(len(line) - len(line.strip()) for line in nonEmptyLines) |
797 indent = min(len(line) - len(line.strip()) for line in nonEmptyLines) |
822 if context.contextType() == "module": |
798 expectedIndent = ( |
823 expectedIndent = 0 |
799 0 |
824 else: |
800 if context.contextType() == "module" else |
825 expectedIndent = len(context.indent()) + 4 |
801 len(context.indent()) + 4 |
|
802 ) |
826 if indent != expectedIndent: |
803 if indent != expectedIndent: |
827 self.__error(docstringContext.start(), 0, "D122") |
804 self.__error(docstringContext.start(), 0, "D122") |
828 |
805 |
829 def __checkSummary(self, docstringContext, context): |
806 def __checkSummary(self, docstringContext, context): |
830 """ |
807 """ |
884 |
861 |
885 functionName = context.source()[0].lstrip().split()[1].split("(")[0] |
862 functionName = context.source()[0].lstrip().split()[1].split("(")[0] |
886 summary, lineNumber = self.__getSummaryLine(docstringContext) |
863 summary, lineNumber = self.__getSummaryLine(docstringContext) |
887 if ( |
864 if ( |
888 functionName + "(" in summary.replace(" ", "") and |
865 functionName + "(" in summary.replace(" ", "") and |
889 not functionName + "()" in summary.replace(" ", "") |
866 functionName + "()" not in summary.replace(" ", "") |
890 ): |
867 ): |
891 # report only, if it is not an abbreviated form (i.e. function() ) |
868 # report only, if it is not an abbreviated form (i.e. function() ) |
892 self.__error(docstringContext.start() + lineNumber, 0, "D133") |
869 self.__error(docstringContext.start() + lineNumber, 0, "D133") |
893 |
870 |
894 def __checkReturnType(self, docstringContext, context): |
871 def __checkReturnType(self, docstringContext, context): |
993 if len(docstrings) <= 3: |
970 if len(docstrings) <= 3: |
994 # correct/invalid one-liner |
971 # correct/invalid one-liner |
995 return |
972 return |
996 |
973 |
997 summary, lineNumber = self.__getSummaryLine(docstringContext) |
974 summary, lineNumber = self.__getSummaryLine(docstringContext) |
998 if len(docstrings) > 2: |
975 if ( |
999 if docstrings[lineNumber + 1].strip(): |
976 len(docstrings) > 2 and |
1000 self.__error(docstringContext.start() + lineNumber, 0, "D144") |
977 docstrings[lineNumber + 1].strip() |
|
978 ): |
|
979 self.__error(docstringContext.start() + lineNumber, 0, "D144") |
1001 |
980 |
1002 def __checkBlankAfterLastParagraph(self, docstringContext, context): |
981 def __checkBlankAfterLastParagraph(self, docstringContext, context): |
1003 """ |
982 """ |
1004 Private method to check, that the last paragraph of docstrings is |
983 Private method to check, that the last paragraph of docstrings is |
1005 followed by a blank line. |
984 followed by a blank line. |
1055 summaryLines.pop(-1) |
1034 summaryLines.pop(-1) |
1056 summary = " ".join([s.strip() for s in summaryLines if s]) |
1035 summary = " ".join([s.strip() for s in summaryLines if s]) |
1057 if ( |
1036 if ( |
1058 summary and |
1037 summary and |
1059 not summary.endswith(".") and |
1038 not summary.endswith(".") and |
1060 not summary.split(None, 1)[0].lower() == "constructor" |
1039 summary.split(None, 1)[0].lower() != "constructor" |
1061 ): |
1040 ): |
1062 self.__error( |
1041 self.__error( |
1063 docstringContext.start() + lineNumber + |
1042 docstringContext.start() + lineNumber + |
1064 len(summaryLines) - 1, |
1043 len(summaryLines) - 1, |
1065 0, "D231") |
1044 0, "D231") |
1306 if len(docstrings) <= 3: |
1285 if len(docstrings) <= 3: |
1307 # correct/invalid one-liner |
1286 # correct/invalid one-liner |
1308 return |
1287 return |
1309 |
1288 |
1310 summaryLines, lineNumber = self.__getSummaryLines(docstringContext) |
1289 summaryLines, lineNumber = self.__getSummaryLines(docstringContext) |
1311 if len(docstrings) - 2 > lineNumber + len(summaryLines) - 1: |
1290 if ( |
1312 if docstrings[lineNumber + len(summaryLines)].strip(): |
1291 len(docstrings) - 2 > lineNumber + len(summaryLines) - 1 and |
1313 self.__error(docstringContext.start() + lineNumber, 0, "D246") |
1292 docstrings[lineNumber + len(summaryLines)].strip() |
|
1293 ): |
|
1294 self.__error(docstringContext.start() + lineNumber, 0, "D246") |
1314 |
1295 |
1315 def __checkEricNoBlankBeforeAndAfterClassOrFunction( |
1296 def __checkEricNoBlankBeforeAndAfterClassOrFunction( |
1316 self, docstringContext, context): |
1297 self, docstringContext, context): |
1317 """ |
1298 """ |
1318 Private method to check, that class and function/method docstrings |
1299 Private method to check, that class and function/method docstrings |