UtilitiesPython2/Pep257CheckerPy2.py

changeset 2929
28ab0bc63d69
parent 2917
fe82710d02cb
child 2934
82811ddafea2
equal deleted inserted replaced
2928:4f74d3f595ce 2929:28ab0bc63d69
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
23 import sys
22 24
23 25
24 class Pep257Context(object): 26 class Pep257Context(object):
25 """ 27 """
26 Class implementing the source context. 28 Class implementing the source context.
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
452 self.__parseClasses() + 594 self.__parseClasses() +
453 self.__parseMethods()) 595 self.__parseMethods())
454 return [] # fall back 596 return [] # fall back
455 597
456 ################################################################## 598 ##################################################################
457 ## Checking functionality below 599 ## Checking functionality below (PEP-257)
458 ################################################################## 600 ##################################################################
459 601
460 def __checkModulesDocstrings(self, docstringContext, context): 602 def __checkModulesDocstrings(self, docstringContext, context):
461 """ 603 """
462 Private method to check, if the module has a docstring. 604 Private method to check, if the module has a docstring.
485 # assume nothing is exported 627 # assume nothing is exported
486 return 628 return
487 629
488 functionName = context.source()[0].lstrip().split()[1].split("(")[0] 630 functionName = context.source()[0].lstrip().split()[1].split("(")[0]
489 if functionName.startswith('_') and not functionName.endswith('__'): 631 if functionName.startswith('_') and not functionName.endswith('__'):
490 code = "D103" 632 if self.__docType == "eric":
633 code = "D203"
634 else:
635 code = "D103"
491 else: 636 else:
492 code = "D102" 637 code = "D102"
493 638
494 if docstringContext is None: 639 if docstringContext is None:
495 self.__error(context.start(), 0, code) 640 self.__error(context.start(), 0, code)
512 # assume nothing is exported 657 # assume nothing is exported
513 return 658 return
514 659
515 className = context.source()[0].lstrip().split()[1].split("(")[0] 660 className = context.source()[0].lstrip().split()[1].split("(")[0]
516 if className.startswith('_'): 661 if className.startswith('_'):
517 code = "D105" 662 if self.__docType == "eric":
663 code = "D205"
664 else:
665 code = "D105"
518 else: 666 else:
519 code = "D104" 667 code = "D104"
520 668
521 if docstringContext is None: 669 if docstringContext is None:
522 self.__error(context.start(), 0, code) 670 self.__error(context.start(), 0, code)
590 if len(nonEmptyLines) == 1: 738 if len(nonEmptyLines) == 1:
591 modLen = len(context.indent() + '"""' + 739 modLen = len(context.indent() + '"""' +
592 nonEmptyLines[0].strip() + '"""') 740 nonEmptyLines[0].strip() + '"""')
593 if context.contextType() != "module": 741 if context.contextType() != "module":
594 modLen += 4 742 modLen += 4
743 if not nonEmptyLines[0].strip().endswith("."):
744 # account for a trailing dot
745 modLen += 1
595 if modLen <= self.__maxLineLength: 746 if modLen <= self.__maxLineLength:
596 self.__error(docstringContext.start(), 0, "D121") 747 self.__error(docstringContext.start(), 0, "D121")
597 748
598 def __checkIndent(self, docstringContext, context): 749 def __checkIndent(self, docstringContext, context):
599 """ 750 """
702 cti = 0 853 cti = 0
703 while cti < len(contextLines) and \ 854 while cti < len(contextLines) and \
704 not contextLines[cti].strip().startswith( 855 not contextLines[cti].strip().startswith(
705 ('"""', 'r"""', 'u"""', "'''", "r'''", "u'''")): 856 ('"""', 'r"""', 'u"""', "'''", "r'''", "u'''")):
706 cti += 1 857 cti += 1
707
708 if cti == len(contextLines): 858 if cti == len(contextLines):
709 return 859 return
710 860
711 if not contextLines[cti - 1].strip(): 861 if not contextLines[cti - 1].strip():
712 self.__error(docstringContext.start(), 0, "D141") 862 self.__error(docstringContext.start(), 0, "D141")
726 cti = 0 876 cti = 0
727 while cti < len(contextLines) and \ 877 while cti < len(contextLines) and \
728 not contextLines[cti].strip().startswith( 878 not contextLines[cti].strip().startswith(
729 ('"""', 'r"""', 'u"""', "'''", "r'''", "u'''")): 879 ('"""', 'r"""', 'u"""', "'''", "r'''", "u'''")):
730 cti += 1 880 cti += 1
731
732 if cti == len(contextLines): 881 if cti == len(contextLines):
733 return 882 return
734 883
735 start = cti 884 start = cti
736 if contextLines[cti].strip() in ( 885 if contextLines[cti].strip() in (
740 889
741 while cti < len(contextLines) and \ 890 while cti < len(contextLines) and \
742 not contextLines[cti].strip().endswith(('"""', "'''")): 891 not contextLines[cti].strip().endswith(('"""', "'''")):
743 cti += 1 892 cti += 1
744 end = cti 893 end = cti
894 if cti == len(contextLines):
895 return
745 896
746 if contextLines[start - 1].strip(): 897 if contextLines[start - 1].strip():
747 self.__error(docstringContext.start(), 0, "D142") 898 self.__error(docstringContext.start(), 0, "D142")
748 if contextLines[end + 1].strip(): 899 if contextLines[end + 1].strip():
749 self.__error(docstringContext.end(), 0, "D143") 900 self.__error(docstringContext.end(), 0, "D143")
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

eric ide

mercurial