Plugins/CheckerPlugins/CodeStyleChecker/MiscellaneousChecker.py

changeset 4511
b5e4e7efa904
parent 4510
43437fc9f4c9
child 4515
d7cebe39ffba
equal deleted inserted replaced
4510:43437fc9f4c9 4511:b5e4e7efa904
10 from __future__ import unicode_literals 10 from __future__ import unicode_literals
11 11
12 import sys 12 import sys
13 import ast 13 import ast
14 import re 14 import re
15 import itertools
16 from string import Formatter
15 17
16 18
17 class MiscellaneousChecker(object): 19 class MiscellaneousChecker(object):
18 """ 20 """
19 Class implementing a checker for miscellaneous checks. 21 Class implementing a checker for miscellaneous checks.
20 """ 22 """
21 Codes = [ 23 Codes = [
22 "M101", "M102", 24 "M101", "M102",
23 "M111", "M112", 25 "M111", "M112",
24 "M121", 26 "M121",
25 "M131", 27
28 "M601",
29 "M611", "M612", "M613",
30 "M621", "M622", "M623", "M624", "M625",
31 "M631", "M632",
32
26 "M701", "M702", 33 "M701", "M702",
34
27 "M801", 35 "M801",
28 "M811", 36 "M811",
29 37
30 "M901", 38 "M901",
31 ] 39 ]
40
41 Formatter = Formatter()
42 FormatFieldRegex = re.compile(r'^((?:\s|.)*?)(\..*|\[.*\])?$')
32 43
33 def __init__(self, source, filename, select, ignore, expected, repeat, 44 def __init__(self, source, filename, select, ignore, expected, repeat,
34 args): 45 args):
35 """ 46 """
36 Constructor 47 Constructor
68 79
69 # collection of detected errors 80 # collection of detected errors
70 self.errors = [] 81 self.errors = []
71 82
72 checkersWithCodes = [ 83 checkersWithCodes = [
73 # TODO: fill this
74 (self.__checkCoding, ("M101", "M102")), 84 (self.__checkCoding, ("M101", "M102")),
75 (self.__checkCopyright, ("M111", "M112")), 85 (self.__checkCopyright, ("M111", "M112")),
76 (self.__checkBlindExcept, ("M121",)), 86 (self.__checkBlindExcept, ("M121",)),
77 (self.__checkPep3101, ("M131",)), 87 (self.__checkPep3101, ("M601",)),
88 (self.__checkFormatString, ("M611", "M612", "M613",
89 "M621", "M622", "M623", "M624", "M625",
90 "M631", "M632")),
91 (self.__checkFuture, ("M701", "M702")),
78 (self.__checkPrintStatements, ("M801",)), 92 (self.__checkPrintStatements, ("M801",)),
79 (self.__checkTuple, ("M811", )), 93 (self.__checkTuple, ("M811", )),
80 (self.__checkFuture, ("M701", "M702")),
81 ] 94 ]
82 95
83 self.__defaultArgs = { 96 self.__defaultArgs = {
84 "CodingChecker": 'latin-1, utf-8', 97 "CodingChecker": 'latin-1, utf-8',
85 "CopyrightChecker": { 98 "CopyrightChecker": {
241 """ 254 """
242 for lineno, line in enumerate(self.__source): 255 for lineno, line in enumerate(self.__source):
243 match = self.__blindExceptRegex.search(line) 256 match = self.__blindExceptRegex.search(line)
244 if match: 257 if match:
245 self.__error(lineno, match.start(), "M121") 258 self.__error(lineno, match.start(), "M121")
259
260 def __checkPrintStatements(self):
261 """
262 Private method to check for print statements.
263 """
264 for node in ast.walk(self.__tree):
265 if (isinstance(node, ast.Call) and
266 getattr(node.func, 'id', None) == 'print') or \
267 (hasattr(ast, 'Print') and isinstance(node, ast.Print)):
268 self.__error(node.lineno - 1, node.col_offset, "M801")
269
270 def __checkTuple(self):
271 """
272 Private method to check for one element tuples.
273 """
274 for node in ast.walk(self.__tree):
275 if isinstance(node, ast.Tuple) and \
276 len(node.elts) == 1:
277 self.__error(node.lineno - 1, node.col_offset, "M811")
278
279 def __checkFuture(self):
280 """
281 Private method to check the __future__ imports.
282 """
283 expectedImports = set(
284 [i.strip()
285 for i in self.__args.get("FutureChecker", "").split(",")
286 if bool(i.strip())])
287 if len(expectedImports) == 0:
288 # nothing to check for; disabling the check
289 return
290
291 imports = set()
292 node = None
293
294 for node in ast.walk(self.__tree):
295 if (isinstance(node, ast.ImportFrom) and
296 node.module == '__future__'):
297 imports |= set(name.name for name in node.names)
298 elif isinstance(node, ast.Expr):
299 if not isinstance(node.value, ast.Str):
300 break
301 elif not isinstance(node, ast.Module):
302 break
303
304 if isinstance(node, ast.Module):
305 return
306
307 if not (imports >= expectedImports):
308 if imports:
309 self.__error(node.lineno - 1, node.col_offset, "M701",
310 ", ".join(expectedImports), ", ".join(imports))
311 else:
312 self.__error(node.lineno - 1, node.col_offset, "M702",
313 ", ".join(expectedImports))
246 314
247 def __checkPep3101(self): 315 def __checkPep3101(self):
248 """ 316 """
249 Private method to check for old style string formatting. 317 Private method to check for old style string formatting.
250 """ 318 """
263 if pos >= lineLen: 331 if pos >= lineLen:
264 break 332 break
265 c = line[pos] 333 c = line[pos]
266 if c in "diouxXeEfFgGcrs": 334 if c in "diouxXeEfFgGcrs":
267 formatter += c 335 formatter += c
268 self.__error(lineno, formatPos, "M131", formatter) 336 self.__error(lineno, formatPos, "M601", formatter)
269 337
270 def __checkPrintStatements(self): 338 def __checkFormatString(self):
271 """ 339 """
272 Private method to check for print statements. 340 Private method to check string format strings.
273 """ 341 """
274 for node in ast.walk(self.__tree): 342 visitor = TextVisitor()
275 if (isinstance(node, ast.Call) and 343 visitor.visit(self.__tree)
276 getattr(node.func, 'id', None) == 'print') or \ 344 for node in visitor.nodes:
277 (hasattr(ast, 'Print') and isinstance(node, ast.Print)): 345 text = node.s
278 self.__error(node.lineno - 1, node.col_offset, "M801") 346 if sys.version_info[0] > 2 and isinstance(text, bytes):
279 347 try:
280 def __checkTuple(self): 348 # TODO: Maybe decode using file encoding?
281 """ 349 text = text.decode('utf-8')
282 Private method to check for one element tuples. 350 except UnicodeDecodeError:
283 """ 351 continue
284 for node in ast.walk(self.__tree): 352 fields, implicit, explicit = self.__getFields(text)
285 if isinstance(node, ast.Tuple) and \ 353 if implicit:
286 len(node.elts) == 1: 354 if node in visitor.calls:
287 self.__error(node.lineno - 1, node.col_offset, "M811") 355 self.__error(node.lineno - 1, node.col_offset, "M611")
288 356 else:
289 def __checkFuture(self): 357 if node.is_docstring:
290 """ 358 self.__error(node.lineno - 1, node.col_offset, "M612")
291 Private method to check the __future__ imports. 359 else:
292 """ 360 self.__error(node.lineno - 1, node.col_offset, "M613")
293 expectedImports = set( 361
294 [i.strip() 362 if node in visitor.calls:
295 for i in self.__args.get("FutureChecker", "").split(",") 363 call, strArgs = visitor.calls[node]
296 if bool(i.strip())]) 364
297 if len(expectedImports) == 0: 365 numbers = set()
298 # nothing to check for; disabling the check 366 names = set()
299 return 367 # Determine which fields require a keyword and which an arg
300 368 for name in fields:
301 imports = set() 369 fieldMatch = self.FormatFieldRegex.match(name)
302 node = None 370 try:
303 371 number = int(fieldMatch.group(1))
304 for node in ast.walk(self.__tree): 372 except ValueError:
305 if (isinstance(node, ast.ImportFrom) and 373 number = -1
306 node.module == '__future__'): 374 # negative numbers are considered keywords
307 imports |= set(name.name for name in node.names) 375 if number >= 0:
308 elif isinstance(node, ast.Expr): 376 numbers.add(number)
309 if not isinstance(node.value, ast.Str): 377 else:
310 break 378 names.add(fieldMatch.group(1))
311 elif not isinstance(node, ast.Module): 379
312 break 380 keywords = set(keyword.arg for keyword in call.keywords)
313 381 numArgs = len(call.args)
314 if isinstance(node, ast.Module): 382 if strArgs:
315 return 383 numArgs -= 1
316 384 if sys.version_info < (3, 5):
317 if not (imports >= expectedImports): 385 hasKwArgs = bool(call.kwargs)
318 if imports: 386 hasStarArgs = bool(call.starargs)
319 self.__error(node.lineno - 1, node.col_offset, "M701", 387 else:
320 ", ".join(expectedImports), ", ".join(imports)) 388 hasKwArgs = any(kw.arg is None for kw in call.keywords)
321 else: 389 hasStarArgs = sum(1 for arg in call.args
322 self.__error(node.lineno - 1, node.col_offset, "M702", 390 if isinstance(arg, ast.Starred))
323 ", ".join(expectedImports)) 391
392 if hasKwArgs:
393 keywords.discard(None)
394 if hasStarArgs:
395 numArgs -= 1
396
397 # if starargs or kwargs is not None, it can't count the
398 # parameters but at least check if the args are used
399 if hasKwArgs:
400 if not names:
401 # No names but kwargs
402 self.__error(call.lineno - 1, call.col_offset, "M623")
403 if hasStarArgs:
404 if not numbers:
405 # No numbers but args
406 self.__error(call.lineno - 1, call.col_offset, "M624")
407
408 if not hasKwArgs and not hasStarArgs:
409 # can actually verify numbers and names
410 for number in sorted(numbers):
411 if number >= numArgs:
412 self.__error(call.lineno - 1, call.col_offset,
413 "M621", number)
414
415 for name in sorted(names):
416 if name not in keywords:
417 self.__error(call.lineno - 1, call.col_offset,
418 "M622", name)
419
420 for arg in range(numArgs):
421 if arg not in numbers:
422 self.__error(call.lineno - 1, call.col_offset, "M631",
423 arg)
424
425 for keyword in keywords:
426 if keyword not in names:
427 self.__error(call.lineno - 1, call.col_offset, "M632",
428 keyword)
429
430 if implicit and explicit:
431 self.__error(call.lineno - 1, call.col_offset, "M625")
432
433 def __getFields(self, string):
434 """
435 Private method to extract the format field information.
436
437 @param string format string to be parsed
438 @type str
439 @return format field information as a tuple with fields, implicit
440 field definitions present and explicit field definitions present
441 @rtype tuple of set of str, bool, bool
442 """
443 fields = set()
444 cnt = itertools.count()
445 implicit = False
446 explicit = False
447 try:
448 for literal, field, spec, conv in self.Formatter.parse(string):
449 if field is not None and (conv is None or conv in 'rsa'):
450 if not field:
451 field = str(next(cnt))
452 implicit = True
453 else:
454 explicit = True
455 fields.add(field)
456 fields.update(parsedSpec[1]
457 for parsedSpec in self.Formatter.parse(spec)
458 if parsedSpec[1] is not None)
459 except ValueError:
460 return set(), False, False
461 else:
462 return fields, implicit, explicit
463
464
465 class TextVisitor(ast.NodeVisitor):
466 """
467 Class implementing a node visitor for bytes and str instances.
468
469 It tries to detect docstrings as string of the first expression of each
470 module, class or function.
471 """
472 # modelled after the string format flake8 extension
473
474 def __init__(self):
475 """
476 Constructor
477 """
478 super(TextVisitor, self).__init__()
479 self.nodes = []
480 self.calls = {}
481
482 def __addNode(self, node):
483 """
484 Private method to add a node to our list of nodes.
485
486 @param node reference to the node to add
487 @type ast.AST
488 """
489 if not hasattr(node, 'is_docstring'):
490 node.is_docstring = False
491 self.nodes.append(node)
492
493 def __isBaseString(self, node):
494 """
495 Private method to determine, if a node is a base string node.
496
497 @param node reference to the node to check
498 @type ast.AST
499 @return flag indicating a base string
500 @rtype bool
501 """
502 typ = (ast.Str,)
503 if sys.version_info[0] > 2:
504 typ += (ast.Bytes,)
505 return isinstance(node, typ)
506
507 def visit_Str(self, node):
508 """
509 Public method to record a string node.
510
511 @param node reference to the string node
512 @type ast.Str
513 """
514 self.__addNode(node)
515
516 def visit_Bytes(self, node):
517 """
518 Public method to record a bytes node.
519
520 @param node reference to the bytes node
521 @type ast.Bytes
522 """
523 self.__addNode(node)
524
525 def __visitDefinition(self, node):
526 """
527 Private method handling class and function definitions.
528
529 @param node reference to the node to handle
530 @type ast.FunctionDef or ast.ClassDef
531 """
532 # Manually traverse class or function definition
533 # * Handle decorators normally
534 # * Use special check for body content
535 # * Don't handle the rest (e.g. bases)
536 for decorator in node.decorator_list:
537 self.visit(decorator)
538 self.__visitBody(node)
539
540 def __visitBody(self, node):
541 """
542 Private method to traverse the body of the node manually.
543
544 If the first node is an expression which contains a string or bytes it
545 marks that as a docstring.
546
547 @param node reference to the node to traverse
548 @type ast.AST
549 """
550 if (node.body and isinstance(node.body[0], ast.Expr) and
551 self.__isBaseString(node.body[0].value)):
552 node.body[0].value.is_docstring = True
553
554 for subnode in node.body:
555 self.visit(subnode)
556
557 def visit_Module(self, node):
558 """
559 Public method to handle a module.
560
561 @param node reference to the node to handle
562 @type ast.Module
563 """
564 self.__visitBody(node)
565
566 def visit_ClassDef(self, node):
567 """
568 Public method to handle a class definition.
569
570 @param node reference to the node to handle
571 @type ast.ClassDef
572 """
573 # Skipped nodes: ('name', 'bases', 'keywords', 'starargs', 'kwargs')
574 self.__visitDefinition(node)
575
576 def visit_FunctionDef(self, node):
577 """
578 Public method to handle a function definition.
579
580 @param node reference to the node to handle
581 @type ast.FunctionDef
582 """
583 # Skipped nodes: ('name', 'args', 'returns')
584 self.__visitDefinition(node)
585
586 def visit_Call(self, node):
587 """
588 Public method to handle a function call.
589
590 @param node reference to the node to handle
591 @type ast.Call
592 """
593 if (isinstance(node.func, ast.Attribute) and
594 node.func.attr == 'format'):
595 if self.__isBaseString(node.func.value):
596 self.calls[node.func.value] = (node, False)
597 elif (isinstance(node.func.value, ast.Name) and
598 node.func.value.id == 'str' and node.args and
599 self.__isBaseString(node.args[0])):
600 self.calls[node.args[0]] = (node, True)
601 super(TextVisitor, self).generic_visit(node)

eric ide

mercurial