eric6/Plugins/CheckerPlugins/CodeStyleChecker/DocStyle/DocStyleChecker.py

branch
maintenance
changeset 8273
698ae46f40a4
parent 8176
31965986ecd1
parent 8259
2bbec88047dd
equal deleted inserted replaced
8190:fb0ef164f536 8273:698ae46f40a4
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

eric ide

mercurial