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))