From 1dbe1d77c6ad956679a9cf0fd312f67cc75ff26b Mon Sep 17 00:00:00 2001 From: Laura M?dioni Date: Fri, 30 Oct 2015 18:08:08 +0100 Subject: check if the number of nested block in a function or a method isn't too high related to issue #668 --- CONTRIBUTORS.txt | 2 +- pylint/checkers/base.py | 107 +++++++++++++++++++++- pylint/test/functional/too_many_nested_blocks.py | 95 +++++++++++++++++++ pylint/test/functional/too_many_nested_blocks.txt | 2 + 4 files changed, 203 insertions(+), 3 deletions(-) create mode 100644 pylint/test/functional/too_many_nested_blocks.py create mode 100644 pylint/test/functional/too_many_nested_blocks.txt 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' : '', + '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) -- cgit v1.2.1