Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

mccabe.py 5.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. # Copyright (c) 2016-2017 Claudiu Popa <pcmanticore@gmail.com>
  2. # Copyright (c) 2016 Moises Lopez <moylop260@vauxoo.com>
  3. # Copyright (c) 2017 hippo91 <guillaume.peillex@gmail.com>
  4. # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  5. # For details: https://github.com/PyCQA/pylint/blob/master/COPYING
  6. """Module to add McCabe checker class for pylint. """
  7. from __future__ import absolute_import
  8. from mccabe import PathGraph as Mccabe_PathGraph, \
  9. PathGraphingAstVisitor as Mccabe_PathGraphingAstVisitor
  10. from pylint import checkers
  11. from pylint.checkers.utils import check_messages
  12. from pylint.interfaces import HIGH, IAstroidChecker
  13. class PathGraph(Mccabe_PathGraph):
  14. def __init__(self, node):
  15. super(PathGraph, self).__init__(name='', entity='', lineno=1)
  16. self.root = node
  17. class PathGraphingAstVisitor(Mccabe_PathGraphingAstVisitor):
  18. def __init__(self):
  19. super(PathGraphingAstVisitor, self).__init__()
  20. self._bottom_counter = 0
  21. def default(self, node, *args):
  22. for child in node.get_children():
  23. self.dispatch(child, *args)
  24. def dispatch(self, node, *args):
  25. self.node = node
  26. klass = node.__class__
  27. meth = self._cache.get(klass)
  28. if meth is None:
  29. className = klass.__name__
  30. meth = getattr(self.visitor, 'visit' + className, self.default)
  31. self._cache[klass] = meth
  32. return meth(node, *args)
  33. def visitFunctionDef(self, node):
  34. if self.graph is not None:
  35. # closure
  36. pathnode = self._append_node(node)
  37. self.tail = pathnode
  38. self.dispatch_list(node.body)
  39. bottom = "%s" % self._bottom_counter
  40. self._bottom_counter += 1
  41. self.graph.connect(self.tail, bottom)
  42. self.graph.connect(node, bottom)
  43. self.tail = bottom
  44. else:
  45. self.graph = PathGraph(node)
  46. self.tail = node
  47. self.dispatch_list(node.body)
  48. self.graphs["%s%s" % (self.classname, node.name)] = self.graph
  49. self.reset()
  50. visitAsyncFunctionDef = visitFunctionDef
  51. def visitSimpleStatement(self, node):
  52. self._append_node(node)
  53. visitAssert = visitAssign = visitAugAssign = visitDelete = visitPrint = \
  54. visitRaise = visitYield = visitImport = visitCall = visitSubscript = \
  55. visitPass = visitContinue = visitBreak = visitGlobal = visitReturn = \
  56. visitExpr = visitAwait = visitSimpleStatement
  57. def visitWith(self, node):
  58. self._append_node(node)
  59. self.dispatch_list(node.body)
  60. visitAsyncWith = visitWith
  61. def _append_node(self, node):
  62. if not self.tail:
  63. return None
  64. self.graph.connect(self.tail, node)
  65. self.tail = node
  66. return node
  67. def _subgraph(self, node, name, extra_blocks=()):
  68. """create the subgraphs representing any `if` and `for` statements"""
  69. if self.graph is None:
  70. # global loop
  71. self.graph = PathGraph(node)
  72. self._subgraph_parse(node, node, extra_blocks)
  73. self.graphs["%s%s" % (self.classname, name)] = self.graph
  74. self.reset()
  75. else:
  76. self._append_node(node)
  77. self._subgraph_parse(node, node, extra_blocks)
  78. def _subgraph_parse(self, node, pathnode, extra_blocks): # pylint: disable=unused-argument
  79. """parse the body and any `else` block of `if` and `for` statements"""
  80. loose_ends = []
  81. self.tail = node
  82. self.dispatch_list(node.body)
  83. loose_ends.append(self.tail)
  84. for extra in extra_blocks:
  85. self.tail = node
  86. self.dispatch_list(extra.body)
  87. loose_ends.append(self.tail)
  88. if node.orelse:
  89. self.tail = node
  90. self.dispatch_list(node.orelse)
  91. loose_ends.append(self.tail)
  92. else:
  93. loose_ends.append(node)
  94. if node:
  95. bottom = "%s" % self._bottom_counter
  96. self._bottom_counter += 1
  97. for le in loose_ends:
  98. self.graph.connect(le, bottom)
  99. self.tail = bottom
  100. class McCabeMethodChecker(checkers.BaseChecker):
  101. """Checks McCabe complexity cyclomatic threshold in methods and functions
  102. to validate a too complex code.
  103. """
  104. __implements__ = IAstroidChecker
  105. name = 'design'
  106. msgs = {
  107. 'R1260': (
  108. "%s is too complex. The McCabe rating is %d",
  109. 'too-complex',
  110. 'Used when a method or function is too complex based on '
  111. 'McCabe Complexity Cyclomatic'),
  112. }
  113. options = (
  114. ('max-complexity', {
  115. 'default': 10,
  116. 'type': 'int',
  117. 'metavar': '<int>',
  118. 'help': 'McCabe complexity cyclomatic threshold',
  119. }),
  120. )
  121. @check_messages('too-complex')
  122. def visit_module(self, node):
  123. """visit an astroid.Module node to check too complex rating and
  124. add message if is greather than max_complexity stored from options"""
  125. visitor = PathGraphingAstVisitor()
  126. for child in node.body:
  127. visitor.preorder(child, visitor)
  128. for graph in visitor.graphs.values():
  129. complexity = graph.complexity()
  130. node = graph.root
  131. if hasattr(node, 'name'):
  132. node_name = "'%s'" % node.name
  133. else:
  134. node_name = "This '%s'" % node.__class__.__name__.lower()
  135. if complexity <= self.config.max_complexity:
  136. continue
  137. self.add_message(
  138. 'too-complex', node=node, confidence=HIGH,
  139. args=(node_name, complexity))
  140. def register(linter):
  141. """Required method to auto register this checker.
  142. :param linter: Main interface object for Pylint plugins
  143. :type linter: Pylint object
  144. """
  145. linter.register_checker(McCabeMethodChecker(linter))