summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaura M?dioni <laura.medioni@logilab.fr>2015-10-30 18:08:08 +0100
committerLaura M?dioni <laura.medioni@logilab.fr>2015-10-30 18:08:08 +0100
commit1dbe1d77c6ad956679a9cf0fd312f67cc75ff26b (patch)
tree2ebd540563fb3a46a8b52408dfcd7a8c195183b0
parent9ec0cfac7937cdf56519f033c074e8f6b4cf1ccf (diff)
downloadpylint-1dbe1d77c6ad956679a9cf0fd312f67cc75ff26b.tar.gz
check if the number of nested block in a function or a method isn't too high
related to issue #668
-rw-r--r--CONTRIBUTORS.txt2
-rw-r--r--pylint/checkers/base.py107
-rw-r--r--pylint/test/functional/too_many_nested_blocks.py95
-rw-r--r--pylint/test/functional/too_many_nested_blocks.txt2
4 files changed, 203 insertions, 3 deletions
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 7dc35ab..f4d66b8 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -74,4 +74,4 @@ Order doesn't matter (not that much, at least ;)
* Dmitry Pribysh: multiple-imports, not-iterable, not-a-mapping, various patches.
* Laura Medioni (Logilab, on behalf of the CNES): misplaced-comparison-constant,
- no-classmethod-decorator, no-staticmethod-decorator
+ no-classmethod-decorator, no-staticmethod-decorator, too-many-nested-blocks
diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py
index 8f4eca5..0ee5481 100644
--- a/pylint/checkers/base.py
+++ b/pylint/checkers/base.py
@@ -30,10 +30,11 @@ import astroid.scoped_nodes
from astroid import are_exclusive, InferenceError
from astroid import helpers
-from pylint.interfaces import IAstroidChecker, INFERENCE, INFERENCE_FAILURE, HIGH
+from pylint.interfaces import (IAstroidChecker, ITokenChecker, INFERENCE,
+ INFERENCE_FAILURE, HIGH)
from pylint.utils import EmptyReport, deprecated_option
from pylint.reporters import diff_string
-from pylint.checkers import BaseChecker
+from pylint.checkers import BaseChecker, BaseTokenChecker
from pylint.checkers.utils import (
check_messages,
clobber_in_except,
@@ -1635,6 +1636,107 @@ class ComparisonChecker(_BasicChecker):
self.add_message('unidiomatic-typecheck', node=node)
+class ElifChecker(BaseTokenChecker):
+ """Checks needing to distinguish "else if" from "elif"
+
+ This checker mixes the astroid and the token approaches in order to create
+ knowledge about whether a "else if" node is a true "else if" node, or a
+ "elif" node.
+
+ The following checks depend on this implementation:
+ - check for too many nested blocks (if/elif structures aren't considered
+ as nested)
+ - to be continued
+ """
+ __implements__ = (ITokenChecker, IAstroidChecker)
+ name = 'elif'
+ msgs = {'R0101': ('Too many nested blocks (%s/%s)',
+ 'too-many-nested-blocks',
+ 'Used when a function or a method has too many nested '
+ 'blocks.'),
+ }
+ options = (('max-nested-blocks',
+ {'default' : 5, 'type' : 'int', 'metavar' : '<int>',
+ 'help': 'Maximum number of nested blocks for function / '
+ 'method body'}
+ ),)
+
+ def __init__(self, linter=None):
+ BaseTokenChecker.__init__(self, linter)
+ self._init()
+
+ def _init(self):
+ self._nested_blocks = []
+ self._elifs = []
+ self._if_counter = 0
+ self._nested_blocks_msg = None
+
+ def process_tokens(self, tokens):
+ # Process tokens and look for 'if' or 'elif'
+ for _, token, _, _, _ in tokens:
+ if token == 'elif':
+ self._elifs.append(True)
+ elif token == 'if':
+ self._elifs.append(False)
+
+ def leave_module(self, node):
+ self._init()
+
+ @check_messages('too-many-nested-blocks')
+ def visit_tryexcept(self, node):
+ self._check_nested_blocks(node)
+
+ visit_tryfinally = visit_tryexcept
+ visit_while = visit_tryexcept
+ visit_for = visit_while
+
+ @check_messages('too-many-nested-blocks')
+ def visit_if(self, node):
+ self._check_nested_blocks(node)
+ self._if_counter += 1
+
+ @check_messages('too-many-nested-blocks')
+ def leave_functiondef(self, node):
+ self._nested_blocks = []
+ if self._nested_blocks_msg:
+ self.add_message('too-many-nested-blocks',
+ node=self._nested_blocks_msg[0],
+ args=self._nested_blocks_msg[1])
+ self._nested_blocks_msg = None
+
+ def _check_nested_blocks(self, node):
+ """Update and check the number of nested blocks
+ """
+ # only check block levels inside functions or methods
+ if not isinstance(node.scope(), astroid.FunctionDef):
+ return
+ nested_blocks = self._nested_blocks[:]
+ if node.parent == node.scope():
+ self._nested_blocks = [node]
+ else:
+ # go through ancestors from the most nested to the less
+ for ancestor_node in reversed(self._nested_blocks):
+ if ancestor_node == node.parent:
+ break
+ self._nested_blocks.pop()
+ # if the node is a elif, this should not be another nesting level
+ if isinstance(node, astroid.If) and self._elifs[self._if_counter]:
+ if self._nested_blocks:
+ self._nested_blocks.pop()
+ self._nested_blocks.append(node)
+ # send message only once per group of nested blocks
+ if len(nested_blocks) > self.config.max_nested_blocks:
+ if len(nested_blocks) > len(self._nested_blocks):
+ self.add_message('too-many-nested-blocks', node=nested_blocks[0],
+ args=(len(nested_blocks),
+ self.config.max_nested_blocks))
+ self._nested_blocks_msg = None
+ else:
+ self._nested_blocks_msg = (self._nested_blocks[0],
+ (len(self._nested_blocks),
+ self.config.max_nested_blocks))
+
+
def register(linter):
"""required method to auto register this checker"""
linter.register_checker(BasicErrorChecker(linter))
@@ -1645,3 +1747,4 @@ def register(linter):
linter.register_checker(LambdaForComprehensionChecker(linter))
linter.register_checker(ComparisonChecker(linter))
linter.register_checker(RecommandationChecker(linter))
+ linter.register_checker(ElifChecker(linter))
diff --git a/pylint/test/functional/too_many_nested_blocks.py b/pylint/test/functional/too_many_nested_blocks.py
new file mode 100644
index 0000000..8d371dd
--- /dev/null
+++ b/pylint/test/functional/too_many_nested_blocks.py
@@ -0,0 +1,95 @@
+"""Checks the maximum block level is smaller than 6 in function definitions"""
+
+#pylint: disable=using-constant-test, missing-docstring, too-many-return-statements
+
+def my_function():
+ if 1: # [too-many-nested-blocks]
+ for i in range(10):
+ if i == 2:
+ while True:
+ try:
+ if True:
+ i += 1
+ except IOError:
+ pass
+
+ if 1:
+ for i in range(10):
+ if i == 2:
+ while True:
+ try:
+ i += 1
+ except IOError:
+ pass
+
+ def nested_func():
+ if True:
+ for i in range(10):
+ while True:
+ if True:
+ if True:
+ print i
+
+ nested_func()
+
+def more_complex_function():
+ attr1 = attr2 = attr3 = [1, 2, 3]
+ if attr1:
+ for i in attr1:
+ if attr2:
+ return i
+ else:
+ return 'duh'
+ elif attr2:
+ for i in attr2:
+ if attr2:
+ return i
+ else:
+ return 'duh'
+ else:
+ for i in range(15):
+ if attr3:
+ return i
+ else:
+ return 'doh'
+ return None
+
+def elif_function():
+ arg = None
+ if arg == 1:
+ return 1
+ elif arg == 2:
+ return 2
+ elif arg == 3:
+ return 3
+ elif arg == 4:
+ return 4
+ elif arg == 5:
+ return 5
+ elif arg == 6:
+ return 6
+ elif arg == 7:
+ return 7
+
+def else_if_function():
+ arg = None
+ if arg == 1: # [too-many-nested-blocks]
+ return 1
+ else:
+ if arg == 2:
+ return 2
+ else:
+ if arg == 3:
+ return 3
+ else:
+ if arg == 4:
+ return 4
+ else:
+ if arg == 5:
+ return 5
+ else:
+ if arg == 6:
+ return 6
+ else:
+ if arg == 7:
+ return 7
diff --git a/pylint/test/functional/too_many_nested_blocks.txt b/pylint/test/functional/too_many_nested_blocks.txt
new file mode 100644
index 0000000..dcee9d2
--- /dev/null
+++ b/pylint/test/functional/too_many_nested_blocks.txt
@@ -0,0 +1,2 @@
+too-many-nested-blocks:6:my_function:Too many nested blocks (6/5)
+too-many-nested-blocks:76:else_if_function:Too many nested blocks (7/5)