Beispiel #1
0
    def walk(self, astroid: nodes.NodeNG) -> None:
        """Call visit events of astroid checkers for the given node, recurse on
        its children, then leave events.
        """
        cid = astroid.__class__.__name__.lower()

        # Detect if the node is a new name for a deprecated alias.
        # In this case, favour the methods for the deprecated
        # alias if any,  in order to maintain backwards
        # compatibility.
        visit_events: Sequence[AstCallback] = self.visit_events.get(cid, ())
        leave_events: Sequence[AstCallback] = self.leave_events.get(cid, ())

        try:
            if astroid.is_statement:
                self.nbstatements += 1
            # generate events for this node on each checker
            for callback in visit_events:
                callback(astroid)
            # recurse on children
            for child in astroid.get_children():
                self.walk(child)
            for callback in leave_events:
                callback(astroid)
        except Exception:
            if self.exception_msg is False:
                file = getattr(astroid.root(), "file", None)
                print(
                    f"Exception on node {repr(astroid)} in file '{file}'",
                    file=sys.stderr,
                )
                traceback.print_exc()
                self.exception_msg = True
            raise
Beispiel #2
0
    def check_deprecated_method(self, node: nodes.Call,
                                inferred: nodes.NodeNG) -> None:
        """Executes the checker for the given node.

        This method should be called from the checker implementing this mixin.
        """

        # Reject nodes which aren't of interest to us.
        if not isinstance(inferred, ACCEPTABLE_NODES):
            return

        if isinstance(node.func, nodes.Attribute):
            func_name = node.func.attrname
        elif isinstance(node.func, nodes.Name):
            func_name = node.func.name
        else:
            # Not interested in other nodes.
            return

        if hasattr(inferred.parent, "qname") and inferred.parent.qname():
            # Handling the situation when deprecated function is
            # alias to existing function.
            qnames = {
                inferred.qname(),
                f"{inferred.parent.qname()}.{func_name}",
                func_name,
            }
        else:
            qnames = {inferred.qname(), func_name}
        if any(name in self.deprecated_methods() for name in qnames):
            self.add_message("deprecated-method",
                             node=node,
                             args=(func_name, ))
            return
        num_of_args = len(node.args)
        kwargs = {kw.arg for kw in node.keywords} if node.keywords else {}
        deprecated_arguments = (self.deprecated_arguments(qn) for qn in qnames)
        for position, arg_name in chain(*deprecated_arguments):
            if arg_name in kwargs:
                # function was called with deprecated argument as keyword argument
                self.add_message("deprecated-argument",
                                 node=node,
                                 args=(arg_name, func_name))
            elif position is not None and position < num_of_args:
                # function was called with deprecated argument as positional argument
                self.add_message("deprecated-argument",
                                 node=node,
                                 args=(arg_name, func_name))
 def _modified_iterating_check_on_node_and_children(
     self, body_node: nodes.NodeNG, iter_obj: nodes.NodeNG
 ) -> None:
     """See if node or any of its children raises modified iterating messages."""
     self._modified_iterating_check(body_node, iter_obj)
     for child in body_node.get_children():
         self._modified_iterating_check_on_node_and_children(child, iter_obj)
Beispiel #4
0
 def get_nodes(
         self,
         statement: nodes.NodeNG) -> Generator[nodes.NodeNG, None, None]:
     multiple_nodes = lambda nodes_: chain.from_iterable(
         self.get_nodes(node) for node in nodes_)
     if isinstance(statement, nodes.Assign):
         # RHS is evaluated before assigned in an assignment statement
         yield from self.get_nodes(statement.value)
         yield from multiple_nodes(
             statement.targets)  # statement.targets is a list of nodes
     elif isinstance(statement, (nodes.ListComp, nodes.SetComp,
                                 nodes.DictComp, nodes.GeneratorExp)):
         # Comprehension targets are assigned before expression is evaluated.
         yield from multiple_nodes(
             statement.generators
         )  # statement.generators is a list of nodes
         if not hasattr(statement, "elt"):
             yield from self.get_nodes(
                 statement.key)  # keys evaluated first
             yield from self.get_nodes(statement.value)
         else:
             yield from self.get_nodes(statement.elt)
     else:
         yield from statement.nodes_of_class(
             (nodes.AssignName, nodes.DelName, nodes.Name),
             nodes.FunctionDef)
Beispiel #5
0
def ignore_type_check_filter(node: nc.NodeNG) -> nc.NodeNG:
    """Ignore stuff under 'if TYPE_CHECKING:' block at module level."""

    # Look for a non-nested 'if TYPE_CHECKING:'
    if (isinstance(node.test, astroid.Name)
            and node.test.name == 'TYPE_CHECKING'
            and isinstance(node.parent, astroid.Module)):

        # Find the module node.
        mnode = node
        while mnode.parent is not None:
            mnode = mnode.parent

        # First off, remove any names that are getting defined
        # in this block from the module locals.
        for cnode in node.body:
            _strip_import(cnode, mnode)

        # Now replace the body with a simple 'pass'. This will
        # keep pylint from complaining about grouped imports/etc.
        passnode = astroid.Pass(parent=node,
                                lineno=node.lineno + 1,
                                col_offset=node.col_offset + 1)
        node.body = [passnode]
    return node
def ignore_type_check_filter(if_node: nc.NodeNG) -> nc.NodeNG:
    """Ignore stuff under 'if TYPE_CHECKING:' block at module level."""

    # Look for a non-nested 'if TYPE_CHECKING:'
    if (isinstance(if_node.test, astroid.Name)
            and if_node.test.name == 'TYPE_CHECKING'
            and isinstance(if_node.parent, astroid.Module)):

        module_node = if_node.parent

        # Remove any locals getting defined under this if statement.
        # (ideally should recurse in case we have nested if statements/etc
        # but keeping it simple for now).
        for name, locations in list(module_node.locals.items()):
            # Calc which remaining name locations are outside of the if
            # block. Update or delete the list as needed.
            new_locs = [l for l in locations if not _under_if(l, if_node)]
            if len(new_locs) == len(locations):
                continue
            if new_locs:
                module_node.locals[name] = new_locs
                continue
            del module_node.locals[name]

        # Now replace its children with a simple pass statement.
        passnode = astroid.Pass(parent=if_node,
                                lineno=if_node.lineno + 1,
                                col_offset=if_node.col_offset + 1)
        if_node.body = [passnode]
    return if_node
Beispiel #7
0
def is_requests_func(node: NodeNG) -> bool:
    """
    Checks if the node represents a requests HTTP call function.

    Examples are: requests.get/post/..., requests.Session.get/post/...
    """

    # simple case
    if isinstance(node, nodes.Attribute) and node.attrname in SESSION_METHODS:
        if (
            is_requests_api_module(node.expr)
            and node.attrname not in SESSION_ONLY_METHODS
        ) or is_requests_session(node.expr):
            return True

    if isinstance(node, nodes.Name):
        _, assigns = node.lookup(node.name)
        for assign in assigns:
            if (
                isinstance(assign, nodes.ImportFrom)
                and assign.modname in REQUESTS_API_MODULES
                and assign.real_name(node.name) in HTTP_METHOD_FUNCTIONS
            ):
                return True

            if hasattr(assign, 'assign_type'):
                assign = assign.assign_type()

            if isinstance(assign, nodes.Assign) and is_requests_func(assign.value):
                return True

    return False
Beispiel #8
0
 def _set_state_on_block_lines(
     self,
     msgs_store: MessageDefinitionStore,
     node: nodes.NodeNG,
     msg: MessageDefinition,
     msg_state: dict[int, bool],
 ) -> None:
     """Recursively walk (depth first) AST to collect block level options
     line numbers and set the state correctly.
     """
     for child in node.get_children():
         self._set_state_on_block_lines(msgs_store, child, msg, msg_state)
     # first child line number used to distinguish between disable
     # which are the first child of scoped node with those defined later.
     # For instance in the code below:
     #
     # 1.   def meth8(self):
     # 2.        """test late disabling"""
     # 3.        pylint: disable=not-callable, useless-suppression
     # 4.        print(self.blip)
     # 5.        pylint: disable=no-member, useless-suppression
     # 6.        print(self.bla)
     #
     # E1102 should be disabled from line 1 to 6 while E1101 from line 5 to 6
     #
     # this is necessary to disable locally messages applying to class /
     # function using their fromlineno
     if (isinstance(node, (nodes.Module, nodes.ClassDef, nodes.FunctionDef))
             and node.body):
         firstchildlineno = node.body[0].fromlineno
     else:
         firstchildlineno = node.tolineno
     self._set_message_state_in_block(msg, msg_state, node,
                                      firstchildlineno)
Beispiel #9
0
def _strip_import(cnode: nc.NodeNG, mnode: nc.NodeNG) -> None:
    if isinstance(cnode, (astroid.Import, astroid.ImportFrom)):
        for name, val in list(mnode.locals.items()):
            if cnode in val:

                # Pull us out of the list.
                valnew = [v for v in val if v is not cnode]
                if valnew:
                    mnode.locals[name] = valnew
                else:
                    del mnode.locals[name]
Beispiel #10
0
 def _is_float_nan(node: nodes.NodeNG) -> bool:
     try:
         if isinstance(node, nodes.Call) and len(node.args) == 1:
             if (
                 node.args[0].value.lower() == "nan"
                 and node.inferred()[0].pytype() == "builtins.float"
             ):
                 return True
         return False
     except AttributeError:
         return False
Beispiel #11
0
def is_requests_api_module(node: NodeNG) -> bool:
    """True if node represents the requests or requests.api module."""

    if isinstance(node, (nodes.Name, nodes.Expr)):
        _, assigns = node.lookup(node.name)
        for assign in assigns:
            if (
                isinstance(assign, (nodes.Import, nodes.ImportFrom))
                and assign.real_name(node.name) in REQUESTS_API_MODULES
            ):
                return True

    return False
Beispiel #12
0
 def visit_default(self, node: nodes.NodeNG) -> None:
     """Check the node line number and check it if not yet done."""
     if not node.is_statement:
         return
     if not node.root().pure_python:
         return
     prev_sibl = node.previous_sibling()
     if prev_sibl is not None:
         prev_line = prev_sibl.fromlineno
     # The line on which a 'finally': occurs in a 'try/finally'
     # is not directly represented in the AST. We infer it
     # by taking the last line of the body and adding 1, which
     # should be the line of finally:
     elif (isinstance(node.parent, nodes.TryFinally)
           and node in node.parent.finalbody):
         prev_line = node.parent.body[0].tolineno + 1
     elif isinstance(node.parent, nodes.Module):
         prev_line = 0
     else:
         prev_line = node.parent.statement(future=True).fromlineno
     line = node.fromlineno
     assert line, node
     if prev_line == line and self._visited_lines.get(line) != 2:
         self._check_multi_statement_line(node, line)
         return
     if line in self._visited_lines:
         return
     try:
         tolineno = node.blockstart_tolineno
     except AttributeError:
         tolineno = node.tolineno
     assert tolineno, node
     lines = []
     for line in range(line, tolineno + 1):
         self._visited_lines[line] = 1
         try:
             lines.append(self._lines[line].rstrip())
         except KeyError:
             lines.append("")
Beispiel #13
0
 def transform(node: NodeNG,
               infer_function: InferFn = infer_function) -> NodeNG:
     if (raise_on_overwrite and node._explicit_inference is not None
             and node._explicit_inference is not infer_function):
         raise InferenceOverwriteError(
             "Inference already set to {existing_inference}. "
             "Trying to overwrite with {new_inference} for {node}".format(
                 existing_inference=infer_function,
                 new_inference=node._explicit_inference,
                 node=node,
             ))
     # pylint: disable=no-value-for-parameter
     node._explicit_inference = _inference_tip_cached(infer_function)
     return node
Beispiel #14
0
def get_module_and_frameid(node: nodes.NodeNG) -> Tuple[str, str]:
    """Return the module name and the frame id in the module."""
    frame = node.frame(future=True)
    module, obj = "", []
    while frame:
        if isinstance(frame, Module):
            module = frame.name
        else:
            obj.append(getattr(frame, "name", "<lambda>"))
        try:
            frame = frame.parent.frame(future=True)
        except AttributeError:
            break
    obj.reverse()
    return module, ".".join(obj)
Beispiel #15
0
    def visit(self, node: nodes.NodeNG) -> Any:
        """Launch the visit starting from the given node."""
        if node in self._visited:
            return None

        self._visited.add(node)
        methods = self.get_callbacks(node)
        if methods[0] is not None:
            methods[0](node)
        if hasattr(node, "locals"):  # skip Instance and other proxy
            for local_node in node.values():
                self.visit(local_node)
        if methods[1] is not None:
            return methods[1](node)
        return None
Beispiel #16
0
def is_session_class(node: NodeNG) -> bool:
    """True if node represents the requests.Session class."""

    try:
        for inferred in node.infer():
            if (
                isinstance(inferred, nodes.ClassDef)
                and inferred.name == SESSION_CLASS_NAME
                and isinstance(inferred.parent, nodes.Module)
                and inferred.parent.name == SESSION_MODULE
            ):
                return True
    except InferenceError:
        pass

    return False
Beispiel #17
0
def _check_key_in_kwargs(unpacked: NodeNG, key: str) -> bool:
    """Given a **kwargs node in a function call, tries to check presence of a key."""

    try:
        for inferred in unpacked.infer():
            if (
                isinstance(inferred, nodes.Dict)
                and inferred.items
                and not _check_key_in_dict(inferred, key)
            ):
                # only if we know that it is a dict and astroid inferred some values
                return False
    except InferenceError:
        pass

    return True
Beispiel #18
0
def _lookup(node: NodeNG) -> List[NodeNG]:
    """Given a name or attribute node, returns the assignments to that name/attr."""

    if isinstance(node, nodes.Name):
        return node.lookup(node.name)[1]

    if isinstance(node, nodes.Attribute):
        try:
            obj = next(node.expr.infer())
        except InferenceError:
            return []

        if isinstance(obj, bases.Instance):
            try:
                return obj.getattr(node.attrname)
            except AttributeInferenceError:
                return []

    return []
def func_annotations_filter(node: nc.NodeNG) -> nc.NodeNG:
    """Filter annotated function args/retvals.

    This accounts for deferred evaluation available in in Python 3.7+
    via 'from __future__ import annotations'. In this case we don't
    want Pylint to complain about missing symbols in annotations when
    they aren't actually needed at runtime.
    """
    # Only do this if deferred annotations are on.
    if not using_future_annotations(node):
        return node

    # Wipe out argument annotations.

    # Special-case: certain function decorators *do*
    # evaluate annotations at runtime so we want to leave theirs intact.
    # This includes functools.singledispatch, ba.dispatchmethod, and
    # efro.MessageReceiver.
    # Lets just look for a @XXX.register or @XXX.handler decorators for
    # now; can get more specific if we get false positives.
    if node.decorators is not None:
        for dnode in node.decorators.nodes:
            if (isinstance(dnode, astroid.nodes.Name)
                    and dnode.name in {'dispatchmethod', 'singledispatch'}):
                return node  # Leave annotations intact.

            if (isinstance(dnode, astroid.nodes.Attribute)
                    and dnode.attrname in {'register', 'handler'}):
                return node  # Leave annotations intact.

    node.args.annotations = [None for _ in node.args.args]
    node.args.varargannotation = None
    node.args.kwargannotation = None
    node.args.kwonlyargs_annotations = [None for _ in node.args.kwonlyargs]
    node.args.posonlyargs_annotations = [None for _ in node.args.kwonlyargs]

    # Wipe out return-value annotation.
    if node.returns is not None:
        node.returns = None

    return node
Beispiel #20
0
def func_annotations_filter(node: nc.NodeNG) -> nc.NodeNG:
    """Filter annotated function args/retvals.

    This accounts for deferred evaluation available in in Python 3.7+
    via 'from __future__ import annotations'. In this case we don't
    want Pylint to complain about missing symbols in annotations when
    they aren't actually needed at runtime.
    """
    # Only do this if deferred annotations are on.
    if not using_future_annotations(node):
        return node

    # Wipe out argument annotations.

    # Special-case: functools.singledispatch and ba.dispatchmethod *do*
    # evaluate annotations at runtime so we want to leave theirs intact.
    # Lets just look for a @XXX.register decorator used by both I guess.
    if node.decorators is not None:
        for dnode in node.decorators.nodes:
            if (isinstance(dnode, astroid.nodes.Name)
                    and dnode.name in ('dispatchmethod', 'singledispatch')):
                return node  # Leave annotations intact.

            if (isinstance(dnode, astroid.nodes.Attribute)
                    and dnode.attrname == 'register'):
                return node  # Leave annotations intact.

    node.args.annotations = [None for _ in node.args.args]
    node.args.varargannotation = None
    node.args.kwargannotation = None
    node.args.kwonlyargs_annotations = [None for _ in node.args.kwonlyargs]
    node.args.posonlyargs_annotations = [None for _ in node.args.kwonlyargs]

    # Wipe out return-value annotation.
    if node.returns is not None:
        node.returns = None

    return node
def class_generics_filter(node: nc.NodeNG) -> nc.NodeNG:
    """Filter generics subscripts out of class declarations."""

    # First, quick-out if nothing here should be filtered.
    found = False
    for base in node.bases:
        if _is_strippable_subscript(base):
            found = True

    if not found:
        return node

    # Now strip subscripts from base classes.
    new_bases: list[nc.NodeNG] = []
    for base in node.bases:
        if _is_strippable_subscript(base):
            new_bases.append(base.value)
            base.value.parent = node
        else:
            new_bases.append(base)
    node.bases = new_bases

    return node
Beispiel #22
0
def _to_literal(node: nodes.NodeNG) -> Any:
    # Can raise SyntaxError or ValueError from ast.literal_eval
    # Can raise AttributeError from node.as_string() as not all nodes have a visitor
    # Is this the stupidest idea or the simplest idea?
    return ast.literal_eval(node.as_string())
Beispiel #23
0
    def _set_message_state_in_block(
        self,
        msg: MessageDefinition,
        lines: dict[int, bool],
        node: nodes.NodeNG,
        firstchildlineno: int,
    ) -> None:
        """Set the state of a message in a block of lines."""
        first = node.fromlineno
        last = node.tolineno
        for lineno, state in list(lines.items()):
            original_lineno = lineno
            if first > lineno or last < lineno:
                continue
            # Set state for all lines for this block, if the
            # warning is applied to nodes.
            if msg.scope == WarningScope.NODE:
                if lineno > firstchildlineno:
                    state = True
                first_, last_ = node.block_range(lineno)
                # pylint: disable=useless-suppression
                # For block nodes first_ is their definition line. For example, we
                # set the state of line zero for a module to allow disabling
                # invalid-name for the module. For example:
                # 1. # pylint: disable=invalid-name
                # 2. ...
                # OR
                # 1. """Module docstring"""
                # 2. # pylint: disable=invalid-name
                # 3. ...
                #
                # But if we already visited line 0 we don't need to set its state again
                # 1. # pylint: disable=invalid-name
                # 2. # pylint: enable=invalid-name
                # 3. ...
                # The state should come from line 1, not from line 2
                # Therefore, if the 'fromlineno' is already in the states we just start
                # with the lineno we were originally visiting.
                # pylint: enable=useless-suppression
                if (first_ == node.fromlineno and first_ >= firstchildlineno
                        and node.fromlineno in self._module_msgs_state.get(
                            msg.msgid, ())):
                    first_ = lineno

            else:
                first_ = lineno
                last_ = last
            for line in range(first_, last_ + 1):
                # Do not override existing entries. This is especially important
                # when parsing the states for a scoped node where some line-disables
                # have already been parsed.
                if ((isinstance(node, nodes.Module)
                     and node.fromlineno <= line < lineno) or
                    (not isinstance(node, nodes.Module)
                     and node.fromlineno < line < lineno)
                    ) and line in self._module_msgs_state.get(msg.msgid, ()):
                    continue
                if line in lines:  # state change in the same block
                    state = lines[line]
                    original_lineno = line

                # Update suppression mapping
                if not state:
                    self._suppression_mapping[(msg.msgid,
                                               line)] = original_lineno
                else:
                    self._suppression_mapping.pop((msg.msgid, line), None)

                # Update message state for respective line
                try:
                    self._module_msgs_state[msg.msgid][line] = state
                except KeyError:
                    self._module_msgs_state[msg.msgid] = {line: state}
            del lines[lineno]
Beispiel #24
0
def _filter_stmts(base_node: nodes.NodeNG, stmts, frame, offset):
    """Filter the given list of statements to remove ignorable statements.

    If base_node is not a frame itself and the name is found in the inner
    frame locals, statements will be filtered to remove ignorable
    statements according to base_node's location.

    :param stmts: The statements to filter.
    :type stmts: list(nodes.NodeNG)

    :param frame: The frame that all of the given statements belong to.
    :type frame: nodes.NodeNG

    :param offset: The line offset to filter statements up to.
    :type offset: int

    :returns: The filtered statements.
    :rtype: list(nodes.NodeNG)
    """
    # if offset == -1, my actual frame is not the inner frame but its parent
    #
    # class A(B): pass
    #
    # we need this to resolve B correctly
    if offset == -1:
        myframe = base_node.frame().parent.frame()
    else:
        myframe = base_node.frame()
        # If the frame of this node is the same as the statement
        # of this node, then the node is part of a class or
        # a function definition and the frame of this node should be the
        # the upper frame, not the frame of the definition.
        # For more information why this is important,
        # see Pylint issue #295.
        # For example, for 'b', the statement is the same
        # as the frame / scope:
        #
        # def test(b=1):
        #     ...
        if (base_node.parent and base_node.statement(future=True) is myframe
                and myframe.parent):
            myframe = myframe.parent.frame()

    mystmt: Optional[nodes.Statement] = None
    if base_node.parent:
        mystmt = base_node.statement(future=True)

    # line filtering if we are in the same frame
    #
    # take care node may be missing lineno information (this is the case for
    # nodes inserted for living objects)
    if myframe is frame and mystmt and mystmt.fromlineno is not None:
        assert mystmt.fromlineno is not None, mystmt
        mylineno = mystmt.fromlineno + offset
    else:
        # disabling lineno filtering
        mylineno = 0

    _stmts = []
    _stmt_parents = []
    statements = _get_filtered_node_statements(base_node, stmts)
    for node, stmt in statements:
        # line filtering is on and we have reached our location, break
        if stmt.fromlineno and stmt.fromlineno > mylineno > 0:
            break
        # Ignore decorators with the same name as the
        # decorated function
        # Fixes issue #375
        if mystmt is stmt and _is_from_decorator(base_node):
            continue
        assert hasattr(node, "assign_type"), (
            node,
            node.scope(),
            node.scope().locals,
        )
        assign_type = node.assign_type()
        if node.has_base(base_node):
            break

        _stmts, done = assign_type._get_filtered_stmts(base_node, node, _stmts,
                                                       mystmt)
        if done:
            break

        optional_assign = assign_type.optional_assign
        if optional_assign and assign_type.parent_of(base_node):
            # we are inside a loop, loop var assignment is hiding previous
            # assignment
            _stmts = [node]
            _stmt_parents = [stmt.parent]
            continue

        if isinstance(assign_type, nodes.NamedExpr):
            # If the NamedExpr is in an if statement we do some basic control flow inference
            if_parent = _get_if_statement_ancestor(assign_type)
            if if_parent:
                # If the if statement is within another if statement we append the node
                # to possible statements
                if _get_if_statement_ancestor(if_parent):
                    optional_assign = False
                    _stmts.append(node)
                    _stmt_parents.append(stmt.parent)
                # If the if statement is first-level and not within an orelse block
                # we know that it will be evaluated
                elif not if_parent.is_orelse:
                    _stmts = [node]
                    _stmt_parents = [stmt.parent]
                # Else we do not known enough about the control flow to be 100% certain
                # and we append to possible statements
                else:
                    _stmts.append(node)
                    _stmt_parents.append(stmt.parent)
            else:
                _stmts = [node]
                _stmt_parents = [stmt.parent]

        # XXX comment various branches below!!!
        try:
            pindex = _stmt_parents.index(stmt.parent)
        except ValueError:
            pass
        else:
            # we got a parent index, this means the currently visited node
            # is at the same block level as a previously visited node
            if _stmts[pindex].assign_type().parent_of(assign_type):
                # both statements are not at the same block level
                continue
            # if currently visited node is following previously considered
            # assignment and both are not exclusive, we can drop the
            # previous one. For instance in the following code ::
            #
            #   if a:
            #     x = 1
            #   else:
            #     x = 2
            #   print x
            #
            # we can't remove neither x = 1 nor x = 2 when looking for 'x'
            # of 'print x'; while in the following ::
            #
            #   x = 1
            #   x = 2
            #   print x
            #
            # we can remove x = 1 when we see x = 2
            #
            # moreover, on loop assignment types, assignment won't
            # necessarily be done if the loop has no iteration, so we don't
            # want to clear previous assignments if any (hence the test on
            # optional_assign)
            if not (optional_assign
                    or nodes.are_exclusive(_stmts[pindex], node)):
                del _stmt_parents[pindex]
                del _stmts[pindex]

        # If base_node and node are exclusive, then we can ignore node
        if nodes.are_exclusive(base_node, node):
            continue

        # An AssignName node overrides previous assignments if:
        #   1. node's statement always assigns
        #   2. node and base_node are in the same block (i.e., has the same parent as base_node)
        if isinstance(node, (nodes.NamedExpr, nodes.AssignName)):
            if isinstance(stmt, nodes.ExceptHandler):
                # If node's statement is an ExceptHandler, then it is the variable
                # bound to the caught exception. If base_node is not contained within
                # the exception handler block, node should override previous assignments;
                # otherwise, node should be ignored, as an exception variable
                # is local to the handler block.
                if stmt.parent_of(base_node):
                    _stmts = []
                    _stmt_parents = []
                else:
                    continue
            elif not optional_assign and mystmt and stmt.parent is mystmt.parent:
                _stmts = []
                _stmt_parents = []
        elif isinstance(node, nodes.DelName):
            # Remove all previously stored assignments
            _stmts = []
            _stmt_parents = []
            continue
        # Add the new assignment
        _stmts.append(node)
        if isinstance(node, nodes.Arguments) or isinstance(
                node.parent, nodes.Arguments):
            # Special case for _stmt_parents when node is a function parameter;
            # in this case, stmt is the enclosing FunctionDef, which is what we
            # want to add to _stmt_parents, not stmt.parent. This case occurs when
            # node is an Arguments node (representing varargs or kwargs parameter),
            # and when node.parent is an Arguments node (other parameters).
            # See issue #180.
            _stmt_parents.append(stmt)
        else:
            _stmt_parents.append(stmt.parent)
    return _stmts
Beispiel #25
0
def _get_if_statement_ancestor(node: nodes.NodeNG) -> Optional[nodes.If]:
    """Return the first parent node that is an If node (or None)"""
    for parent in node.node_ancestors():
        if isinstance(parent, nodes.If):
            return parent
    return None
def var_annotations_filter(node: nc.NodeNG) -> nc.NodeNG:
    """Filter annotated function variable assigns.

    This accounts for deferred evaluation.
    """
    # pylint: disable=too-many-branches
    # pylint: disable=too-many-nested-blocks

    if using_future_annotations(node):

        # Future behavior:
        # Annotated assigns under functions are not evaluated.
        # Class and module vars are normally not either. However we
        # do evaluate if we come across an 'ioprepped' dataclass
        # decorator. (the 'ioprepped' decorator explicitly evaluates
        # dataclass annotations).

        fnode = node
        willeval = False
        while fnode is not None:
            if isinstance(fnode, astroid.ClassDef):
                if fnode.decorators is not None:
                    found_ioprepped = False
                    for dec in fnode.decorators.nodes:

                        # Look for dataclassio.ioprepped.
                        if (isinstance(dec, astroid.nodes.Attribute) and
                                dec.attrname in {'ioprepped', 'will_ioprep'}
                                and isinstance(dec.expr, astroid.nodes.Name)
                                and dec.expr.name == 'dataclassio'):
                            found_ioprepped = True
                            break

                        # Look for simply 'ioprepped'.
                        if (isinstance(dec, astroid.nodes.Name)
                                and dec.name in {'ioprepped', 'will_ioprep'}):
                            found_ioprepped = True
                            break

                    if found_ioprepped:
                        willeval = True
                        break

            fnode = fnode.parent

    else:
        # Legacy behavior:
        # Annotated assigns under functions are not evaluated,
        # but class or module vars are.
        fnode = node
        willeval = True
        while fnode is not None:
            if isinstance(fnode,
                          (astroid.FunctionDef, astroid.AsyncFunctionDef)):
                willeval = False
                break
            if isinstance(fnode, astroid.ClassDef):
                willeval = True
                break
            fnode = fnode.parent

    # If this annotation won't be eval'ed, replace it with a dummy string.
    if not willeval:
        dummyval = astroid.Const(parent=node, value='dummyval')
        node.annotation = dummyval

    return node
Beispiel #27
0
 def default(self, node: nodes.NodeNG, *args: Any) -> None:
     for child in node.get_children():
         self.dispatch(child, *args)
Beispiel #28
0
 def _collect_block_lines(
     self,
     msgs_store: "MessageDefinitionStore",
     node: nodes.NodeNG,
     msg_state: MessageStateDict,
 ) -> None:
     """Recursively walk (depth first) AST to collect block level options
     line numbers.
     """
     for child in node.get_children():
         self._collect_block_lines(msgs_store, child, msg_state)
     first = node.fromlineno
     last = node.tolineno
     # first child line number used to distinguish between disable
     # which are the first child of scoped node with those defined later.
     # For instance in the code below:
     #
     # 1.   def meth8(self):
     # 2.        """test late disabling"""
     # 3.        pylint: disable=not-callable, useless-suppression
     # 4.        print(self.blip)
     # 5.        pylint: disable=no-member, useless-suppression
     # 6.        print(self.bla)
     #
     # E1102 should be disabled from line 1 to 6 while E1101 from line 5 to 6
     #
     # this is necessary to disable locally messages applying to class /
     # function using their fromlineno
     if (
         isinstance(node, (nodes.Module, nodes.ClassDef, nodes.FunctionDef))
         and node.body
     ):
         firstchildlineno = node.body[0].fromlineno
     else:
         firstchildlineno = last
     for msgid, lines in msg_state.items():
         for lineno, state in list(lines.items()):
             original_lineno = lineno
             if first > lineno or last < lineno:
                 continue
             # Set state for all lines for this block, if the
             # warning is applied to nodes.
             message_definitions = msgs_store.get_message_definitions(msgid)
             for message_definition in message_definitions:
                 if message_definition.scope == WarningScope.NODE:
                     if lineno > firstchildlineno:
                         state = True
                     first_, last_ = node.block_range(lineno)
                 else:
                     first_ = lineno
                     last_ = last
             for line in range(first_, last_ + 1):
                 # do not override existing entries
                 if line in self._module_msgs_state.get(msgid, ()):
                     continue
                 if line in lines:  # state change in the same block
                     state = lines[line]
                     original_lineno = line
                 if not state:
                     self._suppression_mapping[(msgid, line)] = original_lineno
                 try:
                     self._module_msgs_state[msgid][line] = state
                 except KeyError:
                     self._module_msgs_state[msgid] = {line: state}
             del lines[lineno]
Beispiel #29
0
 def _msg_postponed_eval_hint(self, node: nodes.NodeNG) -> str:
     """Message hint if postponed evaluation isn't enabled."""
     if self._py310_plus or "annotations" in node.root().future_imports:
         return ""
     return ". Add 'from __future__ import annotations' as well"
Beispiel #30
0
    def _check_singleton_comparison(
        self,
        left_value: nodes.NodeNG,
        right_value: nodes.NodeNG,
        root_node: nodes.Compare,
        checking_for_absence: bool = False,
    ) -> None:
        """Check if == or != is being used to compare a singleton value."""
        singleton_values = (True, False, None)

        def _is_singleton_const(node: nodes.NodeNG) -> bool:
            return isinstance(node, nodes.Const) and any(
                node.value is value for value in singleton_values
            )

        if _is_singleton_const(left_value):
            singleton, other_value = left_value.value, right_value
        elif _is_singleton_const(right_value):
            singleton, other_value = right_value.value, left_value
        else:
            return

        singleton_comparison_example = {False: "'{} is {}'", True: "'{} is not {}'"}

        # True/False singletons have a special-cased message in case the user is
        # mistakenly using == or != to check for truthiness
        if singleton in {True, False}:
            suggestion_template = (
                "{} if checking for the singleton value {}, or {} if testing for {}"
            )
            truthiness_example = {False: "not {}", True: "{}"}
            truthiness_phrase = {True: "truthiness", False: "falsiness"}

            # Looks for comparisons like x == True or x != False
            checking_truthiness = singleton is not checking_for_absence

            suggestion = suggestion_template.format(
                singleton_comparison_example[checking_for_absence].format(
                    left_value.as_string(), right_value.as_string()
                ),
                singleton,
                (
                    "'bool({})'"
                    if not utils.is_test_condition(root_node) and checking_truthiness
                    else "'{}'"
                ).format(
                    truthiness_example[checking_truthiness].format(
                        other_value.as_string()
                    )
                ),
                truthiness_phrase[checking_truthiness],
            )
        else:
            suggestion = singleton_comparison_example[checking_for_absence].format(
                left_value.as_string(), right_value.as_string()
            )
        self.add_message(
            "singleton-comparison",
            node=root_node,
            args=(f"'{root_node.as_string()}'", suggestion),
        )