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
145
146
147
148
149
150
151
152
|
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))
|