src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Miscellaneous/MiscellaneousChecker.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9274
86fab0c74430
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
25 25
26 26
27 def composeCallPath(node): 27 def composeCallPath(node):
28 """ 28 """
29 Generator function to assemble the call path of a given node. 29 Generator function to assemble the call path of a given node.
30 30
31 @param node node to assemble call path for 31 @param node node to assemble call path for
32 @type ast.Node 32 @type ast.Node
33 @yield call path components 33 @yield call path components
34 @ytype str 34 @ytype str
35 """ 35 """
42 42
43 class MiscellaneousChecker: 43 class MiscellaneousChecker:
44 """ 44 """
45 Class implementing a checker for miscellaneous checks. 45 Class implementing a checker for miscellaneous checks.
46 """ 46 """
47
47 Codes = [ 48 Codes = [
48 ## Coding line 49 ## Coding line
49 "M101", "M102", 50 "M101",
50 51 "M102",
51 ## Copyright 52 ## Copyright
52 "M111", "M112", 53 "M111",
53 54 "M112",
54 ## Shadowed Builtins 55 ## Shadowed Builtins
55 "M131", "M132", 56 "M131",
56 57 "M132",
57 ## Comprehensions 58 ## Comprehensions
58 "M181", "M182", "M183", "M184", "M185", "M186", "M187", "M188", 59 "M181",
60 "M182",
61 "M183",
62 "M184",
63 "M185",
64 "M186",
65 "M187",
66 "M188",
59 "M189", 67 "M189",
60 "M191", "M192", "M193", "M195", "M196", "M197", "M198", 68 "M191",
61 69 "M192",
70 "M193",
71 "M195",
72 "M196",
73 "M197",
74 "M198",
62 ## Dictionaries with sorted keys 75 ## Dictionaries with sorted keys
63 "M201", 76 "M201",
64
65 ## Naive datetime usage 77 ## Naive datetime usage
66 "M301", "M302", "M303", "M304", "M305", "M306", "M307", "M308", 78 "M301",
67 "M311", "M312", "M313", "M314", "M315", 79 "M302",
80 "M303",
81 "M304",
82 "M305",
83 "M306",
84 "M307",
85 "M308",
86 "M311",
87 "M312",
88 "M313",
89 "M314",
90 "M315",
68 "M321", 91 "M321",
69
70 ## sys.version and sys.version_info usage 92 ## sys.version and sys.version_info usage
71 "M401", "M402", "M403", 93 "M401",
72 "M411", "M412", "M413", "M414", 94 "M402",
73 "M421", "M422", "M423", 95 "M403",
74 96 "M411",
97 "M412",
98 "M413",
99 "M414",
100 "M421",
101 "M422",
102 "M423",
75 ## Bugbear 103 ## Bugbear
76 "M501", "M502", "M503", "M504", "M505", "M506", "M507", "M508", 104 "M501",
105 "M502",
106 "M503",
107 "M504",
108 "M505",
109 "M506",
110 "M507",
111 "M508",
77 "M509", 112 "M509",
78 "M511", "M512", "M513", 113 "M511",
79 "M521", "M522", "M523", "M524", 114 "M512",
80 115 "M513",
116 "M521",
117 "M522",
118 "M523",
119 "M524",
81 ## Format Strings 120 ## Format Strings
82 "M601", 121 "M601",
83 "M611", "M612", "M613", 122 "M611",
84 "M621", "M622", "M623", "M624", "M625", 123 "M612",
85 "M631", "M632", 124 "M613",
86 125 "M621",
126 "M622",
127 "M623",
128 "M624",
129 "M625",
130 "M631",
131 "M632",
87 ## Logging 132 ## Logging
88 "M651", "M652", "M653", "M654", "M655", 133 "M651",
89 134 "M652",
135 "M653",
136 "M654",
137 "M655",
90 ## Future statements 138 ## Future statements
91 "M701", "M702", 139 "M701",
92 140 "M702",
93 ## Gettext 141 ## Gettext
94 "M711", 142 "M711",
95
96 ## print 143 ## print
97 "M801", 144 "M801",
98
99 ## one element tuple 145 ## one element tuple
100 "M811", 146 "M811",
101
102 ## Mutable Defaults 147 ## Mutable Defaults
103 "M821", "M822", 148 "M821",
104 149 "M822",
105 ## return statements 150 ## return statements
106 "M831", "M832", "M833", "M834", 151 "M831",
107 152 "M832",
153 "M833",
154 "M834",
108 ## line continuation 155 ## line continuation
109 "M841", 156 "M841",
110
111 ## commented code 157 ## commented code
112 "M891", 158 "M891",
113 ] 159 ]
114 160
115 Formatter = Formatter() 161 Formatter = Formatter()
116 FormatFieldRegex = re.compile(r'^((?:\s|.)*?)(\..*|\[.*\])?$') 162 FormatFieldRegex = re.compile(r"^((?:\s|.)*?)(\..*|\[.*\])?$")
117 163
118 BuiltinsWhiteList = [ 164 BuiltinsWhiteList = [
119 "__name__", 165 "__name__",
120 "__doc__", 166 "__doc__",
121 "credits", 167 "credits",
122 ] 168 ]
123 169
124 def __init__(self, source, filename, tree, select, ignore, expected, 170 def __init__(self, source, filename, tree, select, ignore, expected, repeat, args):
125 repeat, args):
126 """ 171 """
127 Constructor 172 Constructor
128 173
129 @param source source code to be checked 174 @param source source code to be checked
130 @type list of str 175 @type list of str
131 @param filename name of the source file 176 @param filename name of the source file
132 @type str 177 @type str
133 @param tree AST tree of the source code 178 @param tree AST tree of the source code
142 @type bool 187 @type bool
143 @param args dictionary of arguments for the miscellaneous checks 188 @param args dictionary of arguments for the miscellaneous checks
144 @type dict 189 @type dict
145 """ 190 """
146 self.__select = tuple(select) 191 self.__select = tuple(select)
147 self.__ignore = ('',) if select else tuple(ignore) 192 self.__ignore = ("",) if select else tuple(ignore)
148 self.__expected = expected[:] 193 self.__expected = expected[:]
149 self.__repeat = repeat 194 self.__repeat = repeat
150 self.__filename = filename 195 self.__filename = filename
151 self.__source = source[:] 196 self.__source = source[:]
152 self.__tree = copy.deepcopy(tree) 197 self.__tree = copy.deepcopy(tree)
153 self.__args = args 198 self.__args = args
154 199
155 self.__pep3101FormatRegex = re.compile( 200 self.__pep3101FormatRegex = re.compile(
156 r'^(?:[^\'"]*[\'"][^\'"]*[\'"])*\s*%|^\s*%') 201 r'^(?:[^\'"]*[\'"][^\'"]*[\'"])*\s*%|^\s*%'
157 202 )
203
158 import builtins 204 import builtins
159 self.__builtins = [b for b in dir(builtins) 205
160 if b not in self.BuiltinsWhiteList] 206 self.__builtins = [b for b in dir(builtins) if b not in self.BuiltinsWhiteList]
161 207
162 self.__eradicator = Eradicator() 208 self.__eradicator = Eradicator()
163 209
164 # statistics counters 210 # statistics counters
165 self.counters = {} 211 self.counters = {}
166 212
167 # collection of detected errors 213 # collection of detected errors
168 self.errors = [] 214 self.errors = []
169 215
170 checkersWithCodes = [ 216 checkersWithCodes = [
171 (self.__checkCoding, ("M101", "M102")), 217 (self.__checkCoding, ("M101", "M102")),
172 (self.__checkCopyright, ("M111", "M112")), 218 (self.__checkCopyright, ("M111", "M112")),
173 (self.__checkBuiltins, ("M131", "M132")), 219 (self.__checkBuiltins, ("M131", "M132")),
174 (self.__checkComprehensions, ("M181", "M182", "M183", "M184", 220 (
175 "M185", "M186", "M187", "M188", 221 self.__checkComprehensions,
176 "M189", 222 (
177 "M191", "M192", "M193", 223 "M181",
178 "M195", "M196", "M197", "M198")), 224 "M182",
225 "M183",
226 "M184",
227 "M185",
228 "M186",
229 "M187",
230 "M188",
231 "M189",
232 "M191",
233 "M192",
234 "M193",
235 "M195",
236 "M196",
237 "M197",
238 "M198",
239 ),
240 ),
179 (self.__checkDictWithSortedKeys, ("M201",)), 241 (self.__checkDictWithSortedKeys, ("M201",)),
180 (self.__checkDateTime, ("M301", "M302", "M303", "M304", "M305", 242 (
181 "M306", "M307", "M308", "M311", "M312", 243 self.__checkDateTime,
182 "M313", "M314", "M315", "M321")), 244 (
183 (self.__checkSysVersion, ("M401", "M402", "M403", 245 "M301",
184 "M411", "M412", "M413", "M414", 246 "M302",
185 "M421", "M422", "M423")), 247 "M303",
186 (self.__checkBugBear, ("M501", "M502", "M503", "M504", "M505", 248 "M304",
187 "M506", "M507", "M508", "M509", 249 "M305",
188 "M511", "M512", "M513", 250 "M306",
189 "M521", "M522", "M523", "M524")), 251 "M307",
252 "M308",
253 "M311",
254 "M312",
255 "M313",
256 "M314",
257 "M315",
258 "M321",
259 ),
260 ),
261 (
262 self.__checkSysVersion,
263 (
264 "M401",
265 "M402",
266 "M403",
267 "M411",
268 "M412",
269 "M413",
270 "M414",
271 "M421",
272 "M422",
273 "M423",
274 ),
275 ),
276 (
277 self.__checkBugBear,
278 (
279 "M501",
280 "M502",
281 "M503",
282 "M504",
283 "M505",
284 "M506",
285 "M507",
286 "M508",
287 "M509",
288 "M511",
289 "M512",
290 "M513",
291 "M521",
292 "M522",
293 "M523",
294 "M524",
295 ),
296 ),
190 (self.__checkPep3101, ("M601",)), 297 (self.__checkPep3101, ("M601",)),
191 (self.__checkFormatString, ("M611", "M612", "M613", 298 (
192 "M621", "M622", "M623", "M624", "M625", 299 self.__checkFormatString,
193 "M631", "M632")), 300 (
301 "M611",
302 "M612",
303 "M613",
304 "M621",
305 "M622",
306 "M623",
307 "M624",
308 "M625",
309 "M631",
310 "M632",
311 ),
312 ),
194 (self.__checkLogging, ("M651", "M652", "M653", "M654", "M655")), 313 (self.__checkLogging, ("M651", "M652", "M653", "M654", "M655")),
195 (self.__checkFuture, ("M701", "M702")), 314 (self.__checkFuture, ("M701", "M702")),
196 (self.__checkGettext, ("M711",)), 315 (self.__checkGettext, ("M711",)),
197 (self.__checkPrintStatements, ("M801",)), 316 (self.__checkPrintStatements, ("M801",)),
198 (self.__checkTuple, ("M811",)), 317 (self.__checkTuple, ("M811",)),
199 (self.__checkMutableDefault, ("M821", "M822")), 318 (self.__checkMutableDefault, ("M821", "M822")),
200 (self.__checkReturn, ("M831", "M832", "M833", "M834")), 319 (self.__checkReturn, ("M831", "M832", "M833", "M834")),
201 (self.__checkLineContinuation, ("M841",)), 320 (self.__checkLineContinuation, ("M841",)),
202 (self.__checkCommentedCode, ("M891",)), 321 (self.__checkCommentedCode, ("M891",)),
203 ] 322 ]
204 323
205 # the eradicate whitelist 324 # the eradicate whitelist
206 commentedCodeCheckerArgs = self.__args.get( 325 commentedCodeCheckerArgs = self.__args.get(
207 "CommentedCodeChecker", 326 "CommentedCodeChecker",
208 MiscellaneousCheckerDefaultArgs["CommentedCodeChecker"]) 327 MiscellaneousCheckerDefaultArgs["CommentedCodeChecker"],
328 )
209 commentedCodeCheckerWhitelist = commentedCodeCheckerArgs.get( 329 commentedCodeCheckerWhitelist = commentedCodeCheckerArgs.get(
210 "WhiteList", 330 "WhiteList",
211 MiscellaneousCheckerDefaultArgs[ 331 MiscellaneousCheckerDefaultArgs["CommentedCodeChecker"]["WhiteList"],
212 "CommentedCodeChecker"]["WhiteList"]) 332 )
213 self.__eradicator.update_whitelist(commentedCodeCheckerWhitelist, 333 self.__eradicator.update_whitelist(
214 extend_default=False) 334 commentedCodeCheckerWhitelist, extend_default=False
215 335 )
336
216 self.__checkers = [] 337 self.__checkers = []
217 for checker, codes in checkersWithCodes: 338 for checker, codes in checkersWithCodes:
218 if any(not (code and self.__ignoreCode(code)) 339 if any(not (code and self.__ignoreCode(code)) for code in codes):
219 for code in codes):
220 self.__checkers.append(checker) 340 self.__checkers.append(checker)
221 341
222 def __ignoreCode(self, code): 342 def __ignoreCode(self, code):
223 """ 343 """
224 Private method to check if the message code should be ignored. 344 Private method to check if the message code should be ignored.
225 345
226 @param code message code to check for 346 @param code message code to check for
227 @type str 347 @type str
228 @return flag indicating to ignore the given code 348 @return flag indicating to ignore the given code
229 @rtype bool 349 @rtype bool
230 """ 350 """
231 return (code.startswith(self.__ignore) and 351 return code.startswith(self.__ignore) and not code.startswith(self.__select)
232 not code.startswith(self.__select)) 352
233
234 def __error(self, lineNumber, offset, code, *args): 353 def __error(self, lineNumber, offset, code, *args):
235 """ 354 """
236 Private method to record an issue. 355 Private method to record an issue.
237 356
238 @param lineNumber line number of the issue 357 @param lineNumber line number of the issue
239 @type int 358 @type int
240 @param offset position within line of the issue 359 @param offset position within line of the issue
241 @type int 360 @type int
242 @param code message code 361 @param code message code
244 @param args arguments for the message 363 @param args arguments for the message
245 @type list 364 @type list
246 """ 365 """
247 if self.__ignoreCode(code): 366 if self.__ignoreCode(code):
248 return 367 return
249 368
250 if code in self.counters: 369 if code in self.counters:
251 self.counters[code] += 1 370 self.counters[code] += 1
252 else: 371 else:
253 self.counters[code] = 1 372 self.counters[code] = 1
254 373
255 # Don't care about expected codes 374 # Don't care about expected codes
256 if code in self.__expected: 375 if code in self.__expected:
257 return 376 return
258 377
259 if code and (self.counters[code] == 1 or self.__repeat): 378 if code and (self.counters[code] == 1 or self.__repeat):
260 # record the issue with one based line number 379 # record the issue with one based line number
261 self.errors.append( 380 self.errors.append(
262 { 381 {
263 "file": self.__filename, 382 "file": self.__filename,
265 "offset": offset, 384 "offset": offset,
266 "code": code, 385 "code": code,
267 "args": args, 386 "args": args,
268 } 387 }
269 ) 388 )
270 389
271 def run(self): 390 def run(self):
272 """ 391 """
273 Public method to check the given source against miscellaneous 392 Public method to check the given source against miscellaneous
274 conditions. 393 conditions.
275 """ 394 """
276 if not self.__filename: 395 if not self.__filename:
277 # don't do anything, if essential data is missing 396 # don't do anything, if essential data is missing
278 return 397 return
279 398
280 if not self.__checkers: 399 if not self.__checkers:
281 # don't do anything, if no codes were selected 400 # don't do anything, if no codes were selected
282 return 401 return
283 402
284 for check in self.__checkers: 403 for check in self.__checkers:
285 check() 404 check()
286 405
287 def __getCoding(self): 406 def __getCoding(self):
288 """ 407 """
289 Private method to get the defined coding of the source. 408 Private method to get the defined coding of the source.
290 409
291 @return tuple containing the line number and the coding 410 @return tuple containing the line number and the coding
292 @rtype tuple of int and str 411 @rtype tuple of int and str
293 """ 412 """
294 for lineno, line in enumerate(self.__source[:5]): 413 for lineno, line in enumerate(self.__source[:5]):
295 matched = re.search(r'coding[:=]\s*([-\w_.]+)', 414 matched = re.search(r"coding[:=]\s*([-\w_.]+)", line, re.IGNORECASE)
296 line, re.IGNORECASE)
297 if matched: 415 if matched:
298 return lineno, matched.group(1) 416 return lineno, matched.group(1)
299 else: 417 else:
300 return 0, "" 418 return 0, ""
301 419
302 def __checkCoding(self): 420 def __checkCoding(self):
303 """ 421 """
304 Private method to check the presence of a coding line and valid 422 Private method to check the presence of a coding line and valid
305 encodings. 423 encodings.
306 """ 424 """
307 if len(self.__source) == 0: 425 if len(self.__source) == 0:
308 return 426 return
309 427
310 encodings = [e.lower().strip() 428 encodings = [
311 for e in self.__args.get( 429 e.lower().strip()
312 "CodingChecker", 430 for e in self.__args.get(
313 MiscellaneousCheckerDefaultArgs["CodingChecker"]) 431 "CodingChecker", MiscellaneousCheckerDefaultArgs["CodingChecker"]
314 .split(",")] 432 ).split(",")
433 ]
315 lineno, coding = self.__getCoding() 434 lineno, coding = self.__getCoding()
316 if coding: 435 if coding:
317 if coding.lower() not in encodings: 436 if coding.lower() not in encodings:
318 self.__error(lineno, 0, "M102", coding) 437 self.__error(lineno, 0, "M102", coding)
319 else: 438 else:
320 self.__error(0, 0, "M101") 439 self.__error(0, 0, "M101")
321 440
322 def __checkCopyright(self): 441 def __checkCopyright(self):
323 """ 442 """
324 Private method to check the presence of a copyright statement. 443 Private method to check the presence of a copyright statement.
325 """ 444 """
326 source = "".join(self.__source) 445 source = "".join(self.__source)
327 copyrightArgs = self.__args.get( 446 copyrightArgs = self.__args.get(
328 "CopyrightChecker", 447 "CopyrightChecker", MiscellaneousCheckerDefaultArgs["CopyrightChecker"]
329 MiscellaneousCheckerDefaultArgs["CopyrightChecker"]) 448 )
330 copyrightMinFileSize = copyrightArgs.get( 449 copyrightMinFileSize = copyrightArgs.get(
331 "MinFilesize", 450 "MinFilesize",
332 MiscellaneousCheckerDefaultArgs["CopyrightChecker"]["MinFilesize"]) 451 MiscellaneousCheckerDefaultArgs["CopyrightChecker"]["MinFilesize"],
452 )
333 copyrightAuthor = copyrightArgs.get( 453 copyrightAuthor = copyrightArgs.get(
334 "Author", 454 "Author", MiscellaneousCheckerDefaultArgs["CopyrightChecker"]["Author"]
335 MiscellaneousCheckerDefaultArgs["CopyrightChecker"]["Author"]) 455 )
336 copyrightRegexStr = ( 456 copyrightRegexStr = (
337 r"Copyright\s+(\(C\)\s+)?(\d{{4}}\s+-\s+)?\d{{4}}\s+{author}" 457 r"Copyright\s+(\(C\)\s+)?(\d{{4}}\s+-\s+)?\d{{4}}\s+{author}"
338 ) 458 )
339 459
340 tocheck = max(1024, copyrightMinFileSize) 460 tocheck = max(1024, copyrightMinFileSize)
341 topOfSource = source[:tocheck] 461 topOfSource = source[:tocheck]
342 if len(topOfSource) < copyrightMinFileSize: 462 if len(topOfSource) < copyrightMinFileSize:
343 return 463 return
344 464
345 copyrightRe = re.compile(copyrightRegexStr.format(author=r".*"), 465 copyrightRe = re.compile(copyrightRegexStr.format(author=r".*"), re.IGNORECASE)
346 re.IGNORECASE)
347 if not copyrightRe.search(topOfSource): 466 if not copyrightRe.search(topOfSource):
348 self.__error(0, 0, "M111") 467 self.__error(0, 0, "M111")
349 return 468 return
350 469
351 if copyrightAuthor: 470 if copyrightAuthor:
352 copyrightAuthorRe = re.compile( 471 copyrightAuthorRe = re.compile(
353 copyrightRegexStr.format(author=copyrightAuthor), 472 copyrightRegexStr.format(author=copyrightAuthor), re.IGNORECASE
354 re.IGNORECASE) 473 )
355 if not copyrightAuthorRe.search(topOfSource): 474 if not copyrightAuthorRe.search(topOfSource):
356 self.__error(0, 0, "M112") 475 self.__error(0, 0, "M112")
357 476
358 def __checkCommentedCode(self): 477 def __checkCommentedCode(self):
359 """ 478 """
360 Private method to check for commented code. 479 Private method to check for commented code.
361 """ 480 """
362 source = "".join(self.__source) 481 source = "".join(self.__source)
363 commentedCodeCheckerArgs = self.__args.get( 482 commentedCodeCheckerArgs = self.__args.get(
364 "CommentedCodeChecker", 483 "CommentedCodeChecker",
365 MiscellaneousCheckerDefaultArgs["CommentedCodeChecker"]) 484 MiscellaneousCheckerDefaultArgs["CommentedCodeChecker"],
485 )
366 aggressive = commentedCodeCheckerArgs.get( 486 aggressive = commentedCodeCheckerArgs.get(
367 "Aggressive", 487 "Aggressive",
368 MiscellaneousCheckerDefaultArgs[ 488 MiscellaneousCheckerDefaultArgs["CommentedCodeChecker"]["Aggressive"],
369 "CommentedCodeChecker"]["Aggressive"]) 489 )
370 for markedLine in self.__eradicator.commented_out_code_line_numbers( 490 for markedLine in self.__eradicator.commented_out_code_line_numbers(
371 source, aggressive=aggressive): 491 source, aggressive=aggressive
492 ):
372 self.__error(markedLine - 1, 0, "M891") 493 self.__error(markedLine - 1, 0, "M891")
373 494
374 def __checkLineContinuation(self): 495 def __checkLineContinuation(self):
375 """ 496 """
376 Private method to check line continuation using backslash. 497 Private method to check line continuation using backslash.
377 """ 498 """
378 # generate source lines without comments 499 # generate source lines without comments
385 start = comment[2][1] 506 start = comment[2][1]
386 stop = comment[3][1] 507 stop = comment[3][1]
387 content = stripped[lineno - 1] 508 content = stripped[lineno - 1]
388 withoutComment = content[:start] + content[stop:] 509 withoutComment = content[:start] + content[stop:]
389 stripped[lineno - 1] = withoutComment.rstrip() 510 stripped[lineno - 1] = withoutComment.rstrip()
390 511
391 # perform check with 'cleaned' source 512 # perform check with 'cleaned' source
392 for lineIndex, line in enumerate(stripped): 513 for lineIndex, line in enumerate(stripped):
393 strippedLine = line.strip() 514 strippedLine = line.strip()
394 if (strippedLine.endswith('\\') and 515 if strippedLine.endswith("\\") and not strippedLine.startswith(
395 not strippedLine.startswith(('assert', 'with'))): 516 ("assert", "with")
517 ):
396 self.__error(lineIndex, len(line), "M841") 518 self.__error(lineIndex, len(line), "M841")
397 519
398 def __checkPrintStatements(self): 520 def __checkPrintStatements(self):
399 """ 521 """
400 Private method to check for print statements. 522 Private method to check for print statements.
401 """ 523 """
402 for node in ast.walk(self.__tree): 524 for node in ast.walk(self.__tree):
403 if ( 525 if (
404 (isinstance(node, ast.Call) and 526 isinstance(node, ast.Call) and getattr(node.func, "id", None) == "print"
405 getattr(node.func, 'id', None) == 'print') or 527 ) or (hasattr(ast, "Print") and isinstance(node, ast.Print)):
406 (hasattr(ast, 'Print') and isinstance(node, ast.Print))
407 ):
408 self.__error(node.lineno - 1, node.col_offset, "M801") 528 self.__error(node.lineno - 1, node.col_offset, "M801")
409 529
410 def __checkTuple(self): 530 def __checkTuple(self):
411 """ 531 """
412 Private method to check for one element tuples. 532 Private method to check for one element tuples.
413 """ 533 """
414 for node in ast.walk(self.__tree): 534 for node in ast.walk(self.__tree):
415 if ( 535 if isinstance(node, ast.Tuple) and len(node.elts) == 1:
416 isinstance(node, ast.Tuple) and
417 len(node.elts) == 1
418 ):
419 self.__error(node.lineno - 1, node.col_offset, "M811") 536 self.__error(node.lineno - 1, node.col_offset, "M811")
420 537
421 def __checkFuture(self): 538 def __checkFuture(self):
422 """ 539 """
423 Private method to check the __future__ imports. 540 Private method to check the __future__ imports.
424 """ 541 """
425 expectedImports = { 542 expectedImports = {
426 i.strip() 543 i.strip()
427 for i in self.__args.get("FutureChecker", "").split(",") 544 for i in self.__args.get("FutureChecker", "").split(",")
428 if bool(i.strip())} 545 if bool(i.strip())
546 }
429 if len(expectedImports) == 0: 547 if len(expectedImports) == 0:
430 # nothing to check for; disabling the check 548 # nothing to check for; disabling the check
431 return 549 return
432 550
433 imports = set() 551 imports = set()
434 node = None 552 node = None
435 hasCode = False 553 hasCode = False
436 554
437 for node in ast.walk(self.__tree): 555 for node in ast.walk(self.__tree):
438 if (isinstance(node, ast.ImportFrom) and 556 if isinstance(node, ast.ImportFrom) and node.module == "__future__":
439 node.module == '__future__'):
440 imports |= {name.name for name in node.names} 557 imports |= {name.name for name in node.names}
441 elif isinstance(node, ast.Expr): 558 elif isinstance(node, ast.Expr):
442 if not AstUtilities.isString(node.value): 559 if not AstUtilities.isString(node.value):
443 hasCode = True 560 hasCode = True
444 break 561 break
445 elif not ( 562 elif not (AstUtilities.isString(node) or isinstance(node, ast.Module)):
446 AstUtilities.isString(node) or
447 isinstance(node, ast.Module)
448 ):
449 hasCode = True 563 hasCode = True
450 break 564 break
451 565
452 if isinstance(node, ast.Module) or not hasCode: 566 if isinstance(node, ast.Module) or not hasCode:
453 return 567 return
454 568
455 if imports < expectedImports: 569 if imports < expectedImports:
456 if imports: 570 if imports:
457 self.__error(node.lineno - 1, node.col_offset, "M701", 571 self.__error(
458 ", ".join(expectedImports), ", ".join(imports)) 572 node.lineno - 1,
573 node.col_offset,
574 "M701",
575 ", ".join(expectedImports),
576 ", ".join(imports),
577 )
459 else: 578 else:
460 self.__error(node.lineno - 1, node.col_offset, "M702", 579 self.__error(
461 ", ".join(expectedImports)) 580 node.lineno - 1, node.col_offset, "M702", ", ".join(expectedImports)
462 581 )
582
463 def __checkPep3101(self): 583 def __checkPep3101(self):
464 """ 584 """
465 Private method to check for old style string formatting. 585 Private method to check for old style string formatting.
466 """ 586 """
467 for lineno, line in enumerate(self.__source): 587 for lineno, line in enumerate(self.__source):
468 match = self.__pep3101FormatRegex.search(line) 588 match = self.__pep3101FormatRegex.search(line)
469 if match: 589 if match:
470 lineLen = len(line) 590 lineLen = len(line)
471 pos = line.find('%') 591 pos = line.find("%")
472 formatPos = pos 592 formatPos = pos
473 formatter = '%' 593 formatter = "%"
474 if line[pos + 1] == "(": 594 if line[pos + 1] == "(":
475 pos = line.find(")", pos) 595 pos = line.find(")", pos)
476 c = line[pos] 596 c = line[pos]
477 while c not in "diouxXeEfFgGcrs": 597 while c not in "diouxXeEfFgGcrs":
478 pos += 1 598 pos += 1
480 break 600 break
481 c = line[pos] 601 c = line[pos]
482 if c in "diouxXeEfFgGcrs": 602 if c in "diouxXeEfFgGcrs":
483 formatter += c 603 formatter += c
484 self.__error(lineno, formatPos, "M601", formatter) 604 self.__error(lineno, formatPos, "M601", formatter)
485 605
486 def __checkFormatString(self): 606 def __checkFormatString(self):
487 """ 607 """
488 Private method to check string format strings. 608 Private method to check string format strings.
489 """ 609 """
490 coding = self.__getCoding()[1] 610 coding = self.__getCoding()[1]
491 if not coding: 611 if not coding:
492 # default to utf-8 612 # default to utf-8
493 coding = "utf-8" 613 coding = "utf-8"
494 614
495 visitor = TextVisitor() 615 visitor = TextVisitor()
496 visitor.visit(self.__tree) 616 visitor.visit(self.__tree)
497 for node in visitor.nodes: 617 for node in visitor.nodes:
498 text = node.s 618 text = node.s
499 if isinstance(text, bytes): 619 if isinstance(text, bytes):
508 else: 628 else:
509 if node.is_docstring: 629 if node.is_docstring:
510 self.__error(node.lineno - 1, node.col_offset, "M612") 630 self.__error(node.lineno - 1, node.col_offset, "M612")
511 else: 631 else:
512 self.__error(node.lineno - 1, node.col_offset, "M613") 632 self.__error(node.lineno - 1, node.col_offset, "M613")
513 633
514 if node in visitor.calls: 634 if node in visitor.calls:
515 call, strArgs = visitor.calls[node] 635 call, strArgs = visitor.calls[node]
516 636
517 numbers = set() 637 numbers = set()
518 names = set() 638 names = set()
519 # Determine which fields require a keyword and which an arg 639 # Determine which fields require a keyword and which an arg
520 for name in fields: 640 for name in fields:
521 fieldMatch = self.FormatFieldRegex.match(name) 641 fieldMatch = self.FormatFieldRegex.match(name)
526 # negative numbers are considered keywords 646 # negative numbers are considered keywords
527 if number >= 0: 647 if number >= 0:
528 numbers.add(number) 648 numbers.add(number)
529 else: 649 else:
530 names.add(fieldMatch.group(1)) 650 names.add(fieldMatch.group(1))
531 651
532 keywords = {keyword.arg for keyword in call.keywords} 652 keywords = {keyword.arg for keyword in call.keywords}
533 numArgs = len(call.args) 653 numArgs = len(call.args)
534 if strArgs: 654 if strArgs:
535 numArgs -= 1 655 numArgs -= 1
536 hasKwArgs = any(kw.arg is None for kw in call.keywords) 656 hasKwArgs = any(kw.arg is None for kw in call.keywords)
537 hasStarArgs = sum(1 for arg in call.args 657 hasStarArgs = sum(
538 if isinstance(arg, ast.Starred)) 658 1 for arg in call.args if isinstance(arg, ast.Starred)
539 659 )
660
540 if hasKwArgs: 661 if hasKwArgs:
541 keywords.discard(None) 662 keywords.discard(None)
542 if hasStarArgs: 663 if hasStarArgs:
543 numArgs -= 1 664 numArgs -= 1
544 665
545 # if starargs or kwargs is not None, it can't count the 666 # if starargs or kwargs is not None, it can't count the
546 # parameters but at least check if the args are used 667 # parameters but at least check if the args are used
547 if hasKwArgs and not names: 668 if hasKwArgs and not names:
548 # No names but kwargs 669 # No names but kwargs
549 self.__error(call.lineno - 1, call.col_offset, "M623") 670 self.__error(call.lineno - 1, call.col_offset, "M623")
550 if hasStarArgs and not numbers: 671 if hasStarArgs and not numbers:
551 # No numbers but args 672 # No numbers but args
552 self.__error(call.lineno - 1, call.col_offset, "M624") 673 self.__error(call.lineno - 1, call.col_offset, "M624")
553 674
554 if not hasKwArgs and not hasStarArgs: 675 if not hasKwArgs and not hasStarArgs:
555 # can actually verify numbers and names 676 # can actually verify numbers and names
556 for number in sorted(numbers): 677 for number in sorted(numbers):
557 if number >= numArgs: 678 if number >= numArgs:
558 self.__error(call.lineno - 1, call.col_offset, 679 self.__error(
559 "M621", number) 680 call.lineno - 1, call.col_offset, "M621", number
560 681 )
682
561 for name in sorted(names): 683 for name in sorted(names):
562 if name not in keywords: 684 if name not in keywords:
563 self.__error(call.lineno - 1, call.col_offset, 685 self.__error(call.lineno - 1, call.col_offset, "M622", name)
564 "M622", name) 686
565
566 for arg in range(numArgs): 687 for arg in range(numArgs):
567 if arg not in numbers: 688 if arg not in numbers:
568 self.__error(call.lineno - 1, call.col_offset, "M631", 689 self.__error(call.lineno - 1, call.col_offset, "M631", arg)
569 arg) 690
570
571 for keyword in keywords: 691 for keyword in keywords:
572 if keyword not in names: 692 if keyword not in names:
573 self.__error(call.lineno - 1, call.col_offset, "M632", 693 self.__error(call.lineno - 1, call.col_offset, "M632", keyword)
574 keyword) 694
575
576 if implicit and explicit: 695 if implicit and explicit:
577 self.__error(call.lineno - 1, call.col_offset, "M625") 696 self.__error(call.lineno - 1, call.col_offset, "M625")
578 697
579 def __getFields(self, string): 698 def __getFields(self, string):
580 """ 699 """
581 Private method to extract the format field information. 700 Private method to extract the format field information.
582 701
583 @param string format string to be parsed 702 @param string format string to be parsed
584 @type str 703 @type str
585 @return format field information as a tuple with fields, implicit 704 @return format field information as a tuple with fields, implicit
586 field definitions present and explicit field definitions present 705 field definitions present and explicit field definitions present
587 @rtype tuple of set of str, bool, bool 706 @rtype tuple of set of str, bool, bool
590 cnt = itertools.count() 709 cnt = itertools.count()
591 implicit = False 710 implicit = False
592 explicit = False 711 explicit = False
593 try: 712 try:
594 for _literal, field, spec, conv in self.Formatter.parse(string): 713 for _literal, field, spec, conv in self.Formatter.parse(string):
595 if field is not None and (conv is None or conv in 'rsa'): 714 if field is not None and (conv is None or conv in "rsa"):
596 if not field: 715 if not field:
597 field = str(next(cnt)) 716 field = str(next(cnt))
598 implicit = True 717 implicit = True
599 else: 718 else:
600 explicit = True 719 explicit = True
601 fields.add(field) 720 fields.add(field)
602 fields.update(parsedSpec[1] 721 fields.update(
603 for parsedSpec in self.Formatter.parse(spec) 722 parsedSpec[1]
604 if parsedSpec[1] is not None) 723 for parsedSpec in self.Formatter.parse(spec)
724 if parsedSpec[1] is not None
725 )
605 except ValueError: 726 except ValueError:
606 return set(), False, False 727 return set(), False, False
607 else: 728 else:
608 return fields, implicit, explicit 729 return fields, implicit, explicit
609 730
610 def __checkBuiltins(self): 731 def __checkBuiltins(self):
611 """ 732 """
612 Private method to check, if built-ins are shadowed. 733 Private method to check, if built-ins are shadowed.
613 """ 734 """
614 functionDefs = [ast.FunctionDef] 735 functionDefs = [ast.FunctionDef]
615 with contextlib.suppress(AttributeError): 736 with contextlib.suppress(AttributeError):
616 functionDefs.append(ast.AsyncFunctionDef) 737 functionDefs.append(ast.AsyncFunctionDef)
617 738
618 ignoreBuiltinAssignments = self.__args.get( 739 ignoreBuiltinAssignments = self.__args.get(
619 "BuiltinsChecker", 740 "BuiltinsChecker", MiscellaneousCheckerDefaultArgs["BuiltinsChecker"]
620 MiscellaneousCheckerDefaultArgs["BuiltinsChecker"]) 741 )
621 742
622 for node in ast.walk(self.__tree): 743 for node in ast.walk(self.__tree):
623 if isinstance(node, ast.Assign): 744 if isinstance(node, ast.Assign):
624 # assign statement 745 # assign statement
625 for element in node.targets: 746 for element in node.targets:
626 if ( 747 if isinstance(element, ast.Name) and element.id in self.__builtins:
627 isinstance(element, ast.Name) and
628 element.id in self.__builtins
629 ):
630 value = node.value 748 value = node.value
631 if ( 749 if (
632 isinstance(value, ast.Name) and 750 isinstance(value, ast.Name)
633 element.id in ignoreBuiltinAssignments and 751 and element.id in ignoreBuiltinAssignments
634 value.id in ignoreBuiltinAssignments[element.id] 752 and value.id in ignoreBuiltinAssignments[element.id]
635 ): 753 ):
636 # ignore compatibility assignments 754 # ignore compatibility assignments
637 continue 755 continue
638 self.__error(element.lineno - 1, element.col_offset, 756 self.__error(
639 "M131", element.id) 757 element.lineno - 1, element.col_offset, "M131", element.id
758 )
640 elif isinstance(element, (ast.Tuple, ast.List)): 759 elif isinstance(element, (ast.Tuple, ast.List)):
641 for tupleElement in element.elts: 760 for tupleElement in element.elts:
642 if ( 761 if (
643 isinstance(tupleElement, ast.Name) and 762 isinstance(tupleElement, ast.Name)
644 tupleElement.id in self.__builtins 763 and tupleElement.id in self.__builtins
645 ): 764 ):
646 self.__error(tupleElement.lineno - 1, 765 self.__error(
647 tupleElement.col_offset, 766 tupleElement.lineno - 1,
648 "M131", tupleElement.id) 767 tupleElement.col_offset,
768 "M131",
769 tupleElement.id,
770 )
649 elif isinstance(node, ast.For): 771 elif isinstance(node, ast.For):
650 # for loop 772 # for loop
651 target = node.target 773 target = node.target
652 if ( 774 if isinstance(target, ast.Name) and target.id in self.__builtins:
653 isinstance(target, ast.Name) and 775 self.__error(
654 target.id in self.__builtins 776 target.lineno - 1, target.col_offset, "M131", target.id
655 ): 777 )
656 self.__error(target.lineno - 1, target.col_offset,
657 "M131", target.id)
658 elif isinstance(target, (ast.Tuple, ast.List)): 778 elif isinstance(target, (ast.Tuple, ast.List)):
659 for element in target.elts: 779 for element in target.elts:
660 if ( 780 if (
661 isinstance(element, ast.Name) and 781 isinstance(element, ast.Name)
662 element.id in self.__builtins 782 and element.id in self.__builtins
663 ): 783 ):
664 self.__error(element.lineno - 1, 784 self.__error(
665 element.col_offset, 785 element.lineno - 1,
666 "M131", element.id) 786 element.col_offset,
667 elif any(isinstance(node, functionDef) 787 "M131",
668 for functionDef in functionDefs): 788 element.id,
789 )
790 elif any(isinstance(node, functionDef) for functionDef in functionDefs):
669 # (asynchronous) function definition 791 # (asynchronous) function definition
670 for arg in node.args.args: 792 for arg in node.args.args:
671 if ( 793 if isinstance(arg, ast.arg) and arg.arg in self.__builtins:
672 isinstance(arg, ast.arg) and 794 self.__error(arg.lineno - 1, arg.col_offset, "M132", arg.arg)
673 arg.arg in self.__builtins 795
674 ):
675 self.__error(arg.lineno - 1, arg.col_offset,
676 "M132", arg.arg)
677
678 def __checkComprehensions(self): 796 def __checkComprehensions(self):
679 """ 797 """
680 Private method to check some comprehension related things. 798 Private method to check some comprehension related things.
681 """ 799 """
682 for node in ast.walk(self.__tree): 800 for node in ast.walk(self.__tree):
683 if isinstance(node, ast.Call) and isinstance(node.func, ast.Name): 801 if isinstance(node, ast.Call) and isinstance(node.func, ast.Name):
684 nArgs = len(node.args) 802 nArgs = len(node.args)
685 nKwArgs = len(node.keywords) 803 nKwArgs = len(node.keywords)
686 804
687 if ( 805 if (
688 nArgs == 1 and 806 nArgs == 1
689 isinstance(node.args[0], ast.GeneratorExp) and 807 and isinstance(node.args[0], ast.GeneratorExp)
690 node.func.id in ('list', 'set') 808 and node.func.id in ("list", "set")
691 ): 809 ):
692 errorCode = { 810 errorCode = {
693 "list": "M181", 811 "list": "M181",
694 "set": "M182", 812 "set": "M182",
695 }[node.func.id] 813 }[node.func.id]
696 self.__error(node.lineno - 1, node.col_offset, errorCode) 814 self.__error(node.lineno - 1, node.col_offset, errorCode)
697 815
698 elif ( 816 elif (
699 nArgs == 1 and 817 nArgs == 1
700 isinstance(node.args[0], 818 and isinstance(node.args[0], (ast.GeneratorExp, ast.ListComp))
701 (ast.GeneratorExp, ast.ListComp)) and 819 and isinstance(node.args[0].elt, ast.Tuple)
702 isinstance(node.args[0].elt, ast.Tuple) and 820 and len(node.args[0].elt.elts) == 2
703 len(node.args[0].elt.elts) == 2 and 821 and node.func.id == "dict"
704 node.func.id == "dict"
705 ): 822 ):
706 if isinstance(node.args[0], ast.GeneratorExp): 823 if isinstance(node.args[0], ast.GeneratorExp):
707 errorCode = "M183" 824 errorCode = "M183"
708 else: 825 else:
709 errorCode = "M185" 826 errorCode = "M185"
710 self.__error(node.lineno - 1, node.col_offset, errorCode) 827 self.__error(node.lineno - 1, node.col_offset, errorCode)
711 828
712 elif ( 829 elif (
713 nArgs == 1 and 830 nArgs == 1
714 isinstance(node.args[0], ast.ListComp) and 831 and isinstance(node.args[0], ast.ListComp)
715 node.func.id in ('list', 'set') 832 and node.func.id in ("list", "set")
716 ): 833 ):
717 errorCode = { 834 errorCode = {
718 'list': 'M195', 835 "list": "M195",
719 'set': 'M184', 836 "set": "M184",
720 }[node.func.id] 837 }[node.func.id]
721 self.__error(node.lineno - 1, node.col_offset, errorCode) 838 self.__error(node.lineno - 1, node.col_offset, errorCode)
722 839
723 elif nArgs == 1 and ( 840 elif nArgs == 1 and (
724 isinstance(node.args[0], ast.Tuple) and 841 isinstance(node.args[0], ast.Tuple)
725 node.func.id == "tuple" or 842 and node.func.id == "tuple"
726 isinstance(node.args[0], ast.List) and 843 or isinstance(node.args[0], ast.List)
727 node.func.id == "list" 844 and node.func.id == "list"
728 ): 845 ):
729 errorCode = { 846 errorCode = {
730 'tuple': 'M197', 847 "tuple": "M197",
731 'list': 'M198', 848 "list": "M198",
732 }[node.func.id] 849 }[node.func.id]
733 self.__error(node.lineno - 1, node.col_offset, errorCode, 850 self.__error(
734 type(node.args[0]).__name__.lower(), 851 node.lineno - 1,
735 node.func.id) 852 node.col_offset,
736 853 errorCode,
854 type(node.args[0]).__name__.lower(),
855 node.func.id,
856 )
857
737 elif ( 858 elif (
738 nArgs == 1 and 859 nArgs == 1
739 isinstance(node.args[0], (ast.Tuple, ast.List)) and 860 and isinstance(node.args[0], (ast.Tuple, ast.List))
740 node.func.id in ("tuple", "list", "set", "dict") 861 and node.func.id in ("tuple", "list", "set", "dict")
741 ): 862 ):
742 errorCode = { 863 errorCode = {
743 "tuple": "M192", 864 "tuple": "M192",
744 "list": "M193", 865 "list": "M193",
745 "set": "M191", 866 "set": "M191",
746 "dict": "M191", 867 "dict": "M191",
747 }[node.func.id] 868 }[node.func.id]
748 self.__error(node.lineno - 1, node.col_offset, errorCode, 869 self.__error(
749 type(node.args[0]).__name__.lower(), 870 node.lineno - 1,
750 node.func.id) 871 node.col_offset,
751 872 errorCode,
873 type(node.args[0]).__name__.lower(),
874 node.func.id,
875 )
876
752 elif ( 877 elif (
753 nArgs == 0 and 878 nArgs == 0
754 not any(isinstance(a, ast.Starred) for a in node.args) and 879 and not any(isinstance(a, ast.Starred) for a in node.args)
755 not any(k.arg is None for k in node.keywords) and 880 and not any(k.arg is None for k in node.keywords)
756 node.func.id == "dict" 881 and node.func.id == "dict"
757 ) or ( 882 ) or (
758 nArgs == 0 and 883 nArgs == 0 and nKwArgs == 0 and node.func.id in ("tuple", "list")
759 nKwArgs == 0 and
760 node.func.id in ("tuple", "list")
761 ): 884 ):
762 self.__error(node.lineno - 1, node.col_offset, "M186", 885 self.__error(node.lineno - 1, node.col_offset, "M186", node.func.id)
763 node.func.id) 886
764
765 elif ( 887 elif (
766 node.func.id in {"list", "reversed"} and 888 node.func.id in {"list", "reversed"}
767 nArgs > 0 and 889 and nArgs > 0
768 isinstance(node.args[0], ast.Call) and 890 and isinstance(node.args[0], ast.Call)
769 isinstance(node.args[0].func, ast.Name) and 891 and isinstance(node.args[0].func, ast.Name)
770 node.args[0].func.id == "sorted" 892 and node.args[0].func.id == "sorted"
771 ): 893 ):
772 if node.func.id == "reversed": 894 if node.func.id == "reversed":
773 reverseFlagValue = False 895 reverseFlagValue = False
774 for kw in node.args[0].keywords: 896 for kw in node.args[0].keywords:
775 if kw.arg != "reverse": 897 if kw.arg != "reverse":
781 else: 903 else:
782 # Complex value 904 # Complex value
783 reverseFlagValue = None 905 reverseFlagValue = None
784 906
785 if reverseFlagValue is None: 907 if reverseFlagValue is None:
786 self.__error(node.lineno - 1, node.col_offset, 908 self.__error(
787 "M187a", node.func.id, 909 node.lineno - 1,
788 node.args[0].func.id) 910 node.col_offset,
911 "M187a",
912 node.func.id,
913 node.args[0].func.id,
914 )
789 else: 915 else:
790 self.__error(node.lineno - 1, node.col_offset, 916 self.__error(
791 "M187b", node.func.id, 917 node.lineno - 1,
792 node.args[0].func.id, 918 node.col_offset,
793 not reverseFlagValue) 919 "M187b",
920 node.func.id,
921 node.args[0].func.id,
922 not reverseFlagValue,
923 )
794 else: 924 else:
795 self.__error(node.lineno - 1, node.col_offset, 925 self.__error(
796 "M187c", node.func.id, 926 node.lineno - 1,
797 node.args[0].func.id) 927 node.col_offset,
798 928 "M187c",
929 node.func.id,
930 node.args[0].func.id,
931 )
932
799 elif ( 933 elif (
800 nArgs > 0 and 934 nArgs > 0
801 isinstance(node.args[0], ast.Call) and 935 and isinstance(node.args[0], ast.Call)
802 isinstance(node.args[0].func, ast.Name) and 936 and isinstance(node.args[0].func, ast.Name)
803 ( 937 and (
804 ( 938 (
805 node.func.id in {"set", "sorted"} and 939 node.func.id in {"set", "sorted"}
806 node.args[0].func.id in { 940 and node.args[0].func.id
807 "list", "reversed", "sorted", "tuple"} 941 in {"list", "reversed", "sorted", "tuple"}
808 ) or (
809 node.func.id in {"list", "tuple"} and
810 node.args[0].func.id in {"list", "tuple"}
811 ) or (
812 node.func.id == "set" and
813 node.args[0].func.id == "set"
814 ) 942 )
943 or (
944 node.func.id in {"list", "tuple"}
945 and node.args[0].func.id in {"list", "tuple"}
946 )
947 or (node.func.id == "set" and node.args[0].func.id == "set")
815 ) 948 )
816 ): 949 ):
817 self.__error(node.lineno - 1, node.col_offset, "M188", 950 self.__error(
818 node.args[0].func.id, node.func.id) 951 node.lineno - 1,
819 952 node.col_offset,
953 "M188",
954 node.args[0].func.id,
955 node.func.id,
956 )
957
820 elif ( 958 elif (
821 node.func.id in {"reversed", "set", "sorted"} and 959 node.func.id in {"reversed", "set", "sorted"}
822 nArgs > 0 and 960 and nArgs > 0
823 isinstance(node.args[0], ast.Subscript) and 961 and isinstance(node.args[0], ast.Subscript)
824 isinstance(node.args[0].slice, ast.Slice) and 962 and isinstance(node.args[0].slice, ast.Slice)
825 node.args[0].slice.lower is None and 963 and node.args[0].slice.lower is None
826 node.args[0].slice.upper is None and 964 and node.args[0].slice.upper is None
827 isinstance(node.args[0].slice.step, ast.UnaryOp) and 965 and isinstance(node.args[0].slice.step, ast.UnaryOp)
828 isinstance(node.args[0].slice.step.op, ast.USub) and 966 and isinstance(node.args[0].slice.step.op, ast.USub)
829 isinstance(node.args[0].slice.step.operand, ast.Num) and 967 and isinstance(node.args[0].slice.step.operand, ast.Num)
830 node.args[0].slice.step.operand.n == 1 968 and node.args[0].slice.step.operand.n == 1
831 ): 969 ):
832 self.__error(node.lineno - 1, node.col_offset, 970 self.__error(node.lineno - 1, node.col_offset, "M189", node.func.id)
833 "M189", node.func.id) 971
834 972 elif isinstance(node, (ast.ListComp, ast.SetComp)) and (
835 elif ( 973 len(node.generators) == 1
836 isinstance(node, (ast.ListComp, ast.SetComp)) and ( 974 and not node.generators[0].ifs
837 len(node.generators) == 1 and 975 and not node.generators[0].is_async
838 not node.generators[0].ifs and 976 and (
839 not node.generators[0].is_async and ( 977 isinstance(node.elt, ast.Name)
840 isinstance(node.elt, ast.Name) and 978 and isinstance(node.generators[0].target, ast.Name)
841 isinstance(node.generators[0].target, ast.Name) and 979 and node.elt.id == node.generators[0].target.id
842 node.elt.id == node.generators[0].target.id
843 )
844 ) 980 )
845 ): 981 ):
846 compType = { 982 compType = {
847 ast.DictComp: "dict", 983 ast.DictComp: "dict",
848 ast.ListComp: "list", 984 ast.ListComp: "list",
849 ast.SetComp: "set", 985 ast.SetComp: "set",
850 }[node.__class__] 986 }[node.__class__]
851 987
852 self.__error(node.lineno - 1, node.col_offset, 988 self.__error(node.lineno - 1, node.col_offset, "M196", compType)
853 "M196", compType) 989
854
855 def __checkMutableDefault(self): 990 def __checkMutableDefault(self):
856 """ 991 """
857 Private method to check for use of mutable types as default arguments. 992 Private method to check for use of mutable types as default arguments.
858 """ 993 """
859 mutableTypes = ( 994 mutableTypes = (
880 "frozenset", 1015 "frozenset",
881 ) 1016 )
882 functionDefs = [ast.FunctionDef] 1017 functionDefs = [ast.FunctionDef]
883 with contextlib.suppress(AttributeError): 1018 with contextlib.suppress(AttributeError):
884 functionDefs.append(ast.AsyncFunctionDef) 1019 functionDefs.append(ast.AsyncFunctionDef)
885 1020
886 for node in ast.walk(self.__tree): 1021 for node in ast.walk(self.__tree):
887 if any(isinstance(node, functionDef) 1022 if any(isinstance(node, functionDef) for functionDef in functionDefs):
888 for functionDef in functionDefs):
889 defaults = node.args.defaults[:] 1023 defaults = node.args.defaults[:]
890 with contextlib.suppress(AttributeError): 1024 with contextlib.suppress(AttributeError):
891 defaults += node.args.kw_defaults[:] 1025 defaults += node.args.kw_defaults[:]
892 for default in defaults: 1026 for default in defaults:
893 if any(isinstance(default, mutableType) 1027 if any(
894 for mutableType in mutableTypes): 1028 isinstance(default, mutableType) for mutableType in mutableTypes
1029 ):
895 typeName = type(default).__name__ 1030 typeName = type(default).__name__
896 if isinstance(default, ast.Call): 1031 if isinstance(default, ast.Call):
897 callPath = '.'.join(composeCallPath(default.func)) 1032 callPath = ".".join(composeCallPath(default.func))
898 if callPath in mutableCalls: 1033 if callPath in mutableCalls:
899 self.__error(default.lineno - 1, 1034 self.__error(
900 default.col_offset, 1035 default.lineno - 1,
901 "M823", callPath + "()") 1036 default.col_offset,
1037 "M823",
1038 callPath + "()",
1039 )
902 elif callPath not in immutableCalls: 1040 elif callPath not in immutableCalls:
903 self.__error(default.lineno - 1, 1041 self.__error(
904 default.col_offset, 1042 default.lineno - 1,
905 "M822", typeName) 1043 default.col_offset,
1044 "M822",
1045 typeName,
1046 )
906 else: 1047 else:
907 self.__error(default.lineno - 1, 1048 self.__error(
908 default.col_offset, 1049 default.lineno - 1, default.col_offset, "M821", typeName
909 "M821", typeName) 1050 )
910 1051
911 def __dictShouldBeChecked(self, node): 1052 def __dictShouldBeChecked(self, node):
912 """ 1053 """
913 Private function to test, if the node should be checked. 1054 Private function to test, if the node should be checked.
914 1055
915 @param node reference to the AST node 1056 @param node reference to the AST node
916 @return flag indicating to check the node 1057 @return flag indicating to check the node
917 @rtype bool 1058 @rtype bool
918 """ 1059 """
919 if not all(AstUtilities.isString(key) for key in node.keys): 1060 if not all(AstUtilities.isString(key) for key in node.keys):
920 return False 1061 return False
921 1062
922 if ( 1063 if (
923 "__IGNORE_WARNING__" in self.__source[node.lineno - 1] or 1064 "__IGNORE_WARNING__" in self.__source[node.lineno - 1]
924 "__IGNORE_WARNING_M201__" in self.__source[node.lineno - 1] 1065 or "__IGNORE_WARNING_M201__" in self.__source[node.lineno - 1]
925 ): 1066 ):
926 return False 1067 return False
927 1068
928 lineNumbers = [key.lineno for key in node.keys] 1069 lineNumbers = [key.lineno for key in node.keys]
929 return len(lineNumbers) == len(set(lineNumbers)) 1070 return len(lineNumbers) == len(set(lineNumbers))
930 1071
931 def __checkDictWithSortedKeys(self): 1072 def __checkDictWithSortedKeys(self):
932 """ 1073 """
933 Private method to check, if dictionary keys appear in sorted order. 1074 Private method to check, if dictionary keys appear in sorted order.
934 """ 1075 """
935 for node in ast.walk(self.__tree): 1076 for node in ast.walk(self.__tree):
936 if isinstance(node, ast.Dict) and self.__dictShouldBeChecked(node): 1077 if isinstance(node, ast.Dict) and self.__dictShouldBeChecked(node):
937 for key1, key2 in zip(node.keys, node.keys[1:]): 1078 for key1, key2 in zip(node.keys, node.keys[1:]):
938 if key2.s < key1.s: 1079 if key2.s < key1.s:
939 self.__error(key2.lineno - 1, key2.col_offset, 1080 self.__error(
940 "M201", key2.s, key1.s) 1081 key2.lineno - 1, key2.col_offset, "M201", key2.s, key1.s
941 1082 )
1083
942 def __checkLogging(self): 1084 def __checkLogging(self):
943 """ 1085 """
944 Private method to check logging statements. 1086 Private method to check logging statements.
945 """ 1087 """
946 visitor = LoggingVisitor() 1088 visitor = LoggingVisitor()
947 visitor.visit(self.__tree) 1089 visitor.visit(self.__tree)
948 for node, reason in visitor.violations: 1090 for node, reason in visitor.violations:
949 self.__error(node.lineno - 1, node.col_offset, reason) 1091 self.__error(node.lineno - 1, node.col_offset, reason)
950 1092
951 def __checkGettext(self): 1093 def __checkGettext(self):
952 """ 1094 """
953 Private method to check the 'gettext' import statement. 1095 Private method to check the 'gettext' import statement.
954 """ 1096 """
955 for node in ast.walk(self.__tree): 1097 for node in ast.walk(self.__tree):
956 if ( 1098 if isinstance(node, ast.ImportFrom) and any(
957 isinstance(node, ast.ImportFrom) and 1099 name.asname == "_" for name in node.names
958 any(name.asname == '_' for name in node.names)
959 ): 1100 ):
960 self.__error(node.lineno - 1, node.col_offset, "M711", 1101 self.__error(
961 node.names[0].name) 1102 node.lineno - 1, node.col_offset, "M711", node.names[0].name
962 1103 )
1104
963 def __checkBugBear(self): 1105 def __checkBugBear(self):
964 """ 1106 """
965 Private method for bugbear checks. 1107 Private method for bugbear checks.
966 """ 1108 """
967 visitor = BugBearVisitor() 1109 visitor = BugBearVisitor()
969 for violation in visitor.violations: 1111 for violation in visitor.violations:
970 node = violation[0] 1112 node = violation[0]
971 reason = violation[1] 1113 reason = violation[1]
972 params = violation[2:] 1114 params = violation[2:]
973 self.__error(node.lineno - 1, node.col_offset, reason, *params) 1115 self.__error(node.lineno - 1, node.col_offset, reason, *params)
974 1116
975 def __checkReturn(self): 1117 def __checkReturn(self):
976 """ 1118 """
977 Private method to check return statements. 1119 Private method to check return statements.
978 """ 1120 """
979 visitor = ReturnVisitor() 1121 visitor = ReturnVisitor()
980 visitor.visit(self.__tree) 1122 visitor.visit(self.__tree)
981 for violation in visitor.violations: 1123 for violation in visitor.violations:
982 node = violation[0] 1124 node = violation[0]
983 reason = violation[1] 1125 reason = violation[1]
984 self.__error(node.lineno - 1, node.col_offset, reason) 1126 self.__error(node.lineno - 1, node.col_offset, reason)
985 1127
986 def __checkDateTime(self): 1128 def __checkDateTime(self):
987 """ 1129 """
988 Private method to check use of naive datetime functions. 1130 Private method to check use of naive datetime functions.
989 """ 1131 """
990 # step 1: generate an augmented node tree containing parent info 1132 # step 1: generate an augmented node tree containing parent info
991 # for each child node 1133 # for each child node
992 tree = copy.deepcopy(self.__tree) 1134 tree = copy.deepcopy(self.__tree)
993 for node in ast.walk(tree): 1135 for node in ast.walk(tree):
994 for childNode in ast.iter_child_nodes(node): 1136 for childNode in ast.iter_child_nodes(node):
995 childNode._dtCheckerParent = node 1137 childNode._dtCheckerParent = node
996 1138
997 # step 2: perform checks and report issues 1139 # step 2: perform checks and report issues
998 visitor = DateTimeVisitor() 1140 visitor = DateTimeVisitor()
999 visitor.visit(tree) 1141 visitor.visit(tree)
1000 for violation in visitor.violations: 1142 for violation in visitor.violations:
1001 node = violation[0] 1143 node = violation[0]
1002 reason = violation[1] 1144 reason = violation[1]
1003 self.__error(node.lineno - 1, node.col_offset, reason) 1145 self.__error(node.lineno - 1, node.col_offset, reason)
1004 1146
1005 def __checkSysVersion(self): 1147 def __checkSysVersion(self):
1006 """ 1148 """
1007 Private method to check the use of sys.version and sys.version_info. 1149 Private method to check the use of sys.version and sys.version_info.
1008 """ 1150 """
1009 visitor = SysVersionVisitor() 1151 visitor = SysVersionVisitor()
1019 Class implementing a node visitor for bytes and str instances. 1161 Class implementing a node visitor for bytes and str instances.
1020 1162
1021 It tries to detect docstrings as string of the first expression of each 1163 It tries to detect docstrings as string of the first expression of each
1022 module, class or function. 1164 module, class or function.
1023 """ 1165 """
1166
1024 # modelled after the string format flake8 extension 1167 # modelled after the string format flake8 extension
1025 1168
1026 def __init__(self): 1169 def __init__(self):
1027 """ 1170 """
1028 Constructor 1171 Constructor
1029 """ 1172 """
1030 super().__init__() 1173 super().__init__()
1032 self.calls = {} 1175 self.calls = {}
1033 1176
1034 def __addNode(self, node): 1177 def __addNode(self, node):
1035 """ 1178 """
1036 Private method to add a node to our list of nodes. 1179 Private method to add a node to our list of nodes.
1037 1180
1038 @param node reference to the node to add 1181 @param node reference to the node to add
1039 @type ast.AST 1182 @type ast.AST
1040 """ 1183 """
1041 if not hasattr(node, 'is_docstring'): 1184 if not hasattr(node, "is_docstring"):
1042 node.is_docstring = False 1185 node.is_docstring = False
1043 self.nodes.append(node) 1186 self.nodes.append(node)
1044 1187
1045 def visit_Str(self, node): 1188 def visit_Str(self, node):
1046 """ 1189 """
1047 Public method to record a string node. 1190 Public method to record a string node.
1048 1191
1049 @param node reference to the string node 1192 @param node reference to the string node
1050 @type ast.Str 1193 @type ast.Str
1051 """ 1194 """
1052 self.__addNode(node) 1195 self.__addNode(node)
1053 1196
1054 def visit_Bytes(self, node): 1197 def visit_Bytes(self, node):
1055 """ 1198 """
1056 Public method to record a bytes node. 1199 Public method to record a bytes node.
1057 1200
1058 @param node reference to the bytes node 1201 @param node reference to the bytes node
1059 @type ast.Bytes 1202 @type ast.Bytes
1060 """ 1203 """
1061 self.__addNode(node) 1204 self.__addNode(node)
1062 1205
1063 def visit_Constant(self, node): 1206 def visit_Constant(self, node):
1064 """ 1207 """
1065 Public method to handle constant nodes. 1208 Public method to handle constant nodes.
1066 1209
1067 @param node reference to the bytes node 1210 @param node reference to the bytes node
1068 @type ast.Constant 1211 @type ast.Constant
1069 """ 1212 """
1070 if sys.version_info >= (3, 8, 0): 1213 if sys.version_info >= (3, 8, 0):
1071 if AstUtilities.isBaseString(node): 1214 if AstUtilities.isBaseString(node):
1076 super().generic_visit(node) 1219 super().generic_visit(node)
1077 1220
1078 def __visitDefinition(self, node): 1221 def __visitDefinition(self, node):
1079 """ 1222 """
1080 Private method handling class and function definitions. 1223 Private method handling class and function definitions.
1081 1224
1082 @param node reference to the node to handle 1225 @param node reference to the node to handle
1083 @type ast.FunctionDef, ast.AsyncFunctionDef or ast.ClassDef 1226 @type ast.FunctionDef, ast.AsyncFunctionDef or ast.ClassDef
1084 """ 1227 """
1085 # Manually traverse class or function definition 1228 # Manually traverse class or function definition
1086 # * Handle decorators normally 1229 # * Handle decorators normally
1094 """ 1237 """
1095 Private method to traverse the body of the node manually. 1238 Private method to traverse the body of the node manually.
1096 1239
1097 If the first node is an expression which contains a string or bytes it 1240 If the first node is an expression which contains a string or bytes it
1098 marks that as a docstring. 1241 marks that as a docstring.
1099 1242
1100 @param node reference to the node to traverse 1243 @param node reference to the node to traverse
1101 @type ast.AST 1244 @type ast.AST
1102 """ 1245 """
1103 if ( 1246 if (
1104 node.body and 1247 node.body
1105 isinstance(node.body[0], ast.Expr) and 1248 and isinstance(node.body[0], ast.Expr)
1106 AstUtilities.isBaseString(node.body[0].value) 1249 and AstUtilities.isBaseString(node.body[0].value)
1107 ): 1250 ):
1108 node.body[0].value.is_docstring = True 1251 node.body[0].value.is_docstring = True
1109 1252
1110 for subnode in node.body: 1253 for subnode in node.body:
1111 self.visit(subnode) 1254 self.visit(subnode)
1112 1255
1113 def visit_Module(self, node): 1256 def visit_Module(self, node):
1114 """ 1257 """
1115 Public method to handle a module. 1258 Public method to handle a module.
1116 1259
1117 @param node reference to the node to handle 1260 @param node reference to the node to handle
1118 @type ast.Module 1261 @type ast.Module
1119 """ 1262 """
1120 self.__visitBody(node) 1263 self.__visitBody(node)
1121 1264
1122 def visit_ClassDef(self, node): 1265 def visit_ClassDef(self, node):
1123 """ 1266 """
1124 Public method to handle a class definition. 1267 Public method to handle a class definition.
1125 1268
1126 @param node reference to the node to handle 1269 @param node reference to the node to handle
1127 @type ast.ClassDef 1270 @type ast.ClassDef
1128 """ 1271 """
1129 # Skipped nodes: ('name', 'bases', 'keywords', 'starargs', 'kwargs') 1272 # Skipped nodes: ('name', 'bases', 'keywords', 'starargs', 'kwargs')
1130 self.__visitDefinition(node) 1273 self.__visitDefinition(node)
1131 1274
1132 def visit_FunctionDef(self, node): 1275 def visit_FunctionDef(self, node):
1133 """ 1276 """
1134 Public method to handle a function definition. 1277 Public method to handle a function definition.
1135 1278
1136 @param node reference to the node to handle 1279 @param node reference to the node to handle
1137 @type ast.FunctionDef 1280 @type ast.FunctionDef
1138 """ 1281 """
1139 # Skipped nodes: ('name', 'args', 'returns') 1282 # Skipped nodes: ('name', 'args', 'returns')
1140 self.__visitDefinition(node) 1283 self.__visitDefinition(node)
1141 1284
1142 def visit_AsyncFunctionDef(self, node): 1285 def visit_AsyncFunctionDef(self, node):
1143 """ 1286 """
1144 Public method to handle an asynchronous function definition. 1287 Public method to handle an asynchronous function definition.
1145 1288
1146 @param node reference to the node to handle 1289 @param node reference to the node to handle
1147 @type ast.AsyncFunctionDef 1290 @type ast.AsyncFunctionDef
1148 """ 1291 """
1149 # Skipped nodes: ('name', 'args', 'returns') 1292 # Skipped nodes: ('name', 'args', 'returns')
1150 self.__visitDefinition(node) 1293 self.__visitDefinition(node)
1151 1294
1152 def visit_Call(self, node): 1295 def visit_Call(self, node):
1153 """ 1296 """
1154 Public method to handle a function call. 1297 Public method to handle a function call.
1155 1298
1156 @param node reference to the node to handle 1299 @param node reference to the node to handle
1157 @type ast.Call 1300 @type ast.Call
1158 """ 1301 """
1159 if ( 1302 if isinstance(node.func, ast.Attribute) and node.func.attr == "format":
1160 isinstance(node.func, ast.Attribute) and
1161 node.func.attr == 'format'
1162 ):
1163 if AstUtilities.isBaseString(node.func.value): 1303 if AstUtilities.isBaseString(node.func.value):
1164 self.calls[node.func.value] = (node, False) 1304 self.calls[node.func.value] = (node, False)
1165 elif ( 1305 elif (
1166 isinstance(node.func.value, ast.Name) and 1306 isinstance(node.func.value, ast.Name)
1167 node.func.value.id == 'str' and 1307 and node.func.value.id == "str"
1168 node.args and 1308 and node.args
1169 AstUtilities.isBaseString(node.args[0]) 1309 and AstUtilities.isBaseString(node.args[0])
1170 ): 1310 ):
1171 self.calls[node.args[0]] = (node, True) 1311 self.calls[node.args[0]] = (node, True)
1172 super().generic_visit(node) 1312 super().generic_visit(node)
1173 1313
1174 1314
1175 class LoggingVisitor(ast.NodeVisitor): 1315 class LoggingVisitor(ast.NodeVisitor):
1176 """ 1316 """
1177 Class implementing a node visitor to check logging statements. 1317 Class implementing a node visitor to check logging statements.
1178 """ 1318 """
1319
1179 LoggingLevels = { 1320 LoggingLevels = {
1180 "debug", 1321 "debug",
1181 "critical", 1322 "critical",
1182 "error", 1323 "error",
1183 "info", 1324 "info",
1184 "warn", 1325 "warn",
1185 "warning", 1326 "warning",
1186 } 1327 }
1187 1328
1188 def __init__(self): 1329 def __init__(self):
1189 """ 1330 """
1190 Constructor 1331 Constructor
1191 """ 1332 """
1192 super().__init__() 1333 super().__init__()
1193 1334
1194 self.__currentLoggingCall = None 1335 self.__currentLoggingCall = None
1195 self.__currentLoggingArgument = None 1336 self.__currentLoggingArgument = None
1196 self.__currentLoggingLevel = None 1337 self.__currentLoggingLevel = None
1197 self.__currentExtraKeyword = None 1338 self.__currentExtraKeyword = None
1198 self.violations = [] 1339 self.violations = []
1199 1340
1200 def __withinLoggingStatement(self): 1341 def __withinLoggingStatement(self):
1201 """ 1342 """
1202 Private method to check, if we are inside a logging statement. 1343 Private method to check, if we are inside a logging statement.
1203 1344
1204 @return flag indicating we are inside a logging statement 1345 @return flag indicating we are inside a logging statement
1205 @rtype bool 1346 @rtype bool
1206 """ 1347 """
1207 return self.__currentLoggingCall is not None 1348 return self.__currentLoggingCall is not None
1208 1349
1209 def __withinLoggingArgument(self): 1350 def __withinLoggingArgument(self):
1210 """ 1351 """
1211 Private method to check, if we are inside a logging argument. 1352 Private method to check, if we are inside a logging argument.
1212 1353
1213 @return flag indicating we are inside a logging argument 1354 @return flag indicating we are inside a logging argument
1214 @rtype bool 1355 @rtype bool
1215 """ 1356 """
1216 return self.__currentLoggingArgument is not None 1357 return self.__currentLoggingArgument is not None
1217 1358
1218 def __withinExtraKeyword(self, node): 1359 def __withinExtraKeyword(self, node):
1219 """ 1360 """
1220 Private method to check, if we are inside the extra keyword. 1361 Private method to check, if we are inside the extra keyword.
1221 1362
1222 @param node reference to the node to be checked 1363 @param node reference to the node to be checked
1223 @type ast.keyword 1364 @type ast.keyword
1224 @return flag indicating we are inside the extra keyword 1365 @return flag indicating we are inside the extra keyword
1225 @rtype bool 1366 @rtype bool
1226 """ 1367 """
1227 return ( 1368 return (
1228 self.__currentExtraKeyword is not None and 1369 self.__currentExtraKeyword is not None
1229 self.__currentExtraKeyword != node 1370 and self.__currentExtraKeyword != node
1230 ) 1371 )
1231 1372
1232 def __detectLoggingLevel(self, node): 1373 def __detectLoggingLevel(self, node):
1233 """ 1374 """
1234 Private method to decide whether an AST Call is a logging call. 1375 Private method to decide whether an AST Call is a logging call.
1235 1376
1236 @param node reference to the node to be processed 1377 @param node reference to the node to be processed
1237 @type ast.Call 1378 @type ast.Call
1238 @return logging level 1379 @return logging level
1239 @rtype str or None 1380 @rtype str or None
1240 """ 1381 """
1241 with contextlib.suppress(AttributeError): 1382 with contextlib.suppress(AttributeError):
1242 if node.func.value.id == "warnings": 1383 if node.func.value.id == "warnings":
1243 return None 1384 return None
1244 1385
1245 if node.func.attr in LoggingVisitor.LoggingLevels: 1386 if node.func.attr in LoggingVisitor.LoggingLevels:
1246 return node.func.attr 1387 return node.func.attr
1247 1388
1248 return None 1389 return None
1249 1390
1250 def __isFormatCall(self, node): 1391 def __isFormatCall(self, node):
1251 """ 1392 """
1252 Private method to check if a function call uses format. 1393 Private method to check if a function call uses format.
1258 """ 1399 """
1259 try: 1400 try:
1260 return node.func.attr == "format" 1401 return node.func.attr == "format"
1261 except AttributeError: 1402 except AttributeError:
1262 return False 1403 return False
1263 1404
1264 def visit_Call(self, node): 1405 def visit_Call(self, node):
1265 """ 1406 """
1266 Public method to handle a function call. 1407 Public method to handle a function call.
1267 1408
1268 Every logging statement and string format is expected to be a function 1409 Every logging statement and string format is expected to be a function
1269 call. 1410 call.
1270 1411
1271 @param node reference to the node to be processed 1412 @param node reference to the node to be processed
1272 @type ast.Call 1413 @type ast.Call
1273 """ 1414 """
1274 # we are in a logging statement 1415 # we are in a logging statement
1275 if ( 1416 if (
1276 self.__withinLoggingStatement() and 1417 self.__withinLoggingStatement()
1277 self.__withinLoggingArgument() and 1418 and self.__withinLoggingArgument()
1278 self.__isFormatCall(node) 1419 and self.__isFormatCall(node)
1279 ): 1420 ):
1280 self.violations.append((node, "M651")) 1421 self.violations.append((node, "M651"))
1281 super().generic_visit(node) 1422 super().generic_visit(node)
1282 return 1423 return
1283 1424
1284 loggingLevel = self.__detectLoggingLevel(node) 1425 loggingLevel = self.__detectLoggingLevel(node)
1285 1426
1286 if loggingLevel and self.__currentLoggingLevel is None: 1427 if loggingLevel and self.__currentLoggingLevel is None:
1287 self.__currentLoggingLevel = loggingLevel 1428 self.__currentLoggingLevel = loggingLevel
1288 1429
1289 # we are in some other statement 1430 # we are in some other statement
1290 if loggingLevel is None: 1431 if loggingLevel is None:
1291 super().generic_visit(node) 1432 super().generic_visit(node)
1292 return 1433 return
1293 1434
1294 # we are entering a new logging statement 1435 # we are entering a new logging statement
1295 self.__currentLoggingCall = node 1436 self.__currentLoggingCall = node
1296 1437
1297 if loggingLevel == "warn": 1438 if loggingLevel == "warn":
1298 self.violations.append((node, "M655")) 1439 self.violations.append((node, "M655"))
1299 1440
1300 for index, child in enumerate(ast.iter_child_nodes(node)): 1441 for index, child in enumerate(ast.iter_child_nodes(node)):
1301 if index == 1: 1442 if index == 1:
1302 self.__currentLoggingArgument = child 1443 self.__currentLoggingArgument = child
1303 if ( 1444 if index > 1 and isinstance(child, ast.keyword) and child.arg == "extra":
1304 index > 1 and
1305 isinstance(child, ast.keyword) and
1306 child.arg == "extra"
1307 ):
1308 self.__currentExtraKeyword = child 1445 self.__currentExtraKeyword = child
1309 1446
1310 super().visit(child) 1447 super().visit(child)
1311 1448
1312 self.__currentLoggingArgument = None 1449 self.__currentLoggingArgument = None
1313 self.__currentExtraKeyword = None 1450 self.__currentExtraKeyword = None
1314 1451
1315 self.__currentLoggingCall = None 1452 self.__currentLoggingCall = None
1316 self.__currentLoggingLevel = None 1453 self.__currentLoggingLevel = None
1317 1454
1318 def visit_BinOp(self, node): 1455 def visit_BinOp(self, node):
1319 """ 1456 """
1320 Public method to handle binary operations while processing the first 1457 Public method to handle binary operations while processing the first
1321 logging argument. 1458 logging argument.
1322 1459
1323 @param node reference to the node to be processed 1460 @param node reference to the node to be processed
1324 @type ast.BinOp 1461 @type ast.BinOp
1325 """ 1462 """
1326 if self.__withinLoggingStatement() and self.__withinLoggingArgument(): 1463 if self.__withinLoggingStatement() and self.__withinLoggingArgument():
1327 # handle percent format 1464 # handle percent format
1328 if isinstance(node.op, ast.Mod): 1465 if isinstance(node.op, ast.Mod):
1329 self.violations.append((node, "M652")) 1466 self.violations.append((node, "M652"))
1330 1467
1331 # handle string concat 1468 # handle string concat
1332 if isinstance(node.op, ast.Add): 1469 if isinstance(node.op, ast.Add):
1333 self.violations.append((node, "M653")) 1470 self.violations.append((node, "M653"))
1334 1471
1335 super().generic_visit(node) 1472 super().generic_visit(node)
1336 1473
1337 def visit_JoinedStr(self, node): 1474 def visit_JoinedStr(self, node):
1338 """ 1475 """
1339 Public method to handle f-string arguments. 1476 Public method to handle f-string arguments.
1340 1477
1341 @param node reference to the node to be processed 1478 @param node reference to the node to be processed
1342 @type ast.JoinedStr 1479 @type ast.JoinedStr
1343 """ 1480 """
1344 if ( 1481 if (
1345 self.__withinLoggingStatement() and 1482 self.__withinLoggingStatement()
1346 any(isinstance(i, ast.FormattedValue) for i in node.values) and 1483 and any(isinstance(i, ast.FormattedValue) for i in node.values)
1347 self.__withinLoggingArgument() 1484 and self.__withinLoggingArgument()
1348 ): 1485 ):
1349 self.violations.append((node, "M654")) 1486 self.violations.append((node, "M654"))
1350 1487
1351 super().generic_visit(node) 1488 super().generic_visit(node)
1352 1489
1353 1490
1354 class BugBearVisitor(ast.NodeVisitor): 1491 class BugBearVisitor(ast.NodeVisitor):
1355 """ 1492 """
1356 Class implementing a node visitor to check for various topics. 1493 Class implementing a node visitor to check for various topics.
1357 """ 1494 """
1495
1358 # 1496 #
1359 # This class was implemented along the BugBear flake8 extension (v 19.3.0). 1497 # This class was implemented along the BugBear flake8 extension (v 19.3.0).
1360 # Original: Copyright (c) 2016 Łukasz Langa 1498 # Original: Copyright (c) 2016 Łukasz Langa
1361 # 1499 #
1362 1500
1363 NodeWindowSize = 4 1501 NodeWindowSize = 4
1364 1502
1365 def __init__(self): 1503 def __init__(self):
1366 """ 1504 """
1367 Constructor 1505 Constructor
1368 """ 1506 """
1369 super().__init__() 1507 super().__init__()
1370 1508
1371 self.__nodeStack = [] 1509 self.__nodeStack = []
1372 self.__nodeWindow = [] 1510 self.__nodeWindow = []
1373 self.violations = [] 1511 self.violations = []
1374 1512
1375 def visit(self, node): 1513 def visit(self, node):
1376 """ 1514 """
1377 Public method to traverse a given AST node. 1515 Public method to traverse a given AST node.
1378 1516
1379 @param node AST node to be traversed 1517 @param node AST node to be traversed
1380 @type ast.Node 1518 @type ast.Node
1381 """ 1519 """
1382 self.__nodeStack.append(node) 1520 self.__nodeStack.append(node)
1383 self.__nodeWindow.append(node) 1521 self.__nodeWindow.append(node)
1384 self.__nodeWindow = self.__nodeWindow[-BugBearVisitor.NodeWindowSize:] 1522 self.__nodeWindow = self.__nodeWindow[-BugBearVisitor.NodeWindowSize :]
1385 1523
1386 super().visit(node) 1524 super().visit(node)
1387 1525
1388 self.__nodeStack.pop() 1526 self.__nodeStack.pop()
1389 1527
1390 def visit_UAdd(self, node): 1528 def visit_UAdd(self, node):
1391 """ 1529 """
1392 Public method to handle unary additions. 1530 Public method to handle unary additions.
1393 1531
1394 @param node reference to the node to be processed 1532 @param node reference to the node to be processed
1395 @type ast.UAdd 1533 @type ast.UAdd
1396 """ 1534 """
1397 trailingNodes = list(map(type, self.__nodeWindow[-4:])) 1535 trailingNodes = list(map(type, self.__nodeWindow[-4:]))
1398 if trailingNodes == [ast.UnaryOp, ast.UAdd, ast.UnaryOp, ast.UAdd]: 1536 if trailingNodes == [ast.UnaryOp, ast.UAdd, ast.UnaryOp, ast.UAdd]:
1399 originator = self.__nodeWindow[-4] 1537 originator = self.__nodeWindow[-4]
1400 self.violations.append((originator, "M501")) 1538 self.violations.append((originator, "M501"))
1401 1539
1402 self.generic_visit(node) 1540 self.generic_visit(node)
1403 1541
1404 def visit_Call(self, node): 1542 def visit_Call(self, node):
1405 """ 1543 """
1406 Public method to handle a function call. 1544 Public method to handle a function call.
1407 1545
1408 @param node reference to the node to be processed 1546 @param node reference to the node to be processed
1409 @type ast.Call 1547 @type ast.Call
1410 """ 1548 """
1411 validPaths = ("six", "future.utils", "builtins") 1549 validPaths = ("six", "future.utils", "builtins")
1412 methodsDict = { 1550 methodsDict = {
1413 "M521": ("iterkeys", "itervalues", "iteritems", "iterlists"), 1551 "M521": ("iterkeys", "itervalues", "iteritems", "iterlists"),
1414 "M522": ("viewkeys", "viewvalues", "viewitems", "viewlists"), 1552 "M522": ("viewkeys", "viewvalues", "viewitems", "viewlists"),
1415 "M523": ("next",), 1553 "M523": ("next",),
1416 } 1554 }
1417 1555
1418 if isinstance(node.func, ast.Attribute): 1556 if isinstance(node.func, ast.Attribute):
1419 for code, methods in methodsDict.items(): 1557 for code, methods in methodsDict.items():
1420 if node.func.attr in methods: 1558 if node.func.attr in methods:
1421 callPath = ".".join(composeCallPath(node.func.value)) 1559 callPath = ".".join(composeCallPath(node.func.value))
1422 if callPath not in validPaths: 1560 if callPath not in validPaths:
1428 with contextlib.suppress(AttributeError, IndexError): 1566 with contextlib.suppress(AttributeError, IndexError):
1429 # bad super() call 1567 # bad super() call
1430 if isinstance(node.func, ast.Name) and node.func.id == "super": 1568 if isinstance(node.func, ast.Name) and node.func.id == "super":
1431 args = node.args 1569 args = node.args
1432 if ( 1570 if (
1433 len(args) == 2 and 1571 len(args) == 2
1434 isinstance(args[0], ast.Attribute) and 1572 and isinstance(args[0], ast.Attribute)
1435 isinstance(args[0].value, ast.Name) and 1573 and isinstance(args[0].value, ast.Name)
1436 args[0].value.id == 'self' and 1574 and args[0].value.id == "self"
1437 args[0].attr == '__class__' 1575 and args[0].attr == "__class__"
1438 ): 1576 ):
1439 self.violations.append((node, "M509")) 1577 self.violations.append((node, "M509"))
1440 1578
1441 # bad getattr and setattr 1579 # bad getattr and setattr
1442 if ( 1580 if (
1443 node.func.id in ("getattr", "hasattr") and 1581 node.func.id in ("getattr", "hasattr")
1444 node.args[1].s == "__call__" 1582 and node.args[1].s == "__call__"
1445 ): 1583 ):
1446 self.violations.append((node, "M511")) 1584 self.violations.append((node, "M511"))
1447 if ( 1585 if (
1448 node.func.id == "getattr" and 1586 node.func.id == "getattr"
1449 len(node.args) == 2 and 1587 and len(node.args) == 2
1450 AstUtilities.isString(node.args[1]) 1588 and AstUtilities.isString(node.args[1])
1451 ): 1589 ):
1452 self.violations.append((node, "M512")) 1590 self.violations.append((node, "M512"))
1453 elif ( 1591 elif (
1454 node.func.id == "setattr" and 1592 node.func.id == "setattr"
1455 len(node.args) == 3 and 1593 and len(node.args) == 3
1456 AstUtilities.isString(node.args[1]) 1594 and AstUtilities.isString(node.args[1])
1457 ): 1595 ):
1458 self.violations.append((node, "M513")) 1596 self.violations.append((node, "M513"))
1459 1597
1460 self.generic_visit(node) 1598 self.generic_visit(node)
1461 1599
1462 def visit_Attribute(self, node): 1600 def visit_Attribute(self, node):
1463 """ 1601 """
1464 Public method to handle attributes. 1602 Public method to handle attributes.
1465 1603
1466 @param node reference to the node to be processed 1604 @param node reference to the node to be processed
1467 @type ast.Attribute 1605 @type ast.Attribute
1468 """ 1606 """
1469 callPath = list(composeCallPath(node)) 1607 callPath = list(composeCallPath(node))
1470 1608
1471 if '.'.join(callPath) == 'sys.maxint': 1609 if ".".join(callPath) == "sys.maxint":
1472 self.violations.append((node, "M504")) 1610 self.violations.append((node, "M504"))
1473 1611
1474 elif ( 1612 elif len(callPath) == 2 and callPath[1] == "message":
1475 len(callPath) == 2 and
1476 callPath[1] == 'message'
1477 ):
1478 name = callPath[0] 1613 name = callPath[0]
1479 for elem in reversed(self.__nodeStack[:-1]): 1614 for elem in reversed(self.__nodeStack[:-1]):
1480 if isinstance(elem, ast.ExceptHandler) and elem.name == name: 1615 if isinstance(elem, ast.ExceptHandler) and elem.name == name:
1481 self.violations.append((node, "M505")) 1616 self.violations.append((node, "M505"))
1482 break 1617 break
1483 1618
1484 def visit_Assign(self, node): 1619 def visit_Assign(self, node):
1485 """ 1620 """
1486 Public method to handle assignments. 1621 Public method to handle assignments.
1487 1622
1488 @param node reference to the node to be processed 1623 @param node reference to the node to be processed
1489 @type ast.Assign 1624 @type ast.Assign
1490 """ 1625 """
1491 if isinstance(self.__nodeStack[-2], ast.ClassDef): 1626 if isinstance(self.__nodeStack[-2], ast.ClassDef):
1492 # By using 'hasattr' below we're ignoring starred arguments, slices 1627 # By using 'hasattr' below we're ignoring starred arguments, slices
1493 # and tuples for simplicity. 1628 # and tuples for simplicity.
1494 assignTargets = {t.id for t in node.targets if hasattr(t, 'id')} 1629 assignTargets = {t.id for t in node.targets if hasattr(t, "id")}
1495 if '__metaclass__' in assignTargets: 1630 if "__metaclass__" in assignTargets:
1496 self.violations.append((node, "M524")) 1631 self.violations.append((node, "M524"))
1497 1632
1498 elif len(node.targets) == 1: 1633 elif len(node.targets) == 1:
1499 target = node.targets[0] 1634 target = node.targets[0]
1500 if ( 1635 if (
1501 isinstance(target, ast.Attribute) and 1636 isinstance(target, ast.Attribute)
1502 isinstance(target.value, ast.Name) and 1637 and isinstance(target.value, ast.Name)
1503 (target.value.id, target.attr) == ('os', 'environ') 1638 and (target.value.id, target.attr) == ("os", "environ")
1504 ): 1639 ):
1505 self.violations.append((node, "M506")) 1640 self.violations.append((node, "M506"))
1506 1641
1507 self.generic_visit(node) 1642 self.generic_visit(node)
1508 1643
1509 def visit_For(self, node): 1644 def visit_For(self, node):
1510 """ 1645 """
1511 Public method to handle 'for' statements. 1646 Public method to handle 'for' statements.
1512 1647
1513 @param node reference to the node to be processed 1648 @param node reference to the node to be processed
1514 @type ast.For 1649 @type ast.For
1515 """ 1650 """
1516 self.__checkForM507(node) 1651 self.__checkForM507(node)
1517 1652
1518 self.generic_visit(node) 1653 self.generic_visit(node)
1519 1654
1520 def visit_AsyncFor(self, node): 1655 def visit_AsyncFor(self, node):
1521 """ 1656 """
1522 Public method to handle 'for' statements. 1657 Public method to handle 'for' statements.
1523 1658
1524 @param node reference to the node to be processed 1659 @param node reference to the node to be processed
1525 @type ast.AsyncFor 1660 @type ast.AsyncFor
1526 """ 1661 """
1527 self.__checkForM507(node) 1662 self.__checkForM507(node)
1528 1663
1529 self.generic_visit(node) 1664 self.generic_visit(node)
1530 1665
1531 def visit_Assert(self, node): 1666 def visit_Assert(self, node):
1532 """ 1667 """
1533 Public method to handle 'assert' statements. 1668 Public method to handle 'assert' statements.
1534 1669
1535 @param node reference to the node to be processed 1670 @param node reference to the node to be processed
1536 @type ast.Assert 1671 @type ast.Assert
1537 """ 1672 """
1538 if ( 1673 if (
1539 AstUtilities.isNameConstant(node.test) and 1674 AstUtilities.isNameConstant(node.test)
1540 AstUtilities.getValue(node.test) is False 1675 and AstUtilities.getValue(node.test) is False
1541 ): 1676 ):
1542 self.violations.append((node, "M503")) 1677 self.violations.append((node, "M503"))
1543 1678
1544 self.generic_visit(node) 1679 self.generic_visit(node)
1545 1680
1546 def visit_JoinedStr(self, node): 1681 def visit_JoinedStr(self, node):
1547 """ 1682 """
1548 Public method to handle f-string arguments. 1683 Public method to handle f-string arguments.
1549 1684
1550 @param node reference to the node to be processed 1685 @param node reference to the node to be processed
1551 @type ast.JoinedStr 1686 @type ast.JoinedStr
1552 """ 1687 """
1553 for value in node.values: 1688 for value in node.values:
1554 if isinstance(value, ast.FormattedValue): 1689 if isinstance(value, ast.FormattedValue):
1555 return 1690 return
1556 1691
1557 self.violations.append((node, "M508")) 1692 self.violations.append((node, "M508"))
1558 1693
1559 def __checkForM502(self, node): 1694 def __checkForM502(self, node):
1560 """ 1695 """
1561 Private method to check the use of *strip(). 1696 Private method to check the use of *strip().
1562 1697
1563 @param node reference to the node to be processed 1698 @param node reference to the node to be processed
1564 @type ast.Call 1699 @type ast.Call
1565 """ 1700 """
1566 if node.func.attr not in ("lstrip", "rstrip", "strip"): 1701 if node.func.attr not in ("lstrip", "rstrip", "strip"):
1567 return # method name doesn't match 1702 return # method name doesn't match
1568 1703
1569 if len(node.args) != 1 or not AstUtilities.isString(node.args[0]): 1704 if len(node.args) != 1 or not AstUtilities.isString(node.args[0]):
1570 return # used arguments don't match the builtin strip 1705 return # used arguments don't match the builtin strip
1571 1706
1572 s = AstUtilities.getValue(node.args[0]) 1707 s = AstUtilities.getValue(node.args[0])
1573 if len(s) == 1: 1708 if len(s) == 1:
1574 return # stripping just one character 1709 return # stripping just one character
1575 1710
1576 if len(s) == len(set(s)): 1711 if len(s) == len(set(s)):
1577 return # no characters appear more than once 1712 return # no characters appear more than once
1578 1713
1579 self.violations.append((node, "M502")) 1714 self.violations.append((node, "M502"))
1580 1715
1581 def __checkForM507(self, node): 1716 def __checkForM507(self, node):
1582 """ 1717 """
1583 Private method to check for unused loop variables. 1718 Private method to check for unused loop variables.
1584 1719
1585 @param node reference to the node to be processed 1720 @param node reference to the node to be processed
1586 @type ast.For 1721 @type ast.For
1587 """ 1722 """
1588 targets = NameFinder() 1723 targets = NameFinder()
1589 targets.visit(node.target) 1724 targets.visit(node.target)
1590 ctrlNames = set(filter(lambda s: not s.startswith('_'), 1725 ctrlNames = set(filter(lambda s: not s.startswith("_"), targets.getNames()))
1591 targets.getNames()))
1592 body = NameFinder() 1726 body = NameFinder()
1593 for expr in node.body: 1727 for expr in node.body:
1594 body.visit(expr) 1728 body.visit(expr)
1595 usedNames = set(body.getNames()) 1729 usedNames = set(body.getNames())
1596 for name in sorted(ctrlNames - usedNames): 1730 for name in sorted(ctrlNames - usedNames):
1600 1734
1601 class NameFinder(ast.NodeVisitor): 1735 class NameFinder(ast.NodeVisitor):
1602 """ 1736 """
1603 Class to extract a name out of a tree of nodes. 1737 Class to extract a name out of a tree of nodes.
1604 """ 1738 """
1739
1605 def __init__(self): 1740 def __init__(self):
1606 """ 1741 """
1607 Constructor 1742 Constructor
1608 """ 1743 """
1609 super().__init__() 1744 super().__init__()
1610 1745
1611 self.__names = {} 1746 self.__names = {}
1612 1747
1613 def visit_Name(self, node): 1748 def visit_Name(self, node):
1614 """ 1749 """
1615 Public method to handle 'Name' nodes. 1750 Public method to handle 'Name' nodes.
1616 1751
1617 @param node reference to the node to be processed 1752 @param node reference to the node to be processed
1618 @type ast.Name 1753 @type ast.Name
1619 """ 1754 """
1620 self.__names.setdefault(node.id, []).append(node) 1755 self.__names.setdefault(node.id, []).append(node)
1621 1756
1622 def visit(self, node): 1757 def visit(self, node):
1623 """ 1758 """
1624 Public method to traverse a given AST node. 1759 Public method to traverse a given AST node.
1625 1760
1626 @param node AST node to be traversed 1761 @param node AST node to be traversed
1627 @type ast.Node 1762 @type ast.Node
1628 """ 1763 """
1629 if isinstance(node, list): 1764 if isinstance(node, list):
1630 for elem in node: 1765 for elem in node:
1631 super().visit(elem) 1766 super().visit(elem)
1632 else: 1767 else:
1633 super().visit(node) 1768 super().visit(node)
1634 1769
1635 def getNames(self): 1770 def getNames(self):
1636 """ 1771 """
1637 Public method to return the extracted names and Name nodes. 1772 Public method to return the extracted names and Name nodes.
1638 1773
1639 @return dictionary containing the names as keys and the list of nodes 1774 @return dictionary containing the names as keys and the list of nodes
1640 @rtype dict 1775 @rtype dict
1641 """ 1776 """
1642 return self.__names 1777 return self.__names
1643 1778
1644 1779
1645 class ReturnVisitor(ast.NodeVisitor): 1780 class ReturnVisitor(ast.NodeVisitor):
1646 """ 1781 """
1647 Class implementing a node visitor to check return statements. 1782 Class implementing a node visitor to check return statements.
1648 """ 1783 """
1649 Assigns = 'assigns' 1784
1650 Refs = 'refs' 1785 Assigns = "assigns"
1651 Returns = 'returns' 1786 Refs = "refs"
1652 1787 Returns = "returns"
1788
1653 def __init__(self): 1789 def __init__(self):
1654 """ 1790 """
1655 Constructor 1791 Constructor
1656 """ 1792 """
1657 super().__init__() 1793 super().__init__()
1658 1794
1659 self.__stack = [] 1795 self.__stack = []
1660 self.violations = [] 1796 self.violations = []
1661 self.__loopCount = 0 1797 self.__loopCount = 0
1662 1798
1663 @property 1799 @property
1664 def assigns(self): 1800 def assigns(self):
1665 """ 1801 """
1666 Public method to get the Assign nodes. 1802 Public method to get the Assign nodes.
1667 1803
1668 @return dictionary containing the node name as key and line number 1804 @return dictionary containing the node name as key and line number
1669 as value 1805 as value
1670 @rtype dict 1806 @rtype dict
1671 """ 1807 """
1672 return self.__stack[-1][ReturnVisitor.Assigns] 1808 return self.__stack[-1][ReturnVisitor.Assigns]
1673 1809
1674 @property 1810 @property
1675 def refs(self): 1811 def refs(self):
1676 """ 1812 """
1677 Public method to get the References nodes. 1813 Public method to get the References nodes.
1678 1814
1679 @return dictionary containing the node name as key and line number 1815 @return dictionary containing the node name as key and line number
1680 as value 1816 as value
1681 @rtype dict 1817 @rtype dict
1682 """ 1818 """
1683 return self.__stack[-1][ReturnVisitor.Refs] 1819 return self.__stack[-1][ReturnVisitor.Refs]
1684 1820
1685 @property 1821 @property
1686 def returns(self): 1822 def returns(self):
1687 """ 1823 """
1688 Public method to get the Return nodes. 1824 Public method to get the Return nodes.
1689 1825
1690 @return dictionary containing the node name as key and line number 1826 @return dictionary containing the node name as key and line number
1691 as value 1827 as value
1692 @rtype dict 1828 @rtype dict
1693 """ 1829 """
1694 return self.__stack[-1][ReturnVisitor.Returns] 1830 return self.__stack[-1][ReturnVisitor.Returns]
1695 1831
1696 def visit_For(self, node): 1832 def visit_For(self, node):
1697 """ 1833 """
1698 Public method to handle a for loop. 1834 Public method to handle a for loop.
1699 1835
1700 @param node reference to the for node to handle 1836 @param node reference to the for node to handle
1701 @type ast.For 1837 @type ast.For
1702 """ 1838 """
1703 self.__visitLoop(node) 1839 self.__visitLoop(node)
1704 1840
1705 def visit_AsyncFor(self, node): 1841 def visit_AsyncFor(self, node):
1706 """ 1842 """
1707 Public method to handle an async for loop. 1843 Public method to handle an async for loop.
1708 1844
1709 @param node reference to the async for node to handle 1845 @param node reference to the async for node to handle
1710 @type ast.AsyncFor 1846 @type ast.AsyncFor
1711 """ 1847 """
1712 self.__visitLoop(node) 1848 self.__visitLoop(node)
1713 1849
1714 def visit_While(self, node): 1850 def visit_While(self, node):
1715 """ 1851 """
1716 Public method to handle a while loop. 1852 Public method to handle a while loop.
1717 1853
1718 @param node reference to the while node to handle 1854 @param node reference to the while node to handle
1719 @type ast.While 1855 @type ast.While
1720 """ 1856 """
1721 self.__visitLoop(node) 1857 self.__visitLoop(node)
1722 1858
1723 def __visitLoop(self, node): 1859 def __visitLoop(self, node):
1724 """ 1860 """
1725 Private method to handle loop nodes. 1861 Private method to handle loop nodes.
1726 1862
1727 @param node reference to the loop node to handle 1863 @param node reference to the loop node to handle
1728 @type ast.For, ast.AsyncFor or ast.While 1864 @type ast.For, ast.AsyncFor or ast.While
1729 """ 1865 """
1730 self.__loopCount += 1 1866 self.__loopCount += 1
1731 self.generic_visit(node) 1867 self.generic_visit(node)
1732 self.__loopCount -= 1 1868 self.__loopCount -= 1
1733 1869
1734 def __visitWithStack(self, node): 1870 def __visitWithStack(self, node):
1735 """ 1871 """
1736 Private method to traverse a given function node using a stack. 1872 Private method to traverse a given function node using a stack.
1737 1873
1738 @param node AST node to be traversed 1874 @param node AST node to be traversed
1739 @type ast.FunctionDef or ast.AsyncFunctionDef 1875 @type ast.FunctionDef or ast.AsyncFunctionDef
1740 """ 1876 """
1741 self.__stack.append({ 1877 self.__stack.append(
1742 ReturnVisitor.Assigns: defaultdict(list), 1878 {
1743 ReturnVisitor.Refs: defaultdict(list), 1879 ReturnVisitor.Assigns: defaultdict(list),
1744 ReturnVisitor.Returns: [] 1880 ReturnVisitor.Refs: defaultdict(list),
1745 }) 1881 ReturnVisitor.Returns: [],
1746 1882 }
1883 )
1884
1747 self.generic_visit(node) 1885 self.generic_visit(node)
1748 self.__checkFunction(node) 1886 self.__checkFunction(node)
1749 self.__stack.pop() 1887 self.__stack.pop()
1750 1888
1751 def visit_FunctionDef(self, node): 1889 def visit_FunctionDef(self, node):
1752 """ 1890 """
1753 Public method to handle a function definition. 1891 Public method to handle a function definition.
1754 1892
1755 @param node reference to the node to handle 1893 @param node reference to the node to handle
1756 @type ast.FunctionDef 1894 @type ast.FunctionDef
1757 """ 1895 """
1758 self.__visitWithStack(node) 1896 self.__visitWithStack(node)
1759 1897
1760 def visit_AsyncFunctionDef(self, node): 1898 def visit_AsyncFunctionDef(self, node):
1761 """ 1899 """
1762 Public method to handle a function definition. 1900 Public method to handle a function definition.
1763 1901
1764 @param node reference to the node to handle 1902 @param node reference to the node to handle
1765 @type ast.AsyncFunctionDef 1903 @type ast.AsyncFunctionDef
1766 """ 1904 """
1767 self.__visitWithStack(node) 1905 self.__visitWithStack(node)
1768 1906
1769 def visit_Return(self, node): 1907 def visit_Return(self, node):
1770 """ 1908 """
1771 Public method to handle a return node. 1909 Public method to handle a return node.
1772 1910
1773 @param node reference to the node to handle 1911 @param node reference to the node to handle
1774 @type ast.Return 1912 @type ast.Return
1775 """ 1913 """
1776 self.returns.append(node) 1914 self.returns.append(node)
1777 self.generic_visit(node) 1915 self.generic_visit(node)
1778 1916
1779 def visit_Assign(self, node): 1917 def visit_Assign(self, node):
1780 """ 1918 """
1781 Public method to handle an assign node. 1919 Public method to handle an assign node.
1782 1920
1783 @param node reference to the node to handle 1921 @param node reference to the node to handle
1784 @type ast.Assign 1922 @type ast.Assign
1785 """ 1923 """
1786 if not self.__stack: 1924 if not self.__stack:
1787 return 1925 return
1788 1926
1789 self.generic_visit(node.value) 1927 self.generic_visit(node.value)
1790 1928
1791 target = node.targets[0] 1929 target = node.targets[0]
1792 if ( 1930 if isinstance(target, ast.Tuple) and not isinstance(node.value, ast.Tuple):
1793 isinstance(target, ast.Tuple) and
1794 not isinstance(node.value, ast.Tuple)
1795 ):
1796 # skip unpacking assign 1931 # skip unpacking assign
1797 return 1932 return
1798 1933
1799 self.__visitAssignTarget(target) 1934 self.__visitAssignTarget(target)
1800 1935
1801 def visit_Name(self, node): 1936 def visit_Name(self, node):
1802 """ 1937 """
1803 Public method to handle a name node. 1938 Public method to handle a name node.
1804 1939
1805 @param node reference to the node to handle 1940 @param node reference to the node to handle
1806 @type ast.Name 1941 @type ast.Name
1807 """ 1942 """
1808 if self.__stack: 1943 if self.__stack:
1809 self.refs[node.id].append(node.lineno) 1944 self.refs[node.id].append(node.lineno)
1810 1945
1811 def __visitAssignTarget(self, node): 1946 def __visitAssignTarget(self, node):
1812 """ 1947 """
1813 Private method to handle an assign target node. 1948 Private method to handle an assign target node.
1814 1949
1815 @param node reference to the node to handle 1950 @param node reference to the node to handle
1816 @type ast.AST 1951 @type ast.AST
1817 """ 1952 """
1818 if isinstance(node, ast.Tuple): 1953 if isinstance(node, ast.Tuple):
1819 for elt in node.elts: 1954 for elt in node.elts:
1820 self.__visitAssignTarget(elt) 1955 self.__visitAssignTarget(elt)
1821 return 1956 return
1822 1957
1823 if not self.__loopCount and isinstance(node, ast.Name): 1958 if not self.__loopCount and isinstance(node, ast.Name):
1824 self.assigns[node.id].append(node.lineno) 1959 self.assigns[node.id].append(node.lineno)
1825 return 1960 return
1826 1961
1827 self.generic_visit(node) 1962 self.generic_visit(node)
1828 1963
1829 def __checkFunction(self, node): 1964 def __checkFunction(self, node):
1830 """ 1965 """
1831 Private method to check a function definition node. 1966 Private method to check a function definition node.
1832 1967
1833 @param node reference to the node to check 1968 @param node reference to the node to check
1834 @type ast.AsyncFunctionDef or ast.FunctionDef 1969 @type ast.AsyncFunctionDef or ast.FunctionDef
1835 """ 1970 """
1836 if not self.returns or not node.body: 1971 if not self.returns or not node.body:
1837 return 1972 return
1838 1973
1839 if len(node.body) == 1 and isinstance(node.body[-1], ast.Return): 1974 if len(node.body) == 1 and isinstance(node.body[-1], ast.Return):
1840 # skip functions that consist of `return None` only 1975 # skip functions that consist of `return None` only
1841 return 1976 return
1842 1977
1843 if not self.__resultExists(): 1978 if not self.__resultExists():
1844 self.__checkUnnecessaryReturnNone() 1979 self.__checkUnnecessaryReturnNone()
1845 return 1980 return
1846 1981
1847 self.__checkImplicitReturnValue() 1982 self.__checkImplicitReturnValue()
1848 self.__checkImplicitReturn(node.body[-1]) 1983 self.__checkImplicitReturn(node.body[-1])
1849 1984
1850 for n in self.returns: 1985 for n in self.returns:
1851 if n.value: 1986 if n.value:
1852 self.__checkUnnecessaryAssign(n.value) 1987 self.__checkUnnecessaryAssign(n.value)
1853 1988
1854 def __isNone(self, node): 1989 def __isNone(self, node):
1855 """ 1990 """
1856 Private method to check, if a node value is None. 1991 Private method to check, if a node value is None.
1857 1992
1858 @param node reference to the node to check 1993 @param node reference to the node to check
1859 @type ast.AST 1994 @type ast.AST
1860 @return flag indicating the node contains a None value 1995 @return flag indicating the node contains a None value
1861 @rtype bool 1996 @rtype bool
1862 """ 1997 """
1863 return ( 1998 return AstUtilities.isNameConstant(node) and AstUtilities.getValue(node) is None
1864 AstUtilities.isNameConstant(node) and 1999
1865 AstUtilities.getValue(node) is None
1866 )
1867
1868 def __isFalse(self, node): 2000 def __isFalse(self, node):
1869 """ 2001 """
1870 Private method to check, if a node value is False. 2002 Private method to check, if a node value is False.
1871 2003
1872 @param node reference to the node to check 2004 @param node reference to the node to check
1873 @type ast.AST 2005 @type ast.AST
1874 @return flag indicating the node contains a False value 2006 @return flag indicating the node contains a False value
1875 @rtype bool 2007 @rtype bool
1876 """ 2008 """
1877 return ( 2009 return (
1878 AstUtilities.isNameConstant(node) and 2010 AstUtilities.isNameConstant(node) and AstUtilities.getValue(node) is False
1879 AstUtilities.getValue(node) is False
1880 ) 2011 )
1881 2012
1882 def __resultExists(self): 2013 def __resultExists(self):
1883 """ 2014 """
1884 Private method to check the existance of a return result. 2015 Private method to check the existance of a return result.
1885 2016
1886 @return flag indicating the existence of a return result 2017 @return flag indicating the existence of a return result
1887 @rtype bool 2018 @rtype bool
1888 """ 2019 """
1889 for node in self.returns: 2020 for node in self.returns:
1890 value = node.value 2021 value = node.value
1891 if value and not self.__isNone(value): 2022 if value and not self.__isNone(value):
1892 return True 2023 return True
1893 2024
1894 return False 2025 return False
1895 2026
1896 def __checkImplicitReturnValue(self): 2027 def __checkImplicitReturnValue(self):
1897 """ 2028 """
1898 Private method to check for implicit return values. 2029 Private method to check for implicit return values.
1899 """ 2030 """
1900 for node in self.returns: 2031 for node in self.returns:
1901 if not node.value: 2032 if not node.value:
1902 self.violations.append((node, "M832")) 2033 self.violations.append((node, "M832"))
1903 2034
1904 def __checkUnnecessaryReturnNone(self): 2035 def __checkUnnecessaryReturnNone(self):
1905 """ 2036 """
1906 Private method to check for an unnecessary 'return None' statement. 2037 Private method to check for an unnecessary 'return None' statement.
1907 """ 2038 """
1908 for node in self.returns: 2039 for node in self.returns:
1909 if self.__isNone(node.value): 2040 if self.__isNone(node.value):
1910 self.violations.append((node, "M831")) 2041 self.violations.append((node, "M831"))
1911 2042
1912 def __checkImplicitReturn(self, node): 2043 def __checkImplicitReturn(self, node):
1913 """ 2044 """
1914 Private method to check for an implicit return statement. 2045 Private method to check for an implicit return statement.
1915 2046
1916 @param node reference to the node to check 2047 @param node reference to the node to check
1917 @type ast.AST 2048 @type ast.AST
1918 """ 2049 """
1919 if isinstance(node, ast.If): 2050 if isinstance(node, ast.If):
1920 if not node.body or not node.orelse: 2051 if not node.body or not node.orelse:
1921 self.violations.append((node, "M833")) 2052 self.violations.append((node, "M833"))
1922 return 2053 return
1923 2054
1924 self.__checkImplicitReturn(node.body[-1]) 2055 self.__checkImplicitReturn(node.body[-1])
1925 self.__checkImplicitReturn(node.orelse[-1]) 2056 self.__checkImplicitReturn(node.orelse[-1])
1926 return 2057 return
1927 2058
1928 if isinstance(node, (ast.For, ast.AsyncFor)) and node.orelse: 2059 if isinstance(node, (ast.For, ast.AsyncFor)) and node.orelse:
1929 self.__checkImplicitReturn(node.orelse[-1]) 2060 self.__checkImplicitReturn(node.orelse[-1])
1930 return 2061 return
1931 2062
1932 if isinstance(node, (ast.With, ast.AsyncWith)): 2063 if isinstance(node, (ast.With, ast.AsyncWith)):
1933 self.__checkImplicitReturn(node.body[-1]) 2064 self.__checkImplicitReturn(node.body[-1])
1934 return 2065 return
1935 2066
1936 if isinstance(node, ast.Assert) and self.__isFalse(node.test): 2067 if isinstance(node, ast.Assert) and self.__isFalse(node.test):
1937 return 2068 return
1938 2069
1939 try: 2070 try:
1940 okNodes = (ast.Return, ast.Raise, ast.While, ast.Try) 2071 okNodes = (ast.Return, ast.Raise, ast.While, ast.Try)
1941 except AttributeError: 2072 except AttributeError:
1942 okNodes = (ast.Return, ast.Raise, ast.While) 2073 okNodes = (ast.Return, ast.Raise, ast.While)
1943 if not isinstance(node, okNodes): 2074 if not isinstance(node, okNodes):
1944 self.violations.append((node, "M833")) 2075 self.violations.append((node, "M833"))
1945 2076
1946 def __checkUnnecessaryAssign(self, node): 2077 def __checkUnnecessaryAssign(self, node):
1947 """ 2078 """
1948 Private method to check for an unnecessary assign statement. 2079 Private method to check for an unnecessary assign statement.
1949 2080
1950 @param node reference to the node to check 2081 @param node reference to the node to check
1951 @type ast.AST 2082 @type ast.AST
1952 """ 2083 """
1953 if not isinstance(node, ast.Name): 2084 if not isinstance(node, ast.Name):
1954 return 2085 return
1955 2086
1956 varname = node.id 2087 varname = node.id
1957 returnLineno = node.lineno 2088 returnLineno = node.lineno
1958 2089
1959 if varname not in self.assigns: 2090 if varname not in self.assigns:
1960 return 2091 return
1961 2092
1962 if varname not in self.refs: 2093 if varname not in self.refs:
1963 self.violations.append((node, "M834")) 2094 self.violations.append((node, "M834"))
1964 return 2095 return
1965 2096
1966 if self.__hasRefsBeforeNextAssign(varname, returnLineno): 2097 if self.__hasRefsBeforeNextAssign(varname, returnLineno):
1967 return 2098 return
1968 2099
1969 self.violations.append((node, "M834")) 2100 self.violations.append((node, "M834"))
1970 2101
1971 def __hasRefsBeforeNextAssign(self, varname, returnLineno): 2102 def __hasRefsBeforeNextAssign(self, varname, returnLineno):
1972 """ 2103 """
1973 Private method to check for references before a following assign 2104 Private method to check for references before a following assign
1974 statement. 2105 statement.
1975 2106
1976 @param varname variable name to check for 2107 @param varname variable name to check for
1977 @type str 2108 @type str
1978 @param returnLineno line number of the return statement 2109 @param returnLineno line number of the return statement
1979 @type int 2110 @type int
1980 @return flag indicating the existence of references 2111 @return flag indicating the existence of references
1981 @rtype bool 2112 @rtype bool
1982 """ 2113 """
1983 beforeAssign = 0 2114 beforeAssign = 0
1984 afterAssign = None 2115 afterAssign = None
1985 2116
1986 for lineno in sorted(self.assigns[varname]): 2117 for lineno in sorted(self.assigns[varname]):
1987 if lineno > returnLineno: 2118 if lineno > returnLineno:
1988 afterAssign = lineno 2119 afterAssign = lineno
1989 break 2120 break
1990 2121
1991 if lineno <= returnLineno: 2122 if lineno <= returnLineno:
1992 beforeAssign = lineno 2123 beforeAssign = lineno
1993 2124
1994 for lineno in self.refs[varname]: 2125 for lineno in self.refs[varname]:
1995 if lineno == returnLineno: 2126 if lineno == returnLineno:
1996 continue 2127 continue
1997 2128
1998 if afterAssign: 2129 if afterAssign:
1999 if beforeAssign < lineno <= afterAssign: 2130 if beforeAssign < lineno <= afterAssign:
2000 return True 2131 return True
2001 2132
2002 elif beforeAssign < lineno: 2133 elif beforeAssign < lineno:
2003 return True 2134 return True
2004 2135
2005 return False 2136 return False
2006 2137
2007 2138
2008 class DateTimeVisitor(ast.NodeVisitor): 2139 class DateTimeVisitor(ast.NodeVisitor):
2009 """ 2140 """
2010 Class implementing a node visitor to check datetime function calls. 2141 Class implementing a node visitor to check datetime function calls.
2011 2142
2012 Note: This class is modelled after flake8_datetimez checker. 2143 Note: This class is modelled after flake8_datetimez checker.
2013 """ 2144 """
2145
2014 def __init__(self): 2146 def __init__(self):
2015 """ 2147 """
2016 Constructor 2148 Constructor
2017 """ 2149 """
2018 super().__init__() 2150 super().__init__()
2019 2151
2020 self.violations = [] 2152 self.violations = []
2021 2153
2022 def __getFromKeywords(self, keywords, name): 2154 def __getFromKeywords(self, keywords, name):
2023 """ 2155 """
2024 Private method to get a keyword node given its name. 2156 Private method to get a keyword node given its name.
2025 2157
2026 @param keywords list of keyword argument nodes 2158 @param keywords list of keyword argument nodes
2027 @type list of ast.AST 2159 @type list of ast.AST
2028 @param name name of the keyword node 2160 @param name name of the keyword node
2029 @type str 2161 @type str
2030 @return keyword node 2162 @return keyword node
2031 @rtype ast.AST 2163 @rtype ast.AST
2032 """ 2164 """
2033 for keyword in keywords: 2165 for keyword in keywords:
2034 if keyword.arg == name: 2166 if keyword.arg == name:
2035 return keyword 2167 return keyword
2036 2168
2037 return None 2169 return None
2038 2170
2039 def visit_Call(self, node): 2171 def visit_Call(self, node):
2040 """ 2172 """
2041 Public method to handle a function call. 2173 Public method to handle a function call.
2042 2174
2043 Every datetime related function call is check for use of the naive 2175 Every datetime related function call is check for use of the naive
2044 variant (i.e. use without TZ info). 2176 variant (i.e. use without TZ info).
2045 2177
2046 @param node reference to the node to be processed 2178 @param node reference to the node to be processed
2047 @type ast.Call 2179 @type ast.Call
2048 """ 2180 """
2049 # datetime.something() 2181 # datetime.something()
2050 isDateTimeClass = ( 2182 isDateTimeClass = (
2051 isinstance(node.func, ast.Attribute) and 2183 isinstance(node.func, ast.Attribute)
2052 isinstance(node.func.value, ast.Name) and 2184 and isinstance(node.func.value, ast.Name)
2053 node.func.value.id == 'datetime') 2185 and node.func.value.id == "datetime"
2054 2186 )
2187
2055 # datetime.datetime.something() 2188 # datetime.datetime.something()
2056 isDateTimeModuleAndClass = ( 2189 isDateTimeModuleAndClass = (
2057 isinstance(node.func, ast.Attribute) and 2190 isinstance(node.func, ast.Attribute)
2058 isinstance(node.func.value, ast.Attribute) and 2191 and isinstance(node.func.value, ast.Attribute)
2059 node.func.value.attr == 'datetime' and 2192 and node.func.value.attr == "datetime"
2060 isinstance(node.func.value.value, ast.Name) and 2193 and isinstance(node.func.value.value, ast.Name)
2061 node.func.value.value.id == 'datetime') 2194 and node.func.value.value.id == "datetime"
2062 2195 )
2196
2063 if isDateTimeClass: 2197 if isDateTimeClass:
2064 if node.func.attr == 'datetime': 2198 if node.func.attr == "datetime":
2065 # datetime.datetime(2000, 1, 1, 0, 0, 0, 0, 2199 # datetime.datetime(2000, 1, 1, 0, 0, 0, 0,
2066 # datetime.timezone.utc) 2200 # datetime.timezone.utc)
2201 isCase1 = len(node.args) >= 8 and not (
2202 AstUtilities.isNameConstant(node.args[7])
2203 and AstUtilities.getValue(node.args[7]) is None
2204 )
2205
2206 # datetime.datetime(2000, 1, 1, tzinfo=datetime.timezone.utc)
2207 tzinfoKeyword = self.__getFromKeywords(node.keywords, "tzinfo")
2208 isCase2 = tzinfoKeyword is not None and not (
2209 AstUtilities.isNameConstant(tzinfoKeyword.value)
2210 and AstUtilities.getValue(tzinfoKeyword.value) is None
2211 )
2212
2213 if not (isCase1 or isCase2):
2214 self.violations.append((node, "M301"))
2215
2216 elif node.func.attr == "time":
2217 # time(12, 10, 45, 0, datetime.timezone.utc)
2218 isCase1 = len(node.args) >= 5 and not (
2219 AstUtilities.isNameConstant(node.args[4])
2220 and AstUtilities.getValue(node.args[4]) is None
2221 )
2222
2223 # datetime.time(12, 10, 45, tzinfo=datetime.timezone.utc)
2224 tzinfoKeyword = self.__getFromKeywords(node.keywords, "tzinfo")
2225 isCase2 = tzinfoKeyword is not None and not (
2226 AstUtilities.isNameConstant(tzinfoKeyword.value)
2227 and AstUtilities.getValue(tzinfoKeyword.value) is None
2228 )
2229
2230 if not (isCase1 or isCase2):
2231 self.violations.append((node, "M321"))
2232
2233 elif node.func.attr == "date":
2234 self.violations.append((node, "M311"))
2235
2236 if isDateTimeClass or isDateTimeModuleAndClass:
2237 if node.func.attr == "today":
2238 self.violations.append((node, "M302"))
2239
2240 elif node.func.attr == "utcnow":
2241 self.violations.append((node, "M303"))
2242
2243 elif node.func.attr == "utcfromtimestamp":
2244 self.violations.append((node, "M304"))
2245
2246 elif node.func.attr in "now":
2247 # datetime.now(UTC)
2067 isCase1 = ( 2248 isCase1 = (
2068 len(node.args) >= 8 and 2249 len(node.args) == 1
2069 not ( 2250 and len(node.keywords) == 0
2070 AstUtilities.isNameConstant(node.args[7]) and 2251 and not (
2071 AstUtilities.getValue(node.args[7]) is None 2252 AstUtilities.isNameConstant(node.args[0])
2253 and AstUtilities.getValue(node.args[0]) is None
2072 ) 2254 )
2073 ) 2255 )
2074 2256
2075 # datetime.datetime(2000, 1, 1, tzinfo=datetime.timezone.utc) 2257 # datetime.now(tz=UTC)
2076 tzinfoKeyword = self.__getFromKeywords(node.keywords, 'tzinfo') 2258 tzKeyword = self.__getFromKeywords(node.keywords, "tz")
2077 isCase2 = ( 2259 isCase2 = tzKeyword is not None and not (
2078 tzinfoKeyword is not None and 2260 AstUtilities.isNameConstant(tzKeyword.value)
2079 not ( 2261 and AstUtilities.getValue(tzKeyword.value) is None
2080 AstUtilities.isNameConstant(tzinfoKeyword.value) and 2262 )
2081 AstUtilities.getValue(tzinfoKeyword.value) is None 2263
2264 if not (isCase1 or isCase2):
2265 self.violations.append((node, "M305"))
2266
2267 elif node.func.attr == "fromtimestamp":
2268 # datetime.fromtimestamp(1234, UTC)
2269 isCase1 = (
2270 len(node.args) == 2
2271 and len(node.keywords) == 0
2272 and not (
2273 AstUtilities.isNameConstant(node.args[1])
2274 and AstUtilities.getValue(node.args[1]) is None
2082 ) 2275 )
2083 ) 2276 )
2084 2277
2085 if not (isCase1 or isCase2): 2278 # datetime.fromtimestamp(1234, tz=UTC)
2086 self.violations.append((node, "M301")) 2279 tzKeyword = self.__getFromKeywords(node.keywords, "tz")
2087 2280 isCase2 = tzKeyword is not None and not (
2088 elif node.func.attr == 'time': 2281 AstUtilities.isNameConstant(tzKeyword.value)
2089 # time(12, 10, 45, 0, datetime.timezone.utc) 2282 and AstUtilities.getValue(tzKeyword.value) is None
2090 isCase1 = (
2091 len(node.args) >= 5 and
2092 not (
2093 AstUtilities.isNameConstant(node.args[4]) and
2094 AstUtilities.getValue(node.args[4]) is None
2095 )
2096 ) 2283 )
2097 2284
2098 # datetime.time(12, 10, 45, tzinfo=datetime.timezone.utc)
2099 tzinfoKeyword = self.__getFromKeywords(node.keywords, 'tzinfo')
2100 isCase2 = (
2101 tzinfoKeyword is not None and
2102 not (
2103 AstUtilities.isNameConstant(tzinfoKeyword.value) and
2104 AstUtilities.getValue(tzinfoKeyword.value) is None
2105 )
2106 )
2107
2108 if not (isCase1 or isCase2):
2109 self.violations.append((node, "M321"))
2110
2111 elif node.func.attr == 'date':
2112 self.violations.append((node, "M311"))
2113
2114 if isDateTimeClass or isDateTimeModuleAndClass:
2115 if node.func.attr == 'today':
2116 self.violations.append((node, "M302"))
2117
2118 elif node.func.attr == 'utcnow':
2119 self.violations.append((node, "M303"))
2120
2121 elif node.func.attr == 'utcfromtimestamp':
2122 self.violations.append((node, "M304"))
2123
2124 elif node.func.attr in 'now':
2125 # datetime.now(UTC)
2126 isCase1 = (
2127 len(node.args) == 1 and
2128 len(node.keywords) == 0 and
2129 not (
2130 AstUtilities.isNameConstant(node.args[0]) and
2131 AstUtilities.getValue(node.args[0]) is None
2132 )
2133 )
2134
2135 # datetime.now(tz=UTC)
2136 tzKeyword = self.__getFromKeywords(node.keywords, 'tz')
2137 isCase2 = (
2138 tzKeyword is not None and
2139 not (
2140 AstUtilities.isNameConstant(tzKeyword.value) and
2141 AstUtilities.getValue(tzKeyword.value) is None
2142 )
2143 )
2144
2145 if not (isCase1 or isCase2):
2146 self.violations.append((node, "M305"))
2147
2148 elif node.func.attr == 'fromtimestamp':
2149 # datetime.fromtimestamp(1234, UTC)
2150 isCase1 = (
2151 len(node.args) == 2 and
2152 len(node.keywords) == 0 and
2153 not (
2154 AstUtilities.isNameConstant(node.args[1]) and
2155 AstUtilities.getValue(node.args[1]) is None
2156 )
2157 )
2158
2159 # datetime.fromtimestamp(1234, tz=UTC)
2160 tzKeyword = self.__getFromKeywords(node.keywords, 'tz')
2161 isCase2 = (
2162 tzKeyword is not None and
2163 not (
2164 AstUtilities.isNameConstant(tzKeyword.value) and
2165 AstUtilities.getValue(tzKeyword.value) is None
2166 )
2167 )
2168
2169 if not (isCase1 or isCase2): 2285 if not (isCase1 or isCase2):
2170 self.violations.append((node, "M306")) 2286 self.violations.append((node, "M306"))
2171 2287
2172 elif node.func.attr == 'strptime': 2288 elif node.func.attr == "strptime":
2173 # datetime.strptime(...).replace(tzinfo=UTC) 2289 # datetime.strptime(...).replace(tzinfo=UTC)
2174 parent = getattr(node, '_dtCheckerParent', None) 2290 parent = getattr(node, "_dtCheckerParent", None)
2175 pparent = getattr(parent, '_dtCheckerParent', None) 2291 pparent = getattr(parent, "_dtCheckerParent", None)
2176 if ( 2292 if not (
2177 not (isinstance(parent, ast.Attribute) and 2293 isinstance(parent, ast.Attribute) and parent.attr == "replace"
2178 parent.attr == 'replace') or 2294 ) or not isinstance(pparent, ast.Call):
2179 not isinstance(pparent, ast.Call)
2180 ):
2181 isCase1 = False 2295 isCase1 = False
2182 else: 2296 else:
2183 tzinfoKeyword = self.__getFromKeywords(pparent.keywords, 2297 tzinfoKeyword = self.__getFromKeywords(pparent.keywords, "tzinfo")
2184 'tzinfo') 2298 isCase1 = tzinfoKeyword is not None and not (
2185 isCase1 = ( 2299 AstUtilities.isNameConstant(tzinfoKeyword.value)
2186 tzinfoKeyword is not None and 2300 and AstUtilities.getValue(tzinfoKeyword.value) is None
2187 not (
2188 AstUtilities.isNameConstant(
2189 tzinfoKeyword.value) and
2190 AstUtilities.getValue(tzinfoKeyword.value) is None
2191 )
2192 ) 2301 )
2193 2302
2194 if not isCase1: 2303 if not isCase1:
2195 self.violations.append((node, "M307")) 2304 self.violations.append((node, "M307"))
2196 2305
2197 elif node.func.attr == 'fromordinal': 2306 elif node.func.attr == "fromordinal":
2198 self.violations.append((node, "M308")) 2307 self.violations.append((node, "M308"))
2199 2308
2200 # date.something() 2309 # date.something()
2201 isDateClass = (isinstance(node.func, ast.Attribute) and 2310 isDateClass = (
2202 isinstance(node.func.value, ast.Name) and 2311 isinstance(node.func, ast.Attribute)
2203 node.func.value.id == 'date') 2312 and isinstance(node.func.value, ast.Name)
2204 2313 and node.func.value.id == "date"
2314 )
2315
2205 # datetime.date.something() 2316 # datetime.date.something()
2206 isDateModuleAndClass = (isinstance(node.func, ast.Attribute) and 2317 isDateModuleAndClass = (
2207 isinstance(node.func.value, ast.Attribute) and 2318 isinstance(node.func, ast.Attribute)
2208 node.func.value.attr == 'date' and 2319 and isinstance(node.func.value, ast.Attribute)
2209 isinstance(node.func.value.value, ast.Name) and 2320 and node.func.value.attr == "date"
2210 node.func.value.value.id == 'datetime') 2321 and isinstance(node.func.value.value, ast.Name)
2211 2322 and node.func.value.value.id == "datetime"
2323 )
2324
2212 if isDateClass or isDateModuleAndClass: 2325 if isDateClass or isDateModuleAndClass:
2213 if node.func.attr == 'today': 2326 if node.func.attr == "today":
2214 self.violations.append((node, "M312")) 2327 self.violations.append((node, "M312"))
2215 2328
2216 elif node.func.attr == 'fromtimestamp': 2329 elif node.func.attr == "fromtimestamp":
2217 self.violations.append((node, "M313")) 2330 self.violations.append((node, "M313"))
2218 2331
2219 elif node.func.attr == 'fromordinal': 2332 elif node.func.attr == "fromordinal":
2220 self.violations.append((node, "M314")) 2333 self.violations.append((node, "M314"))
2221 2334
2222 elif node.func.attr == 'fromisoformat': 2335 elif node.func.attr == "fromisoformat":
2223 self.violations.append((node, "M315")) 2336 self.violations.append((node, "M315"))
2224 2337
2225 self.generic_visit(node) 2338 self.generic_visit(node)
2226 2339
2227 2340
2228 class SysVersionVisitor(ast.NodeVisitor): 2341 class SysVersionVisitor(ast.NodeVisitor):
2229 """ 2342 """
2230 Class implementing a node visitor to check the use of sys.version and 2343 Class implementing a node visitor to check the use of sys.version and
2231 sys.version_info. 2344 sys.version_info.
2232 2345
2233 Note: This class is modelled after flake8-2020 checker. 2346 Note: This class is modelled after flake8-2020 checker.
2234 """ 2347 """
2348
2235 def __init__(self): 2349 def __init__(self):
2236 """ 2350 """
2237 Constructor 2351 Constructor
2238 """ 2352 """
2239 super().__init__() 2353 super().__init__()
2240 2354
2241 self.violations = [] 2355 self.violations = []
2242 self.__fromImports = {} 2356 self.__fromImports = {}
2243 2357
2244 def visit_ImportFrom(self, node): 2358 def visit_ImportFrom(self, node):
2245 """ 2359 """
2246 Public method to handle a from ... import ... statement. 2360 Public method to handle a from ... import ... statement.
2247 2361
2248 @param node reference to the node to be processed 2362 @param node reference to the node to be processed
2249 @type ast.ImportFrom 2363 @type ast.ImportFrom
2250 """ 2364 """
2251 for alias in node.names: 2365 for alias in node.names:
2252 if node.module is not None and not alias.asname: 2366 if node.module is not None and not alias.asname:
2253 self.__fromImports[alias.name] = node.module 2367 self.__fromImports[alias.name] = node.module
2254 2368
2255 self.generic_visit(node) 2369 self.generic_visit(node)
2256 2370
2257 def __isSys(self, attr, node): 2371 def __isSys(self, attr, node):
2258 """ 2372 """
2259 Private method to check for a reference to sys attribute. 2373 Private method to check for a reference to sys attribute.
2260 2374
2261 @param attr attribute name 2375 @param attr attribute name
2262 @type str 2376 @type str
2263 @param node reference to the node to be checked 2377 @param node reference to the node to be checked
2264 @type ast.Node 2378 @type ast.Node
2265 @return flag indicating a match 2379 @return flag indicating a match
2266 @rtype bool 2380 @rtype bool
2267 """ 2381 """
2268 match = False 2382 match = False
2269 if ( 2383 if (
2270 (isinstance(node, ast.Attribute) and 2384 isinstance(node, ast.Attribute)
2271 isinstance(node.value, ast.Name) and 2385 and isinstance(node.value, ast.Name)
2272 node.value.id == "sys" and 2386 and node.value.id == "sys"
2273 node.attr == attr) or 2387 and node.attr == attr
2274 (isinstance(node, ast.Name) and 2388 ) or (
2275 node.id == attr and 2389 isinstance(node, ast.Name)
2276 self.__fromImports.get(node.id) == "sys") 2390 and node.id == attr
2391 and self.__fromImports.get(node.id) == "sys"
2277 ): 2392 ):
2278 match = True 2393 match = True
2279 2394
2280 return match 2395 return match
2281 2396
2282 def __isSysVersionUpperSlice(self, node, n): 2397 def __isSysVersionUpperSlice(self, node, n):
2283 """ 2398 """
2284 Private method to check the upper slice of sys.version. 2399 Private method to check the upper slice of sys.version.
2285 2400
2286 @param node reference to the node to be checked 2401 @param node reference to the node to be checked
2287 @type ast.Node 2402 @type ast.Node
2288 @param n slice value to check against 2403 @param n slice value to check against
2289 @type int 2404 @type int
2290 @return flag indicating a match 2405 @return flag indicating a match
2291 @rtype bool 2406 @rtype bool
2292 """ 2407 """
2293 return ( 2408 return (
2294 self.__isSys("version", node.value) and 2409 self.__isSys("version", node.value)
2295 isinstance(node.slice, ast.Slice) and 2410 and isinstance(node.slice, ast.Slice)
2296 node.slice.lower is None and 2411 and node.slice.lower is None
2297 AstUtilities.isNumber(node.slice.upper) and 2412 and AstUtilities.isNumber(node.slice.upper)
2298 AstUtilities.getValue(node.slice.upper) == n and 2413 and AstUtilities.getValue(node.slice.upper) == n
2299 node.slice.step is None 2414 and node.slice.step is None
2300 ) 2415 )
2301 2416
2302 def visit_Subscript(self, node): 2417 def visit_Subscript(self, node):
2303 """ 2418 """
2304 Public method to handle a subscript. 2419 Public method to handle a subscript.
2305 2420
2306 @param node reference to the node to be processed 2421 @param node reference to the node to be processed
2307 @type ast.Subscript 2422 @type ast.Subscript
2308 """ 2423 """
2309 if self.__isSysVersionUpperSlice(node, 1): 2424 if self.__isSysVersionUpperSlice(node, 1):
2310 self.violations.append((node.value, "M423")) 2425 self.violations.append((node.value, "M423"))
2311 elif self.__isSysVersionUpperSlice(node, 3): 2426 elif self.__isSysVersionUpperSlice(node, 3):
2312 self.violations.append((node.value, "M401")) 2427 self.violations.append((node.value, "M401"))
2313 elif ( 2428 elif (
2314 self.__isSys('version', node.value) and 2429 self.__isSys("version", node.value)
2315 isinstance(node.slice, ast.Index) and 2430 and isinstance(node.slice, ast.Index)
2316 AstUtilities.isNumber(node.slice.value) and 2431 and AstUtilities.isNumber(node.slice.value)
2317 AstUtilities.getValue(node.slice.value) == 2 2432 and AstUtilities.getValue(node.slice.value) == 2
2318 ): 2433 ):
2319 self.violations.append((node.value, "M402")) 2434 self.violations.append((node.value, "M402"))
2320 elif ( 2435 elif (
2321 self.__isSys('version', node.value) and 2436 self.__isSys("version", node.value)
2322 isinstance(node.slice, ast.Index) and 2437 and isinstance(node.slice, ast.Index)
2323 AstUtilities.isNumber(node.slice.value) and 2438 and AstUtilities.isNumber(node.slice.value)
2324 AstUtilities.getValue(node.slice.value) == 0 2439 and AstUtilities.getValue(node.slice.value) == 0
2325 ): 2440 ):
2326 self.violations.append((node.value, "M421")) 2441 self.violations.append((node.value, "M421"))
2327 2442
2328 self.generic_visit(node) 2443 self.generic_visit(node)
2329 2444
2330 def visit_Compare(self, node): 2445 def visit_Compare(self, node):
2331 """ 2446 """
2332 Public method to handle a comparison. 2447 Public method to handle a comparison.
2333 2448
2334 @param node reference to the node to be processed 2449 @param node reference to the node to be processed
2335 @type ast.Compare 2450 @type ast.Compare
2336 """ 2451 """
2337 if ( 2452 if (
2338 isinstance(node.left, ast.Subscript) and 2453 isinstance(node.left, ast.Subscript)
2339 self.__isSys('version_info', node.left.value) and 2454 and self.__isSys("version_info", node.left.value)
2340 isinstance(node.left.slice, ast.Index) and 2455 and isinstance(node.left.slice, ast.Index)
2341 AstUtilities.isNumber(node.left.slice.value) and 2456 and AstUtilities.isNumber(node.left.slice.value)
2342 AstUtilities.getValue(node.left.slice.value) == 0 and 2457 and AstUtilities.getValue(node.left.slice.value) == 0
2343 len(node.ops) == 1 and 2458 and len(node.ops) == 1
2344 isinstance(node.ops[0], ast.Eq) and 2459 and isinstance(node.ops[0], ast.Eq)
2345 AstUtilities.isNumber(node.comparators[0]) and 2460 and AstUtilities.isNumber(node.comparators[0])
2346 AstUtilities.getValue(node.comparators[0]) == 3 2461 and AstUtilities.getValue(node.comparators[0]) == 3
2347 ): 2462 ):
2348 self.violations.append((node.left, "M411")) 2463 self.violations.append((node.left, "M411"))
2349 elif ( 2464 elif (
2350 self.__isSys('version', node.left) and 2465 self.__isSys("version", node.left)
2351 len(node.ops) == 1 and 2466 and len(node.ops) == 1
2352 isinstance(node.ops[0], (ast.Lt, ast.LtE, ast.Gt, ast.GtE)) and 2467 and isinstance(node.ops[0], (ast.Lt, ast.LtE, ast.Gt, ast.GtE))
2353 AstUtilities.isString(node.comparators[0]) 2468 and AstUtilities.isString(node.comparators[0])
2354 ): 2469 ):
2355 if len(AstUtilities.getValue(node.comparators[0])) == 1: 2470 if len(AstUtilities.getValue(node.comparators[0])) == 1:
2356 errorCode = "M422" 2471 errorCode = "M422"
2357 else: 2472 else:
2358 errorCode = "M403" 2473 errorCode = "M403"
2359 self.violations.append((node.left, errorCode)) 2474 self.violations.append((node.left, errorCode))
2360 elif ( 2475 elif (
2361 isinstance(node.left, ast.Subscript) and 2476 isinstance(node.left, ast.Subscript)
2362 self.__isSys('version_info', node.left.value) and 2477 and self.__isSys("version_info", node.left.value)
2363 isinstance(node.left.slice, ast.Index) and 2478 and isinstance(node.left.slice, ast.Index)
2364 AstUtilities.isNumber(node.left.slice.value) and 2479 and AstUtilities.isNumber(node.left.slice.value)
2365 AstUtilities.getValue(node.left.slice.value) == 1 and 2480 and AstUtilities.getValue(node.left.slice.value) == 1
2366 len(node.ops) == 1 and 2481 and len(node.ops) == 1
2367 isinstance(node.ops[0], (ast.Lt, ast.LtE, ast.Gt, ast.GtE)) and 2482 and isinstance(node.ops[0], (ast.Lt, ast.LtE, ast.Gt, ast.GtE))
2368 AstUtilities.isNumber(node.comparators[0]) 2483 and AstUtilities.isNumber(node.comparators[0])
2369 ): 2484 ):
2370 self.violations.append((node, "M413")) 2485 self.violations.append((node, "M413"))
2371 elif ( 2486 elif (
2372 isinstance(node.left, ast.Attribute) and 2487 isinstance(node.left, ast.Attribute)
2373 self.__isSys('version_info', node.left.value) and 2488 and self.__isSys("version_info", node.left.value)
2374 node.left.attr == 'minor' and 2489 and node.left.attr == "minor"
2375 len(node.ops) == 1 and 2490 and len(node.ops) == 1
2376 isinstance(node.ops[0], (ast.Lt, ast.LtE, ast.Gt, ast.GtE)) and 2491 and isinstance(node.ops[0], (ast.Lt, ast.LtE, ast.Gt, ast.GtE))
2377 AstUtilities.isNumber(node.comparators[0]) 2492 and AstUtilities.isNumber(node.comparators[0])
2378 ): 2493 ):
2379 self.violations.append((node, "M414")) 2494 self.violations.append((node, "M414"))
2380 2495
2381 self.generic_visit(node) 2496 self.generic_visit(node)
2382 2497
2383 def visit_Attribute(self, node): 2498 def visit_Attribute(self, node):
2384 """ 2499 """
2385 Public method to handle an attribute. 2500 Public method to handle an attribute.
2386 2501
2387 @param node reference to the node to be processed 2502 @param node reference to the node to be processed
2388 @type ast.Attribute 2503 @type ast.Attribute
2389 """ 2504 """
2390 if ( 2505 if (
2391 isinstance(node.value, ast.Name) and 2506 isinstance(node.value, ast.Name)
2392 node.value.id == 'six' and 2507 and node.value.id == "six"
2393 node.attr == 'PY3' 2508 and node.attr == "PY3"
2394 ): 2509 ):
2395 self.violations.append((node, "M412")) 2510 self.violations.append((node, "M412"))
2396 2511
2397 self.generic_visit(node) 2512 self.generic_visit(node)
2398 2513
2399 def visit_Name(self, node): 2514 def visit_Name(self, node):
2400 """ 2515 """
2401 Public method to handle an name. 2516 Public method to handle an name.
2402 2517
2403 @param node reference to the node to be processed 2518 @param node reference to the node to be processed
2404 @type ast.Name 2519 @type ast.Name
2405 """ 2520 """
2406 if node.id == 'PY3' and self.__fromImports.get(node.id) == 'six': 2521 if node.id == "PY3" and self.__fromImports.get(node.id) == "six":
2407 self.violations.append((node, "M412")) 2522 self.violations.append((node, "M412"))
2408 2523
2409 self.generic_visit(node) 2524 self.generic_visit(node)
2525
2410 2526
2411 # 2527 #
2412 # eflag: noqa = M891 2528 # eflag: noqa = M891

eric ide

mercurial