src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/DocStyle/DocStyleChecker.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9473
3f23dbf37dbe
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
16 import ast 16 import ast
17 from io import StringIO 17 from io import StringIO
18 import contextlib 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: 26 class DocStyleContext:
27 """ 27 """
28 Class implementing the source context. 28 Class implementing the source context.
29 """ 29 """
30
30 def __init__(self, source, startLine, contextType): 31 def __init__(self, source, startLine, contextType):
31 """ 32 """
32 Constructor 33 Constructor
33 34
34 @param source source code of the context (list of string or string) 35 @param source source code of the context (list of string or string)
35 @param startLine line number the context starts in the source (integer) 36 @param startLine line number the context starts in the source (integer)
36 @param contextType type of the context object (string) 37 @param contextType type of the context object (string)
37 """ 38 """
38 if isinstance(source, str): 39 if isinstance(source, str):
41 self.__source = source[:] 42 self.__source = source[:]
42 self.__start = startLine 43 self.__start = startLine
43 self.__indent = "" 44 self.__indent = ""
44 self.__type = contextType 45 self.__type = contextType
45 self.__special = "" 46 self.__special = ""
46 47
47 # ensure first line is left justified 48 # ensure first line is left justified
48 if self.__source: 49 if self.__source:
49 self.__indent = self.__source[0].replace( 50 self.__indent = self.__source[0].replace(self.__source[0].lstrip(), "")
50 self.__source[0].lstrip(), "")
51 self.__source[0] = self.__source[0].lstrip() 51 self.__source[0] = self.__source[0].lstrip()
52 52
53 def source(self): 53 def source(self):
54 """ 54 """
55 Public method to get the source. 55 Public method to get the source.
56 56
57 @return source (list of string) 57 @return source (list of string)
58 """ 58 """
59 return self.__source 59 return self.__source
60 60
61 def ssource(self): 61 def ssource(self):
62 """ 62 """
63 Public method to get the joined source lines. 63 Public method to get the joined source lines.
64 64
65 @return source (string) 65 @return source (string)
66 """ 66 """
67 return "".join(self.__source) 67 return "".join(self.__source)
68 68
69 def start(self): 69 def start(self):
70 """ 70 """
71 Public method to get the start line number. 71 Public method to get the start line number.
72 72
73 @return start line number (integer) 73 @return start line number (integer)
74 """ 74 """
75 return self.__start 75 return self.__start
76 76
77 def end(self): 77 def end(self):
78 """ 78 """
79 Public method to get the end line number. 79 Public method to get the end line number.
80 80
81 @return end line number (integer) 81 @return end line number (integer)
82 """ 82 """
83 return self.__start + len(self.__source) - 1 83 return self.__start + len(self.__source) - 1
84 84
85 def indent(self): 85 def indent(self):
86 """ 86 """
87 Public method to get the indentation of the first line. 87 Public method to get the indentation of the first line.
88 88
89 @return indentation string (string) 89 @return indentation string (string)
90 """ 90 """
91 return self.__indent 91 return self.__indent
92 92
93 def contextType(self): 93 def contextType(self):
94 """ 94 """
95 Public method to get the context type. 95 Public method to get the context type.
96 96
97 @return context type (string) 97 @return context type (string)
98 """ 98 """
99 return self.__type 99 return self.__type
100 100
101 def setSpecial(self, special): 101 def setSpecial(self, special):
102 """ 102 """
103 Public method to set a special attribute for the context. 103 Public method to set a special attribute for the context.
104 104
105 @param special attribute string 105 @param special attribute string
106 @type str 106 @type str
107 """ 107 """
108 self.__special = special 108 self.__special = special
109 109
110 def special(self): 110 def special(self):
111 """ 111 """
112 Public method to get the special context attribute string. 112 Public method to get the special context attribute string.
113 113
114 @return attribute string 114 @return attribute string
115 @rtype str 115 @rtype str
116 """ 116 """
117 return self.__special 117 return self.__special
118 118
119 119
120 class DocStyleChecker: 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
124 Codes = [ 125 Codes = [
125 "D101", "D102", "D103", "D104", "D105", 126 "D101",
126 "D111", "D112", 127 "D102",
127 "D121", "D122", 128 "D103",
128 "D130", "D131", "D132", "D133", "D134", 129 "D104",
129 "D141", "D142", "D143", "D144", "D145", 130 "D105",
130 131 "D111",
131 "D201", "D202.1", "D202.2", "D203", "D205", "D206", 132 "D112",
132 "D221", "D222", 133 "D121",
133 "D231", "D232", "D234r", "D234y", "D235r", "D235y", "D236", "D237", 134 "D122",
134 "D238", "D239", 135 "D130",
135 "D242", "D243", "D244", "D245", "D246", "D247", 136 "D131",
136 "D250", "D251", "D252", "D253", 137 "D132",
137 "D260", "D261", "D262", "D263", 138 "D133",
139 "D134",
140 "D141",
141 "D142",
142 "D143",
143 "D144",
144 "D145",
145 "D201",
146 "D202.1",
147 "D202.2",
148 "D203",
149 "D205",
150 "D206",
151 "D221",
152 "D222",
153 "D231",
154 "D232",
155 "D234r",
156 "D234y",
157 "D235r",
158 "D235y",
159 "D236",
160 "D237",
161 "D238",
162 "D239",
163 "D242",
164 "D243",
165 "D244",
166 "D245",
167 "D246",
168 "D247",
169 "D250",
170 "D251",
171 "D252",
172 "D253",
173 "D260",
174 "D261",
175 "D262",
176 "D263",
138 ] 177 ]
139 178
140 def __init__(self, source, filename, select, ignore, expected, repeat, 179 def __init__(
141 maxLineLength=88, docType="pep257"): 180 self,
181 source,
182 filename,
183 select,
184 ignore,
185 expected,
186 repeat,
187 maxLineLength=88,
188 docType="pep257",
189 ):
142 """ 190 """
143 Constructor 191 Constructor
144 192
145 @param source source code to be checked (list of string) 193 @param source source code to be checked (list of string)
146 @param filename name of the source file (string) 194 @param filename name of the source file (string)
147 @param select list of selected codes (list of string) 195 @param select list of selected codes (list of string)
148 @param ignore list of codes to be ignored (list of string) 196 @param ignore list of codes to be ignored (list of string)
149 @param expected list of expected codes (list of string) 197 @param expected list of expected codes (list of string)
152 @param maxLineLength allowed line length (integer) 200 @param maxLineLength allowed line length (integer)
153 @param docType type of the documentation strings 201 @param docType type of the documentation strings
154 (string, one of 'eric' or 'pep257') 202 (string, one of 'eric' or 'pep257')
155 """ 203 """
156 self.__select = tuple(select) 204 self.__select = tuple(select)
157 self.__ignore = ('',) if select else tuple(ignore) 205 self.__ignore = ("",) if select else tuple(ignore)
158 self.__expected = expected[:] 206 self.__expected = expected[:]
159 self.__repeat = repeat 207 self.__repeat = repeat
160 self.__maxLineLength = maxLineLength 208 self.__maxLineLength = maxLineLength
161 self.__docType = docType 209 self.__docType = docType
162 self.__filename = filename 210 self.__filename = filename
163 self.__source = source[:] 211 self.__source = source[:]
164 212
165 # statistics counters 213 # statistics counters
166 self.counters = {} 214 self.counters = {}
167 215
168 # collection of detected errors 216 # collection of detected errors
169 self.errors = [] 217 self.errors = []
170 218
171 self.__lineNumber = 0 219 self.__lineNumber = 0
172 220
173 # caches 221 # caches
174 self.__functionsCache = None 222 self.__functionsCache = None
175 self.__classesCache = None 223 self.__classesCache = None
176 self.__methodsCache = None 224 self.__methodsCache = None
177 225
178 self.__keywords = [ 226 self.__keywords = [
179 'moduleDocstring', 'functionDocstring', 227 "moduleDocstring",
180 'classDocstring', 'methodDocstring', 228 "functionDocstring",
181 'defDocstring', 'docstring' 229 "classDocstring",
230 "methodDocstring",
231 "defDocstring",
232 "docstring",
182 ] 233 ]
183 if self.__docType == "pep257": 234 if self.__docType == "pep257":
184 checkersWithCodes = { 235 checkersWithCodes = {
185 "moduleDocstring": [ 236 "moduleDocstring": [
186 (self.__checkModulesDocstrings, ("D101",)), 237 (self.__checkModulesDocstrings, ("D101",)),
187 ], 238 ],
188 "functionDocstring": [ 239 "functionDocstring": [],
189 ],
190 "classDocstring": [ 240 "classDocstring": [
191 (self.__checkClassDocstring, ("D104", "D105")), 241 (self.__checkClassDocstring, ("D104", "D105")),
192 (self.__checkBlankBeforeAndAfterClass, ("D142", "D143")), 242 (self.__checkBlankBeforeAndAfterClass, ("D142", "D143")),
193 ], 243 ],
194 "methodDocstring": [ 244 "methodDocstring": [],
195 ],
196 "defDocstring": [ 245 "defDocstring": [
197 (self.__checkFunctionDocstring, ("D102", "D103")), 246 (self.__checkFunctionDocstring, ("D102", "D103")),
198 (self.__checkImperativeMood, ("D132",)), 247 (self.__checkImperativeMood, ("D132",)),
199 (self.__checkNoSignature, ("D133",)), 248 (self.__checkNoSignature, ("D133",)),
200 (self.__checkReturnType, ("D134",)), 249 (self.__checkReturnType, ("D134",)),
214 elif self.__docType in ("eric", "eric_black"): 263 elif self.__docType in ("eric", "eric_black"):
215 checkersWithCodes = { 264 checkersWithCodes = {
216 "moduleDocstring": [ 265 "moduleDocstring": [
217 (self.__checkModulesDocstrings, ("D101", "D201")), 266 (self.__checkModulesDocstrings, ("D101", "D201")),
218 ], 267 ],
219 "functionDocstring": [ 268 "functionDocstring": [],
220 ],
221 "classDocstring": [ 269 "classDocstring": [
222 (self.__checkClassDocstring, ("D104", "D205", "D206")), 270 (self.__checkClassDocstring, ("D104", "D205", "D206")),
223 (self.__checkEricNoBlankBeforeAndAfterClassOrFunction, 271 (
224 ("D242", "D243")), 272 self.__checkEricNoBlankBeforeAndAfterClassOrFunction,
273 ("D242", "D243"),
274 ),
225 (self.__checkEricSignal, ("D260", "D261", "D262", "D263")), 275 (self.__checkEricSignal, ("D260", "D261", "D262", "D263")),
226 ], 276 ],
227 "methodDocstring": [ 277 "methodDocstring": [
228 (self.__checkEricSummary, ("D232")), 278 (self.__checkEricSummary, ("D232")),
229 ], 279 ],
230 "defDocstring": [ 280 "defDocstring": [
231 (self.__checkFunctionDocstring, 281 (
232 ("D102", "D202.1", "D202.2", "D203")), 282 self.__checkFunctionDocstring,
283 ("D102", "D202.1", "D202.2", "D203"),
284 ),
233 (self.__checkImperativeMood, ("D132",)), 285 (self.__checkImperativeMood, ("D132",)),
234 (self.__checkNoSignature, ("D133",)), 286 (self.__checkNoSignature, ("D133",)),
235 (self.__checkEricReturn, ("D234r", "D235r")), 287 (self.__checkEricReturn, ("D234r", "D235r")),
236 (self.__checkEricYield, ("D234y", "D235y")), 288 (self.__checkEricYield, ("D234y", "D235y")),
237 (self.__checkEricFunctionArguments, 289 (
238 ("D236", "D237", "D238", "D239")), 290 self.__checkEricFunctionArguments,
239 (self.__checkEricNoBlankBeforeAndAfterClassOrFunction, 291 ("D236", "D237", "D238", "D239"),
240 ("D244", "D245")), 292 ),
241 (self.__checkEricException, 293 (
242 ("D250", "D251", "D252", "D253")), 294 self.__checkEricNoBlankBeforeAndAfterClassOrFunction,
295 ("D244", "D245"),
296 ),
297 (self.__checkEricException, ("D250", "D251", "D252", "D253")),
243 ], 298 ],
244 "docstring": [ 299 "docstring": [
245 (self.__checkTripleDoubleQuotes, ("D111",)), 300 (self.__checkTripleDoubleQuotes, ("D111",)),
246 (self.__checkBackslashes, ("D112",)), 301 (self.__checkBackslashes, ("D112",)),
247 (self.__checkIndent, ("D122",)), 302 (self.__checkIndent, ("D122",)),
248 (self.__checkSummary, ("D130",)), 303 (self.__checkSummary, ("D130",)),
249 (self.__checkEricEndsWithPeriod, ("D231",)), 304 (self.__checkEricEndsWithPeriod, ("D231",)),
250 (self.__checkEricBlankAfterSummary, ("D246",)), 305 (self.__checkEricBlankAfterSummary, ("D246",)),
251 (self.__checkEricNBlankAfterLastParagraph, ("D247",)), 306 (self.__checkEricNBlankAfterLastParagraph, ("D247",)),
252 (self.__checkEricQuotesOnSeparateLines, ("D222", "D223")) 307 (self.__checkEricQuotesOnSeparateLines, ("D222", "D223")),
253 ], 308 ],
254 } 309 }
255 310
256 self.__checkers = {} 311 self.__checkers = {}
257 for key, checkers in checkersWithCodes.items(): 312 for key, checkers in checkersWithCodes.items():
258 for checker, codes in checkers: 313 for checker, codes in checkers:
259 if any(not (code and self.__ignoreCode(code)) 314 if any(not (code and self.__ignoreCode(code)) for code in codes):
260 for code in codes):
261 if key not in self.__checkers: 315 if key not in self.__checkers:
262 self.__checkers[key] = [] 316 self.__checkers[key] = []
263 self.__checkers[key].append(checker) 317 self.__checkers[key].append(checker)
264 318
265 def __ignoreCode(self, code): 319 def __ignoreCode(self, code):
266 """ 320 """
267 Private method to check if the error code should be ignored. 321 Private method to check if the error code should be ignored.
268 322
269 @param code message code to check for (string) 323 @param code message code to check for (string)
270 @return flag indicating to ignore the given code (boolean) 324 @return flag indicating to ignore the given code (boolean)
271 """ 325 """
272 return (code.startswith(self.__ignore) and 326 return code.startswith(self.__ignore) and not code.startswith(self.__select)
273 not code.startswith(self.__select)) 327
274
275 def __error(self, lineNumber, offset, code, *args): 328 def __error(self, lineNumber, offset, code, *args):
276 """ 329 """
277 Private method to record an issue. 330 Private method to record an issue.
278 331
279 @param lineNumber line number of the issue (integer) 332 @param lineNumber line number of the issue (integer)
280 @param offset position within line of the issue (integer) 333 @param offset position within line of the issue (integer)
281 @param code message code (string) 334 @param code message code (string)
282 @param args arguments for the message (list) 335 @param args arguments for the message (list)
283 """ 336 """
284 if self.__ignoreCode(code): 337 if self.__ignoreCode(code):
285 return 338 return
286 339
287 if code in self.counters: 340 if code in self.counters:
288 self.counters[code] += 1 341 self.counters[code] += 1
289 else: 342 else:
290 self.counters[code] = 1 343 self.counters[code] = 1
291 344
292 # Don't care about expected codes 345 # Don't care about expected codes
293 if code in self.__expected: 346 if code in self.__expected:
294 return 347 return
295 348
296 if code and (self.counters[code] == 1 or self.__repeat): 349 if code and (self.counters[code] == 1 or self.__repeat):
297 # record the issue with one based line number 350 # record the issue with one based line number
298 self.errors.append( 351 self.errors.append(
299 { 352 {
300 "file": self.__filename, 353 "file": self.__filename,
302 "offset": offset, 355 "offset": offset,
303 "code": code, 356 "code": code,
304 "args": args, 357 "args": args,
305 } 358 }
306 ) 359 )
307 360
308 def __resetReadline(self): 361 def __resetReadline(self):
309 """ 362 """
310 Private method to reset the internal readline function. 363 Private method to reset the internal readline function.
311 """ 364 """
312 self.__lineNumber = 0 365 self.__lineNumber = 0
313 366
314 def __readline(self): 367 def __readline(self):
315 """ 368 """
316 Private method to get the next line from the source. 369 Private method to get the next line from the source.
317 370
318 @return next line of source (string) 371 @return next line of source (string)
319 """ 372 """
320 self.__lineNumber += 1 373 self.__lineNumber += 1
321 if self.__lineNumber > len(self.__source): 374 if self.__lineNumber > len(self.__source):
322 return '' 375 return ""
323 return self.__source[self.__lineNumber - 1] 376 return self.__source[self.__lineNumber - 1]
324 377
325 def run(self): 378 def run(self):
326 """ 379 """
327 Public method to check the given source for violations of doc string 380 Public method to check the given source for violations of doc string
328 conventions. 381 conventions.
329 """ 382 """
330 if not self.__filename: 383 if not self.__filename:
331 # don't do anything, if essential data is missing 384 # don't do anything, if essential data is missing
332 return 385 return
333 386
334 if not self.__checkers: 387 if not self.__checkers:
335 # don't do anything, if no codes were selected 388 # don't do anything, if no codes were selected
336 return 389 return
337 390
338 for keyword in self.__keywords: 391 for keyword in self.__keywords:
339 if keyword in self.__checkers: 392 if keyword in self.__checkers:
340 for check in self.__checkers[keyword]: 393 for check in self.__checkers[keyword]:
341 for context in self.__parseContexts(keyword): 394 for context in self.__parseContexts(keyword):
342 docstring = self.__parseDocstring(context, keyword) 395 docstring = self.__parseDocstring(context, keyword)
343 check(docstring, context) 396 check(docstring, context)
344 397
345 def __getSummaryLine(self, docstringContext): 398 def __getSummaryLine(self, docstringContext):
346 """ 399 """
347 Private method to extract the summary line. 400 Private method to extract the summary line.
348 401
349 @param docstringContext docstring context (DocStyleContext) 402 @param docstringContext docstring context (DocStyleContext)
350 @return summary line (string) and the line it was found on (integer) 403 @return summary line (string) and the line it was found on (integer)
351 """ 404 """
352 lines = docstringContext.source() 405 lines = docstringContext.source()
353 406
354 line = (lines[0] 407 line = (
355 .replace('r"""', "", 1) 408 lines[0]
356 .replace('u"""', "", 1) 409 .replace('r"""', "", 1)
357 .replace('"""', "") 410 .replace('u"""', "", 1)
358 .replace("r'''", "", 1) 411 .replace('"""', "")
359 .replace("u'''", "", 1) 412 .replace("r'''", "", 1)
360 .replace("'''", "") 413 .replace("u'''", "", 1)
361 .strip()) 414 .replace("'''", "")
362 415 .strip()
416 )
417
363 if len(lines) == 1 or len(line) > 0: 418 if len(lines) == 1 or len(line) > 0:
364 return line, 0 419 return line, 0
365 return lines[1].strip().replace('"""', "").replace("'''", ""), 1 420 return lines[1].strip().replace('"""', "").replace("'''", ""), 1
366 421
367 def __getSummaryLines(self, docstringContext): 422 def __getSummaryLines(self, docstringContext):
368 """ 423 """
369 Private method to extract the summary lines. 424 Private method to extract the summary lines.
370 425
371 @param docstringContext docstring context (DocStyleContext) 426 @param docstringContext docstring context (DocStyleContext)
372 @return summary lines (list of string) and the line it was found on 427 @return summary lines (list of string) and the line it was found on
373 (integer) 428 (integer)
374 """ 429 """
375 summaries = [] 430 summaries = []
376 lines = docstringContext.source() 431 lines = docstringContext.source()
377 432
378 line0 = (lines[0] 433 line0 = (
379 .replace('r"""', "", 1) 434 lines[0]
380 .replace('u"""', "", 1) 435 .replace('r"""', "", 1)
381 .replace('"""', "") 436 .replace('u"""', "", 1)
382 .replace("r'''", "", 1) 437 .replace('"""', "")
383 .replace("u'''", "", 1) 438 .replace("r'''", "", 1)
384 .replace("'''", "") 439 .replace("u'''", "", 1)
385 .strip()) 440 .replace("'''", "")
441 .strip()
442 )
386 line1 = ( 443 line1 = (
387 lines[1].strip().replace('"""', "").replace("'''", "") 444 lines[1].strip().replace('"""', "").replace("'''", "")
388 if len(lines) > 1 else 445 if len(lines) > 1
389 "" 446 else ""
390 ) 447 )
391 line2 = ( 448 line2 = (
392 lines[2].strip().replace('"""', "").replace("'''", "") 449 lines[2].strip().replace('"""', "").replace("'''", "")
393 if len(lines) > 2 else 450 if len(lines) > 2
394 "" 451 else ""
395 ) 452 )
396 if line0: 453 if line0:
397 lineno = 0 454 lineno = 0
398 summaries.append(line0) 455 summaries.append(line0)
399 if not line0.endswith(".") and line1: 456 if not line0.endswith(".") and line1:
407 summaries.append(line2) 464 summaries.append(line2)
408 else: 465 else:
409 lineno = 2 466 lineno = 2
410 summaries.append(line2) 467 summaries.append(line2)
411 return summaries, lineno 468 return summaries, lineno
412 469
413 def __getArgNames(self, node): 470 def __getArgNames(self, node):
414 """ 471 """
415 Private method to get the argument names of a function node. 472 Private method to get the argument names of a function node.
416 473
417 @param node AST node to extract arguments names from 474 @param node AST node to extract arguments names from
418 @return tuple of two list of argument names, one for arguments 475 @return tuple of two list of argument names, one for arguments
419 and one for keyword arguments (tuple of list of string) 476 and one for keyword arguments (tuple of list of string)
420 """ 477 """
421 arguments = [] 478 arguments = []
422 arguments.extend([arg.arg for arg in node.args.args]) 479 arguments.extend([arg.arg for arg in node.args.args])
423 if node.args.vararg is not None: 480 if node.args.vararg is not None:
424 arguments.append(node.args.vararg.arg) 481 arguments.append(node.args.vararg.arg)
425 482
426 kwarguments = [] 483 kwarguments = []
427 kwarguments.extend([arg.arg for arg in node.args.kwonlyargs]) 484 kwarguments.extend([arg.arg for arg in node.args.kwonlyargs])
428 if node.args.kwarg is not None: 485 if node.args.kwarg is not None:
429 kwarguments.append(node.args.kwarg.arg) 486 kwarguments.append(node.args.kwarg.arg)
430 return arguments, kwarguments 487 return arguments, kwarguments
431 488
432 ################################################################## 489 ##################################################################
433 ## Parsing functionality below 490 ## Parsing functionality below
434 ################################################################## 491 ##################################################################
435 492
436 def __parseModuleDocstring(self, source): 493 def __parseModuleDocstring(self, source):
437 """ 494 """
438 Private method to extract a docstring given a module source. 495 Private method to extract a docstring given a module source.
439 496
440 @param source source to parse (list of string) 497 @param source source to parse (list of string)
441 @return context of extracted docstring (DocStyleContext) 498 @return context of extracted docstring (DocStyleContext)
442 """ 499 """
443 for kind, value, (line, _char), _, _ in tokenize.generate_tokens( 500 for kind, value, (line, _char), _, _ in tokenize.generate_tokens(
444 StringIO("".join(source)).readline): 501 StringIO("".join(source)).readline
502 ):
445 if kind in [tokenize.COMMENT, tokenize.NEWLINE, tokenize.NL]: 503 if kind in [tokenize.COMMENT, tokenize.NEWLINE, tokenize.NL]:
446 continue 504 continue
447 elif kind == tokenize.STRING: # first STRING should be docstring 505 elif kind == tokenize.STRING: # first STRING should be docstring
448 return DocStyleContext(value, line - 1, "docstring") 506 return DocStyleContext(value, line - 1, "docstring")
449 else: 507 else:
450 return None 508 return None
451 509
452 return None 510 return None
453 511
454 def __parseDocstring(self, context, what=''): 512 def __parseDocstring(self, context, what=""):
455 """ 513 """
456 Private method to extract a docstring given `def` or `class` source. 514 Private method to extract a docstring given `def` or `class` source.
457 515
458 @param context context data to get the docstring from (DocStyleContext) 516 @param context context data to get the docstring from (DocStyleContext)
459 @param what string denoting what is being parsed (string) 517 @param what string denoting what is being parsed (string)
460 @return context of extracted docstring (DocStyleContext) 518 @return context of extracted docstring (DocStyleContext)
461 """ 519 """
462 moduleDocstring = self.__parseModuleDocstring(context.source()) 520 moduleDocstring = self.__parseModuleDocstring(context.source())
463 if what.startswith('module') or context.contextType() == "module": 521 if what.startswith("module") or context.contextType() == "module":
464 return moduleDocstring 522 return moduleDocstring
465 if moduleDocstring: 523 if moduleDocstring:
466 return moduleDocstring 524 return moduleDocstring
467 525
468 tokenGenerator = tokenize.generate_tokens( 526 tokenGenerator = tokenize.generate_tokens(StringIO(context.ssource()).readline)
469 StringIO(context.ssource()).readline)
470 with contextlib.suppress(StopIteration): 527 with contextlib.suppress(StopIteration):
471 kind = None 528 kind = None
472 while kind != tokenize.INDENT: 529 while kind != tokenize.INDENT:
473 kind, _, _, _, _ = next(tokenGenerator) 530 kind, _, _, _, _ = next(tokenGenerator)
474 kind, value, (line, char), _, _ = next(tokenGenerator) 531 kind, value, (line, char), _, _ = next(tokenGenerator)
475 if kind == tokenize.STRING: # STRING after INDENT is a docstring 532 if kind == tokenize.STRING: # STRING after INDENT is a docstring
476 return DocStyleContext( 533 return DocStyleContext(value, context.start() + line - 1, "docstring")
477 value, context.start() + line - 1, "docstring") 534
478
479 return None 535 return None
480 536
481 def __parseTopLevel(self, keyword): 537 def __parseTopLevel(self, keyword):
482 """ 538 """
483 Private method to extract top-level functions or classes. 539 Private method to extract top-level functions or classes.
484 540
485 @param keyword keyword signaling what to extract (string) 541 @param keyword keyword signaling what to extract (string)
486 @return extracted function or class contexts (list of DocStyleContext) 542 @return extracted function or class contexts (list of DocStyleContext)
487 """ 543 """
488 self.__resetReadline() 544 self.__resetReadline()
489 tokenGenerator = tokenize.generate_tokens(self.__readline) 545 tokenGenerator = tokenize.generate_tokens(self.__readline)
490 kind, value, char = None, None, None 546 kind, value, char = None, None, None
491 contexts = [] 547 contexts = []
492 try: 548 try:
493 while True: 549 while True:
494 start, end = None, None 550 start, end = None, None
495 while not (kind == tokenize.NAME and 551 while not (kind == tokenize.NAME and value == keyword and char == 0):
496 value == keyword and
497 char == 0):
498 kind, value, (line, char), _, _ = next(tokenGenerator) 552 kind, value, (line, char), _, _ = next(tokenGenerator)
499 start = line - 1, char 553 start = line - 1, char
500 while not (kind == tokenize.DEDENT and 554 while not (kind == tokenize.DEDENT and value == "" and char == 0):
501 value == '' and
502 char == 0):
503 kind, value, (line, char), _, _ = next(tokenGenerator) 555 kind, value, (line, char), _, _ = next(tokenGenerator)
504 end = line - 1, char 556 end = line - 1, char
505 contexts.append(DocStyleContext( 557 contexts.append(
506 self.__source[start[0]:end[0]], start[0], keyword)) 558 DocStyleContext(self.__source[start[0] : end[0]], start[0], keyword)
559 )
507 except StopIteration: 560 except StopIteration:
508 return contexts 561 return contexts
509 562
510 def __parseFunctions(self): 563 def __parseFunctions(self):
511 """ 564 """
512 Private method to extract top-level functions. 565 Private method to extract top-level functions.
513 566
514 @return extracted function contexts (list of DocStyleContext) 567 @return extracted function contexts (list of DocStyleContext)
515 """ 568 """
516 if not self.__functionsCache: 569 if not self.__functionsCache:
517 self.__functionsCache = self.__parseTopLevel('def') 570 self.__functionsCache = self.__parseTopLevel("def")
518 return self.__functionsCache 571 return self.__functionsCache
519 572
520 def __parseClasses(self): 573 def __parseClasses(self):
521 """ 574 """
522 Private method to extract top-level classes. 575 Private method to extract top-level classes.
523 576
524 @return extracted class contexts (list of DocStyleContext) 577 @return extracted class contexts (list of DocStyleContext)
525 """ 578 """
526 if not self.__classesCache: 579 if not self.__classesCache:
527 self.__classesCache = self.__parseTopLevel('class') 580 self.__classesCache = self.__parseTopLevel("class")
528 return self.__classesCache 581 return self.__classesCache
529 582
530 def __skipIndentedBlock(self, tokenGenerator): 583 def __skipIndentedBlock(self, tokenGenerator):
531 """ 584 """
532 Private method to skip over an indented block of source code. 585 Private method to skip over an indented block of source code.
533 586
534 @param tokenGenerator token generator 587 @param tokenGenerator token generator
535 @return last token of the indented block 588 @return last token of the indented block
536 """ 589 """
537 kind, value, start, end, raw = next(tokenGenerator) 590 kind, value, start, end, raw = next(tokenGenerator)
538 while kind != tokenize.INDENT: 591 while kind != tokenize.INDENT:
543 indent += 1 596 indent += 1
544 elif kind == tokenize.DEDENT: 597 elif kind == tokenize.DEDENT:
545 indent -= 1 598 indent -= 1
546 if indent == 0: 599 if indent == 0:
547 return kind, value, start, end, raw 600 return kind, value, start, end, raw
548 601
549 return None 602 return None
550 603
551 def __parseMethods(self): 604 def __parseMethods(self):
552 """ 605 """
553 Private method to extract methods of all classes. 606 Private method to extract methods of all classes.
554 607
555 @return extracted method contexts (list of DocStyleContext) 608 @return extracted method contexts (list of DocStyleContext)
556 """ 609 """
557 if not self.__methodsCache: 610 if not self.__methodsCache:
558 contexts = [] 611 contexts = []
559 for classContext in self.__parseClasses(): 612 for classContext in self.__parseClasses():
560 tokenGenerator = tokenize.generate_tokens( 613 tokenGenerator = tokenize.generate_tokens(
561 StringIO(classContext.ssource()).readline) 614 StringIO(classContext.ssource()).readline
615 )
562 kind, value, char = None, None, None 616 kind, value, char = None, None, None
563 with contextlib.suppress(StopIteration): 617 with contextlib.suppress(StopIteration):
564 while True: 618 while True:
565 start, end = None, None 619 start, end = None, None
566 while not (kind == tokenize.NAME and value == 'def'): 620 while not (kind == tokenize.NAME and value == "def"):
567 kind, value, (line, char), _, _ = ( 621 kind, value, (line, char), _, _ = next(tokenGenerator)
568 next(tokenGenerator)
569 )
570 start = line - 1, char 622 start = line - 1, char
571 kind, value, (line, char), _, _ = ( 623 kind, value, (line, char), _, _ = self.__skipIndentedBlock(
572 self.__skipIndentedBlock(tokenGenerator) 624 tokenGenerator
573 ) 625 )
574 end = line - 1, char 626 end = line - 1, char
575 startLine = classContext.start() + start[0] 627 startLine = classContext.start() + start[0]
576 endLine = classContext.start() + end[0] 628 endLine = classContext.start() + end[0]
577 context = DocStyleContext( 629 context = DocStyleContext(
578 self.__source[startLine:endLine], 630 self.__source[startLine:endLine], startLine, "def"
579 startLine, "def") 631 )
580 if startLine > 0: 632 if startLine > 0:
581 if ( 633 if self.__source[startLine - 1].strip() == "@staticmethod":
582 self.__source[startLine - 1].strip() ==
583 "@staticmethod"
584 ):
585 context.setSpecial("staticmethod") 634 context.setSpecial("staticmethod")
586 elif ( 635 elif self.__source[startLine - 1].strip() == "@classmethod":
587 self.__source[startLine - 1].strip() ==
588 "@classmethod"
589 ):
590 context.setSpecial("classmethod") 636 context.setSpecial("classmethod")
591 contexts.append(context) 637 contexts.append(context)
592 self.__methodsCache = contexts 638 self.__methodsCache = contexts
593 639
594 return self.__methodsCache 640 return self.__methodsCache
595 641
596 def __parseContexts(self, kind): 642 def __parseContexts(self, kind):
597 """ 643 """
598 Private method to extract a context from the source. 644 Private method to extract a context from the source.
599 645
600 @param kind kind of context to extract (string) 646 @param kind kind of context to extract (string)
601 @return requested contexts (list of DocStyleContext) 647 @return requested contexts (list of DocStyleContext)
602 """ 648 """
603 if kind == 'moduleDocstring': 649 if kind == "moduleDocstring":
604 return [DocStyleContext(self.__source, 0, "module")] 650 return [DocStyleContext(self.__source, 0, "module")]
605 if kind == 'functionDocstring': 651 if kind == "functionDocstring":
606 return self.__parseFunctions() 652 return self.__parseFunctions()
607 if kind == 'classDocstring': 653 if kind == "classDocstring":
608 return self.__parseClasses() 654 return self.__parseClasses()
609 if kind == 'methodDocstring': 655 if kind == "methodDocstring":
610 return self.__parseMethods() 656 return self.__parseMethods()
611 if kind == 'defDocstring': 657 if kind == "defDocstring":
612 return self.__parseFunctions() + self.__parseMethods() 658 return self.__parseFunctions() + self.__parseMethods()
613 if kind == 'docstring': 659 if kind == "docstring":
614 return ([DocStyleContext(self.__source, 0, "module")] + 660 return (
615 self.__parseFunctions() + 661 [DocStyleContext(self.__source, 0, "module")]
616 self.__parseClasses() + 662 + self.__parseFunctions()
617 self.__parseMethods()) 663 + self.__parseClasses()
618 return [] # fall back 664 + self.__parseMethods()
619 665 )
666 return [] # fall back
667
620 ################################################################## 668 ##################################################################
621 ## Checking functionality below (PEP-257) 669 ## Checking functionality below (PEP-257)
622 ################################################################## 670 ##################################################################
623 671
624 def __checkModulesDocstrings(self, docstringContext, context): 672 def __checkModulesDocstrings(self, docstringContext, context):
625 """ 673 """
626 Private method to check, if the module has a docstring. 674 Private method to check, if the module has a docstring.
627 675
628 @param docstringContext docstring context (DocStyleContext) 676 @param docstringContext docstring context (DocStyleContext)
629 @param context context of the docstring (DocStyleContext) 677 @param context context of the docstring (DocStyleContext)
630 """ 678 """
631 if docstringContext is None: 679 if docstringContext is None:
632 self.__error(context.start(), 0, "D101") 680 self.__error(context.start(), 0, "D101")
633 return 681 return
634 682
635 docstring = docstringContext.ssource() 683 docstring = docstringContext.ssource()
636 if (not docstring or not docstring.strip() or 684 if not docstring or not docstring.strip() or not docstring.strip("'\""):
637 not docstring.strip('\'"')):
638 self.__error(context.start(), 0, "D101") 685 self.__error(context.start(), 0, "D101")
639 686
640 if ( 687 if (
641 self.__docType == "eric" and 688 self.__docType == "eric"
642 docstring.strip('\'"').strip() == 689 and docstring.strip("'\"").strip() == "Module documentation goes here."
643 "Module documentation goes here."
644 ): 690 ):
645 self.__error(docstringContext.end(), 0, "D201") 691 self.__error(docstringContext.end(), 0, "D201")
646 return 692 return
647 693
648 def __checkFunctionDocstring(self, docstringContext, context): 694 def __checkFunctionDocstring(self, docstringContext, context):
649 """ 695 """
650 Private method to check, that all public functions and methods 696 Private method to check, that all public functions and methods
651 have a docstring. 697 have a docstring.
652 698
653 @param docstringContext docstring context (DocStyleContext) 699 @param docstringContext docstring context (DocStyleContext)
654 @param context context of the docstring (DocStyleContext) 700 @param context context of the docstring (DocStyleContext)
655 """ 701 """
656 functionName = context.source()[0].lstrip().split()[1].split("(")[0] 702 functionName = context.source()[0].lstrip().split()[1].split("(")[0]
657 if functionName.startswith('_') and not functionName.endswith('__'): 703 if functionName.startswith("_") and not functionName.endswith("__"):
658 if self.__docType == "eric": 704 if self.__docType == "eric":
659 code = "D203" 705 code = "D203"
660 else: 706 else:
661 code = "D103" 707 code = "D103"
662 else: 708 else:
663 code = "D102" 709 code = "D102"
664 710
665 if docstringContext is None: 711 if docstringContext is None:
666 self.__error(context.start(), 0, code) 712 self.__error(context.start(), 0, code)
667 return 713 return
668 714
669 docstring = docstringContext.ssource() 715 docstring = docstringContext.ssource()
670 if (not docstring or not docstring.strip() or 716 if not docstring or not docstring.strip() or not docstring.strip("'\""):
671 not docstring.strip('\'"')):
672 self.__error(context.start(), 0, code) 717 self.__error(context.start(), 0, code)
673 718
674 if self.__docType == "eric": 719 if self.__docType == "eric":
675 if ( 720 if docstring.strip("'\"").strip() == "Function documentation goes here.":
676 docstring.strip('\'"').strip() ==
677 "Function documentation goes here."
678 ):
679 self.__error(docstringContext.end(), 0, "D202.1") 721 self.__error(docstringContext.end(), 0, "D202.1")
680 return 722 return
681 723
682 if ( 724 if "DESCRIPTION" in docstring or "TYPE" in docstring:
683 "DESCRIPTION" in docstring or
684 "TYPE" in docstring
685 ):
686 self.__error(docstringContext.end(), 0, "D202.2") 725 self.__error(docstringContext.end(), 0, "D202.2")
687 return 726 return
688 727
689 def __checkClassDocstring(self, docstringContext, context): 728 def __checkClassDocstring(self, docstringContext, context):
690 """ 729 """
691 Private method to check, that all public functions and methods 730 Private method to check, that all public functions and methods
692 have a docstring. 731 have a docstring.
693 732
694 @param docstringContext docstring context (DocStyleContext) 733 @param docstringContext docstring context (DocStyleContext)
695 @param context context of the docstring (DocStyleContext) 734 @param context context of the docstring (DocStyleContext)
696 """ 735 """
697 className = context.source()[0].lstrip().split()[1].split("(")[0] 736 className = context.source()[0].lstrip().split()[1].split("(")[0]
698 if className.startswith('_'): 737 if className.startswith("_"):
699 if self.__docType == "eric": 738 if self.__docType == "eric":
700 code = "D205" 739 code = "D205"
701 else: 740 else:
702 code = "D105" 741 code = "D105"
703 else: 742 else:
704 code = "D104" 743 code = "D104"
705 744
706 if docstringContext is None: 745 if docstringContext is None:
707 self.__error(context.start(), 0, code) 746 self.__error(context.start(), 0, code)
708 return 747 return
709 748
710 docstring = docstringContext.ssource() 749 docstring = docstringContext.ssource()
711 if (not docstring or not docstring.strip() or 750 if not docstring or not docstring.strip() or not docstring.strip("'\""):
712 not docstring.strip('\'"')):
713 self.__error(context.start(), 0, code) 751 self.__error(context.start(), 0, code)
714 return 752 return
715 753
716 if ( 754 if (
717 self.__docType == "eric" and 755 self.__docType == "eric"
718 docstring.strip('\'"').strip() == "Class documentation goes here." 756 and docstring.strip("'\"").strip() == "Class documentation goes here."
719 ): 757 ):
720 self.__error(docstringContext.end(), 0, "D206") 758 self.__error(docstringContext.end(), 0, "D206")
721 return 759 return
722 760
723 def __checkTripleDoubleQuotes(self, docstringContext, context): 761 def __checkTripleDoubleQuotes(self, docstringContext, context):
724 """ 762 """
725 Private method to check, that all docstrings are surrounded 763 Private method to check, that all docstrings are surrounded
726 by triple double quotes. 764 by triple double quotes.
727 765
728 @param docstringContext docstring context (DocStyleContext) 766 @param docstringContext docstring context (DocStyleContext)
729 @param context context of the docstring (DocStyleContext) 767 @param context context of the docstring (DocStyleContext)
730 """ 768 """
731 if docstringContext is None: 769 if docstringContext is None:
732 return 770 return
733 771
734 docstring = docstringContext.ssource().strip() 772 docstring = docstringContext.ssource().strip()
735 if not docstring.startswith(('"""', 'r"""', 'u"""')): 773 if not docstring.startswith(('"""', 'r"""', 'u"""')):
736 self.__error(docstringContext.start(), 0, "D111") 774 self.__error(docstringContext.start(), 0, "D111")
737 775
738 def __checkBackslashes(self, docstringContext, context): 776 def __checkBackslashes(self, docstringContext, context):
739 """ 777 """
740 Private method to check, that all docstrings containing 778 Private method to check, that all docstrings containing
741 backslashes are surrounded by raw triple double quotes. 779 backslashes are surrounded by raw triple double quotes.
742 780
743 @param docstringContext docstring context (DocStyleContext) 781 @param docstringContext docstring context (DocStyleContext)
744 @param context context of the docstring (DocStyleContext) 782 @param context context of the docstring (DocStyleContext)
745 """ 783 """
746 if docstringContext is None: 784 if docstringContext is None:
747 return 785 return
748 786
749 docstring = docstringContext.ssource().strip() 787 docstring = docstringContext.ssource().strip()
750 if "\\" in docstring and not docstring.startswith('r"""'): 788 if "\\" in docstring and not docstring.startswith('r"""'):
751 self.__error(docstringContext.start(), 0, "D112") 789 self.__error(docstringContext.start(), 0, "D112")
752 790
753 def __checkOneLiner(self, docstringContext, context): 791 def __checkOneLiner(self, docstringContext, context):
754 """ 792 """
755 Private method to check, that one-liner docstrings fit on 793 Private method to check, that one-liner docstrings fit on
756 one line with quotes. 794 one line with quotes.
757 795
758 @param docstringContext docstring context (DocStyleContext) 796 @param docstringContext docstring context (DocStyleContext)
759 @param context context of the docstring (DocStyleContext) 797 @param context context of the docstring (DocStyleContext)
760 """ 798 """
761 if docstringContext is None: 799 if docstringContext is None:
762 return 800 return
763 801
764 lines = docstringContext.source() 802 lines = docstringContext.source()
765 if len(lines) > 1: 803 if len(lines) > 1:
766 nonEmptyLines = [line for line in lines 804 nonEmptyLines = [line for line in lines if line.strip().strip("'\"")]
767 if line.strip().strip('\'"')]
768 if len(nonEmptyLines) == 1: 805 if len(nonEmptyLines) == 1:
769 modLen = len(context.indent() + '"""' + 806 modLen = len(
770 nonEmptyLines[0].strip() + '"""') 807 context.indent() + '"""' + nonEmptyLines[0].strip() + '"""'
808 )
771 if context.contextType() != "module": 809 if context.contextType() != "module":
772 modLen += 4 810 modLen += 4
773 if not nonEmptyLines[0].strip().endswith("."): 811 if not nonEmptyLines[0].strip().endswith("."):
774 # account for a trailing dot 812 # account for a trailing dot
775 modLen += 1 813 modLen += 1
776 if modLen <= self.__maxLineLength: 814 if modLen <= self.__maxLineLength:
777 self.__error(docstringContext.start(), 0, "D121") 815 self.__error(docstringContext.start(), 0, "D121")
778 816
779 def __checkIndent(self, docstringContext, context): 817 def __checkIndent(self, docstringContext, context):
780 """ 818 """
781 Private method to check, that docstrings are properly indented. 819 Private method to check, that docstrings are properly indented.
782 820
783 @param docstringContext docstring context (DocStyleContext) 821 @param docstringContext docstring context (DocStyleContext)
784 @param context context of the docstring (DocStyleContext) 822 @param context context of the docstring (DocStyleContext)
785 """ 823 """
786 if docstringContext is None: 824 if docstringContext is None:
787 return 825 return
788 826
789 lines = docstringContext.source() 827 lines = docstringContext.source()
790 if len(lines) == 1: 828 if len(lines) == 1:
791 return 829 return
792 830
793 nonEmptyLines = [line.rstrip() for line in lines[1:] if line.strip()] 831 nonEmptyLines = [line.rstrip() for line in lines[1:] if line.strip()]
794 if not nonEmptyLines: 832 if not nonEmptyLines:
795 return 833 return
796 834
797 indent = min(len(line) - len(line.strip()) for line in nonEmptyLines) 835 indent = min(len(line) - len(line.strip()) for line in nonEmptyLines)
798 expectedIndent = ( 836 expectedIndent = (
799 0 837 0 if context.contextType() == "module" else len(context.indent()) + 4
800 if context.contextType() == "module" else
801 len(context.indent()) + 4
802 ) 838 )
803 if indent != expectedIndent: 839 if indent != expectedIndent:
804 self.__error(docstringContext.start(), 0, "D122") 840 self.__error(docstringContext.start(), 0, "D122")
805 841
806 def __checkSummary(self, docstringContext, context): 842 def __checkSummary(self, docstringContext, context):
807 """ 843 """
808 Private method to check, that docstring summaries contain some text. 844 Private method to check, that docstring summaries contain some text.
809 845
810 @param docstringContext docstring context (DocStyleContext) 846 @param docstringContext docstring context (DocStyleContext)
811 @param context context of the docstring (DocStyleContext) 847 @param context context of the docstring (DocStyleContext)
812 """ 848 """
813 if docstringContext is None: 849 if docstringContext is None:
814 return 850 return
815 851
816 summary, lineNumber = self.__getSummaryLine(docstringContext) 852 summary, lineNumber = self.__getSummaryLine(docstringContext)
817 if summary == "": 853 if summary == "":
818 self.__error(docstringContext.start() + lineNumber, 0, "D130") 854 self.__error(docstringContext.start() + lineNumber, 0, "D130")
819 855
820 def __checkEndsWithPeriod(self, docstringContext, context): 856 def __checkEndsWithPeriod(self, docstringContext, context):
821 """ 857 """
822 Private method to check, that docstring summaries end with a period. 858 Private method to check, that docstring summaries end with a period.
823 859
824 @param docstringContext docstring context (DocStyleContext) 860 @param docstringContext docstring context (DocStyleContext)
825 @param context context of the docstring (DocStyleContext) 861 @param context context of the docstring (DocStyleContext)
826 """ 862 """
827 if docstringContext is None: 863 if docstringContext is None:
828 return 864 return
829 865
830 summary, lineNumber = self.__getSummaryLine(docstringContext) 866 summary, lineNumber = self.__getSummaryLine(docstringContext)
831 if not summary.endswith("."): 867 if not summary.endswith("."):
832 self.__error(docstringContext.start() + lineNumber, 0, "D131") 868 self.__error(docstringContext.start() + lineNumber, 0, "D131")
833 869
834 def __checkImperativeMood(self, docstringContext, context): 870 def __checkImperativeMood(self, docstringContext, context):
835 """ 871 """
836 Private method to check, that docstring summaries are in 872 Private method to check, that docstring summaries are in
837 imperative mood. 873 imperative mood.
838 874
839 @param docstringContext docstring context (DocStyleContext) 875 @param docstringContext docstring context (DocStyleContext)
840 @param context context of the docstring (DocStyleContext) 876 @param context context of the docstring (DocStyleContext)
841 """ 877 """
842 if docstringContext is None: 878 if docstringContext is None:
843 return 879 return
844 880
845 summary, lineNumber = self.__getSummaryLine(docstringContext) 881 summary, lineNumber = self.__getSummaryLine(docstringContext)
846 if summary: 882 if summary:
847 firstWord = summary.strip().split()[0] 883 firstWord = summary.strip().split()[0]
848 if firstWord.endswith("s") and not firstWord.endswith("ss"): 884 if firstWord.endswith("s") and not firstWord.endswith("ss"):
849 self.__error(docstringContext.start() + lineNumber, 0, "D132") 885 self.__error(docstringContext.start() + lineNumber, 0, "D132")
850 886
851 def __checkNoSignature(self, docstringContext, context): 887 def __checkNoSignature(self, docstringContext, context):
852 """ 888 """
853 Private method to check, that docstring summaries don't repeat 889 Private method to check, that docstring summaries don't repeat
854 the function's signature. 890 the function's signature.
855 891
856 @param docstringContext docstring context (DocStyleContext) 892 @param docstringContext docstring context (DocStyleContext)
857 @param context context of the docstring (DocStyleContext) 893 @param context context of the docstring (DocStyleContext)
858 """ 894 """
859 if docstringContext is None: 895 if docstringContext is None:
860 return 896 return
861 897
862 functionName = context.source()[0].lstrip().split()[1].split("(")[0] 898 functionName = context.source()[0].lstrip().split()[1].split("(")[0]
863 summary, lineNumber = self.__getSummaryLine(docstringContext) 899 summary, lineNumber = self.__getSummaryLine(docstringContext)
864 if ( 900 if functionName + "(" in summary.replace(
865 functionName + "(" in summary.replace(" ", "") and 901 " ", ""
866 functionName + "()" not in summary.replace(" ", "") 902 ) and functionName + "()" not in summary.replace(" ", ""):
867 ):
868 # report only, if it is not an abbreviated form (i.e. function() ) 903 # report only, if it is not an abbreviated form (i.e. function() )
869 self.__error(docstringContext.start() + lineNumber, 0, "D133") 904 self.__error(docstringContext.start() + lineNumber, 0, "D133")
870 905
871 def __checkReturnType(self, docstringContext, context): 906 def __checkReturnType(self, docstringContext, context):
872 """ 907 """
873 Private method to check, that docstrings mention the return value type. 908 Private method to check, that docstrings mention the return value type.
874 909
875 @param docstringContext docstring context (DocStyleContext) 910 @param docstringContext docstring context (DocStyleContext)
876 @param context context of the docstring (DocStyleContext) 911 @param context context of the docstring (DocStyleContext)
877 """ 912 """
878 if docstringContext is None: 913 if docstringContext is None:
879 return 914 return
880 915
881 if "return" not in docstringContext.ssource().lower(): 916 if "return" not in docstringContext.ssource().lower():
882 tokens = list( 917 tokens = list(
883 tokenize.generate_tokens(StringIO(context.ssource()).readline)) 918 tokenize.generate_tokens(StringIO(context.ssource()).readline)
884 return_ = [tokens[i + 1][0] for i, token in enumerate(tokens) 919 )
885 if token[1] == "return"] 920 return_ = [
886 if (set(return_) - 921 tokens[i + 1][0]
887 {tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE} != 922 for i, token in enumerate(tokens)
888 set()): 923 if token[1] == "return"
924 ]
925 if (
926 set(return_) - {tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE}
927 != set()
928 ):
889 self.__error(docstringContext.end(), 0, "D134") 929 self.__error(docstringContext.end(), 0, "D134")
890 930
891 def __checkNoBlankLineBefore(self, docstringContext, context): 931 def __checkNoBlankLineBefore(self, docstringContext, context):
892 """ 932 """
893 Private method to check, that function/method docstrings are not 933 Private method to check, that function/method docstrings are not
894 preceded by a blank line. 934 preceded by a blank line.
895 935
896 @param docstringContext docstring context (DocStyleContext) 936 @param docstringContext docstring context (DocStyleContext)
897 @param context context of the docstring (DocStyleContext) 937 @param context context of the docstring (DocStyleContext)
898 """ 938 """
899 if docstringContext is None: 939 if docstringContext is None:
900 return 940 return
901 941
902 contextLines = context.source() 942 contextLines = context.source()
903 cti = 0 943 cti = 0
904 while ( 944 while cti < len(contextLines) and not contextLines[cti].strip().startswith(
905 cti < len(contextLines) and 945 ('"""', 'r"""', 'u"""', "'''", "r'''", "u'''")
906 not contextLines[cti].strip().startswith(
907 ('"""', 'r"""', 'u"""', "'''", "r'''", "u'''"))
908 ): 946 ):
909 cti += 1 947 cti += 1
910 if cti == len(contextLines): 948 if cti == len(contextLines):
911 return 949 return
912 950
913 if not contextLines[cti - 1].strip(): 951 if not contextLines[cti - 1].strip():
914 self.__error(docstringContext.start(), 0, "D141") 952 self.__error(docstringContext.start(), 0, "D141")
915 953
916 def __checkBlankBeforeAndAfterClass(self, docstringContext, context): 954 def __checkBlankBeforeAndAfterClass(self, docstringContext, context):
917 """ 955 """
918 Private method to check, that class docstrings have one 956 Private method to check, that class docstrings have one
919 blank line around them. 957 blank line around them.
920 958
921 @param docstringContext docstring context (DocStyleContext) 959 @param docstringContext docstring context (DocStyleContext)
922 @param context context of the docstring (DocStyleContext) 960 @param context context of the docstring (DocStyleContext)
923 """ 961 """
924 if docstringContext is None: 962 if docstringContext is None:
925 return 963 return
926 964
927 contextLines = context.source() 965 contextLines = context.source()
928 cti = 0 966 cti = 0
929 while ( 967 while cti < len(contextLines) and not contextLines[cti].strip().startswith(
930 cti < len(contextLines) and 968 ('"""', 'r"""', 'u"""', "'''", "r'''", "u'''")
931 not contextLines[cti].strip().startswith(
932 ('"""', 'r"""', 'u"""', "'''", "r'''", "u'''"))
933 ): 969 ):
934 cti += 1 970 cti += 1
935 if cti == len(contextLines): 971 if cti == len(contextLines):
936 return 972 return
937 973
938 start = cti 974 start = cti
939 if contextLines[cti].strip() in ( 975 if contextLines[cti].strip() in ('"""', 'r"""', 'u"""', "'''", "r'''", "u'''"):
940 '"""', 'r"""', 'u"""', "'''", "r'''", "u'''"):
941 # it is a multi line docstring 976 # it is a multi line docstring
942 cti += 1 977 cti += 1
943 978
944 while ( 979 while cti < len(contextLines) and not contextLines[cti].strip().endswith(
945 cti < len(contextLines) and 980 ('"""', "'''")
946 not contextLines[cti].strip().endswith(('"""', "'''"))
947 ): 981 ):
948 cti += 1 982 cti += 1
949 end = cti 983 end = cti
950 if cti >= len(contextLines) - 1: 984 if cti >= len(contextLines) - 1:
951 return 985 return
952 986
953 if contextLines[start - 1].strip(): 987 if contextLines[start - 1].strip():
954 self.__error(docstringContext.start(), 0, "D142") 988 self.__error(docstringContext.start(), 0, "D142")
955 if contextLines[end + 1].strip(): 989 if contextLines[end + 1].strip():
956 self.__error(docstringContext.end(), 0, "D143") 990 self.__error(docstringContext.end(), 0, "D143")
957 991
958 def __checkBlankAfterSummary(self, docstringContext, context): 992 def __checkBlankAfterSummary(self, docstringContext, context):
959 """ 993 """
960 Private method to check, that docstring summaries are followed 994 Private method to check, that docstring summaries are followed
961 by a blank line. 995 by a blank line.
962 996
963 @param docstringContext docstring context (DocStyleContext) 997 @param docstringContext docstring context (DocStyleContext)
964 @param context context of the docstring (DocStyleContext) 998 @param context context of the docstring (DocStyleContext)
965 """ 999 """
966 if docstringContext is None: 1000 if docstringContext is None:
967 return 1001 return
968 1002
969 docstrings = docstringContext.source() 1003 docstrings = docstringContext.source()
970 if len(docstrings) <= 3: 1004 if len(docstrings) <= 3:
971 # correct/invalid one-liner 1005 # correct/invalid one-liner
972 return 1006 return
973 1007
974 summary, lineNumber = self.__getSummaryLine(docstringContext) 1008 summary, lineNumber = self.__getSummaryLine(docstringContext)
975 if ( 1009 if len(docstrings) > 2 and docstrings[lineNumber + 1].strip():
976 len(docstrings) > 2 and
977 docstrings[lineNumber + 1].strip()
978 ):
979 self.__error(docstringContext.start() + lineNumber, 0, "D144") 1010 self.__error(docstringContext.start() + lineNumber, 0, "D144")
980 1011
981 def __checkBlankAfterLastParagraph(self, docstringContext, context): 1012 def __checkBlankAfterLastParagraph(self, docstringContext, context):
982 """ 1013 """
983 Private method to check, that the last paragraph of docstrings is 1014 Private method to check, that the last paragraph of docstrings is
984 followed by a blank line. 1015 followed by a blank line.
985 1016
986 @param docstringContext docstring context (DocStyleContext) 1017 @param docstringContext docstring context (DocStyleContext)
987 @param context context of the docstring (DocStyleContext) 1018 @param context context of the docstring (DocStyleContext)
988 """ 1019 """
989 if docstringContext is None: 1020 if docstringContext is None:
990 return 1021 return
991 1022
992 docstrings = docstringContext.source() 1023 docstrings = docstringContext.source()
993 if len(docstrings) <= 3: 1024 if len(docstrings) <= 3:
994 # correct/invalid one-liner 1025 # correct/invalid one-liner
995 return 1026 return
996 1027
997 if docstrings[-2].strip(): 1028 if docstrings[-2].strip():
998 self.__error(docstringContext.end(), 0, "D145") 1029 self.__error(docstringContext.end(), 0, "D145")
999 1030
1000 ################################################################## 1031 ##################################################################
1001 ## Checking functionality below (eric specific ones) 1032 ## Checking functionality below (eric specific ones)
1002 ################################################################## 1033 ##################################################################
1003 1034
1004 def __checkEricQuotesOnSeparateLines(self, docstringContext, context): 1035 def __checkEricQuotesOnSeparateLines(self, docstringContext, context):
1005 """ 1036 """
1006 Private method to check, that leading and trailing quotes are on 1037 Private method to check, that leading and trailing quotes are on
1007 a line by themselves. 1038 a line by themselves.
1008 1039
1009 @param docstringContext docstring context (DocStyleContext) 1040 @param docstringContext docstring context (DocStyleContext)
1010 @param context context of the docstring (DocStyleContext) 1041 @param context context of the docstring (DocStyleContext)
1011 """ 1042 """
1012 if docstringContext is None: 1043 if docstringContext is None:
1013 return 1044 return
1014 1045
1015 lines = docstringContext.source() 1046 lines = docstringContext.source()
1016 if lines[0].strip().strip('ru"\''): 1047 if lines[0].strip().strip("ru\"'"):
1017 self.__error(docstringContext.start(), 0, "D221") 1048 self.__error(docstringContext.start(), 0, "D221")
1018 if lines[-1].strip().strip('"\''): 1049 if lines[-1].strip().strip("\"'"):
1019 self.__error(docstringContext.end(), 0, "D222") 1050 self.__error(docstringContext.end(), 0, "D222")
1020 1051
1021 def __checkEricEndsWithPeriod(self, docstringContext, context): 1052 def __checkEricEndsWithPeriod(self, docstringContext, context):
1022 """ 1053 """
1023 Private method to check, that docstring summaries end with a period. 1054 Private method to check, that docstring summaries end with a period.
1024 1055
1025 @param docstringContext docstring context (DocStyleContext) 1056 @param docstringContext docstring context (DocStyleContext)
1026 @param context context of the docstring (DocStyleContext) 1057 @param context context of the docstring (DocStyleContext)
1027 """ 1058 """
1028 if docstringContext is None: 1059 if docstringContext is None:
1029 return 1060 return
1030 1061
1031 summaryLines, lineNumber = self.__getSummaryLines(docstringContext) 1062 summaryLines, lineNumber = self.__getSummaryLines(docstringContext)
1032 if summaryLines: 1063 if summaryLines:
1033 if summaryLines[-1].lstrip().startswith("@"): 1064 if summaryLines[-1].lstrip().startswith("@"):
1034 summaryLines.pop(-1) 1065 summaryLines.pop(-1)
1035 summary = " ".join([s.strip() for s in summaryLines if s]) 1066 summary = " ".join([s.strip() for s in summaryLines if s])
1036 if ( 1067 if (
1037 summary and 1068 summary
1038 not summary.endswith(".") and 1069 and not summary.endswith(".")
1039 summary.split(None, 1)[0].lower() != "constructor" 1070 and summary.split(None, 1)[0].lower() != "constructor"
1040 ): 1071 ):
1041 self.__error( 1072 self.__error(
1042 docstringContext.start() + lineNumber + 1073 docstringContext.start() + lineNumber + len(summaryLines) - 1,
1043 len(summaryLines) - 1, 1074 0,
1044 0, "D231") 1075 "D231",
1045 1076 )
1077
1046 def __checkEricReturn(self, docstringContext, context): 1078 def __checkEricReturn(self, docstringContext, context):
1047 """ 1079 """
1048 Private method to check, that docstrings contain an &#64;return line 1080 Private method to check, that docstrings contain an &#64;return line
1049 if they return anything and don't otherwise. 1081 if they return anything and don't otherwise.
1050 1082
1051 @param docstringContext docstring context (DocStyleContext) 1083 @param docstringContext docstring context (DocStyleContext)
1052 @param context context of the docstring (DocStyleContext) 1084 @param context context of the docstring (DocStyleContext)
1053 """ 1085 """
1054 if docstringContext is None: 1086 if docstringContext is None:
1055 return 1087 return
1056 1088
1057 tokens = list( 1089 tokens = list(tokenize.generate_tokens(StringIO(context.ssource()).readline))
1058 tokenize.generate_tokens(StringIO(context.ssource()).readline)) 1090 return_ = [
1059 return_ = [tokens[i + 1][0] for i, token in enumerate(tokens) 1091 tokens[i + 1][0] for i, token in enumerate(tokens) if token[1] == "return"
1060 if token[1] == "return"] 1092 ]
1061 if "@return" not in docstringContext.ssource(): 1093 if "@return" not in docstringContext.ssource():
1062 if (set(return_) - 1094 if (
1063 {tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE} != 1095 set(return_) - {tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE}
1064 set()): 1096 != set()
1097 ):
1065 self.__error(docstringContext.end(), 0, "D234r") 1098 self.__error(docstringContext.end(), 0, "D234r")
1066 else: 1099 else:
1067 if (set(return_) - 1100 if (
1068 {tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE} == 1101 set(return_) - {tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE}
1069 set()): 1102 == set()
1103 ):
1070 self.__error(docstringContext.end(), 0, "D235r") 1104 self.__error(docstringContext.end(), 0, "D235r")
1071 1105
1072 def __checkEricYield(self, docstringContext, context): 1106 def __checkEricYield(self, docstringContext, context):
1073 """ 1107 """
1074 Private method to check, that docstrings contain an &#64;yield line 1108 Private method to check, that docstrings contain an &#64;yield line
1075 if they return anything and don't otherwise. 1109 if they return anything and don't otherwise.
1076 1110
1077 @param docstringContext docstring context (DocStyleContext) 1111 @param docstringContext docstring context (DocStyleContext)
1078 @param context context of the docstring (DocStyleContext) 1112 @param context context of the docstring (DocStyleContext)
1079 """ 1113 """
1080 if docstringContext is None: 1114 if docstringContext is None:
1081 return 1115 return
1082 1116
1083 tokens = list( 1117 tokens = list(tokenize.generate_tokens(StringIO(context.ssource()).readline))
1084 tokenize.generate_tokens(StringIO(context.ssource()).readline)) 1118 yield_ = [
1085 yield_ = [tokens[i + 1][0] for i, token in enumerate(tokens) 1119 tokens[i + 1][0] for i, token in enumerate(tokens) if token[1] == "yield"
1086 if token[1] == "yield"] 1120 ]
1087 if "@yield" not in docstringContext.ssource(): 1121 if "@yield" not in docstringContext.ssource():
1088 if (set(yield_) - 1122 if set(yield_) - {tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE} != set():
1089 {tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE} !=
1090 set()):
1091 self.__error(docstringContext.end(), 0, "D234y") 1123 self.__error(docstringContext.end(), 0, "D234y")
1092 else: 1124 else:
1093 if (set(yield_) - 1125 if set(yield_) - {tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE} == set():
1094 {tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE} ==
1095 set()):
1096 self.__error(docstringContext.end(), 0, "D235y") 1126 self.__error(docstringContext.end(), 0, "D235y")
1097 1127
1098 def __checkEricFunctionArguments(self, docstringContext, context): 1128 def __checkEricFunctionArguments(self, docstringContext, context):
1099 """ 1129 """
1100 Private method to check, that docstrings contain an &#64;param and/or 1130 Private method to check, that docstrings contain an &#64;param and/or
1101 &#64;keyparam line for each argument. 1131 &#64;keyparam line for each argument.
1102 1132
1103 @param docstringContext docstring context (DocStyleContext) 1133 @param docstringContext docstring context (DocStyleContext)
1104 @param context context of the docstring (DocStyleContext) 1134 @param context context of the docstring (DocStyleContext)
1105 """ 1135 """
1106 if docstringContext is None: 1136 if docstringContext is None:
1107 return 1137 return
1108 1138
1109 try: 1139 try:
1110 tree = ast.parse(context.ssource()) 1140 tree = ast.parse(context.ssource())
1111 except (SyntaxError, TypeError): 1141 except (SyntaxError, TypeError):
1112 return 1142 return
1113 if (isinstance(tree, ast.Module) and len(tree.body) == 1 and 1143 if (
1114 isinstance(tree.body[0], 1144 isinstance(tree, ast.Module)
1115 (ast.FunctionDef, ast.AsyncFunctionDef))): 1145 and len(tree.body) == 1
1146 and isinstance(tree.body[0], (ast.FunctionDef, ast.AsyncFunctionDef))
1147 ):
1116 functionDef = tree.body[0] 1148 functionDef = tree.body[0]
1117 argNames, kwNames = self.__getArgNames(functionDef) 1149 argNames, kwNames = self.__getArgNames(functionDef)
1118 if "self" in argNames: 1150 if "self" in argNames:
1119 argNames.remove("self") 1151 argNames.remove("self")
1120 if "cls" in argNames: 1152 if "cls" in argNames:
1121 argNames.remove("cls") 1153 argNames.remove("cls")
1122 1154
1123 docstring = docstringContext.ssource() 1155 docstring = docstringContext.ssource()
1124 if (docstring.count("@param") + docstring.count("@keyparam") < 1156 if docstring.count("@param") + docstring.count("@keyparam") < len(
1125 len(argNames + kwNames)): 1157 argNames + kwNames
1158 ):
1126 self.__error(docstringContext.end(), 0, "D236") 1159 self.__error(docstringContext.end(), 0, "D236")
1127 elif (docstring.count("@param") + docstring.count("@keyparam") > 1160 elif docstring.count("@param") + docstring.count("@keyparam") > len(
1128 len(argNames + kwNames)): 1161 argNames + kwNames
1162 ):
1129 self.__error(docstringContext.end(), 0, "D237") 1163 self.__error(docstringContext.end(), 0, "D237")
1130 else: 1164 else:
1131 # extract @param and @keyparam from docstring 1165 # extract @param and @keyparam from docstring
1132 args = [] 1166 args = []
1133 kwargs = [] 1167 kwargs = []
1137 if len(paramParts) >= 2: 1171 if len(paramParts) >= 2:
1138 at, name = paramParts[:2] 1172 at, name = paramParts[:2]
1139 if at == "@keyparam": 1173 if at == "@keyparam":
1140 kwargs.append(name.lstrip("*")) 1174 kwargs.append(name.lstrip("*"))
1141 args.append(name.lstrip("*")) 1175 args.append(name.lstrip("*"))
1142 1176
1143 # do the checks 1177 # do the checks
1144 for name in kwNames: 1178 for name in kwNames:
1145 if name not in kwargs: 1179 if name not in kwargs:
1146 self.__error(docstringContext.end(), 0, "D238") 1180 self.__error(docstringContext.end(), 0, "D238")
1147 return 1181 return
1148 if argNames + kwNames != args: 1182 if argNames + kwNames != args:
1149 self.__error(docstringContext.end(), 0, "D239") 1183 self.__error(docstringContext.end(), 0, "D239")
1150 1184
1151 def __checkEricException(self, docstringContext, context): 1185 def __checkEricException(self, docstringContext, context):
1152 """ 1186 """
1153 Private method to check, that docstrings contain an &#64;exception line 1187 Private method to check, that docstrings contain an &#64;exception line
1154 if they raise an exception and don't otherwise. 1188 if they raise an exception and don't otherwise.
1155 1189
1156 Note: This method also checks the raised and documented exceptions for 1190 Note: This method also checks the raised and documented exceptions for
1157 completeness (i.e. raised exceptions that are not documented or 1191 completeness (i.e. raised exceptions that are not documented or
1158 documented exceptions that are not raised) 1192 documented exceptions that are not raised)
1159 1193
1160 @param docstringContext docstring context (DocStyleContext) 1194 @param docstringContext docstring context (DocStyleContext)
1161 @param context context of the docstring (DocStyleContext) 1195 @param context context of the docstring (DocStyleContext)
1162 """ 1196 """
1163 if docstringContext is None: 1197 if docstringContext is None:
1164 return 1198 return
1165 1199
1166 tokens = list( 1200 tokens = list(tokenize.generate_tokens(StringIO(context.ssource()).readline))
1167 tokenize.generate_tokens(StringIO(context.ssource()).readline))
1168 exceptions = set() 1201 exceptions = set()
1169 raisedExceptions = set() 1202 raisedExceptions = set()
1170 tokensLen = len(tokens) 1203 tokensLen = len(tokens)
1171 for i, token in enumerate(tokens): 1204 for i, token in enumerate(tokens):
1172 if token[1] == "raise": 1205 if token[1] == "raise":
1173 exceptions.add(tokens[i + 1][0]) 1206 exceptions.add(tokens[i + 1][0])
1174 if tokens[i + 1][0] == tokenize.NAME: 1207 if tokens[i + 1][0] == tokenize.NAME:
1175 if ( 1208 if tokensLen > (i + 2) and tokens[i + 2][1] == ".":
1176 tokensLen > (i + 2) and 1209 raisedExceptions.add(
1177 tokens[i + 2][1] == "." 1210 "{0}.{1}".format(tokens[i + 1][1], tokens[i + 3][1])
1178 ): 1211 )
1179 raisedExceptions.add("{0}.{1}".format(
1180 tokens[i + 1][1], tokens[i + 3][1]))
1181 else: 1212 else:
1182 raisedExceptions.add(tokens[i + 1][1]) 1213 raisedExceptions.add(tokens[i + 1][1])
1183 1214
1184 if ( 1215 if (
1185 "@exception" not in docstringContext.ssource() and 1216 "@exception" not in docstringContext.ssource()
1186 "@throws" not in docstringContext.ssource() and 1217 and "@throws" not in docstringContext.ssource()
1187 "@raise" not in docstringContext.ssource() 1218 and "@raise" not in docstringContext.ssource()
1188 ): 1219 ):
1189 if (exceptions - 1220 if exceptions - {tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE} != set():
1190 {tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE} !=
1191 set()):
1192 self.__error(docstringContext.end(), 0, "D250") 1221 self.__error(docstringContext.end(), 0, "D250")
1193 else: 1222 else:
1194 if (exceptions - 1223 if exceptions - {tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE} == set():
1195 {tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE} ==
1196 set()):
1197 self.__error(docstringContext.end(), 0, "D251") 1224 self.__error(docstringContext.end(), 0, "D251")
1198 else: 1225 else:
1199 # step 1: extract documented exceptions 1226 # step 1: extract documented exceptions
1200 documentedExceptions = set() 1227 documentedExceptions = set()
1201 for line in docstringContext.source(): 1228 for line in docstringContext.source():
1202 line = line.strip() 1229 line = line.strip()
1203 if line.startswith(("@exception", "@throws", "@raise")): 1230 if line.startswith(("@exception", "@throws", "@raise")):
1204 exceptionTokens = line.split(None, 2) 1231 exceptionTokens = line.split(None, 2)
1205 if len(exceptionTokens) >= 2: 1232 if len(exceptionTokens) >= 2:
1206 documentedExceptions.add(exceptionTokens[1]) 1233 documentedExceptions.add(exceptionTokens[1])
1207 1234
1208 # step 2: report undocumented exceptions 1235 # step 2: report undocumented exceptions
1209 for exception in raisedExceptions: 1236 for exception in raisedExceptions:
1210 if exception not in documentedExceptions: 1237 if exception not in documentedExceptions:
1211 self.__error(docstringContext.end(), 0, "D252", 1238 self.__error(docstringContext.end(), 0, "D252", exception)
1212 exception) 1239
1213
1214 # step 3: report undefined signals 1240 # step 3: report undefined signals
1215 for exception in documentedExceptions: 1241 for exception in documentedExceptions:
1216 if exception not in raisedExceptions: 1242 if exception not in raisedExceptions:
1217 self.__error(docstringContext.end(), 0, "D253", 1243 self.__error(docstringContext.end(), 0, "D253", exception)
1218 exception) 1244
1219
1220 def __checkEricSignal(self, docstringContext, context): 1245 def __checkEricSignal(self, docstringContext, context):
1221 """ 1246 """
1222 Private method to check, that docstrings contain an &#64;signal line 1247 Private method to check, that docstrings contain an &#64;signal line
1223 if they define signals and don't otherwise. 1248 if they define signals and don't otherwise.
1224 1249
1225 Note: This method also checks the defined and documented signals for 1250 Note: This method also checks the defined and documented signals for
1226 completeness (i.e. defined signals that are not documented or 1251 completeness (i.e. defined signals that are not documented or
1227 documented signals that are not defined) 1252 documented signals that are not defined)
1228 1253
1229 @param docstringContext docstring context (DocStyleContext) 1254 @param docstringContext docstring context (DocStyleContext)
1230 @param context context of the docstring (DocStyleContext) 1255 @param context context of the docstring (DocStyleContext)
1231 """ 1256 """
1232 if docstringContext is None: 1257 if docstringContext is None:
1233 return 1258 return
1234 1259
1235 tokens = list( 1260 tokens = list(tokenize.generate_tokens(StringIO(context.ssource()).readline))
1236 tokenize.generate_tokens(StringIO(context.ssource()).readline))
1237 definedSignals = set() 1261 definedSignals = set()
1238 for i, token in enumerate(tokens): 1262 for i, token in enumerate(tokens):
1239 if token[1] in ("pyqtSignal", "Signal"): 1263 if token[1] in ("pyqtSignal", "Signal"):
1240 if tokens[i - 1][1] == "." and tokens[i - 2][1] == "QtCore": 1264 if tokens[i - 1][1] == "." and tokens[i - 2][1] == "QtCore":
1241 definedSignals.add(tokens[i - 4][1]) 1265 definedSignals.add(tokens[i - 4][1])
1242 elif tokens[i - 1][1] == "=": 1266 elif tokens[i - 1][1] == "=":
1243 definedSignals.add(tokens[i - 2][1]) 1267 definedSignals.add(tokens[i - 2][1])
1244 1268
1245 if "@signal" not in docstringContext.ssource() and definedSignals: 1269 if "@signal" not in docstringContext.ssource() and definedSignals:
1246 self.__error(docstringContext.end(), 0, "D260") 1270 self.__error(docstringContext.end(), 0, "D260")
1247 elif "@signal" in docstringContext.ssource(): 1271 elif "@signal" in docstringContext.ssource():
1248 if not definedSignals: 1272 if not definedSignals:
1249 self.__error(docstringContext.end(), 0, "D261") 1273 self.__error(docstringContext.end(), 0, "D261")
1257 if len(signalTokens) >= 2: 1281 if len(signalTokens) >= 2:
1258 signal = signalTokens[1] 1282 signal = signalTokens[1]
1259 if "(" in signal: 1283 if "(" in signal:
1260 signal = signal.split("(", 1)[0] 1284 signal = signal.split("(", 1)[0]
1261 documentedSignals.add(signal) 1285 documentedSignals.add(signal)
1262 1286
1263 # step 2: report undocumented signals 1287 # step 2: report undocumented signals
1264 for signal in definedSignals: 1288 for signal in definedSignals:
1265 if signal not in documentedSignals: 1289 if signal not in documentedSignals:
1266 self.__error(docstringContext.end(), 0, "D262", signal) 1290 self.__error(docstringContext.end(), 0, "D262", signal)
1267 1291
1268 # step 3: report undefined signals 1292 # step 3: report undefined signals
1269 for signal in documentedSignals: 1293 for signal in documentedSignals:
1270 if signal not in definedSignals: 1294 if signal not in definedSignals:
1271 self.__error(docstringContext.end(), 0, "D263", signal) 1295 self.__error(docstringContext.end(), 0, "D263", signal)
1272 1296
1273 def __checkEricBlankAfterSummary(self, docstringContext, context): 1297 def __checkEricBlankAfterSummary(self, docstringContext, context):
1274 """ 1298 """
1275 Private method to check, that docstring summaries are followed 1299 Private method to check, that docstring summaries are followed
1276 by a blank line. 1300 by a blank line.
1277 1301
1278 @param docstringContext docstring context (DocStyleContext) 1302 @param docstringContext docstring context (DocStyleContext)
1279 @param context context of the docstring (DocStyleContext) 1303 @param context context of the docstring (DocStyleContext)
1280 """ 1304 """
1281 if docstringContext is None: 1305 if docstringContext is None:
1282 return 1306 return
1283 1307
1284 docstrings = docstringContext.source() 1308 docstrings = docstringContext.source()
1285 if len(docstrings) <= 3: 1309 if len(docstrings) <= 3:
1286 # correct/invalid one-liner 1310 # correct/invalid one-liner
1287 return 1311 return
1288 1312
1289 summaryLines, lineNumber = self.__getSummaryLines(docstringContext) 1313 summaryLines, lineNumber = self.__getSummaryLines(docstringContext)
1290 if ( 1314 if (
1291 len(docstrings) - 2 > lineNumber + len(summaryLines) - 1 and 1315 len(docstrings) - 2 > lineNumber + len(summaryLines) - 1
1292 docstrings[lineNumber + len(summaryLines)].strip() 1316 and docstrings[lineNumber + len(summaryLines)].strip()
1293 ): 1317 ):
1294 self.__error(docstringContext.start() + lineNumber, 0, "D246") 1318 self.__error(docstringContext.start() + lineNumber, 0, "D246")
1295 1319
1296 def __checkEricNoBlankBeforeAndAfterClassOrFunction( 1320 def __checkEricNoBlankBeforeAndAfterClassOrFunction(
1297 self, docstringContext, context): 1321 self, docstringContext, context
1322 ):
1298 """ 1323 """
1299 Private method to check, that class and function/method docstrings 1324 Private method to check, that class and function/method docstrings
1300 have no blank line around them. 1325 have no blank line around them.
1301 1326
1302 @param docstringContext docstring context (DocStyleContext) 1327 @param docstringContext docstring context (DocStyleContext)
1303 @param context context of the docstring (DocStyleContext) 1328 @param context context of the docstring (DocStyleContext)
1304 """ 1329 """
1305 if docstringContext is None: 1330 if docstringContext is None:
1306 return 1331 return
1307 1332
1308 contextLines = context.source() 1333 contextLines = context.source()
1309 isClassContext = contextLines[0].lstrip().startswith("class ") 1334 isClassContext = contextLines[0].lstrip().startswith("class ")
1310 cti = 0 1335 cti = 0
1311 while ( 1336 while cti < len(contextLines) and not contextLines[cti].strip().startswith(
1312 cti < len(contextLines) and 1337 ('"""', 'r"""', 'u"""', "'''", "r'''", "u'''")
1313 not contextLines[cti].strip().startswith(
1314 ('"""', 'r"""', 'u"""', "'''", "r'''", "u'''"))
1315 ): 1338 ):
1316 cti += 1 1339 cti += 1
1317 if cti == len(contextLines): 1340 if cti == len(contextLines):
1318 return 1341 return
1319 1342
1320 start = cti 1343 start = cti
1321 if contextLines[cti].strip() in ( 1344 if contextLines[cti].strip() in ('"""', 'r"""', 'u"""', "'''", "r'''", "u'''"):
1322 '"""', 'r"""', 'u"""', "'''", "r'''", "u'''"):
1323 # it is a multi line docstring 1345 # it is a multi line docstring
1324 cti += 1 1346 cti += 1
1325 1347
1326 while ( 1348 while cti < len(contextLines) and not contextLines[cti].strip().endswith(
1327 cti < len(contextLines) and 1349 ('"""', "'''")
1328 not contextLines[cti].strip().endswith(('"""', "'''"))
1329 ): 1350 ):
1330 cti += 1 1351 cti += 1
1331 end = cti 1352 end = cti
1332 if cti >= len(contextLines) - 1: 1353 if cti >= len(contextLines) - 1:
1333 return 1354 return
1334 1355
1335 if isClassContext: 1356 if isClassContext:
1336 if not contextLines[start - 1].strip(): 1357 if not contextLines[start - 1].strip():
1337 self.__error(docstringContext.start(), 0, "D242") 1358 self.__error(docstringContext.start(), 0, "D242")
1338 if not contextLines[end + 1].strip() and self.__docType == "eric": 1359 if not contextLines[end + 1].strip() and self.__docType == "eric":
1339 self.__error(docstringContext.end(), 0, "D243") 1360 self.__error(docstringContext.end(), 0, "D243")
1342 else: 1363 else:
1343 if not contextLines[start - 1].strip(): 1364 if not contextLines[start - 1].strip():
1344 self.__error(docstringContext.start(), 0, "D244") 1365 self.__error(docstringContext.start(), 0, "D244")
1345 if not contextLines[end + 1].strip(): 1366 if not contextLines[end + 1].strip():
1346 if ( 1367 if (
1347 self.__docType == "eric_black" and 1368 self.__docType == "eric_black"
1348 len(contextLines) > end + 2 and 1369 and len(contextLines) > end + 2
1349 contextLines[end + 2].strip().startswith("def ") 1370 and contextLines[end + 2].strip().startswith("def ")
1350 ): 1371 ):
1351 return 1372 return
1352 1373
1353 self.__error(docstringContext.end(), 0, "D245") 1374 self.__error(docstringContext.end(), 0, "D245")
1354 1375
1355 def __checkEricNBlankAfterLastParagraph(self, docstringContext, context): 1376 def __checkEricNBlankAfterLastParagraph(self, docstringContext, context):
1356 """ 1377 """
1357 Private method to check, that the last paragraph of docstrings is 1378 Private method to check, that the last paragraph of docstrings is
1358 not followed by a blank line. 1379 not followed by a blank line.
1359 1380
1360 @param docstringContext docstring context (DocStyleContext) 1381 @param docstringContext docstring context (DocStyleContext)
1361 @param context context of the docstring (DocStyleContext) 1382 @param context context of the docstring (DocStyleContext)
1362 """ 1383 """
1363 if docstringContext is None: 1384 if docstringContext is None:
1364 return 1385 return
1365 1386
1366 docstrings = docstringContext.source() 1387 docstrings = docstringContext.source()
1367 if len(docstrings) <= 3: 1388 if len(docstrings) <= 3:
1368 # correct/invalid one-liner 1389 # correct/invalid one-liner
1369 return 1390 return
1370 1391
1371 if not docstrings[-2].strip(): 1392 if not docstrings[-2].strip():
1372 self.__error(docstringContext.end(), 0, "D247") 1393 self.__error(docstringContext.end(), 0, "D247")
1373 1394
1374 def __checkEricSummary(self, docstringContext, context): 1395 def __checkEricSummary(self, docstringContext, context):
1375 """ 1396 """
1376 Private method to check, that method docstring summaries start with 1397 Private method to check, that method docstring summaries start with
1377 specific words. 1398 specific words.
1378 1399
1379 @param docstringContext docstring context (DocStyleContext) 1400 @param docstringContext docstring context (DocStyleContext)
1380 @param context context of the docstring (DocStyleContext) 1401 @param context context of the docstring (DocStyleContext)
1381 """ 1402 """
1382 if docstringContext is None: 1403 if docstringContext is None:
1383 return 1404 return
1384 1405
1385 summary, lineNumber = self.__getSummaryLine(docstringContext) 1406 summary, lineNumber = self.__getSummaryLine(docstringContext)
1386 if summary: 1407 if summary:
1387 # check, if the first word is 'Constructor', 'Public', 1408 # check, if the first word is 'Constructor', 'Public',
1388 # 'Protected' or 'Private' 1409 # 'Protected' or 'Private'
1389 functionName, arguments = ( 1410 functionName, arguments = (
1390 context.source()[0].lstrip().split()[1].split("(", 1) 1411 context.source()[0].lstrip().split()[1].split("(", 1)
1391 ) 1412 )
1392 firstWord = summary.strip().split(None, 1)[0].lower() 1413 firstWord = summary.strip().split(None, 1)[0].lower()
1393 if functionName == '__init__': 1414 if functionName == "__init__":
1394 if firstWord != 'constructor': 1415 if firstWord != "constructor":
1395 self.__error(docstringContext.start() + lineNumber, 0, 1416 self.__error(
1396 "D232", 'constructor') 1417 docstringContext.start() + lineNumber, 0, "D232", "constructor"
1397 elif ( 1418 )
1398 functionName.startswith('__') and 1419 elif functionName.startswith("__") and functionName.endswith("__"):
1399 functionName.endswith('__') 1420 if firstWord != "special":
1400 ): 1421 self.__error(
1401 if firstWord != 'special': 1422 docstringContext.start() + lineNumber, 0, "D232", "special"
1402 self.__error(docstringContext.start() + lineNumber, 0, 1423 )
1403 "D232", 'special')
1404 elif context.special() == "staticmethod": 1424 elif context.special() == "staticmethod":
1405 secondWord = summary.strip().split(None, 2)[1].lower() 1425 secondWord = summary.strip().split(None, 2)[1].lower()
1406 if firstWord != 'static' and secondWord != 'static': 1426 if firstWord != "static" and secondWord != "static":
1407 self.__error(docstringContext.start() + lineNumber, 0, 1427 self.__error(
1408 "D232", 'static') 1428 docstringContext.start() + lineNumber, 0, "D232", "static"
1409 elif secondWord == 'static': 1429 )
1410 if functionName.startswith(('__', 'on_')): 1430 elif secondWord == "static":
1411 if firstWord != 'private': 1431 if functionName.startswith(("__", "on_")):
1412 self.__error(docstringContext.start() + lineNumber, 1432 if firstWord != "private":
1413 0, "D232", 'private static') 1433 self.__error(
1414 elif ( 1434 docstringContext.start() + lineNumber,
1415 functionName.startswith('_') or 1435 0,
1416 functionName.endswith('Event') 1436 "D232",
1417 ): 1437 "private static",
1418 if firstWord != 'protected': 1438 )
1419 self.__error(docstringContext.start() + lineNumber, 1439 elif functionName.startswith("_") or functionName.endswith("Event"):
1420 0, "D232", 'protected static') 1440 if firstWord != "protected":
1441 self.__error(
1442 docstringContext.start() + lineNumber,
1443 0,
1444 "D232",
1445 "protected static",
1446 )
1421 else: 1447 else:
1422 if firstWord != 'public': 1448 if firstWord != "public":
1423 self.__error(docstringContext.start() + lineNumber, 1449 self.__error(
1424 0, "D232", 'public static') 1450 docstringContext.start() + lineNumber,
1451 0,
1452 "D232",
1453 "public static",
1454 )
1425 elif ( 1455 elif (
1426 arguments.startswith(('cls,', 'cls)')) or 1456 arguments.startswith(("cls,", "cls)"))
1427 context.special() == "classmethod" 1457 or context.special() == "classmethod"
1428 ): 1458 ):
1429 secondWord = summary.strip().split(None, 2)[1].lower() 1459 secondWord = summary.strip().split(None, 2)[1].lower()
1430 if firstWord != 'class' and secondWord != 'class': 1460 if firstWord != "class" and secondWord != "class":
1431 self.__error(docstringContext.start() + lineNumber, 0, 1461 self.__error(
1432 "D232", 'class') 1462 docstringContext.start() + lineNumber, 0, "D232", "class"
1433 elif secondWord == 'class': 1463 )
1434 if functionName.startswith(('__', 'on_')): 1464 elif secondWord == "class":
1435 if firstWord != 'private': 1465 if functionName.startswith(("__", "on_")):
1436 self.__error(docstringContext.start() + lineNumber, 1466 if firstWord != "private":
1437 0, "D232", 'private class') 1467 self.__error(
1438 elif ( 1468 docstringContext.start() + lineNumber,
1439 functionName.startswith('_') or 1469 0,
1440 functionName.endswith('Event') 1470 "D232",
1441 ): 1471 "private class",
1442 if firstWord != 'protected': 1472 )
1443 self.__error(docstringContext.start() + lineNumber, 1473 elif functionName.startswith("_") or functionName.endswith("Event"):
1444 0, "D232", 'protected class') 1474 if firstWord != "protected":
1475 self.__error(
1476 docstringContext.start() + lineNumber,
1477 0,
1478 "D232",
1479 "protected class",
1480 )
1445 else: 1481 else:
1446 if firstWord != 'public': 1482 if firstWord != "public":
1447 self.__error(docstringContext.start() + lineNumber, 1483 self.__error(
1448 0, "D232", 'public class') 1484 docstringContext.start() + lineNumber,
1449 elif functionName.startswith(('__', 'on_')): 1485 0,
1450 if firstWord != 'private': 1486 "D232",
1451 self.__error(docstringContext.start() + lineNumber, 0, 1487 "public class",
1452 "D232", 'private') 1488 )
1453 elif ( 1489 elif functionName.startswith(("__", "on_")):
1454 functionName.startswith('_') or 1490 if firstWord != "private":
1455 functionName.endswith('Event') 1491 self.__error(
1456 ): 1492 docstringContext.start() + lineNumber, 0, "D232", "private"
1457 if firstWord != 'protected': 1493 )
1458 self.__error(docstringContext.start() + lineNumber, 0, 1494 elif functionName.startswith("_") or functionName.endswith("Event"):
1459 "D232", 'protected') 1495 if firstWord != "protected":
1496 self.__error(
1497 docstringContext.start() + lineNumber, 0, "D232", "protected"
1498 )
1460 else: 1499 else:
1461 if firstWord != 'public': 1500 if firstWord != "public":
1462 self.__error(docstringContext.start() + lineNumber, 0, 1501 self.__error(
1463 "D232", 'public') 1502 docstringContext.start() + lineNumber, 0, "D232", "public"
1503 )

eric ide

mercurial