diff options
Diffstat (limited to 'pylint/checkers/base.py')
-rw-r--r-- | pylint/checkers/base.py | 69 |
1 files changed, 69 insertions, 0 deletions
diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py index f368a65..c6f6ac5 100644 --- a/pylint/checkers/base.py +++ b/pylint/checkers/base.py @@ -1463,6 +1463,74 @@ class LambdaForComprehensionChecker(_BasicChecker): self.add_message('deprecated-lambda', node=node) +class RecommandationChecker(_BasicChecker): + msgs = {'C0200': ('Consider using enumerate instead of iterating with range and len', + 'consider-using-enumerate', + 'Emitted when code that iterates with range and len is ' + 'encountered. Such code can be simplified by using the ' + 'enumerate builtin.') + } + + @staticmethod + def _is_builtin(node, function): + inferred = helpers.safe_infer(node) + if not inferred: + return False + return is_builtin_object(inferred) and inferred.name == function + + @check_messages('consider-using-enumerate') + def visit_for(self, node): + """Emit a convention whenever range and len are used for indexing.""" + # Verify that we have a `range(len(...))` call and that the object + # which is iterated is used as a subscript in the body of the for. + + # Is it a proper range call? + if not isinstance(node.iter, astroid.Call): + return + if not self._is_builtin(node.iter.func, 'range'): + return + if len(node.iter.args) != 1: + return + + # Is it a proper len call? + if not isinstance(node.iter.args[0], astroid.Call): + return + second_func = node.iter.args[0].func + if not self._is_builtin(second_func, 'len'): + return + len_args = node.iter.args[0].args + if not len_args or len(len_args) != 1: + return + iterating_object = len_args[0] + if not isinstance(iterating_object, astroid.Name): + return + + # Verify that the body of the for loop uses a subscript + # with the object that was iterated. This uses some heuristics + # in order to make sure that the same object is used in the + # for body. + for child in node.body: + for subscript in child.nodes_of_class(astroid.Subscript): + if not isinstance(subscript.value, astroid.Name): + continue + if not isinstance(subscript.slice, astroid.Index): + continue + if not isinstance(subscript.slice.value, astroid.Name): + continue + if subscript.slice.value.name != node.target.name: + continue + if iterating_object.name != subscript.value.name: + continue + if subscript.value.scope() != node.scope(): + # Ignore this subscript if it's not in the same + # scope. This means that in the body of the for + # loop, another scope was created, where the same + # name for the iterating object was used. + continue + self.add_message('consider-using-enumerate', node=node) + return + + def _is_one_arg_pos_call(call): """Is this a call with exactly 1 argument, where that argument is positional? @@ -1576,3 +1644,4 @@ def register(linter): linter.register_checker(PassChecker(linter)) linter.register_checker(LambdaForComprehensionChecker(linter)) linter.register_checker(ComparisonChecker(linter)) + linter.register_checker(RecommandationChecker(linter)) |