summaryrefslogtreecommitdiff
path: root/venv/Lib/site-packages/pylint/checkers/refactoring.py
diff options
context:
space:
mode:
Diffstat (limited to 'venv/Lib/site-packages/pylint/checkers/refactoring.py')
-rw-r--r--venv/Lib/site-packages/pylint/checkers/refactoring.py1510
1 files changed, 0 insertions, 1510 deletions
diff --git a/venv/Lib/site-packages/pylint/checkers/refactoring.py b/venv/Lib/site-packages/pylint/checkers/refactoring.py
deleted file mode 100644
index 2831343..0000000
--- a/venv/Lib/site-packages/pylint/checkers/refactoring.py
+++ /dev/null
@@ -1,1510 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2016-2018 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2016-2017 Łukasz Rogalski <rogalski.91@gmail.com>
-# Copyright (c) 2016 Moises Lopez <moylop260@vauxoo.com>
-# Copyright (c) 2016 Alexander Todorov <atodorov@otb.bg>
-# Copyright (c) 2017-2018 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2017 Ville Skyttä <ville.skytta@iki.fi>
-# Copyright (c) 2017-2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2017 Hugo <hugovk@users.noreply.github.com>
-# Copyright (c) 2017 Łukasz Sznuk <ls@rdprojekt.pl>
-# Copyright (c) 2017 Alex Hearn <alex.d.hearn@gmail.com>
-# Copyright (c) 2017 Antonio Ossa <aaossa@uc.cl>
-# Copyright (c) 2018 Konstantin Manna <Konstantin@Manna.uno>
-# Copyright (c) 2018 Konstantin <Github@pheanex.de>
-# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com>
-# Copyright (c) 2018 Matej Marušák <marusak.matej@gmail.com>
-# Copyright (c) 2018 Ville Skyttä <ville.skytta@upcloud.com>
-# Copyright (c) 2018 Mr. Senko <atodorov@mrsenko.com>
-
-# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
-# For details: https://github.com/PyCQA/pylint/blob/master/COPYING
-
-"""Looks for code which can be refactored."""
-import builtins
-import collections
-import itertools
-import tokenize
-from functools import reduce
-
-import astroid
-from astroid import decorators
-
-from pylint import checkers, interfaces
-from pylint import utils as lint_utils
-from pylint.checkers import utils
-
-KNOWN_INFINITE_ITERATORS = {"itertools.count"}
-BUILTIN_EXIT_FUNCS = frozenset(("quit", "exit"))
-
-
-def _if_statement_is_always_returning(if_node, returning_node_class):
- for node in if_node.body:
- if isinstance(node, returning_node_class):
- return True
- return False
-
-
-def _is_len_call(node):
- """Checks if node is len(SOMETHING)."""
- return (
- isinstance(node, astroid.Call)
- and isinstance(node.func, astroid.Name)
- and node.func.name == "len"
- )
-
-
-def _is_constant_zero(node):
- return isinstance(node, astroid.Const) and node.value == 0
-
-
-def _node_is_test_condition(node):
- """ Checks if node is an if, while, assert or if expression statement."""
- return isinstance(node, (astroid.If, astroid.While, astroid.Assert, astroid.IfExp))
-
-
-def _is_trailing_comma(tokens, index):
- """Check if the given token is a trailing comma
-
- :param tokens: Sequence of modules tokens
- :type tokens: list[tokenize.TokenInfo]
- :param int index: Index of token under check in tokens
- :returns: True if the token is a comma which trails an expression
- :rtype: bool
- """
- token = tokens[index]
- if token.exact_type != tokenize.COMMA:
- return False
- # Must have remaining tokens on the same line such as NEWLINE
- left_tokens = itertools.islice(tokens, index + 1, None)
- same_line_remaining_tokens = list(
- itertools.takewhile(
- lambda other_token, _token=token: other_token.start[0] == _token.start[0],
- left_tokens,
- )
- )
- # Note: If the newline is tokenize.NEWLINE and not tokenize.NL
- # then the newline denotes the end of expression
- is_last_element = all(
- other_token.type in (tokenize.NEWLINE, tokenize.COMMENT)
- for other_token in same_line_remaining_tokens
- )
- if not same_line_remaining_tokens or not is_last_element:
- return False
-
- def get_curline_index_start():
- """Get the index denoting the start of the current line"""
- for subindex, token in enumerate(reversed(tokens[:index])):
- # See Lib/tokenize.py and Lib/token.py in cpython for more info
- if token.type in (tokenize.NEWLINE, tokenize.NL):
- return index - subindex
- return 0
-
- curline_start = get_curline_index_start()
- expected_tokens = {"return", "yield"}
- for prevtoken in tokens[curline_start:index]:
- if "=" in prevtoken.string or prevtoken.string in expected_tokens:
- return True
- return False
-
-
-class RefactoringChecker(checkers.BaseTokenChecker):
- """Looks for code which can be refactored
-
- This checker also mixes the astroid and the token approaches
- in order to create knowledge about whether an "else if" node
- is a true "else if" node, or an "elif" node.
- """
-
- __implements__ = (interfaces.ITokenChecker, interfaces.IAstroidChecker)
-
- name = "refactoring"
-
- msgs = {
- "R1701": (
- "Consider merging these isinstance calls to isinstance(%s, (%s))",
- "consider-merging-isinstance",
- "Used when multiple consecutive isinstance calls can be merged into one.",
- ),
- "R1706": (
- "Consider using ternary (%s)",
- "consider-using-ternary",
- "Used when one of known pre-python 2.5 ternary syntax is used.",
- ),
- "R1709": (
- "Boolean expression may be simplified to %s",
- "simplify-boolean-expression",
- "Emitted when redundant pre-python 2.5 ternary syntax is used.",
- ),
- "R1702": (
- "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.",
- {"old_names": [("R0101", "old-too-many-nested-blocks")]},
- ),
- "R1703": (
- "The if statement can be replaced with %s",
- "simplifiable-if-statement",
- "Used when an if statement can be replaced with 'bool(test)'. ",
- {"old_names": [("R0102", "old-simplifiable-if-statement")]},
- ),
- "R1704": (
- "Redefining argument with the local name %r",
- "redefined-argument-from-local",
- "Used when a local name is redefining an argument, which might "
- "suggest a potential error. This is taken in account only for "
- "a handful of name binding operations, such as for iteration, "
- "with statement assignment and exception handler assignment.",
- ),
- "R1705": (
- 'Unnecessary "%s" after "return"',
- "no-else-return",
- "Used in order to highlight an unnecessary block of "
- "code following an if containing a return statement. "
- "As such, it will warn when it encounters an else "
- "following a chain of ifs, all of them containing a "
- "return statement.",
- ),
- "R1707": (
- "Disallow trailing comma tuple",
- "trailing-comma-tuple",
- "In Python, a tuple is actually created by the comma symbol, "
- "not by the parentheses. Unfortunately, one can actually create a "
- "tuple by misplacing a trailing comma, which can lead to potential "
- "weird bugs in your code. You should always use parentheses "
- "explicitly for creating a tuple.",
- ),
- "R1708": (
- "Do not raise StopIteration in generator, use return statement instead",
- "stop-iteration-return",
- "According to PEP479, the raise of StopIteration to end the loop of "
- "a generator may lead to hard to find bugs. This PEP specify that "
- "raise StopIteration has to be replaced by a simple return statement",
- ),
- "R1710": (
- "Either all return statements in a function should return an expression, "
- "or none of them should.",
- "inconsistent-return-statements",
- "According to PEP8, if any return statement returns an expression, "
- "any return statements where no value is returned should explicitly "
- "state this as return None, and an explicit return statement "
- "should be present at the end of the function (if reachable)",
- ),
- "R1711": (
- "Useless return at end of function or method",
- "useless-return",
- 'Emitted when a single "return" or "return None" statement is found '
- "at the end of function or method definition. This statement can safely be "
- "removed because Python will implicitly return None",
- ),
- "R1712": (
- "Consider using tuple unpacking for swapping variables",
- "consider-swap-variables",
- "You do not have to use a temporary variable in order to "
- 'swap variables. Using "tuple unpacking" to directly swap '
- "variables makes the intention more clear.",
- ),
- "R1713": (
- "Consider using str.join(sequence) for concatenating "
- "strings from an iterable",
- "consider-using-join",
- "Using str.join(sequence) is faster, uses less memory "
- "and increases readability compared to for-loop iteration.",
- ),
- "R1714": (
- 'Consider merging these comparisons with "in" to %r',
- "consider-using-in",
- "To check if a variable is equal to one of many values,"
- 'combine the values into a tuple and check if the variable is contained "in" it '
- "instead of checking for equality against each of the values."
- "This is faster and less verbose.",
- ),
- "R1715": (
- "Consider using dict.get for getting values from a dict "
- "if a key is present or a default if not",
- "consider-using-get",
- "Using the builtin dict.get for getting a value from a dictionary "
- "if a key is present or a default if not, is simpler and considered "
- "more idiomatic, although sometimes a bit slower",
- ),
- "R1716": (
- "Simplify chained comparison between the operands",
- "chained-comparison",
- "This message is emitted when pylint encounters boolean operation like"
- '"a < b and b < c", suggesting instead to refactor it to "a < b < c"',
- ),
- "R1717": (
- "Consider using a dictionary comprehension",
- "consider-using-dict-comprehension",
- "Emitted when we detect the creation of a dictionary "
- "using the dict() callable and a transient list. "
- "Although there is nothing syntactically wrong with this code, "
- "it is hard to read and can be simplified to a dict comprehension."
- "Also it is faster since you don't need to create another "
- "transient list",
- ),
- "R1718": (
- "Consider using a set comprehension",
- "consider-using-set-comprehension",
- "Although there is nothing syntactically wrong with this code, "
- "it is hard to read and can be simplified to a set comprehension."
- "Also it is faster since you don't need to create another "
- "transient list",
- ),
- "R1719": (
- "The if expression can be replaced with %s",
- "simplifiable-if-expression",
- "Used when an if expression can be replaced with 'bool(test)'. ",
- ),
- "R1720": (
- 'Unnecessary "%s" after "raise"',
- "no-else-raise",
- "Used in order to highlight an unnecessary block of "
- "code following an if containing a raise statement. "
- "As such, it will warn when it encounters an else "
- "following a chain of ifs, all of them containing a "
- "raise statement.",
- ),
- "R1721": (
- "Unnecessary use of a comprehension",
- "unnecessary-comprehension",
- "Instead of using an identitiy comprehension, "
- "consider using the list, dict or set constructor. "
- "It is faster and simpler.",
- ),
- "R1722": (
- "Consider using sys.exit()",
- "consider-using-sys-exit",
- "Instead of using exit() or quit(), consider using the sys.exit().",
- ),
- "R1723": (
- 'Unnecessary "%s" after "break"',
- "no-else-break",
- "Used in order to highlight an unnecessary block of "
- "code following an if containing a break statement. "
- "As such, it will warn when it encounters an else "
- "following a chain of ifs, all of them containing a "
- "break statement.",
- ),
- "R1724": (
- 'Unnecessary "%s" after "continue"',
- "no-else-continue",
- "Used in order to highlight an unnecessary block of "
- "code following an if containing a continue statement. "
- "As such, it will warn when it encounters an else "
- "following a chain of ifs, all of them containing a "
- "continue statement.",
- ),
- }
- options = (
- (
- "max-nested-blocks",
- {
- "default": 5,
- "type": "int",
- "metavar": "<int>",
- "help": "Maximum number of nested blocks for function / method body",
- },
- ),
- (
- "never-returning-functions",
- {
- "default": ("sys.exit",),
- "type": "csv",
- "help": "Complete name of functions that never returns. When checking "
- "for inconsistent-return-statements if a never returning function is "
- "called then it will be considered as an explicit return statement "
- "and no message will be printed.",
- },
- ),
- )
-
- priority = 0
-
- def __init__(self, linter=None):
- checkers.BaseTokenChecker.__init__(self, linter)
- self._return_nodes = {}
- self._init()
- self._never_returning_functions = None
-
- def _init(self):
- self._nested_blocks = []
- self._elifs = []
- self._nested_blocks_msg = None
- self._reported_swap_nodes = set()
-
- def open(self):
- # do this in open since config not fully initialized in __init__
- self._never_returning_functions = set(self.config.never_returning_functions)
-
- @decorators.cachedproperty
- def _dummy_rgx(self):
- return lint_utils.get_global_option(self, "dummy-variables-rgx", default=None)
-
- @staticmethod
- def _is_bool_const(node):
- return isinstance(node.value, astroid.Const) and isinstance(
- node.value.value, bool
- )
-
- def _is_actual_elif(self, node):
- """Check if the given node is an actual elif
-
- This is a problem we're having with the builtin ast module,
- which splits `elif` branches into a separate if statement.
- Unfortunately we need to know the exact type in certain
- cases.
- """
- if isinstance(node.parent, astroid.If):
- orelse = node.parent.orelse
- # current if node must directly follow an "else"
- if orelse and orelse == [node]:
- if (node.lineno, node.col_offset) in self._elifs:
- return True
- return False
-
- def _check_simplifiable_if(self, node):
- """Check if the given if node can be simplified.
-
- The if statement can be reduced to a boolean expression
- in some cases. For instance, if there are two branches
- and both of them return a boolean value that depends on
- the result of the statement's test, then this can be reduced
- to `bool(test)` without losing any functionality.
- """
-
- if self._is_actual_elif(node):
- # Not interested in if statements with multiple branches.
- return
- if len(node.orelse) != 1 or len(node.body) != 1:
- return
-
- # Check if both branches can be reduced.
- first_branch = node.body[0]
- else_branch = node.orelse[0]
- if isinstance(first_branch, astroid.Return):
- if not isinstance(else_branch, astroid.Return):
- return
- first_branch_is_bool = self._is_bool_const(first_branch)
- else_branch_is_bool = self._is_bool_const(else_branch)
- reduced_to = "'return bool(test)'"
- elif isinstance(first_branch, astroid.Assign):
- if not isinstance(else_branch, astroid.Assign):
- return
-
- # Check if we assign to the same value
- first_branch_targets = [
- target.name
- for target in first_branch.targets
- if isinstance(target, astroid.AssignName)
- ]
- else_branch_targets = [
- target.name
- for target in else_branch.targets
- if isinstance(target, astroid.AssignName)
- ]
- if not first_branch_targets or not else_branch_targets:
- return
- if sorted(first_branch_targets) != sorted(else_branch_targets):
- return
-
- first_branch_is_bool = self._is_bool_const(first_branch)
- else_branch_is_bool = self._is_bool_const(else_branch)
- reduced_to = "'var = bool(test)'"
- else:
- return
-
- if not first_branch_is_bool or not else_branch_is_bool:
- return
- if not first_branch.value.value:
- # This is a case that can't be easily simplified and
- # if it can be simplified, it will usually result in a
- # code that's harder to understand and comprehend.
- # Let's take for instance `arg and arg <= 3`. This could theoretically be
- # reduced to `not arg or arg > 3`, but the net result is that now the
- # condition is harder to understand, because it requires understanding of
- # an extra clause:
- # * first, there is the negation of truthness with `not arg`
- # * the second clause is `arg > 3`, which occurs when arg has a
- # a truth value, but it implies that `arg > 3` is equivalent
- # with `arg and arg > 3`, which means that the user must
- # think about this assumption when evaluating `arg > 3`.
- # The original form is easier to grasp.
- return
-
- self.add_message("simplifiable-if-statement", node=node, args=(reduced_to,))
-
- def process_tokens(self, tokens):
- # Process tokens and look for 'if' or 'elif'
- for index, token in enumerate(tokens):
- token_string = token[1]
- if token_string == "elif":
- # AST exists by the time process_tokens is called, so
- # it's safe to assume tokens[index+1]
- # exists. tokens[index+1][2] is the elif's position as
- # reported by CPython and PyPy,
- # tokens[index][2] is the actual position and also is
- # reported by IronPython.
- self._elifs.extend([tokens[index][2], tokens[index + 1][2]])
- elif _is_trailing_comma(tokens, index):
- if self.linter.is_message_enabled("trailing-comma-tuple"):
- self.add_message("trailing-comma-tuple", line=token.start[0])
-
- def leave_module(self, _):
- self._init()
-
- @utils.check_messages("too-many-nested-blocks")
- def visit_tryexcept(self, node):
- self._check_nested_blocks(node)
-
- visit_tryfinally = visit_tryexcept
- visit_while = visit_tryexcept
-
- def _check_redefined_argument_from_local(self, name_node):
- if self._dummy_rgx and self._dummy_rgx.match(name_node.name):
- return
- if not name_node.lineno:
- # Unknown position, maybe it is a manually built AST?
- return
-
- scope = name_node.scope()
- if not isinstance(scope, astroid.FunctionDef):
- return
-
- for defined_argument in scope.args.nodes_of_class(
- astroid.AssignName, skip_klass=(astroid.Lambda,)
- ):
- if defined_argument.name == name_node.name:
- self.add_message(
- "redefined-argument-from-local",
- node=name_node,
- args=(name_node.name,),
- )
-
- @utils.check_messages("redefined-argument-from-local", "too-many-nested-blocks")
- def visit_for(self, node):
- self._check_nested_blocks(node)
-
- for name in node.target.nodes_of_class(astroid.AssignName):
- self._check_redefined_argument_from_local(name)
-
- @utils.check_messages("redefined-argument-from-local")
- def visit_excepthandler(self, node):
- if node.name and isinstance(node.name, astroid.AssignName):
- self._check_redefined_argument_from_local(node.name)
-
- @utils.check_messages("redefined-argument-from-local")
- def visit_with(self, node):
- for _, names in node.items:
- if not names:
- continue
- for name in names.nodes_of_class(astroid.AssignName):
- self._check_redefined_argument_from_local(name)
-
- def _check_superfluous_else(self, node, msg_id, returning_node_class):
- if not node.orelse:
- # Not interested in if statements without else.
- return
-
- if self._is_actual_elif(node):
- # Not interested in elif nodes; only if
- return
-
- if _if_statement_is_always_returning(node, returning_node_class):
- orelse = node.orelse[0]
- followed_by_elif = (orelse.lineno, orelse.col_offset) in self._elifs
- self.add_message(
- msg_id, node=node, args="elif" if followed_by_elif else "else"
- )
-
- def _check_superfluous_else_return(self, node):
- return self._check_superfluous_else(
- node, msg_id="no-else-return", returning_node_class=astroid.Return
- )
-
- def _check_superfluous_else_raise(self, node):
- return self._check_superfluous_else(
- node, msg_id="no-else-raise", returning_node_class=astroid.Raise
- )
-
- def _check_superfluous_else_break(self, node):
- return self._check_superfluous_else(
- node, msg_id="no-else-break", returning_node_class=astroid.Break
- )
-
- def _check_superfluous_else_continue(self, node):
- return self._check_superfluous_else(
- node, msg_id="no-else-continue", returning_node_class=astroid.Continue
- )
-
- def _check_consider_get(self, node):
- def type_and_name_are_equal(node_a, node_b):
- for _type in [astroid.Name, astroid.AssignName]:
- if all(isinstance(_node, _type) for _node in [node_a, node_b]):
- return node_a.name == node_b.name
- if all(isinstance(_node, astroid.Const) for _node in [node_a, node_b]):
- return node_a.value == node_b.value
- return False
-
- if_block_ok = (
- isinstance(node.test, astroid.Compare)
- and len(node.body) == 1
- and isinstance(node.body[0], astroid.Assign)
- and isinstance(node.body[0].value, astroid.Subscript)
- and type_and_name_are_equal(node.body[0].value.value, node.test.ops[0][1])
- and isinstance(node.body[0].value.slice, astroid.Index)
- and type_and_name_are_equal(node.body[0].value.slice.value, node.test.left)
- and len(node.body[0].targets) == 1
- and isinstance(node.body[0].targets[0], astroid.AssignName)
- and isinstance(utils.safe_infer(node.test.ops[0][1]), astroid.Dict)
- )
-
- if if_block_ok and not node.orelse:
- self.add_message("consider-using-get", node=node)
- elif (
- if_block_ok
- and len(node.orelse) == 1
- and isinstance(node.orelse[0], astroid.Assign)
- and type_and_name_are_equal(
- node.orelse[0].targets[0], node.body[0].targets[0]
- )
- and len(node.orelse[0].targets) == 1
- ):
- self.add_message("consider-using-get", node=node)
-
- @utils.check_messages(
- "too-many-nested-blocks",
- "simplifiable-if-statement",
- "no-else-return",
- "no-else-raise",
- "no-else-break",
- "no-else-continue",
- "consider-using-get",
- )
- def visit_if(self, node):
- self._check_simplifiable_if(node)
- self._check_nested_blocks(node)
- self._check_superfluous_else_return(node)
- self._check_superfluous_else_raise(node)
- self._check_superfluous_else_break(node)
- self._check_superfluous_else_continue(node)
- self._check_consider_get(node)
-
- @utils.check_messages("simplifiable-if-expression")
- def visit_ifexp(self, node):
- self._check_simplifiable_ifexp(node)
-
- def _check_simplifiable_ifexp(self, node):
- if not isinstance(node.body, astroid.Const) or not isinstance(
- node.orelse, astroid.Const
- ):
- return
-
- if not isinstance(node.body.value, bool) or not isinstance(
- node.orelse.value, bool
- ):
- return
-
- if isinstance(node.test, astroid.Compare):
- test_reduced_to = "test"
- else:
- test_reduced_to = "bool(test)"
-
- if (node.body.value, node.orelse.value) == (True, False):
- reduced_to = "'{}'".format(test_reduced_to)
- elif (node.body.value, node.orelse.value) == (False, True):
- reduced_to = "'not test'"
- else:
- return
-
- self.add_message("simplifiable-if-expression", node=node, args=(reduced_to,))
-
- @utils.check_messages(
- "too-many-nested-blocks", "inconsistent-return-statements", "useless-return"
- )
- def leave_functiondef(self, node):
- # check left-over nested blocks stack
- self._emit_nested_blocks_message_if_needed(self._nested_blocks)
- # new scope = reinitialize the stack of nested blocks
- self._nested_blocks = []
- #  check consistent return statements
- self._check_consistent_returns(node)
- # check for single return or return None at the end
- self._check_return_at_the_end(node)
- self._return_nodes[node.name] = []
-
- @utils.check_messages("stop-iteration-return")
- def visit_raise(self, node):
- self._check_stop_iteration_inside_generator(node)
-
- def _check_stop_iteration_inside_generator(self, node):
- """Check if an exception of type StopIteration is raised inside a generator"""
- frame = node.frame()
- if not isinstance(frame, astroid.FunctionDef) or not frame.is_generator():
- return
- if utils.node_ignores_exception(node, StopIteration):
- return
- if not node.exc:
- return
- exc = utils.safe_infer(node.exc)
- if exc is None or exc is astroid.Uninferable:
- return
- if self._check_exception_inherit_from_stopiteration(exc):
- self.add_message("stop-iteration-return", node=node)
-
- @staticmethod
- def _check_exception_inherit_from_stopiteration(exc):
- """Return True if the exception node in argument inherit from StopIteration"""
- stopiteration_qname = "{}.StopIteration".format(utils.EXCEPTIONS_MODULE)
- return any(_class.qname() == stopiteration_qname for _class in exc.mro())
-
- def _check_consider_using_comprehension_constructor(self, node):
- if (
- isinstance(node.func, astroid.Name)
- and node.args
- and isinstance(node.args[0], astroid.ListComp)
- ):
- if node.func.name == "dict" and not isinstance(
- node.args[0].elt, astroid.Call
- ):
- message_name = "consider-using-dict-comprehension"
- self.add_message(message_name, node=node)
- elif node.func.name == "set":
- message_name = "consider-using-set-comprehension"
- self.add_message(message_name, node=node)
-
- @utils.check_messages(
- "stop-iteration-return",
- "consider-using-dict-comprehension",
- "consider-using-set-comprehension",
- "consider-using-sys-exit",
- )
- def visit_call(self, node):
- self._check_raising_stopiteration_in_generator_next_call(node)
- self._check_consider_using_comprehension_constructor(node)
- self._check_quit_exit_call(node)
-
- @staticmethod
- def _has_exit_in_scope(scope):
- exit_func = scope.locals.get("exit")
- return bool(
- exit_func and isinstance(exit_func[0], (astroid.ImportFrom, astroid.Import))
- )
-
- def _check_quit_exit_call(self, node):
-
- if isinstance(node.func, astroid.Name) and node.func.name in BUILTIN_EXIT_FUNCS:
- # If we have `exit` imported from `sys` in the current or global scope, exempt this instance.
- local_scope = node.scope()
- if self._has_exit_in_scope(local_scope) or self._has_exit_in_scope(
- node.root()
- ):
- return
- self.add_message("consider-using-sys-exit", node=node)
-
- def _check_raising_stopiteration_in_generator_next_call(self, node):
- """Check if a StopIteration exception is raised by the call to next function
-
- If the next value has a default value, then do not add message.
-
- :param node: Check to see if this Call node is a next function
- :type node: :class:`astroid.node_classes.Call`
- """
-
- def _looks_like_infinite_iterator(param):
- inferred = utils.safe_infer(param)
- if inferred:
- return inferred.qname() in KNOWN_INFINITE_ITERATORS
- return False
-
- if isinstance(node.func, astroid.Attribute):
- # A next() method, which is now what we want.
- return
-
- inferred = utils.safe_infer(node.func)
- if getattr(inferred, "name", "") == "next":
- frame = node.frame()
- # The next builtin can only have up to two
- # positional arguments and no keyword arguments
- has_sentinel_value = len(node.args) > 1
- if (
- isinstance(frame, astroid.FunctionDef)
- and frame.is_generator()
- and not has_sentinel_value
- and not utils.node_ignores_exception(node, StopIteration)
- and not _looks_like_infinite_iterator(node.args[0])
- ):
- self.add_message("stop-iteration-return", node=node)
-
- 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 an elif, this should not be another nesting level
- if isinstance(node, astroid.If) and self._is_actual_elif(node):
- 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) > len(self._nested_blocks):
- self._emit_nested_blocks_message_if_needed(nested_blocks)
-
- def _emit_nested_blocks_message_if_needed(self, nested_blocks):
- if len(nested_blocks) > self.config.max_nested_blocks:
- self.add_message(
- "too-many-nested-blocks",
- node=nested_blocks[0],
- args=(len(nested_blocks), self.config.max_nested_blocks),
- )
-
- @staticmethod
- def _duplicated_isinstance_types(node):
- """Get the duplicated types from the underlying isinstance calls.
-
- :param astroid.BoolOp node: Node which should contain a bunch of isinstance calls.
- :returns: Dictionary of the comparison objects from the isinstance calls,
- to duplicate values from consecutive calls.
- :rtype: dict
- """
- duplicated_objects = set()
- all_types = collections.defaultdict(set)
-
- for call in node.values:
- if not isinstance(call, astroid.Call) or len(call.args) != 2:
- continue
-
- inferred = utils.safe_infer(call.func)
- if not inferred or not utils.is_builtin_object(inferred):
- continue
-
- if inferred.name != "isinstance":
- continue
-
- isinstance_object = call.args[0].as_string()
- isinstance_types = call.args[1]
-
- if isinstance_object in all_types:
- duplicated_objects.add(isinstance_object)
-
- if isinstance(isinstance_types, astroid.Tuple):
- elems = [
- class_type.as_string() for class_type in isinstance_types.itered()
- ]
- else:
- elems = [isinstance_types.as_string()]
- all_types[isinstance_object].update(elems)
-
- # Remove all keys which not duplicated
- return {
- key: value for key, value in all_types.items() if key in duplicated_objects
- }
-
- def _check_consider_merging_isinstance(self, node):
- """Check isinstance calls which can be merged together."""
- if node.op != "or":
- return
-
- first_args = self._duplicated_isinstance_types(node)
- for duplicated_name, class_names in first_args.items():
- names = sorted(name for name in class_names)
- self.add_message(
- "consider-merging-isinstance",
- node=node,
- args=(duplicated_name, ", ".join(names)),
- )
-
- def _check_consider_using_in(self, node):
- allowed_ops = {"or": "==", "and": "!="}
-
- if node.op not in allowed_ops or len(node.values) < 2:
- return
-
- for value in node.values:
- if (
- not isinstance(value, astroid.Compare)
- or len(value.ops) != 1
- or value.ops[0][0] not in allowed_ops[node.op]
- ):
- return
- for comparable in value.left, value.ops[0][1]:
- if isinstance(comparable, astroid.Call):
- return
-
- # Gather variables and values from comparisons
- variables, values = [], []
- for value in node.values:
- variable_set = set()
- for comparable in value.left, value.ops[0][1]:
- if isinstance(comparable, astroid.Name):
- variable_set.add(comparable.as_string())
- values.append(comparable.as_string())
- variables.append(variable_set)
-
- # Look for (common-)variables that occur in all comparisons
- common_variables = reduce(lambda a, b: a.intersection(b), variables)
-
- if not common_variables:
- return
-
- # Gather information for the suggestion
- common_variable = sorted(list(common_variables))[0]
- comprehension = "in" if node.op == "or" else "not in"
- values = list(collections.OrderedDict.fromkeys(values))
- values.remove(common_variable)
- values_string = ", ".join(values) if len(values) != 1 else values[0] + ","
- suggestion = "%s %s (%s)" % (common_variable, comprehension, values_string)
-
- self.add_message("consider-using-in", node=node, args=(suggestion,))
-
- def _check_chained_comparison(self, node):
- """Check if there is any chained comparison in the expression.
-
- Add a refactoring message if a boolOp contains comparison like a < b and b < c,
- which can be chained as a < b < c.
-
- Care is taken to avoid simplifying a < b < c and b < d.
- """
- if node.op != "and" or len(node.values) < 2:
- return
-
- def _find_lower_upper_bounds(comparison_node, uses):
- left_operand = comparison_node.left
- for operator, right_operand in comparison_node.ops:
- for operand in (left_operand, right_operand):
- value = None
- if isinstance(operand, astroid.Name):
- value = operand.name
- elif isinstance(operand, astroid.Const):
- value = operand.value
-
- if value is None:
- continue
-
- if operator in ("<", "<="):
- if operand is left_operand:
- uses[value]["lower_bound"].add(comparison_node)
- elif operand is right_operand:
- uses[value]["upper_bound"].add(comparison_node)
- elif operator in (">", ">="):
- if operand is left_operand:
- uses[value]["upper_bound"].add(comparison_node)
- elif operand is right_operand:
- uses[value]["lower_bound"].add(comparison_node)
- left_operand = right_operand
-
- uses = collections.defaultdict(
- lambda: {"lower_bound": set(), "upper_bound": set()}
- )
- for comparison_node in node.values:
- if isinstance(comparison_node, astroid.Compare):
- _find_lower_upper_bounds(comparison_node, uses)
-
- for _, bounds in uses.items():
- num_shared = len(bounds["lower_bound"].intersection(bounds["upper_bound"]))
- num_lower_bounds = len(bounds["lower_bound"])
- num_upper_bounds = len(bounds["upper_bound"])
- if num_shared < num_lower_bounds and num_shared < num_upper_bounds:
- self.add_message("chained-comparison", node=node)
- break
-
- @utils.check_messages(
- "consider-merging-isinstance", "consider-using-in", "chained-comparison"
- )
- def visit_boolop(self, node):
- self._check_consider_merging_isinstance(node)
- self._check_consider_using_in(node)
- self._check_chained_comparison(node)
-
- @staticmethod
- def _is_simple_assignment(node):
- return (
- isinstance(node, astroid.Assign)
- and len(node.targets) == 1
- and isinstance(node.targets[0], astroid.node_classes.AssignName)
- and isinstance(node.value, astroid.node_classes.Name)
- )
-
- def _check_swap_variables(self, node):
- if not node.next_sibling() or not node.next_sibling().next_sibling():
- return
- assignments = [node, node.next_sibling(), node.next_sibling().next_sibling()]
- if not all(self._is_simple_assignment(node) for node in assignments):
- return
- if any(node in self._reported_swap_nodes for node in assignments):
- return
- left = [node.targets[0].name for node in assignments]
- right = [node.value.name for node in assignments]
- if left[0] == right[-1] and left[1:] == right[:-1]:
- self._reported_swap_nodes.update(assignments)
- message = "consider-swap-variables"
- self.add_message(message, node=node)
-
- @utils.check_messages(
- "simplify-boolean-expression",
- "consider-using-ternary",
- "consider-swap-variables",
- )
- def visit_assign(self, node):
- self._check_swap_variables(node)
- if self._is_and_or_ternary(node.value):
- cond, truth_value, false_value = self._and_or_ternary_arguments(node.value)
- else:
- return
-
- if all(
- isinstance(value, astroid.Compare) for value in (truth_value, false_value)
- ):
- return
-
- inferred_truth_value = utils.safe_infer(truth_value)
- if inferred_truth_value in (None, astroid.Uninferable):
- truth_boolean_value = True
- else:
- truth_boolean_value = truth_value.bool_value()
-
- if truth_boolean_value is False:
- message = "simplify-boolean-expression"
- suggestion = false_value.as_string()
- else:
- message = "consider-using-ternary"
- suggestion = "{truth} if {cond} else {false}".format(
- truth=truth_value.as_string(),
- cond=cond.as_string(),
- false=false_value.as_string(),
- )
- self.add_message(message, node=node, args=(suggestion,))
-
- visit_return = visit_assign
-
- def _check_consider_using_join(self, aug_assign):
- """
- We start with the augmented assignment and work our way upwards.
- Names of variables for nodes if match successful:
- result = '' # assign
- for number in ['1', '2', '3'] # for_loop
- result += number # aug_assign
- """
- for_loop = aug_assign.parent
- if not isinstance(for_loop, astroid.For) or len(for_loop.body) > 1:
- return
- assign = for_loop.previous_sibling()
- if not isinstance(assign, astroid.Assign):
- return
- result_assign_names = {
- target.name
- for target in assign.targets
- if isinstance(target, astroid.AssignName)
- }
-
- is_concat_loop = (
- aug_assign.op == "+="
- and isinstance(aug_assign.target, astroid.AssignName)
- and len(for_loop.body) == 1
- and aug_assign.target.name in result_assign_names
- and isinstance(assign.value, astroid.Const)
- and isinstance(assign.value.value, str)
- and isinstance(aug_assign.value, astroid.Name)
- and aug_assign.value.name == for_loop.target.name
- )
- if is_concat_loop:
- self.add_message("consider-using-join", node=aug_assign)
-
- @utils.check_messages("consider-using-join")
- def visit_augassign(self, node):
- self._check_consider_using_join(node)
-
- @utils.check_messages("unnecessary-comprehension")
- def visit_comprehension(self, node):
- self._check_unnecessary_comprehension(node)
-
- def _check_unnecessary_comprehension(self, node):
- if (
- isinstance(node.parent, astroid.GeneratorExp)
- or len(node.ifs) != 0
- or len(node.parent.generators) != 1
- or node.is_async
- ):
- return
-
- if (
- isinstance(node.parent, astroid.DictComp)
- and isinstance(node.parent.key, astroid.Name)
- and isinstance(node.parent.value, astroid.Name)
- and isinstance(node.target, astroid.Tuple)
- and all(isinstance(elt, astroid.AssignName) for elt in node.target.elts)
- ):
- expr_list = [node.parent.key.name, node.parent.value.name]
- target_list = [elt.name for elt in node.target.elts]
-
- elif isinstance(node.parent, (astroid.ListComp, astroid.SetComp)):
- expr = node.parent.elt
- if isinstance(expr, astroid.Name):
- expr_list = expr.name
- elif isinstance(expr, astroid.Tuple):
- if any(not isinstance(elt, astroid.Name) for elt in expr.elts):
- return
- expr_list = [elt.name for elt in expr.elts]
- else:
- expr_list = []
- target = node.parent.generators[0].target
- target_list = (
- target.name
- if isinstance(target, astroid.AssignName)
- else (
- [
- elt.name
- for elt in target.elts
- if isinstance(elt, astroid.AssignName)
- ]
- if isinstance(target, astroid.Tuple)
- else []
- )
- )
- else:
- return
- if expr_list == target_list != []:
- self.add_message("unnecessary-comprehension", node=node)
-
- @staticmethod
- def _is_and_or_ternary(node):
- """
- Returns true if node is 'condition and true_value or false_value' form.
-
- All of: condition, true_value and false_value should not be a complex boolean expression
- """
- return (
- isinstance(node, astroid.BoolOp)
- and node.op == "or"
- and len(node.values) == 2
- and isinstance(node.values[0], astroid.BoolOp)
- and not isinstance(node.values[1], astroid.BoolOp)
- and node.values[0].op == "and"
- and not isinstance(node.values[0].values[1], astroid.BoolOp)
- and len(node.values[0].values) == 2
- )
-
- @staticmethod
- def _and_or_ternary_arguments(node):
- false_value = node.values[1]
- condition, true_value = node.values[0].values
- return condition, true_value, false_value
-
- def visit_functiondef(self, node):
- self._return_nodes[node.name] = list(
- node.nodes_of_class(astroid.Return, skip_klass=astroid.FunctionDef)
- )
-
- def _check_consistent_returns(self, node):
- """Check that all return statements inside a function are consistent.
-
- Return statements are consistent if:
- - all returns are explicit and if there is no implicit return;
- - all returns are empty and if there is, possibly, an implicit return.
-
- Args:
- node (astroid.FunctionDef): the function holding the return statements.
-
- """
- # explicit return statements are those with a not None value
- explicit_returns = [
- _node for _node in self._return_nodes[node.name] if _node.value is not None
- ]
- if not explicit_returns:
- return
- if len(explicit_returns) == len(
- self._return_nodes[node.name]
- ) and self._is_node_return_ended(node):
- return
- self.add_message("inconsistent-return-statements", node=node)
-
- def _is_node_return_ended(self, node):
- """Check if the node ends with an explicit return statement.
-
- Args:
- node (astroid.NodeNG): node to be checked.
-
- Returns:
- bool: True if the node ends with an explicit statement, False otherwise.
-
- """
- #  Recursion base case
- if isinstance(node, astroid.Return):
- return True
- if isinstance(node, astroid.Call):
- try:
- funcdef_node = node.func.inferred()[0]
- if self._is_function_def_never_returning(funcdef_node):
- return True
- except astroid.InferenceError:
- pass
- # Avoid the check inside while loop as we don't know
- #  if they will be completed
- if isinstance(node, astroid.While):
- return True
- if isinstance(node, astroid.Raise):
- # a Raise statement doesn't need to end with a return statement
- # but if the exception raised is handled, then the handler has to
- # ends with a return statement
- if not node.exc:
- # Ignore bare raises
- return True
- if not utils.is_node_inside_try_except(node):
- # If the raise statement is not inside a try/except statement
- #  then the exception is raised and cannot be caught. No need
- #  to infer it.
- return True
- exc = utils.safe_infer(node.exc)
- if exc is None or exc is astroid.Uninferable:
- return False
- exc_name = exc.pytype().split(".")[-1]
- handlers = utils.get_exception_handlers(node, exc_name)
- handlers = list(handlers) if handlers is not None else []
- if handlers:
- # among all the handlers handling the exception at least one
- # must end with a return statement
- return any(
- self._is_node_return_ended(_handler) for _handler in handlers
- )
- # if no handlers handle the exception then it's ok
- return True
- if isinstance(node, astroid.If):
- # if statement is returning if there are exactly two return statements in its
- #  children : one for the body part, the other for the orelse part
- # Do not check if inner function definition are return ended.
- is_orelse_returning = any(
- self._is_node_return_ended(_ore)
- for _ore in node.orelse
- if not isinstance(_ore, astroid.FunctionDef)
- )
- is_if_returning = any(
- self._is_node_return_ended(_ifn)
- for _ifn in node.body
- if not isinstance(_ifn, astroid.FunctionDef)
- )
- return is_if_returning and is_orelse_returning
- #  recurses on the children of the node except for those which are except handler
- # because one cannot be sure that the handler will really be used
- return any(
- self._is_node_return_ended(_child)
- for _child in node.get_children()
- if not isinstance(_child, astroid.ExceptHandler)
- )
-
- def _is_function_def_never_returning(self, node):
- """Return True if the function never returns. False otherwise.
-
- Args:
- node (astroid.FunctionDef): function definition node to be analyzed.
-
- Returns:
- bool: True if the function never returns, False otherwise.
- """
- try:
- return node.qname() in self._never_returning_functions
- except TypeError:
- return False
-
- def _check_return_at_the_end(self, node):
- """Check for presence of a *single* return statement at the end of a
- function. "return" or "return None" are useless because None is the
- default return type if they are missing.
-
- NOTE: produces a message only if there is a single return statement
- in the function body. Otherwise _check_consistent_returns() is called!
- Per its implementation and PEP8 we can have a "return None" at the end
- of the function body if there are other return statements before that!
- """
- if len(self._return_nodes[node.name]) > 1:
- return
- if len(node.body) <= 1:
- return
-
- last = node.body[-1]
- if isinstance(last, astroid.Return):
- # e.g. "return"
- if last.value is None:
- self.add_message("useless-return", node=node)
- # return None"
- elif isinstance(last.value, astroid.Const) and (last.value.value is None):
- self.add_message("useless-return", node=node)
-
-
-class RecommandationChecker(checkers.BaseChecker):
- __implements__ = (interfaces.IAstroidChecker,)
- name = "refactoring"
- 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.",
- ),
- "C0201": (
- "Consider iterating the dictionary directly instead of calling .keys()",
- "consider-iterating-dictionary",
- "Emitted when the keys of a dictionary are iterated through the .keys() "
- "method. It is enough to just iterate through the dictionary itself, as "
- 'in "for key in dictionary".',
- ),
- }
-
- @staticmethod
- def _is_builtin(node, function):
- inferred = utils.safe_infer(node)
- if not inferred:
- return False
- return utils.is_builtin_object(inferred) and inferred.name == function
-
- @utils.check_messages("consider-iterating-dictionary")
- def visit_call(self, node):
- if not isinstance(node.func, astroid.Attribute):
- return
- if node.func.attrname != "keys":
- return
- if not isinstance(node.parent, (astroid.For, astroid.Comprehension)):
- return
-
- inferred = utils.safe_infer(node.func)
- if not isinstance(inferred, astroid.BoundMethod) or not isinstance(
- inferred.bound, astroid.Dict
- ):
- return
-
- if isinstance(node.parent, (astroid.For, astroid.Comprehension)):
- self.add_message("consider-iterating-dictionary", node=node)
-
- @utils.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([start], len(...), [stop])` 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) == 2 and not _is_constant_zero(node.iter.args[0]):
- return
- if len(node.iter.args) > 2:
- return
-
- # Is it a proper len call?
- if not isinstance(node.iter.args[-1], astroid.Call):
- return
- second_func = node.iter.args[-1].func
- if not self._is_builtin(second_func, "len"):
- return
- len_args = node.iter.args[-1].args
- if not len_args or len(len_args) != 1:
- return
- iterating_object = len_args[0]
- if not isinstance(iterating_object, astroid.Name):
- return
- # If we're defining __iter__ on self, enumerate won't work
- scope = node.scope()
- if iterating_object.name == "self" and scope.name == "__iter__":
- 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
-
-
-class NotChecker(checkers.BaseChecker):
- """checks for too many not in comparison expressions
-
- - "not not" should trigger a warning
- - "not" followed by a comparison should trigger a warning
- """
-
- __implements__ = (interfaces.IAstroidChecker,)
- msgs = {
- "C0113": (
- 'Consider changing "%s" to "%s"',
- "unneeded-not",
- "Used when a boolean expression contains an unneeded negation.",
- )
- }
- name = "refactoring"
- reverse_op = {
- "<": ">=",
- "<=": ">",
- ">": "<=",
- ">=": "<",
- "==": "!=",
- "!=": "==",
- "in": "not in",
- "is": "is not",
- }
- # sets are not ordered, so for example "not set(LEFT_VALS) <= set(RIGHT_VALS)" is
- # not equivalent to "set(LEFT_VALS) > set(RIGHT_VALS)"
- skipped_nodes = (astroid.Set,)
- # 'builtins' py3, '__builtin__' py2
- skipped_classnames = [
- "%s.%s" % (builtins.__name__, qname) for qname in ("set", "frozenset")
- ]
-
- @utils.check_messages("unneeded-not")
- def visit_unaryop(self, node):
- if node.op != "not":
- return
- operand = node.operand
-
- if isinstance(operand, astroid.UnaryOp) and operand.op == "not":
- self.add_message(
- "unneeded-not",
- node=node,
- args=(node.as_string(), operand.operand.as_string()),
- )
- elif isinstance(operand, astroid.Compare):
- left = operand.left
- # ignore multiple comparisons
- if len(operand.ops) > 1:
- return
- operator, right = operand.ops[0]
- if operator not in self.reverse_op:
- return
- # Ignore __ne__ as function of __eq__
- frame = node.frame()
- if frame.name == "__ne__" and operator == "==":
- return
- for _type in (utils.node_type(left), utils.node_type(right)):
- if not _type:
- return
- if isinstance(_type, self.skipped_nodes):
- return
- if (
- isinstance(_type, astroid.Instance)
- and _type.qname() in self.skipped_classnames
- ):
- return
- suggestion = "%s %s %s" % (
- left.as_string(),
- self.reverse_op[operator],
- right.as_string(),
- )
- self.add_message(
- "unneeded-not", node=node, args=(node.as_string(), suggestion)
- )
-
-
-class LenChecker(checkers.BaseChecker):
- """Checks for incorrect usage of len() inside conditions.
- Pep8 states:
- For sequences, (strings, lists, tuples), use the fact that empty sequences are false.
-
- Yes: if not seq:
- if seq:
-
- No: if len(seq):
- if not len(seq):
-
- Problems detected:
- * if len(sequence):
- * if not len(sequence):
- * elif len(sequence):
- * elif not len(sequence):
- * while len(sequence):
- * while not len(sequence):
- * assert len(sequence):
- * assert not len(sequence):
- """
-
- __implements__ = (interfaces.IAstroidChecker,)
-
- # configuration section name
- name = "refactoring"
- msgs = {
- "C1801": (
- "Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty",
- "len-as-condition",
- "Used when Pylint detects that len(sequence) is being used "
- "without explicit comparison inside a condition to determine if a sequence is empty. "
- "Instead of coercing the length to a boolean, either "
- "rely on the fact that empty sequences are false or "
- "compare the length against a scalar.",
- )
- }
-
- priority = -2
- options = ()
-
- @utils.check_messages("len-as-condition")
- def visit_call(self, node):
- # a len(S) call is used inside a test condition
- # could be if, while, assert or if expression statement
- # e.g. `if len(S):`
- if _is_len_call(node):
- # the len() call could also be nested together with other
- # boolean operations, e.g. `if z or len(x):`
- parent = node.parent
- while isinstance(parent, astroid.BoolOp):
- parent = parent.parent
-
- # we're finally out of any nested boolean operations so check if
- # this len() call is part of a test condition
- if not _node_is_test_condition(parent):
- return
- if not (node is parent.test or parent.test.parent_of(node)):
- return
- self.add_message("len-as-condition", node=node)
-
- @utils.check_messages("len-as-condition")
- def visit_unaryop(self, node):
- """`not len(S)` must become `not S` regardless if the parent block
- is a test condition or something else (boolean expression)
- e.g. `if not len(S):`"""
- if (
- isinstance(node, astroid.UnaryOp)
- and node.op == "not"
- and _is_len_call(node.operand)
- ):
- self.add_message("len-as-condition", node=node)
-
-
-def register(linter):
- """Required method to auto register this checker."""
- linter.register_checker(RefactoringChecker(linter))
- linter.register_checker(NotChecker(linter))
- linter.register_checker(RecommandationChecker(linter))
- linter.register_checker(LenChecker(linter))