about summary refs log blame commit diff stats
path: root/tests/pylint/py2_compat.py
blob: e035326081c6db515f05f3de5a4a53b71a152835 (plain) (tree)


























                                                                            


                                                                        










                                                                                


                                                                         
                                                       

                                                                              





                                                                               


                                                                     














                                                                               
                                               













                                                                                
                                                              



                                                                   
                                                 

                                                                   
                                                 


















                                                                               
                                             








                                                                       
                                                     
 




                                                                             






                                                                               
                                                   
                                                                    

                                                          

                                                                                







                                                            
from __future__ import absolute_import

import astroid

from pylint.checkers import BaseChecker
from pylint.interfaces import IAstroidChecker, HIGH

from pylint.checkers import utils


class Py2CompatibilityChecker(BaseChecker):
    """Verify some simple properties of code compatible with both 2 and 3"""

    __implements__ = IAstroidChecker

    # The name defines a custom section of the config for this checker.
    name = "py2-compat"
    # The priority indicates the order that pylint will run the checkers.
    priority = -1
    # This class variable declares the messages (ie the warnings and errors)
    # that the checker can emit.
    msgs = {
        # Each message has a code, a message that the user will see,
        # a unique symbol that identifies the message,
        # and a detailed help message
        # that will be included in the documentation.
        "E4200": ('Use explicit inheritance from "object"',
                  "old-style-class",
                  'Python 2 requires explicit inheritance from "object"'
                  ' for new-style classes.'),
        # old-division
        # "E4210": ('Use "//" for integer division or import from "__future__"',
        #     "division-without-import",
        #     'Python 2 might perform integer division unless you import'
        #     ' "division" from "__future__".'),
        # no-absolute-import
        # "E4211": ('Always import "absolute_import" from "__future__"',
        #     "old-no-absolute-import",
        #     'Python 2 allows relative imports unless you import'
        #     ' "absolute_import" from "__future__".'),
        "E4212": ('Import "print_function" from "__future__"',
                  "print-without-import",
                  'Python 2 requires importing "print_function" from'
                  ' "__future__" to use the "print()" function syntax.'),
        "E4220": ('Use explicit format spec numbering',
                  "implicit-format-spec",
                  'Python 2.6 does not support implicit format spec numbering'
                  ' "{}", use explicit numbering "{0}" or keywords "{key}".'),
        "E4230": ("Use popen23.Popen with with-statements",
                  "with-popen23",
                  "Python 2 subprocess.Popen objects were not contextmanagers,"
                  "popen23.Popen wraps them to enable use with"
                  "with-statements."),
        "E4240": ("Use format method",
                  "use-format-method",
                  "Python 2 (and <3.6) does not support f-strings."),
    }
    # This class variable declares the options
    # that are configurable by the user.
    options = ()

    def visit_classdef(self, node):
        """Make sure classes explicitly inherit from object"""
        if not node.bases and node.type == 'class' and not node.metaclass():
            # We use confidence HIGH here because this message should only ever
            # be emitted for classes at the root of the inheritance hierarchy
            self.add_message('old-style-class', node=node, confidence=HIGH)

    def visit_call(self, node):
        """Make sure "print_function" is imported if necessary"""
        if (isinstance(node.func, astroid.nodes.Name)
                and node.func.name == "print"):
            if "print_function" in node.root().future_imports:
                def previous(node):
                    if node is not None:
                        parent = node.parent
                    previous = node.previous_sibling()
                    if previous is None:
                        return parent
                    return previous

                prev = previous(node)
                while prev is not None:
                    if (isinstance(prev, astroid.nodes.ImportFrom)
                        and prev.modname == "__future__"
                        and "print_function" in (name_alias[0] for name_alias in
                                                 prev.names)):
                        return
                    prev = previous(prev)

                self.add_message("print-without-import", node=node,
                                 confidence=HIGH)
            else:
                self.add_message("print-without-import", node=node,
                                 confidence=HIGH)

        func = utils.safe_infer(node.func)
        if (
            isinstance(func, astroid.BoundMethod)
            and isinstance(func.bound, astroid.Instance)
            and func.bound.name in ("str", "unicode", "bytes")
        ):
            if func.name == "format":
                if isinstance(node.func, astroid.Attribute) and not isinstance(
                    node.func.expr, astroid.Const
                ):
                    return
                if node.starargs or node.kwargs:
                    return
                try:
                    strnode = next(func.bound.infer())
                except astroid.InferenceError:
                    return
                if not (isinstance(strnode, astroid.Const) and isinstance(
                        strnode.value, str)):
                    return
                try:
                    fields, num_args, manual_pos = (
                        utils.parse_format_method_string(strnode.value)
                    )
                except utils.IncompleteFormatString:
                    self.add_message("bad-format-string", node=node)
                if num_args != 0:
                    self.add_message("implicit-format-spec", node=node,
                                     confidence=HIGH)

    def visit_joinedstr(self, node):
        """Make sure we don't use f-strings"""
        if isinstance(node, astroid.nodes.JoinedStr):
            self.add_message("use-format-method", node=node, confidence=HIGH)

    def visit_with(self, node):
        """Make sure subprocess.Popen objects aren't used in with-statements"""
        for (cm, _) in node.items:
            if isinstance(cm, astroid.nodes.Call):
                if ((isinstance(cm.func, astroid.nodes.Name)
                    and cm.func.name.endswith("Popen")
                    and (node.root().scope_lookup(node.root(), "Popen")[1][0]
                         ).modname == "subprocess")
                    or (isinstance(cm.func, astroid.nodes.Attribute)
                        and cm.func.expr == "subprocess"
                        and cm.func.attrname == "Popen")):
                    self.add_message("with-popen23", node=node, confidence=HIGH)


def register(linter):
    """This required method auto registers the checker.

    :param linter: The linter to register the checker to.
    :type linter: pylint.lint.PyLinter
    """
    linter.register_checker(Py2CompatibilityChecker(linter))