Plugins/CheckerPlugins/Pep8/Pep257Checker.py

changeset 2928
4f74d3f595ce
parent 2925
04896af1b0e1
child 2929
28ab0bc63d69
equal deleted inserted replaced
2927:f36b757378f1 2928:4f74d3f595ce
17 from StringIO import StringIO # __IGNORE_EXCEPTION__ 17 from StringIO import StringIO # __IGNORE_EXCEPTION__
18 except ImportError: 18 except ImportError:
19 # Python 3 19 # Python 3
20 from io import StringIO # __IGNORE_WARNING__ 20 from io import StringIO # __IGNORE_WARNING__
21 import tokenize 21 import tokenize
22 import ast
22 23
23 from PyQt4.QtCore import QT_TRANSLATE_NOOP, QCoreApplication 24 from PyQt4.QtCore import QT_TRANSLATE_NOOP, QCoreApplication
24 25
25 26
26 class Pep257Context(object): 27 class Pep257Context(object):
106 "D101", "D102", "D103", "D104", "D105", 107 "D101", "D102", "D103", "D104", "D105",
107 "D111", "D112", "D113", 108 "D111", "D112", "D113",
108 "D121", "D122", 109 "D121", "D122",
109 "D131", "D132", "D133", "D134", 110 "D131", "D132", "D133", "D134",
110 "D141", "D142", "D143", "D144", "D145", 111 "D141", "D142", "D143", "D144", "D145",
112
113 "D203", "D205",
114 "D221",
115 "D231", "D234", "D235", "D236", "D237",
116 "D242", "D243", "D244", "D245",
111 ] 117 ]
112 118
113 Messages = { 119 Messages = {
114 "D101": QT_TRANSLATE_NOOP( 120 "D101": QT_TRANSLATE_NOOP(
115 "Pep257Checker", "module is missing a docstring"), 121 "Pep257Checker", "module is missing a docstring"),
158 "Pep257Checker", 164 "Pep257Checker",
159 "docstring summary is not followed by a blank line"), 165 "docstring summary is not followed by a blank line"),
160 "D145": QT_TRANSLATE_NOOP( 166 "D145": QT_TRANSLATE_NOOP(
161 "Pep257Checker", 167 "Pep257Checker",
162 "last paragraph of docstring is not followed by a blank line"), 168 "last paragraph of docstring is not followed by a blank line"),
169
170 "D203": QT_TRANSLATE_NOOP(
171 "Pep257Checker", "private function/method is missing a docstring"),
172 "D205": QT_TRANSLATE_NOOP(
173 "Pep257Checker", "private class is missing a docstring"),
174 "D221": QT_TRANSLATE_NOOP(
175 "Pep257Checker", "one-liner docstring not on three lines"),
176 "D231": QT_TRANSLATE_NOOP(
177 "Pep257Checker", "docstring summary does not end with a period"),
178 "D234": QT_TRANSLATE_NOOP(
179 "Pep257Checker", "docstring does not contain a @return line"),
180 "D235": QT_TRANSLATE_NOOP(
181 "Pep257Checker",
182 "docstring does not contain enough @param/@keyparam lines"),
183 "D236": QT_TRANSLATE_NOOP(
184 "Pep257Checker",
185 "docstring contains too many @param/@keyparam lines"),
186 "D237": QT_TRANSLATE_NOOP(
187 "Pep257Checker", "order of @param/@keyparam lines does"
188 " not match the function/method signature"),
189 "D242": QT_TRANSLATE_NOOP(
190 "Pep257Checker", "class docstring is preceded by a blank line"),
191 "D243": QT_TRANSLATE_NOOP(
192 "Pep257Checker", "class docstring is followed by a blank line"),
193 "D244": QT_TRANSLATE_NOOP(
194 "Pep257Checker",
195 "docstring summary is not followed by a blank line"),
196 "D245": QT_TRANSLATE_NOOP(
197 "Pep257Checker",
198 "last paragraph of docstring is followed by a blank line"),
163 } 199 }
164 200
165 def __init__(self, source, filename, select, ignore, expected, repeat, 201 def __init__(self, source, filename, select, ignore, expected, repeat,
166 maxLineLength=79): 202 maxLineLength=79, docType="pep257"):
167 """ 203 """
168 Constructor (according to 'extended' pep8.py API) 204 Constructor
169 205
170 @param source source code to be checked (list of string) 206 @param source source code to be checked (list of string)
171 @param filename name of the source file (string) 207 @param filename name of the source file (string)
172 @param select list of selected codes (list of string) 208 @param select list of selected codes (list of string)
173 @param ignore list of codes to be ignored (list of string) 209 @param ignore list of codes to be ignored (list of string)
174 @param expected list of expected codes (list of string) 210 @param expected list of expected codes (list of string)
175 @param repeat flag indicating to report each occurrence of a code 211 @param repeat flag indicating to report each occurrence of a code
176 (boolean) 212 (boolean)
177 @param maxLineLength allowed line length (integer) 213 @keyparam maxLineLength allowed line length (integer)
178 """ 214 @keyparam docType type of the documentation strings
215 (string, one of 'eric' or 'pep257')
216 """
217 assert docType in ("eric", "pep257")
218
179 self.__select = tuple(select) 219 self.__select = tuple(select)
180 self.__ignore = tuple(ignore) 220 self.__ignore = tuple(ignore)
181 self.__expected = expected[:] 221 self.__expected = expected[:]
182 self.__repeat = repeat 222 self.__repeat = repeat
183 self.__maxLineLength = maxLineLength 223 self.__maxLineLength = maxLineLength
224 self.__docType = docType
184 self.__filename = filename 225 self.__filename = filename
185 self.__source = source[:] 226 self.__source = source[:]
186 self.__isScript = self.__source[0].startswith('#!') 227 self.__isScript = self.__source[0].startswith('#!')
187 228
188 # statistics counters 229 # statistics counters
201 self.__keywords = [ 242 self.__keywords = [
202 'moduleDocstring', 'functionDocstring', 243 'moduleDocstring', 'functionDocstring',
203 'classDocstring', 'methodDocstring', 244 'classDocstring', 'methodDocstring',
204 'defDocstring', 'docstring' 245 'defDocstring', 'docstring'
205 ] 246 ]
206 self.__checkersWithCodes = { 247 if self.__docType == "pep257":
207 "moduleDocstring": [ 248 checkersWithCodes = {
208 (self.__checkModulesDocstrings, ("D101",)), 249 "moduleDocstring": [
209 ], 250 (self.__checkModulesDocstrings, ("D101",)),
210 "functionDocstring": [ 251 ],
211 ], 252 "functionDocstring": [
212 "classDocstring": [ 253 ],
213 (self.__checkClassDocstring, ("D104", "D105")), 254 "classDocstring": [
214 (self.__checkBlankBeforeAndAfterClass, ("D142", "D143")), 255 (self.__checkClassDocstring, ("D104", "D105")),
215 ], 256 (self.__checkBlankBeforeAndAfterClass, ("D142", "D143")),
216 "methodDocstring": [ 257 ],
217 ], 258 "methodDocstring": [
218 "defDocstring": [ 259 ],
219 (self.__checkFunctionDocstring, ("D102", "D103")), 260 "defDocstring": [
220 (self.__checkImperativeMood, ("D132",)), 261 (self.__checkFunctionDocstring, ("D102", "D103")),
221 (self.__checkNoSignature, ("D133",)), 262 (self.__checkImperativeMood, ("D132",)),
222 (self.__checkReturnType, ("D134",)), 263 (self.__checkNoSignature, ("D133",)),
223 (self.__checkNoBlankLineBefore, ("D141",)), 264 (self.__checkReturnType, ("D134",)),
224 ], 265 (self.__checkNoBlankLineBefore, ("D141",)),
225 "docstring": [ 266 ],
226 (self.__checkTripleDoubleQuotes, ("D111",)), 267 "docstring": [
227 (self.__checkBackslashes, ("D112",)), 268 (self.__checkTripleDoubleQuotes, ("D111",)),
228 (self.__checkUnicode, ("D113",)), 269 (self.__checkBackslashes, ("D112",)),
229 (self.__checkOneLiner, ("D121",)), 270 (self.__checkUnicode, ("D113",)),
230 (self.__checkIndent, ("D122",)), 271 (self.__checkOneLiner, ("D121",)),
231 (self.__checkEndsWithPeriod, ("D131",)), 272 (self.__checkIndent, ("D122",)),
232 (self.__checkBlankAfterSummary, ("D144",)), 273 (self.__checkEndsWithPeriod, ("D131",)),
233 (self.__checkBlankAfterLastParagraph, ("D145",)), 274 (self.__checkBlankAfterSummary, ("D144",)),
234 ], 275 (self.__checkBlankAfterLastParagraph, ("D145",)),
235 } 276 ],
277 }
278 elif self.__docType == "eric":
279 checkersWithCodes = {
280 "moduleDocstring": [
281 (self.__checkModulesDocstrings, ("D101",)),
282 ],
283 "functionDocstring": [
284 ],
285 "classDocstring": [
286 (self.__checkClassDocstring, ("D104", "D205")),
287 (self.__checkEricNoBlankBeforeAndAfterClass,
288 ("D242", "D243")),
289 ],
290 "methodDocstring": [
291 ],
292 "defDocstring": [
293 (self.__checkFunctionDocstring, ("D102", "D203")),
294 (self.__checkImperativeMood, ("D132",)),
295 (self.__checkNoSignature, ("D133",)),
296 (self.__checkEricReturn, ("D234",)),
297 (self.__checkEricFunctionArguments,
298 ("D235", "D236", "D237")),
299 (self.__checkNoBlankLineBefore, ("D141",)),
300 ],
301 "docstring": [
302 (self.__checkTripleDoubleQuotes, ("D111",)),
303 (self.__checkBackslashes, ("D112",)),
304 (self.__checkUnicode, ("D113",)),
305 (self.__checkEricOneLiner, ("D221",)),
306 (self.__checkIndent, ("D122",)),
307 (self.__checkEricEndsWithPeriod, ("D231",)),
308 (self.__checkEricBlankAfterSummary, ("D244",)),
309 (self.__checkEricNBlankAfterLastParagraph, ("D245",)),
310 ],
311 }
236 312
237 self.__checkers = {} 313 self.__checkers = {}
238 for key, checkers in self.__checkersWithCodes.items(): 314 for key, checkers in checkersWithCodes.items():
239 for checker, codes in checkers: 315 for checker, codes in checkers:
240 if any(not (code and self.__ignoreCode(code)) 316 if any(not (code and self.__ignoreCode(code))
241 for code in codes): 317 for code in codes):
242 if key not in self.__checkers: 318 if key not in self.__checkers:
243 self.__checkers[key] = [] 319 self.__checkers[key] = []
275 return 351 return
276 352
277 if code and (self.counters[code] == 1 or self.__repeat): 353 if code and (self.counters[code] == 1 or self.__repeat):
278 if code in Pep257Checker.Codes: 354 if code in Pep257Checker.Codes:
279 text = self.getMessage(code, *args) 355 text = self.getMessage(code, *args)
356 else:
357 text = code + " " + QCoreApplication.translate("Pep257Checker",
358 "no message for this code defined")
280 # record the issue with one based line number 359 # record the issue with one based line number
281 self.errors.append((self.__filename, lineNumber + 1, offset, text)) 360 self.errors.append((self.__filename, lineNumber + 1, offset, text))
282 361
283 def getMessage(self, code, *args): 362 def getMessage(self, code, *args):
284 """ 363 """
348 .strip()) 427 .strip())
349 428
350 if len(lines) == 1 or len(line) > 0: 429 if len(lines) == 1 or len(line) > 0:
351 return line, 0 430 return line, 0
352 return lines[1].strip().replace('"""', "").replace("'''", ""), 1 431 return lines[1].strip().replace('"""', "").replace("'''", ""), 1
432
433 def __getSummaryLines(self, docstringContext):
434 """
435 Private method to extract the summary lines.
436
437 @param docstringContext docstring context (Pep257Context)
438 @return summary lines (list of string) and the line it was found on
439 (integer)
440 """
441 summaries = []
442 lines = docstringContext.source()
443
444 line0 = (lines[0]
445 .replace('r"""', "", 1)
446 .replace('u"""', "", 1)
447 .replace('"""', "")
448 .replace("r'''", "", 1)
449 .replace("u'''", "", 1)
450 .replace("'''", "")
451 .strip())
452 if len(lines) > 1:
453 line1 = lines[1].strip().replace('"""', "").replace("'''", "")
454 else:
455 line1 = ""
456 if len(lines) > 2:
457 line2 = lines[2].strip().replace('"""', "").replace("'''", "")
458 else:
459 line2 = ""
460 if line0:
461 lineno = 0
462 summaries.append(line0)
463 if not line0.endswith(".") and line1:
464 # two line summary
465 summaries.append(line1)
466 elif line1:
467 lineno = 1
468 summaries.append(line1)
469 if not line1.endswith(".") and line2:
470 # two line summary
471 summaries.append(line2)
472 else:
473 lineno = 2
474 summaries.append(line2)
475 return summaries, lineno
476
477 def __getArgNames(self, node):
478 """
479 Private method to get the argument names of a function node.
480
481 @param node AST node to extract arguments names from
482 @return list of argument names (list of string)
483 """
484 arguments = []
485 arguments.extend([arg.arg for arg in node.args.args])
486 if node.args.vararg is not None:
487 arguments.append(node.args.vararg)
488 arguments.extend([arg.arg for arg in node.args.kwonlyargs])
489 if node.args.kwarg is not None:
490 arguments.append(node.args.kwarg)
491 return arguments
353 492
354 ################################################################## 493 ##################################################################
355 ## Parsing functionality below 494 ## Parsing functionality below
356 ################################################################## 495 ##################################################################
357 496
523 self.__parseClasses() + 662 self.__parseClasses() +
524 self.__parseMethods()) 663 self.__parseMethods())
525 return [] # fall back 664 return [] # fall back
526 665
527 ################################################################## 666 ##################################################################
528 ## Checking functionality below 667 ## Checking functionality below (PEP-257)
529 ################################################################## 668 ##################################################################
530 669
531 def __checkModulesDocstrings(self, docstringContext, context): 670 def __checkModulesDocstrings(self, docstringContext, context):
532 """ 671 """
533 Private method to check, if the module has a docstring. 672 Private method to check, if the module has a docstring.
556 # assume nothing is exported 695 # assume nothing is exported
557 return 696 return
558 697
559 functionName = context.source()[0].lstrip().split()[1].split("(")[0] 698 functionName = context.source()[0].lstrip().split()[1].split("(")[0]
560 if functionName.startswith('_') and not functionName.endswith('__'): 699 if functionName.startswith('_') and not functionName.endswith('__'):
561 code = "D103" 700 if self.__docType == "eric":
701 code = "D203"
702 else:
703 code = "D103"
562 else: 704 else:
563 code = "D102" 705 code = "D102"
564 706
565 if docstringContext is None: 707 if docstringContext is None:
566 self.__error(context.start(), 0, code) 708 self.__error(context.start(), 0, code)
583 # assume nothing is exported 725 # assume nothing is exported
584 return 726 return
585 727
586 className = context.source()[0].lstrip().split()[1].split("(")[0] 728 className = context.source()[0].lstrip().split()[1].split("(")[0]
587 if className.startswith('_'): 729 if className.startswith('_'):
588 code = "D105" 730 if self.__docType == "eric":
731 code = "D205"
732 else:
733 code = "D105"
589 else: 734 else:
590 code = "D104" 735 code = "D104"
591 736
592 if docstringContext is None: 737 if docstringContext is None:
593 self.__error(context.start(), 0, code) 738 self.__error(context.start(), 0, code)
843 if docstrings[lineNumber + 1].strip(): 988 if docstrings[lineNumber + 1].strip():
844 self.__error(docstringContext.start() + lineNumber, 0, "D144") 989 self.__error(docstringContext.start() + lineNumber, 0, "D144")
845 990
846 def __checkBlankAfterLastParagraph(self, docstringContext, context): 991 def __checkBlankAfterLastParagraph(self, docstringContext, context):
847 """ 992 """
848 Private method to check, that docstring summaries are followed 993 Private method to check, that the last paragraph of docstrings is
849 by a blank line. 994 followed by a blank line.
850 995
851 @param docstringContext docstring context (Pep257Context) 996 @param docstringContext docstring context (Pep257Context)
852 @param context context of the docstring (Pep257Context) 997 @param context context of the docstring (Pep257Context)
853 """ 998 """
854 if docstringContext is None: 999 if docstringContext is None:
859 # correct/invalid one-liner 1004 # correct/invalid one-liner
860 return 1005 return
861 1006
862 if docstrings[-2].strip(): 1007 if docstrings[-2].strip():
863 self.__error(docstringContext.end(), 0, "D145") 1008 self.__error(docstringContext.end(), 0, "D145")
1009
1010 ##################################################################
1011 ## Checking functionality below (eric specific ones)
1012 ##################################################################
1013
1014 def __checkEricOneLiner(self, docstringContext, context):
1015 """
1016 Private method to check, that one-liner docstrings are on
1017 three lines (quotes, docstring, quotes).
1018
1019 @param docstringContext docstring context (Pep257Context)
1020 @param context context of the docstring (Pep257Context)
1021 """
1022 if docstringContext is None:
1023 return
1024
1025 lines = docstringContext.source()
1026 if len(lines) != 3:
1027 nonEmptyLines = [l for l in lines if l.strip().strip('\'"')]
1028 if len(nonEmptyLines) == 1:
1029 self.__error(docstringContext.start(), 0, "D221")
1030
1031 def __checkEricEndsWithPeriod(self, docstringContext, context):
1032 """
1033 Private method to check, that docstring summaries end with a period.
1034
1035 @param docstringContext docstring context (Pep257Context)
1036 @param context context of the docstring (Pep257Context)
1037 """
1038 if docstringContext is None:
1039 return
1040
1041 summaryLines, lineNumber = self.__getSummaryLines(docstringContext)
1042 summary = " ".join([s.strip() for s in summaryLines if s])
1043 if not summary.endswith(".") and \
1044 not summary.split(None, 1)[0].lower() == "constructor":
1045 self.__error(
1046 docstringContext.start() + lineNumber + len(summaryLines) - 1,
1047 0, "D231")
1048
1049 def __checkEricReturn(self, docstringContext, context):
1050 """
1051 Private method to check, that docstrings contain an @return line.
1052
1053 @param docstringContext docstring context (Pep257Context)
1054 @param context context of the docstring (Pep257Context)
1055 """
1056 if docstringContext is None or self.__isScript:
1057 return
1058
1059 if "@return" not in docstringContext.ssource():
1060 tokens = list(
1061 tokenize.generate_tokens(StringIO(context.ssource()).readline))
1062 return_ = [tokens[i + 1][0] for i, token in enumerate(tokens)
1063 if token[1] == "return"]
1064 if (set(return_) -
1065 set([tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE]) !=
1066 set([])):
1067 self.__error(docstringContext.end(), 0, "D234")
1068
1069 def __checkEricFunctionArguments(self, docstringContext, context):
1070 """
1071 Private method to check, that docstrings contain an @param line
1072 for each argument.
1073
1074 @param docstringContext docstring context (Pep257Context)
1075 @param context context of the docstring (Pep257Context)
1076 """
1077 if docstringContext is None or self.__isScript:
1078 return
1079
1080 try:
1081 tree = ast.parse(context.ssource())
1082 except (SyntaxError, TypeError):
1083 return
1084 if (isinstance(tree, ast.Module) and len(tree.body) == 1 and
1085 isinstance(tree.body[0], ast.FunctionDef)):
1086 functionDef = tree.body[0]
1087 argNames = self.__getArgNames(functionDef)
1088 if "self" in argNames:
1089 argNames.remove("self")
1090 if "cls" in argNames:
1091 argNames.remove("cls")
1092
1093 docstring = docstringContext.ssource()
1094 if (docstring.count("@param") + docstring.count("@keyparam") <
1095 len(argNames)):
1096 self.__error(docstringContext.end(), 0, "D235")
1097 elif (docstring.count("@param") + docstring.count("@keyparam") >
1098 len(argNames)):
1099 self.__error(docstringContext.end(), 0, "D236")
1100 # TODO: check order (args, vararg, kwonlyargs, kwarg
1101
1102 def __checkEricBlankAfterSummary(self, docstringContext, context):
1103 """
1104 Private method to check, that docstring summaries are followed
1105 by a blank line.
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 docstrings = docstringContext.source()
1114 if len(docstrings) <= 3:
1115 # correct/invalid one-liner
1116 return
1117
1118 summaryLines, lineNumber = self.__getSummaryLines(docstringContext)
1119 if len(docstrings) > lineNumber + len(summaryLines) - 1:
1120 if docstrings[lineNumber + len(summaryLines)].strip():
1121 self.__error(docstringContext.start() + lineNumber, 0, "D244")
1122
1123 def __checkEricNoBlankBeforeAndAfterClass(self, docstringContext, context):
1124 """
1125 Private method to check, that class docstrings have no blank line
1126 around them.
1127
1128 @param docstringContext docstring context (Pep257Context)
1129 @param context context of the docstring (Pep257Context)
1130 """
1131 if docstringContext is None:
1132 return
1133
1134 contextLines = context.source()
1135 cti = 0
1136 while cti < len(contextLines) and \
1137 not contextLines[cti].strip().startswith(
1138 ('"""', 'r"""', 'u"""', "'''", "r'''", "u'''")):
1139 cti += 1
1140 if cti == len(contextLines):
1141 return
1142
1143 start = cti
1144 if contextLines[cti].strip() in (
1145 '"""', 'r"""', 'u"""', "'''", "r'''", "u'''"):
1146 # it is a multi line docstring
1147 cti += 1
1148
1149 while cti < len(contextLines) and \
1150 not contextLines[cti].strip().endswith(('"""', "'''")):
1151 cti += 1
1152 end = cti
1153 if cti == len(contextLines):
1154 return
1155
1156 if not contextLines[start - 1].strip():
1157 self.__error(docstringContext.start(), 0, "D242")
1158 if not contextLines[end + 1].strip():
1159 self.__error(docstringContext.end(), 0, "D243")
1160
1161 def __checkEricNBlankAfterLastParagraph(self, docstringContext, context):
1162 """
1163 Private method to check, that the last paragraph of docstrings is
1164 not followed by a blank line.
1165
1166 @param docstringContext docstring context (Pep257Context)
1167 @param context context of the docstring (Pep257Context)
1168 """
1169 if docstringContext is None:
1170 return
1171
1172 docstrings = docstringContext.source()
1173 if len(docstrings) <= 3:
1174 # correct/invalid one-liner
1175 return
1176
1177 if not docstrings[-2].strip():
1178 self.__error(docstringContext.end(), 0, "D245")

eric ide

mercurial