diff options
author | Claudiu Popa <pcmanticore@gmail.com> | 2015-11-04 10:43:43 +0200 |
---|---|---|
committer | Claudiu Popa <pcmanticore@gmail.com> | 2015-11-04 10:43:43 +0200 |
commit | 185ee278f064a3e444a54bf1998d343beb9683a3 (patch) | |
tree | 6acee4c51033467e481bc058e1d25af9fadcaba3 /pylint/checkers | |
parent | fabcc0526fb1d6b1a876c76f47f54f70d51023c7 (diff) | |
parent | c63ec9cc702161e23db13b3b2397289904073274 (diff) | |
download | pylint-185ee278f064a3e444a54bf1998d343beb9683a3.tar.gz |
Merged in lmedioni/pylint (pull request #298)
check if the number of nested block in a function or a method isn't too high
Diffstat (limited to 'pylint/checkers')
-rw-r--r-- | pylint/checkers/base.py | 122 |
1 files changed, 120 insertions, 2 deletions
diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py index 8d860ac..bd8a348 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, @@ -1633,6 +1634,122 @@ 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. This makes the code less understandable and ' + 'maintainable.'), + } + 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 + + def visit_ifexp(self, node): + self._if_counter += 1 + + def visit_comprehension(self, node): + for if_test in node.ifs: + self._if_counter += 1 + + @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): + # new scope = reinitialize the stack of nested blocks + self._nested_blocks = [] + # if there is a waiting message left, send it + 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 + # messages are triggered on leaving the nested block. Here we save the + # stack in case the current node isn't nested in the previous one + 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: + # if time has not come yet to send the message (ie the stack of + # nested nodes is still increasing), save it in case the + # current node is the last one of the function + 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)) @@ -1643,3 +1760,4 @@ def register(linter): linter.register_checker(LambdaForComprehensionChecker(linter)) linter.register_checker(ComparisonChecker(linter)) linter.register_checker(RecommandationChecker(linter)) + linter.register_checker(ElifChecker(linter)) |