104 "D101", "D102", "D103", "D104", "D105", |
106 "D101", "D102", "D103", "D104", "D105", |
105 "D111", "D112", "D113", |
107 "D111", "D112", "D113", |
106 "D121", "D122", |
108 "D121", "D122", |
107 "D131", "D132", "D133", "D134", |
109 "D131", "D132", "D133", "D134", |
108 "D141", "D142", "D143", "D144", "D145", |
110 "D141", "D142", "D143", "D144", "D145", |
|
111 |
|
112 "D203", "D205", |
|
113 "D221", |
|
114 "D231", "D234", "D235", "D236", "D237", "D238", |
|
115 "D242", "D243", "D244", "D245", |
109 ] |
116 ] |
110 |
117 |
111 def __init__(self, source, filename, select, ignore, expected, repeat, |
118 def __init__(self, source, filename, select, ignore, expected, repeat, |
112 maxLineLength=79): |
119 maxLineLength=79, docType="pep257"): |
113 """ |
120 """ |
114 Constructor (according to 'extended' pep8.py API) |
121 Constructor |
115 |
122 |
116 @param source source code to be checked (list of string) |
123 @param source source code to be checked (list of string) |
117 @param filename name of the source file (string) |
124 @param filename name of the source file (string) |
118 @param select list of selected codes (list of string) |
125 @param select list of selected codes (list of string) |
119 @param ignore list of codes to be ignored (list of string) |
126 @param ignore list of codes to be ignored (list of string) |
120 @param expected list of expected codes (list of string) |
127 @param expected list of expected codes (list of string) |
121 @param repeat flag indicating to report each occurrence of a code |
128 @param repeat flag indicating to report each occurrence of a code |
122 (boolean) |
129 (boolean) |
123 @param maxLineLength allowed line length (integer) |
130 @keyparam maxLineLength allowed line length (integer) |
124 """ |
131 @keyparam docType type of the documentation strings |
|
132 (string, one of 'eric' or 'pep257') |
|
133 """ |
|
134 assert docType in ("eric", "pep257") |
|
135 |
125 self.__select = tuple(select) |
136 self.__select = tuple(select) |
126 self.__ignore = tuple(ignore) |
137 self.__ignore = tuple(ignore) |
127 self.__expected = expected[:] |
138 self.__expected = expected[:] |
128 self.__repeat = repeat |
139 self.__repeat = repeat |
129 self.__maxLineLength = maxLineLength |
140 self.__maxLineLength = maxLineLength |
|
141 self.__docType = docType |
130 self.__filename = filename |
142 self.__filename = filename |
131 self.__source = source[:] |
143 self.__source = source[:] |
132 self.__isScript = self.__source[0].startswith('#!') |
144 self.__isScript = self.__source[0].startswith('#!') |
133 |
145 |
134 # statistics counters |
146 # statistics counters |
147 self.__keywords = [ |
159 self.__keywords = [ |
148 'moduleDocstring', 'functionDocstring', |
160 'moduleDocstring', 'functionDocstring', |
149 'classDocstring', 'methodDocstring', |
161 'classDocstring', 'methodDocstring', |
150 'defDocstring', 'docstring' |
162 'defDocstring', 'docstring' |
151 ] |
163 ] |
152 self.__checkersWithCodes = { |
164 if self.__docType == "pep257": |
153 "moduleDocstring": [ |
165 checkersWithCodes = { |
154 (self.__checkModulesDocstrings, ("D101",)), |
166 "moduleDocstring": [ |
155 ], |
167 (self.__checkModulesDocstrings, ("D101",)), |
156 "functionDocstring": [ |
168 ], |
157 ], |
169 "functionDocstring": [ |
158 "classDocstring": [ |
170 ], |
159 (self.__checkClassDocstring, ("D104", "D105")), |
171 "classDocstring": [ |
160 (self.__checkBlankBeforeAndAfterClass, ("D142", "D143")), |
172 (self.__checkClassDocstring, ("D104", "D105")), |
161 ], |
173 (self.__checkBlankBeforeAndAfterClass, ("D142", "D143")), |
162 "methodDocstring": [ |
174 ], |
163 ], |
175 "methodDocstring": [ |
164 "defDocstring": [ |
176 ], |
165 (self.__checkFunctionDocstring, ("D102", "D103")), |
177 "defDocstring": [ |
166 (self.__checkImperativeMood, ("D132",)), |
178 (self.__checkFunctionDocstring, ("D102", "D103")), |
167 (self.__checkNoSignature, ("D133",)), |
179 (self.__checkImperativeMood, ("D132",)), |
168 (self.__checkReturnType, ("D134",)), |
180 (self.__checkNoSignature, ("D133",)), |
169 (self.__checkNoBlankLineBefore, ("D141",)), |
181 (self.__checkReturnType, ("D134",)), |
170 ], |
182 (self.__checkNoBlankLineBefore, ("D141",)), |
171 "docstring": [ |
183 ], |
172 (self.__checkTripleDoubleQuotes, ("D111",)), |
184 "docstring": [ |
173 (self.__checkBackslashes, ("D112",)), |
185 (self.__checkTripleDoubleQuotes, ("D111",)), |
174 (self.__checkUnicode, ("D113",)), |
186 (self.__checkBackslashes, ("D112",)), |
175 (self.__checkOneLiner, ("D121",)), |
187 (self.__checkUnicode, ("D113",)), |
176 (self.__checkIndent, ("D122",)), |
188 (self.__checkOneLiner, ("D121",)), |
177 (self.__checkEndsWithPeriod, ("D131",)), |
189 (self.__checkIndent, ("D122",)), |
178 (self.__checkBlankAfterSummary, ("D144",)), |
190 (self.__checkEndsWithPeriod, ("D131",)), |
179 (self.__checkBlankAfterLastParagraph, ("D145",)), |
191 (self.__checkBlankAfterSummary, ("D144",)), |
180 ], |
192 (self.__checkBlankAfterLastParagraph, ("D145",)), |
181 } |
193 ], |
|
194 } |
|
195 elif self.__docType == "eric": |
|
196 checkersWithCodes = { |
|
197 "moduleDocstring": [ |
|
198 (self.__checkModulesDocstrings, ("D101",)), |
|
199 ], |
|
200 "functionDocstring": [ |
|
201 ], |
|
202 "classDocstring": [ |
|
203 (self.__checkClassDocstring, ("D104", "D205")), |
|
204 (self.__checkEricNoBlankBeforeAndAfterClass, |
|
205 ("D242", "D243")), |
|
206 ], |
|
207 "methodDocstring": [ |
|
208 ], |
|
209 "defDocstring": [ |
|
210 (self.__checkFunctionDocstring, ("D102", "D203")), |
|
211 (self.__checkImperativeMood, ("D132",)), |
|
212 (self.__checkNoSignature, ("D133",)), |
|
213 (self.__checkEricReturn, ("D234",)), |
|
214 (self.__checkEricFunctionArguments, |
|
215 ("D235", "D236", "D237", "D238")), |
|
216 (self.__checkNoBlankLineBefore, ("D141",)), |
|
217 ], |
|
218 "docstring": [ |
|
219 (self.__checkTripleDoubleQuotes, ("D111",)), |
|
220 (self.__checkBackslashes, ("D112",)), |
|
221 (self.__checkUnicode, ("D113",)), |
|
222 (self.__checkEricOneLiner, ("D221",)), |
|
223 (self.__checkIndent, ("D122",)), |
|
224 (self.__checkEricEndsWithPeriod, ("D231",)), |
|
225 (self.__checkEricBlankAfterSummary, ("D244",)), |
|
226 (self.__checkEricNBlankAfterLastParagraph, ("D245",)), |
|
227 ], |
|
228 } |
182 |
229 |
183 self.__checkers = {} |
230 self.__checkers = {} |
184 for key, checkers in self.__checkersWithCodes.items(): |
231 for key, checkers in checkersWithCodes.items(): |
185 for checker, codes in checkers: |
232 for checker, codes in checkers: |
186 if any(not (code and self.__ignoreCode(code)) |
233 if any(not (code and self.__ignoreCode(code)) |
187 for code in codes): |
234 for code in codes): |
188 if key not in self.__checkers: |
235 if key not in self.__checkers: |
189 self.__checkers[key] = [] |
236 self.__checkers[key] = [] |
276 .replace("'''", "") |
323 .replace("'''", "") |
277 .strip()) |
324 .strip()) |
278 |
325 |
279 if len(lines) == 1 or len(line) > 0: |
326 if len(lines) == 1 or len(line) > 0: |
280 return line, 0 |
327 return line, 0 |
281 return lines[1].strip(), 1 |
328 return lines[1].strip().replace('"""', "").replace("'''", ""), 1 |
|
329 |
|
330 def __getSummaryLines(self, docstringContext): |
|
331 """ |
|
332 Private method to extract the summary lines. |
|
333 |
|
334 @param docstringContext docstring context (Pep257Context) |
|
335 @return summary lines (list of string) and the line it was found on |
|
336 (integer) |
|
337 """ |
|
338 summaries = [] |
|
339 lines = docstringContext.source() |
|
340 |
|
341 line0 = (lines[0] |
|
342 .replace('r"""', "", 1) |
|
343 .replace('u"""', "", 1) |
|
344 .replace('"""', "") |
|
345 .replace("r'''", "", 1) |
|
346 .replace("u'''", "", 1) |
|
347 .replace("'''", "") |
|
348 .strip()) |
|
349 if len(lines) > 1: |
|
350 line1 = lines[1].strip().replace('"""', "").replace("'''", "") |
|
351 else: |
|
352 line1 = "" |
|
353 if len(lines) > 2: |
|
354 line2 = lines[2].strip().replace('"""', "").replace("'''", "") |
|
355 else: |
|
356 line2 = "" |
|
357 if line0: |
|
358 lineno = 0 |
|
359 summaries.append(line0) |
|
360 if not line0.endswith(".") and line1: |
|
361 # two line summary |
|
362 summaries.append(line1) |
|
363 elif line1: |
|
364 lineno = 1 |
|
365 summaries.append(line1) |
|
366 if not line1.endswith(".") and line2: |
|
367 # two line summary |
|
368 summaries.append(line2) |
|
369 else: |
|
370 lineno = 2 |
|
371 summaries.append(line2) |
|
372 return summaries, lineno |
|
373 |
|
374 if sys.version_info[0] < 3: |
|
375 def __getArgNames(self, node): |
|
376 """ |
|
377 Private method to get the argument names of a function node. |
|
378 |
|
379 @param node AST node to extract arguments names from |
|
380 @return tuple of two list of argument names, one for arguments |
|
381 and one for keyword arguments (tuple of list of string) |
|
382 """ |
|
383 def unpackArgs(args): |
|
384 """ |
|
385 Local helper function to unpack function argument names. |
|
386 |
|
387 @param args list of AST node arguments |
|
388 @return list of argument names (list of string) |
|
389 """ |
|
390 ret = [] |
|
391 for arg in args: |
|
392 if isinstance(arg, ast.Tuple): |
|
393 ret.extend(unpackArgs(arg.elts)) |
|
394 else: |
|
395 ret.append(arg.id) |
|
396 return ret |
|
397 |
|
398 arguments = unpackArgs(node.args.args) |
|
399 if node.args.vararg is not None: |
|
400 arguments.append(node.args.vararg) |
|
401 kwarguments = [] |
|
402 if node.args.kwarg is not None: |
|
403 kwarguments.append(node.args.kwarg) |
|
404 return arguments, kwarguments |
|
405 else: |
|
406 def __getArgNames(self, node): # __IGNORE_WARNING__ |
|
407 """ |
|
408 Private method to get the argument names of a function node. |
|
409 |
|
410 @param node AST node to extract arguments names from |
|
411 @return tuple of two list of argument names, one for arguments |
|
412 and one for keyword arguments (tuple of list of string) |
|
413 """ |
|
414 arguments = [] |
|
415 arguments.extend([arg.arg for arg in node.args.args]) |
|
416 if node.args.vararg is not None: |
|
417 arguments.append(node.args.vararg) |
|
418 |
|
419 kwarguments = [] |
|
420 kwarguments.extend([arg.arg for arg in node.args.kwonlyargs]) |
|
421 if node.args.kwarg is not None: |
|
422 kwarguments.append(node.args.kwarg) |
|
423 return arguments, kwarguments |
282 |
424 |
283 ################################################################## |
425 ################################################################## |
284 ## Parsing functionality below |
426 ## Parsing functionality below |
285 ################################################################## |
427 ################################################################## |
286 |
428 |
758 """ |
909 """ |
759 if docstringContext is None: |
910 if docstringContext is None: |
760 return |
911 return |
761 |
912 |
762 docstrings = docstringContext.source() |
913 docstrings = docstringContext.source() |
763 if len(docstrings) in [1, 3]: |
914 if len(docstrings) <= 3: |
764 # correct/invalid one-liner |
915 # correct/invalid one-liner |
765 return |
916 return |
766 |
917 |
767 summary, lineNumber = self.__getSummaryLine(docstringContext) |
918 summary, lineNumber = self.__getSummaryLine(docstringContext) |
768 if docstrings[lineNumber + 1].strip(): |
919 if len(docstrings) > 2: |
769 self.__error(docstringContext.start() + lineNumber, 0, "D144") |
920 if docstrings[lineNumber + 1].strip(): |
|
921 self.__error(docstringContext.start() + lineNumber, 0, "D144") |
770 |
922 |
771 def __checkBlankAfterLastParagraph(self, docstringContext, context): |
923 def __checkBlankAfterLastParagraph(self, docstringContext, context): |
|
924 """ |
|
925 Private method to check, that the last paragraph of docstrings is |
|
926 followed by a blank line. |
|
927 |
|
928 @param docstringContext docstring context (Pep257Context) |
|
929 @param context context of the docstring (Pep257Context) |
|
930 """ |
|
931 if docstringContext is None: |
|
932 return |
|
933 |
|
934 docstrings = docstringContext.source() |
|
935 if len(docstrings) <= 3: |
|
936 # correct/invalid one-liner |
|
937 return |
|
938 |
|
939 if docstrings[-2].strip(): |
|
940 self.__error(docstringContext.end(), 0, "D145") |
|
941 |
|
942 ################################################################## |
|
943 ## Checking functionality below (eric specific ones) |
|
944 ################################################################## |
|
945 |
|
946 def __checkEricOneLiner(self, docstringContext, context): |
|
947 """ |
|
948 Private method to check, that one-liner docstrings are on |
|
949 three lines (quotes, docstring, quotes). |
|
950 |
|
951 @param docstringContext docstring context (Pep257Context) |
|
952 @param context context of the docstring (Pep257Context) |
|
953 """ |
|
954 if docstringContext is None: |
|
955 return |
|
956 |
|
957 lines = docstringContext.source() |
|
958 if len(lines) != 3: |
|
959 nonEmptyLines = [l for l in lines if l.strip().strip('\'"')] |
|
960 if len(nonEmptyLines) == 1: |
|
961 self.__error(docstringContext.start(), 0, "D221") |
|
962 |
|
963 def __checkEricEndsWithPeriod(self, docstringContext, context): |
|
964 """ |
|
965 Private method to check, that docstring summaries end with a period. |
|
966 |
|
967 @param docstringContext docstring context (Pep257Context) |
|
968 @param context context of the docstring (Pep257Context) |
|
969 """ |
|
970 if docstringContext is None: |
|
971 return |
|
972 |
|
973 summaryLines, lineNumber = self.__getSummaryLines(docstringContext) |
|
974 summary = " ".join([s.strip() for s in summaryLines if s]) |
|
975 if not summary.endswith(".") and \ |
|
976 not summary.split(None, 1)[0].lower() == "constructor": |
|
977 self.__error( |
|
978 docstringContext.start() + lineNumber + len(summaryLines) - 1, |
|
979 0, "D231") |
|
980 |
|
981 def __checkEricReturn(self, docstringContext, context): |
|
982 """ |
|
983 Private method to check, that docstrings contain an @return line. |
|
984 |
|
985 @param docstringContext docstring context (Pep257Context) |
|
986 @param context context of the docstring (Pep257Context) |
|
987 """ |
|
988 if docstringContext is None or self.__isScript: |
|
989 return |
|
990 |
|
991 if "@return" not in docstringContext.ssource(): |
|
992 tokens = list( |
|
993 tokenize.generate_tokens(StringIO(context.ssource()).readline)) |
|
994 return_ = [tokens[i + 1][0] for i, token in enumerate(tokens) |
|
995 if token[1] == "return"] |
|
996 if (set(return_) - |
|
997 set([tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE]) != |
|
998 set([])): |
|
999 self.__error(docstringContext.end(), 0, "D234") |
|
1000 |
|
1001 def __checkEricFunctionArguments(self, docstringContext, context): |
|
1002 """ |
|
1003 Private method to check, that docstrings contain an @param line |
|
1004 for each argument. |
|
1005 |
|
1006 @param docstringContext docstring context (Pep257Context) |
|
1007 @param context context of the docstring (Pep257Context) |
|
1008 """ |
|
1009 if docstringContext is None or self.__isScript: |
|
1010 return |
|
1011 |
|
1012 try: |
|
1013 tree = ast.parse(context.ssource()) |
|
1014 except (SyntaxError, TypeError): |
|
1015 return |
|
1016 if (isinstance(tree, ast.Module) and len(tree.body) == 1 and |
|
1017 isinstance(tree.body[0], ast.FunctionDef)): |
|
1018 functionDef = tree.body[0] |
|
1019 argNames, kwNames = self.__getArgNames(functionDef) |
|
1020 if "self" in argNames: |
|
1021 argNames.remove("self") |
|
1022 if "cls" in argNames: |
|
1023 argNames.remove("cls") |
|
1024 |
|
1025 docstring = docstringContext.ssource() |
|
1026 if (docstring.count("@param") + docstring.count("@keyparam") < |
|
1027 len(argNames + kwNames)): |
|
1028 self.__error(docstringContext.end(), 0, "D235") |
|
1029 elif (docstring.count("@param") + docstring.count("@keyparam") > |
|
1030 len(argNames + kwNames)): |
|
1031 self.__error(docstringContext.end(), 0, "D236") |
|
1032 else: |
|
1033 # extract @param and @keyparam from docstring |
|
1034 args = [] |
|
1035 kwargs = [] |
|
1036 for line in docstringContext.source(): |
|
1037 if line.strip().startswith(("@param", "@keyparam")): |
|
1038 at, name, _ = line.strip().split(None, 2) |
|
1039 if at == "@keyparam": |
|
1040 kwargs.append(name) |
|
1041 args.append(name) |
|
1042 |
|
1043 # do the checks |
|
1044 for name in kwNames: |
|
1045 if name not in kwargs: |
|
1046 self.__error(docstringContext.end(), 0, "D237") |
|
1047 return |
|
1048 if argNames + kwNames != args: |
|
1049 self.__error(docstringContext.end(), 0, "D238") |
|
1050 |
|
1051 def __checkEricBlankAfterSummary(self, docstringContext, context): |
772 """ |
1052 """ |
773 Private method to check, that docstring summaries are followed |
1053 Private method to check, that docstring summaries are followed |
774 by a blank line. |
1054 by a blank line. |
775 |
1055 |
776 @param docstringContext docstring context (Pep257Context) |
1056 @param docstringContext docstring context (Pep257Context) |
778 """ |
1058 """ |
779 if docstringContext is None: |
1059 if docstringContext is None: |
780 return |
1060 return |
781 |
1061 |
782 docstrings = docstringContext.source() |
1062 docstrings = docstringContext.source() |
783 if len(docstrings) in [1, 3]: |
1063 if len(docstrings) <= 3: |
784 # correct/invalid one-liner |
1064 # correct/invalid one-liner |
785 return |
1065 return |
786 |
1066 |
787 if docstrings[-2].strip(): |
1067 summaryLines, lineNumber = self.__getSummaryLines(docstringContext) |
788 self.__error(docstringContext.end(), 0, "D145") |
1068 if len(docstrings) > lineNumber + len(summaryLines) - 1: |
|
1069 if docstrings[lineNumber + len(summaryLines)].strip(): |
|
1070 self.__error(docstringContext.start() + lineNumber, 0, "D244") |
|
1071 |
|
1072 def __checkEricNoBlankBeforeAndAfterClass(self, docstringContext, context): |
|
1073 """ |
|
1074 Private method to check, that class docstrings have no blank line |
|
1075 around them. |
|
1076 |
|
1077 @param docstringContext docstring context (Pep257Context) |
|
1078 @param context context of the docstring (Pep257Context) |
|
1079 """ |
|
1080 if docstringContext is None: |
|
1081 return |
|
1082 |
|
1083 contextLines = context.source() |
|
1084 cti = 0 |
|
1085 while cti < len(contextLines) and \ |
|
1086 not contextLines[cti].strip().startswith( |
|
1087 ('"""', 'r"""', 'u"""', "'''", "r'''", "u'''")): |
|
1088 cti += 1 |
|
1089 if cti == len(contextLines): |
|
1090 return |
|
1091 |
|
1092 start = cti |
|
1093 if contextLines[cti].strip() in ( |
|
1094 '"""', 'r"""', 'u"""', "'''", "r'''", "u'''"): |
|
1095 # it is a multi line docstring |
|
1096 cti += 1 |
|
1097 |
|
1098 while cti < len(contextLines) and \ |
|
1099 not contextLines[cti].strip().endswith(('"""', "'''")): |
|
1100 cti += 1 |
|
1101 end = cti |
|
1102 if cti == len(contextLines): |
|
1103 return |
|
1104 |
|
1105 if not contextLines[start - 1].strip(): |
|
1106 self.__error(docstringContext.start(), 0, "D242") |
|
1107 if not contextLines[end + 1].strip(): |
|
1108 self.__error(docstringContext.end(), 0, "D243") |
|
1109 |
|
1110 def __checkEricNBlankAfterLastParagraph(self, docstringContext, context): |
|
1111 """ |
|
1112 Private method to check, that the last paragraph of docstrings is |
|
1113 not followed by a blank line. |
|
1114 |
|
1115 @param docstringContext docstring context (Pep257Context) |
|
1116 @param context context of the docstring (Pep257Context) |
|
1117 """ |
|
1118 if docstringContext is None: |
|
1119 return |
|
1120 |
|
1121 docstrings = docstringContext.source() |
|
1122 if len(docstrings) <= 3: |
|
1123 # correct/invalid one-liner |
|
1124 return |
|
1125 |
|
1126 if not docstrings[-2].strip(): |
|
1127 self.__error(docstringContext.end(), 0, "D245") |
789 |
1128 |
790 # |
1129 # |
791 # eflag: FileType = Python2 |
1130 # eflag: FileType = Python2 |