about summary refs log tree commit diff stats
path: root/tests/pylint/py2_compat.py
blob: 7e136148722271a238904245668ee3d4aca66f2e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
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."),
    }
    # 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_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))