diff options
Diffstat (limited to 'venv/Lib/site-packages/pylint/checkers/python3.py')
-rw-r--r-- | venv/Lib/site-packages/pylint/checkers/python3.py | 1398 |
1 files changed, 1398 insertions, 0 deletions
diff --git a/venv/Lib/site-packages/pylint/checkers/python3.py b/venv/Lib/site-packages/pylint/checkers/python3.py new file mode 100644 index 0000000..583b1c2 --- /dev/null +++ b/venv/Lib/site-packages/pylint/checkers/python3.py @@ -0,0 +1,1398 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2014-2015 Brett Cannon <brett@python.org> +# Copyright (c) 2015 Simu Toni <simutoni@gmail.com> +# Copyright (c) 2015 Pavel Roskin <proski@gnu.org> +# Copyright (c) 2015 Ionel Cristian Maries <contact@ionelmc.ro> +# Copyright (c) 2015 Cosmin Poieana <cmin@ropython.org> +# Copyright (c) 2015 Viorel Stirbu <viorels@gmail.com> +# Copyright (c) 2016, 2018 Jakub Wilk <jwilk@jwilk.net> +# Copyright (c) 2016-2017 Roy Williams <roy.williams.iii@gmail.com> +# Copyright (c) 2016 Roy Williams <rwilliams@lyft.com> +# Copyright (c) 2016 Łukasz Rogalski <rogalski.91@gmail.com> +# Copyright (c) 2016 Erik <erik.eriksson@yahoo.com> +# Copyright (c) 2017 Ville Skyttä <ville.skytta@iki.fi> +# Copyright (c) 2017 Daniel Miller <millerdev@gmail.com> +# Copyright (c) 2017 hippo91 <guillaume.peillex@gmail.com> +# Copyright (c) 2017 ahirnish <ahirnish@gmail.com> +# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com> +# Copyright (c) 2018 Anthony Sottile <asottile@umich.edu> +# Copyright (c) 2018 Ashley Whetter <ashley@awhetter.co.uk> +# Copyright (c) 2018 Ville Skyttä <ville.skytta@upcloud.com> +# Copyright (c) 2018 gaurikholkar <f2013002@goa.bits-pilani.ac.in> +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/master/COPYING + +"""Check Python 2 code for Python 2/3 source-compatible issues.""" +import re +import tokenize +from collections import namedtuple + +import astroid +from astroid import bases + +from pylint import checkers, interfaces +from pylint.checkers import utils +from pylint.checkers.utils import find_try_except_wrapper_node, node_ignores_exception +from pylint.constants import WarningScope +from pylint.interfaces import INFERENCE, INFERENCE_FAILURE + +_ZERO = re.compile("^0+$") + + +def _is_old_octal(literal): + if _ZERO.match(literal): + return False + if re.match(r"0\d+", literal): + try: + int(literal, 8) + except ValueError: + return False + return True + return None + + +def _inferred_value_is_dict(value): + if isinstance(value, astroid.Dict): + return True + return isinstance(value, astroid.Instance) and "dict" in value.basenames + + +def _is_builtin(node): + return getattr(node, "name", None) in ("__builtin__", "builtins") + + +_ACCEPTS_ITERATOR = { + "iter", + "list", + "tuple", + "sorted", + "set", + "sum", + "any", + "all", + "enumerate", + "dict", + "filter", + "reversed", + "max", + "min", + "frozenset", + "OrderedDict", +} +ATTRIBUTES_ACCEPTS_ITERATOR = {"join", "from_iterable"} +_BUILTIN_METHOD_ACCEPTS_ITERATOR = { + "builtins.list.extend", + "builtins.dict.update", + "builtins.set.update", +} +DICT_METHODS = {"items", "keys", "values"} + + +def _in_iterating_context(node): + """Check if the node is being used as an iterator. + + Definition is taken from lib2to3.fixer_util.in_special_context(). + """ + parent = node.parent + # Since a call can't be the loop variant we only need to know if the node's + # parent is a 'for' loop to know it's being used as the iterator for the + # loop. + if isinstance(parent, astroid.For): + return True + # Need to make sure the use of the node is in the iterator part of the + # comprehension. + if isinstance(parent, astroid.Comprehension): + if parent.iter == node: + return True + # Various built-ins can take in an iterable or list and lead to the same + # value. + elif isinstance(parent, astroid.Call): + if isinstance(parent.func, astroid.Name): + if parent.func.name in _ACCEPTS_ITERATOR: + return True + elif isinstance(parent.func, astroid.Attribute): + if parent.func.attrname in ATTRIBUTES_ACCEPTS_ITERATOR: + return True + + inferred = utils.safe_infer(parent.func) + if inferred: + if inferred.qname() in _BUILTIN_METHOD_ACCEPTS_ITERATOR: + return True + root = inferred.root() + if root and root.name == "itertools": + return True + # If the call is in an unpacking, there's no need to warn, + # since it can be considered iterating. + elif isinstance(parent, astroid.Assign) and isinstance( + parent.targets[0], (astroid.List, astroid.Tuple) + ): + if len(parent.targets[0].elts) > 1: + return True + # If the call is in a containment check, we consider that to + # be an iterating context + elif ( + isinstance(parent, astroid.Compare) + and len(parent.ops) == 1 + and parent.ops[0][0] == "in" + ): + return True + # Also if it's an `yield from`, that's fair + elif isinstance(parent, astroid.YieldFrom): + return True + if isinstance(parent, astroid.Starred): + return True + return False + + +def _is_conditional_import(node): + """Checks if an import node is in the context of a conditional. + """ + parent = node.parent + return isinstance( + parent, (astroid.TryExcept, astroid.ExceptHandler, astroid.If, astroid.IfExp) + ) + + +Branch = namedtuple("Branch", ["node", "is_py2_only"]) + + +class Python3Checker(checkers.BaseChecker): + + __implements__ = interfaces.IAstroidChecker + enabled = False + name = "python3" + + msgs = { + # Errors for what will syntactically break in Python 3, warnings for + # everything else. + "E1601": ( + "print statement used", + "print-statement", + "Used when a print statement is used " + "(`print` is a function in Python 3)", + ), + "E1602": ( + "Parameter unpacking specified", + "parameter-unpacking", + "Used when parameter unpacking is specified for a function" + "(Python 3 doesn't allow it)", + ), + "E1603": ( + "Implicit unpacking of exceptions is not supported in Python 3", + "unpacking-in-except", + "Python3 will not allow implicit unpacking of " + "exceptions in except clauses. " + "See http://www.python.org/dev/peps/pep-3110/", + {"old_names": [("W0712", "old-unpacking-in-except")]}, + ), + "E1604": ( + "Use raise ErrorClass(args) instead of raise ErrorClass, args.", + "old-raise-syntax", + "Used when the alternate raise syntax " + "'raise foo, bar' is used " + "instead of 'raise foo(bar)'.", + {"old_names": [("W0121", "old-old-raise-syntax")]}, + ), + "E1605": ( + "Use of the `` operator", + "backtick", + 'Used when the deprecated "``" (backtick) operator is used ' + "instead of the str() function.", + {"scope": WarningScope.NODE, "old_names": [("W0333", "old-backtick")]}, + ), + "E1609": ( + "Import * only allowed at module level", + "import-star-module-level", + "Used when the import star syntax is used somewhere " + "else than the module level.", + {"maxversion": (3, 0)}, + ), + "W1601": ( + "apply built-in referenced", + "apply-builtin", + "Used when the apply built-in function is referenced " + "(missing from Python 3)", + ), + "W1602": ( + "basestring built-in referenced", + "basestring-builtin", + "Used when the basestring built-in function is referenced " + "(missing from Python 3)", + ), + "W1603": ( + "buffer built-in referenced", + "buffer-builtin", + "Used when the buffer built-in function is referenced " + "(missing from Python 3)", + ), + "W1604": ( + "cmp built-in referenced", + "cmp-builtin", + "Used when the cmp built-in function is referenced " + "(missing from Python 3)", + ), + "W1605": ( + "coerce built-in referenced", + "coerce-builtin", + "Used when the coerce built-in function is referenced " + "(missing from Python 3)", + ), + "W1606": ( + "execfile built-in referenced", + "execfile-builtin", + "Used when the execfile built-in function is referenced " + "(missing from Python 3)", + ), + "W1607": ( + "file built-in referenced", + "file-builtin", + "Used when the file built-in function is referenced " + "(missing from Python 3)", + ), + "W1608": ( + "long built-in referenced", + "long-builtin", + "Used when the long built-in function is referenced " + "(missing from Python 3)", + ), + "W1609": ( + "raw_input built-in referenced", + "raw_input-builtin", + "Used when the raw_input built-in function is referenced " + "(missing from Python 3)", + ), + "W1610": ( + "reduce built-in referenced", + "reduce-builtin", + "Used when the reduce built-in function is referenced " + "(missing from Python 3)", + ), + "W1611": ( + "StandardError built-in referenced", + "standarderror-builtin", + "Used when the StandardError built-in function is referenced " + "(missing from Python 3)", + ), + "W1612": ( + "unicode built-in referenced", + "unicode-builtin", + "Used when the unicode built-in function is referenced " + "(missing from Python 3)", + ), + "W1613": ( + "xrange built-in referenced", + "xrange-builtin", + "Used when the xrange built-in function is referenced " + "(missing from Python 3)", + ), + "W1614": ( + "__coerce__ method defined", + "coerce-method", + "Used when a __coerce__ method is defined " + "(method is not used by Python 3)", + ), + "W1615": ( + "__delslice__ method defined", + "delslice-method", + "Used when a __delslice__ method is defined " + "(method is not used by Python 3)", + ), + "W1616": ( + "__getslice__ method defined", + "getslice-method", + "Used when a __getslice__ method is defined " + "(method is not used by Python 3)", + ), + "W1617": ( + "__setslice__ method defined", + "setslice-method", + "Used when a __setslice__ method is defined " + "(method is not used by Python 3)", + ), + "W1618": ( + "import missing `from __future__ import absolute_import`", + "no-absolute-import", + "Used when an import is not accompanied by " + "``from __future__ import absolute_import`` " + "(default behaviour in Python 3)", + ), + "W1619": ( + "division w/o __future__ statement", + "old-division", + "Used for non-floor division w/o a float literal or " + "``from __future__ import division`` " + "(Python 3 returns a float for int division unconditionally)", + ), + "W1620": ( + "Calling a dict.iter*() method", + "dict-iter-method", + "Used for calls to dict.iterkeys(), itervalues() or iteritems() " + "(Python 3 lacks these methods)", + ), + "W1621": ( + "Calling a dict.view*() method", + "dict-view-method", + "Used for calls to dict.viewkeys(), viewvalues() or viewitems() " + "(Python 3 lacks these methods)", + ), + "W1622": ( + "Called a next() method on an object", + "next-method-called", + "Used when an object's next() method is called " + "(Python 3 uses the next() built-in function)", + ), + "W1623": ( + "Assigning to a class's __metaclass__ attribute", + "metaclass-assignment", + "Used when a metaclass is specified by assigning to __metaclass__ " + "(Python 3 specifies the metaclass as a class statement argument)", + ), + "W1624": ( + "Indexing exceptions will not work on Python 3", + "indexing-exception", + "Indexing exceptions will not work on Python 3. Use " + "`exception.args[index]` instead.", + {"old_names": [("W0713", "old-indexing-exception")]}, + ), + "W1625": ( + "Raising a string exception", + "raising-string", + "Used when a string exception is raised. This will not " + "work on Python 3.", + {"old_names": [("W0701", "old-raising-string")]}, + ), + "W1626": ( + "reload built-in referenced", + "reload-builtin", + "Used when the reload built-in function is referenced " + "(missing from Python 3). You can use instead imp.reload " + "or importlib.reload.", + ), + "W1627": ( + "__oct__ method defined", + "oct-method", + "Used when an __oct__ method is defined " + "(method is not used by Python 3)", + ), + "W1628": ( + "__hex__ method defined", + "hex-method", + "Used when a __hex__ method is defined (method is not used by Python 3)", + ), + "W1629": ( + "__nonzero__ method defined", + "nonzero-method", + "Used when a __nonzero__ method is defined " + "(method is not used by Python 3)", + ), + "W1630": ( + "__cmp__ method defined", + "cmp-method", + "Used when a __cmp__ method is defined (method is not used by Python 3)", + ), + # 'W1631': replaced by W1636 + "W1632": ( + "input built-in referenced", + "input-builtin", + "Used when the input built-in is referenced " + "(backwards-incompatible semantics in Python 3)", + ), + "W1633": ( + "round built-in referenced", + "round-builtin", + "Used when the round built-in is referenced " + "(backwards-incompatible semantics in Python 3)", + ), + "W1634": ( + "intern built-in referenced", + "intern-builtin", + "Used when the intern built-in is referenced " + "(Moved to sys.intern in Python 3)", + ), + "W1635": ( + "unichr built-in referenced", + "unichr-builtin", + "Used when the unichr built-in is referenced (Use chr in Python 3)", + ), + "W1636": ( + "map built-in referenced when not iterating", + "map-builtin-not-iterating", + "Used when the map built-in is referenced in a non-iterating " + "context (returns an iterator in Python 3)", + {"old_names": [("W1631", "implicit-map-evaluation")]}, + ), + "W1637": ( + "zip built-in referenced when not iterating", + "zip-builtin-not-iterating", + "Used when the zip built-in is referenced in a non-iterating " + "context (returns an iterator in Python 3)", + ), + "W1638": ( + "range built-in referenced when not iterating", + "range-builtin-not-iterating", + "Used when the range built-in is referenced in a non-iterating " + "context (returns a range in Python 3)", + ), + "W1639": ( + "filter built-in referenced when not iterating", + "filter-builtin-not-iterating", + "Used when the filter built-in is referenced in a non-iterating " + "context (returns an iterator in Python 3)", + ), + "W1640": ( + "Using the cmp argument for list.sort / sorted", + "using-cmp-argument", + "Using the cmp argument for list.sort or the sorted " + "builtin should be avoided, since it was removed in " + "Python 3. Using either `key` or `functools.cmp_to_key` " + "should be preferred.", + ), + "W1641": ( + "Implementing __eq__ without also implementing __hash__", + "eq-without-hash", + "Used when a class implements __eq__ but not __hash__. In Python 2, objects " + "get object.__hash__ as the default implementation, in Python 3 objects get " + "None as their default __hash__ implementation if they also implement __eq__.", + ), + "W1642": ( + "__div__ method defined", + "div-method", + "Used when a __div__ method is defined. Using `__truediv__` and setting" + "__div__ = __truediv__ should be preferred." + "(method is not used by Python 3)", + ), + "W1643": ( + "__idiv__ method defined", + "idiv-method", + "Used when an __idiv__ method is defined. Using `__itruediv__` and setting" + "__idiv__ = __itruediv__ should be preferred." + "(method is not used by Python 3)", + ), + "W1644": ( + "__rdiv__ method defined", + "rdiv-method", + "Used when a __rdiv__ method is defined. Using `__rtruediv__` and setting" + "__rdiv__ = __rtruediv__ should be preferred." + "(method is not used by Python 3)", + ), + "W1645": ( + "Exception.message removed in Python 3", + "exception-message-attribute", + "Used when the message attribute is accessed on an Exception. Use " + "str(exception) instead.", + ), + "W1646": ( + "non-text encoding used in str.decode", + "invalid-str-codec", + "Used when using str.encode or str.decode with a non-text encoding. Use " + "codecs module to handle arbitrary codecs.", + ), + "W1647": ( + "sys.maxint removed in Python 3", + "sys-max-int", + "Used when accessing sys.maxint. Use sys.maxsize instead.", + ), + "W1648": ( + "Module moved in Python 3", + "bad-python3-import", + "Used when importing a module that no longer exists in Python 3.", + ), + "W1649": ( + "Accessing a deprecated function on the string module", + "deprecated-string-function", + "Used when accessing a string function that has been deprecated in Python 3.", + ), + "W1650": ( + "Using str.translate with deprecated deletechars parameters", + "deprecated-str-translate-call", + "Used when using the deprecated deletechars parameters from str.translate. Use " + "re.sub to remove the desired characters ", + ), + "W1651": ( + "Accessing a deprecated function on the itertools module", + "deprecated-itertools-function", + "Used when accessing a function on itertools that has been removed in Python 3.", + ), + "W1652": ( + "Accessing a deprecated fields on the types module", + "deprecated-types-field", + "Used when accessing a field on types that has been removed in Python 3.", + ), + "W1653": ( + "next method defined", + "next-method-defined", + "Used when a next method is defined that would be an iterator in Python 2 but " + "is treated as a normal function in Python 3.", + ), + "W1654": ( + "dict.items referenced when not iterating", + "dict-items-not-iterating", + "Used when dict.items is referenced in a non-iterating " + "context (returns an iterator in Python 3)", + ), + "W1655": ( + "dict.keys referenced when not iterating", + "dict-keys-not-iterating", + "Used when dict.keys is referenced in a non-iterating " + "context (returns an iterator in Python 3)", + ), + "W1656": ( + "dict.values referenced when not iterating", + "dict-values-not-iterating", + "Used when dict.values is referenced in a non-iterating " + "context (returns an iterator in Python 3)", + ), + "W1657": ( + "Accessing a removed attribute on the operator module", + "deprecated-operator-function", + "Used when accessing a field on operator module that has been " + "removed in Python 3.", + ), + "W1658": ( + "Accessing a removed attribute on the urllib module", + "deprecated-urllib-function", + "Used when accessing a field on urllib module that has been " + "removed or moved in Python 3.", + ), + "W1659": ( + "Accessing a removed xreadlines attribute", + "xreadlines-attribute", + "Used when accessing the xreadlines() function on a file stream, " + "removed in Python 3.", + ), + "W1660": ( + "Accessing a removed attribute on the sys module", + "deprecated-sys-function", + "Used when accessing a field on sys module that has been " + "removed in Python 3.", + ), + "W1661": ( + "Using an exception object that was bound by an except handler", + "exception-escape", + "Emitted when using an exception, that was bound in an except " + "handler, outside of the except handler. On Python 3 these " + "exceptions will be deleted once they get out " + "of the except handler.", + ), + "W1662": ( + "Using a variable that was bound inside a comprehension", + "comprehension-escape", + "Emitted when using a variable, that was bound in a comprehension " + "handler, outside of the comprehension itself. On Python 3 these " + "variables will be deleted outside of the " + "comprehension.", + ), + } + + _bad_builtins = frozenset( + [ + "apply", + "basestring", + "buffer", + "cmp", + "coerce", + "execfile", + "file", + "input", # Not missing, but incompatible semantics + "intern", + "long", + "raw_input", + "reduce", + "round", # Not missing, but incompatible semantics + "StandardError", + "unichr", + "unicode", + "xrange", + "reload", + ] + ) + + _unused_magic_methods = frozenset( + [ + "__coerce__", + "__delslice__", + "__getslice__", + "__setslice__", + "__oct__", + "__hex__", + "__nonzero__", + "__cmp__", + "__div__", + "__idiv__", + "__rdiv__", + ] + ) + + _invalid_encodings = frozenset( + [ + "base64_codec", + "base64", + "base_64", + "bz2_codec", + "bz2", + "hex_codec", + "hex", + "quopri_codec", + "quopri", + "quotedprintable", + "quoted_printable", + "uu_codec", + "uu", + "zlib_codec", + "zlib", + "zip", + "rot13", + "rot_13", + ] + ) + + _bad_python3_module_map = { + "sys-max-int": {"sys": frozenset(["maxint"])}, + "deprecated-itertools-function": { + "itertools": frozenset( + ["izip", "ifilter", "imap", "izip_longest", "ifilterfalse"] + ) + }, + "deprecated-types-field": { + "types": frozenset( + [ + "EllipsisType", + "XRangeType", + "ComplexType", + "StringType", + "TypeType", + "LongType", + "UnicodeType", + "ClassType", + "BufferType", + "StringTypes", + "NotImplementedType", + "NoneType", + "InstanceType", + "FloatType", + "SliceType", + "UnboundMethodType", + "ObjectType", + "IntType", + "TupleType", + "ListType", + "DictType", + "FileType", + "DictionaryType", + "BooleanType", + "DictProxyType", + ] + ) + }, + "bad-python3-import": frozenset( + [ + "anydbm", + "BaseHTTPServer", + "__builtin__", + "CGIHTTPServer", + "ConfigParser", + "copy_reg", + "cPickle", + "cStringIO", + "Cookie", + "cookielib", + "dbhash", + "dumbdbm", + "dumbdb", + "Dialog", + "DocXMLRPCServer", + "FileDialog", + "FixTk", + "gdbm", + "htmlentitydefs", + "HTMLParser", + "httplib", + "markupbase", + "Queue", + "repr", + "robotparser", + "ScrolledText", + "SimpleDialog", + "SimpleHTTPServer", + "SimpleXMLRPCServer", + "StringIO", + "dummy_thread", + "SocketServer", + "test.test_support", + "Tkinter", + "Tix", + "Tkconstants", + "tkColorChooser", + "tkCommonDialog", + "Tkdnd", + "tkFileDialog", + "tkFont", + "tkMessageBox", + "tkSimpleDialog", + "UserList", + "UserString", + "whichdb", + "_winreg", + "xmlrpclib", + "audiodev", + "Bastion", + "bsddb185", + "bsddb3", + "Canvas", + "cfmfile", + "cl", + "commands", + "compiler", + "dircache", + "dl", + "exception", + "fpformat", + "htmllib", + "ihooks", + "imageop", + "imputil", + "linuxaudiodev", + "md5", + "mhlib", + "mimetools", + "MimeWriter", + "mimify", + "multifile", + "mutex", + "new", + "popen2", + "posixfile", + "pure", + "rexec", + "rfc822", + "sets", + "sha", + "sgmllib", + "sre", + "stringold", + "sunaudio", + "sv", + "test.testall", + "thread", + "timing", + "toaiff", + "user", + "urllib2", + "urlparse", + ] + ), + "deprecated-string-function": { + "string": frozenset( + [ + "maketrans", + "atof", + "atoi", + "atol", + "capitalize", + "expandtabs", + "find", + "rfind", + "index", + "rindex", + "count", + "lower", + "letters", + "split", + "rsplit", + "splitfields", + "join", + "joinfields", + "lstrip", + "rstrip", + "strip", + "swapcase", + "translate", + "upper", + "ljust", + "rjust", + "center", + "zfill", + "replace", + "lowercase", + "letters", + "uppercase", + "atol_error", + "atof_error", + "atoi_error", + "index_error", + ] + ) + }, + "deprecated-operator-function": {"operator": frozenset({"div"})}, + "deprecated-urllib-function": { + "urllib": frozenset( + { + "addbase", + "addclosehook", + "addinfo", + "addinfourl", + "always_safe", + "basejoin", + "ftpcache", + "ftperrors", + "ftpwrapper", + "getproxies", + "getproxies_environment", + "getproxies_macosx_sysconf", + "main", + "noheaders", + "pathname2url", + "proxy_bypass", + "proxy_bypass_environment", + "proxy_bypass_macosx_sysconf", + "quote", + "quote_plus", + "reporthook", + "splitattr", + "splithost", + "splitnport", + "splitpasswd", + "splitport", + "splitquery", + "splittag", + "splittype", + "splituser", + "splitvalue", + "unquote", + "unquote_plus", + "unwrap", + "url2pathname", + "urlcleanup", + "urlencode", + "urlopen", + "urlretrieve", + } + ) + }, + "deprecated-sys-function": {"sys": frozenset({"exc_clear"})}, + } + + _python_2_tests = frozenset( + [ + astroid.extract_node(x).repr_tree() + for x in [ + "sys.version_info[0] == 2", + "sys.version_info[0] < 3", + "sys.version_info == (2, 7)", + "sys.version_info <= (2, 7)", + "sys.version_info < (3, 0)", + ] + ] + ) + + def __init__(self, *args, **kwargs): + self._future_division = False + self._future_absolute_import = False + self._modules_warned_about = set() + self._branch_stack = [] + super(Python3Checker, self).__init__(*args, **kwargs) + + # pylint: disable=keyword-arg-before-vararg, arguments-differ + def add_message(self, msg_id, always_warn=False, *args, **kwargs): + if always_warn or not ( + self._branch_stack and self._branch_stack[-1].is_py2_only + ): + super(Python3Checker, self).add_message(msg_id, *args, **kwargs) + + def _is_py2_test(self, node): + if isinstance(node.test, astroid.Attribute) and isinstance( + node.test.expr, astroid.Name + ): + if node.test.expr.name == "six" and node.test.attrname == "PY2": + return True + elif ( + isinstance(node.test, astroid.Compare) + and node.test.repr_tree() in self._python_2_tests + ): + return True + return False + + def visit_if(self, node): + self._branch_stack.append(Branch(node, self._is_py2_test(node))) + + def leave_if(self, node): + assert self._branch_stack.pop().node == node + + def visit_ifexp(self, node): + self._branch_stack.append(Branch(node, self._is_py2_test(node))) + + def leave_ifexp(self, node): + assert self._branch_stack.pop().node == node + + def visit_module(self, node): # pylint: disable=unused-argument + """Clear checker state after previous module.""" + self._future_division = False + self._future_absolute_import = False + + def visit_functiondef(self, node): + if node.is_method(): + if node.name in self._unused_magic_methods: + method_name = node.name + if node.name.startswith("__"): + method_name = node.name[2:-2] + self.add_message(method_name + "-method", node=node) + elif node.name == "next": + # If there is a method named `next` declared, if it is invokable + # with zero arguments then it implements the Iterator protocol. + # This means if the method is an instance method or a + # classmethod 1 argument should cause a failure, if it is a + # staticmethod 0 arguments should cause a failure. + failing_arg_count = 1 + if utils.decorated_with(node, [bases.BUILTINS + ".staticmethod"]): + failing_arg_count = 0 + if len(node.args.args) == failing_arg_count: + self.add_message("next-method-defined", node=node) + + @utils.check_messages("parameter-unpacking") + def visit_arguments(self, node): + for arg in node.args: + if isinstance(arg, astroid.Tuple): + self.add_message("parameter-unpacking", node=arg) + + @utils.check_messages("comprehension-escape") + def visit_listcomp(self, node): + names = { + generator.target.name + for generator in node.generators + if isinstance(generator.target, astroid.AssignName) + } + scope = node.parent.scope() + scope_names = scope.nodes_of_class(astroid.Name, skip_klass=astroid.FunctionDef) + has_redefined_assign_name = any( + assign_name + for assign_name in scope.nodes_of_class( + astroid.AssignName, skip_klass=astroid.FunctionDef + ) + if assign_name.name in names and assign_name.lineno > node.lineno + ) + if has_redefined_assign_name: + return + + emitted_for_names = set() + scope_names = list(scope_names) + for scope_name in scope_names: + if ( + scope_name.name not in names + or scope_name.lineno <= node.lineno + or scope_name.name in emitted_for_names + or scope_name.scope() == node + ): + continue + + emitted_for_names.add(scope_name.name) + self.add_message("comprehension-escape", node=scope_name) + + def visit_name(self, node): + """Detect when a "bad" built-in is referenced.""" + found_node, _ = node.lookup(node.name) + if not _is_builtin(found_node): + return + if node.name not in self._bad_builtins: + return + if node_ignores_exception(node) or isinstance( + find_try_except_wrapper_node(node), astroid.ExceptHandler + ): + return + + message = node.name.lower() + "-builtin" + self.add_message(message, node=node) + + @utils.check_messages("print-statement") + def visit_print(self, node): + self.add_message("print-statement", node=node, always_warn=True) + + def _warn_if_deprecated(self, node, module, attributes, report_on_modules=True): + for message, module_map in self._bad_python3_module_map.items(): + if module in module_map and module not in self._modules_warned_about: + if isinstance(module_map, frozenset): + if report_on_modules: + self._modules_warned_about.add(module) + self.add_message(message, node=node) + elif attributes and module_map[module].intersection(attributes): + self.add_message(message, node=node) + + def visit_importfrom(self, node): + if node.modname == "__future__": + for name, _ in node.names: + if name == "division": + self._future_division = True + elif name == "absolute_import": + self._future_absolute_import = True + else: + if not self._future_absolute_import: + if self.linter.is_message_enabled("no-absolute-import"): + self.add_message("no-absolute-import", node=node) + self._future_absolute_import = True + if not _is_conditional_import(node) and not node.level: + self._warn_if_deprecated(node, node.modname, {x[0] for x in node.names}) + + if node.names[0][0] == "*": + if self.linter.is_message_enabled("import-star-module-level"): + if not isinstance(node.scope(), astroid.Module): + self.add_message("import-star-module-level", node=node) + + def visit_import(self, node): + if not self._future_absolute_import: + if self.linter.is_message_enabled("no-absolute-import"): + self.add_message("no-absolute-import", node=node) + self._future_absolute_import = True + if not _is_conditional_import(node): + for name, _ in node.names: + self._warn_if_deprecated(node, name, None) + + @utils.check_messages("metaclass-assignment") + def visit_classdef(self, node): + if "__metaclass__" in node.locals: + self.add_message("metaclass-assignment", node=node) + locals_and_methods = set(node.locals).union(x.name for x in node.mymethods()) + if "__eq__" in locals_and_methods and "__hash__" not in locals_and_methods: + self.add_message("eq-without-hash", node=node) + + @utils.check_messages("old-division") + def visit_binop(self, node): + if not self._future_division and node.op == "/": + for arg in (node.left, node.right): + inferred = utils.safe_infer(arg) + # If we can infer the object and that object is not an int, bail out. + if inferred and not ( + ( + isinstance(inferred, astroid.Const) + and isinstance(inferred.value, int) + ) + or ( + isinstance(inferred, astroid.Instance) + and inferred.name == "int" + ) + ): + break + else: + self.add_message("old-division", node=node) + + def _check_cmp_argument(self, node): + # Check that the `cmp` argument is used + kwargs = [] + if isinstance(node.func, astroid.Attribute) and node.func.attrname == "sort": + inferred = utils.safe_infer(node.func.expr) + if not inferred: + return + + builtins_list = "{}.list".format(bases.BUILTINS) + if isinstance(inferred, astroid.List) or inferred.qname() == builtins_list: + kwargs = node.keywords + + elif isinstance(node.func, astroid.Name) and node.func.name == "sorted": + inferred = utils.safe_infer(node.func) + if not inferred: + return + + builtins_sorted = "{}.sorted".format(bases.BUILTINS) + if inferred.qname() == builtins_sorted: + kwargs = node.keywords + + for kwarg in kwargs or []: + if kwarg.arg == "cmp": + self.add_message("using-cmp-argument", node=node) + return + + @staticmethod + def _is_constant_string_or_name(node): + if isinstance(node, astroid.Const): + return isinstance(node.value, str) + return isinstance(node, astroid.Name) + + @staticmethod + def _is_none(node): + return isinstance(node, astroid.Const) and node.value is None + + @staticmethod + def _has_only_n_positional_args(node, number_of_args): + return len(node.args) == number_of_args and all(node.args) and not node.keywords + + @staticmethod + def _could_be_string(inferred_types): + confidence = INFERENCE if inferred_types else INFERENCE_FAILURE + for inferred_type in inferred_types: + if inferred_type is astroid.Uninferable: + confidence = INFERENCE_FAILURE + elif not ( + isinstance(inferred_type, astroid.Const) + and isinstance(inferred_type.value, str) + ): + return None + return confidence + + def visit_call(self, node): + self._check_cmp_argument(node) + + if isinstance(node.func, astroid.Attribute): + inferred_types = set() + try: + for inferred_receiver in node.func.expr.infer(): + if inferred_receiver is astroid.Uninferable: + continue + inferred_types.add(inferred_receiver) + if isinstance(inferred_receiver, astroid.Module): + self._warn_if_deprecated( + node, + inferred_receiver.name, + {node.func.attrname}, + report_on_modules=False, + ) + if ( + _inferred_value_is_dict(inferred_receiver) + and node.func.attrname in DICT_METHODS + ): + if not _in_iterating_context(node): + checker = "dict-{}-not-iterating".format(node.func.attrname) + self.add_message(checker, node=node) + except astroid.InferenceError: + pass + if node.args: + is_str_confidence = self._could_be_string(inferred_types) + if is_str_confidence: + if ( + node.func.attrname in ("encode", "decode") + and len(node.args) >= 1 + and node.args[0] + ): + first_arg = node.args[0] + self._validate_encoding(first_arg, node) + if ( + node.func.attrname == "translate" + and self._has_only_n_positional_args(node, 2) + and self._is_none(node.args[0]) + and self._is_constant_string_or_name(node.args[1]) + ): + # The above statement looking for calls of the form: + # + # foo.translate(None, 'abc123') + # + # or + # + # foo.translate(None, some_variable) + # + # This check is somewhat broad and _may_ have some false positives, but + # after checking several large codebases it did not have any false + # positives while finding several real issues. This call pattern seems + # rare enough that the trade off is worth it. + self.add_message( + "deprecated-str-translate-call", + node=node, + confidence=is_str_confidence, + ) + return + if node.keywords: + return + if node.func.attrname == "next": + self.add_message("next-method-called", node=node) + else: + if node.func.attrname in ("iterkeys", "itervalues", "iteritems"): + self.add_message("dict-iter-method", node=node) + elif node.func.attrname in ("viewkeys", "viewvalues", "viewitems"): + self.add_message("dict-view-method", node=node) + elif isinstance(node.func, astroid.Name): + found_node = node.func.lookup(node.func.name)[0] + if _is_builtin(found_node): + if node.func.name in ("filter", "map", "range", "zip"): + if not _in_iterating_context(node): + checker = "{}-builtin-not-iterating".format(node.func.name) + self.add_message(checker, node=node) + if node.func.name == "open" and node.keywords: + kwargs = node.keywords + for kwarg in kwargs or []: + if kwarg.arg == "encoding": + self._validate_encoding(kwarg.value, node) + break + + def _validate_encoding(self, encoding, node): + if isinstance(encoding, astroid.Const): + value = encoding.value + if value in self._invalid_encodings: + self.add_message("invalid-str-codec", node=node) + + @utils.check_messages("indexing-exception") + def visit_subscript(self, node): + """ Look for indexing exceptions. """ + try: + for inferred in node.value.infer(): + if not isinstance(inferred, astroid.Instance): + continue + if utils.inherit_from_std_ex(inferred): + self.add_message("indexing-exception", node=node) + except astroid.InferenceError: + return + + def visit_assignattr(self, node): + if isinstance(node.assign_type(), astroid.AugAssign): + self.visit_attribute(node) + + def visit_delattr(self, node): + self.visit_attribute(node) + + @utils.check_messages("exception-message-attribute", "xreadlines-attribute") + def visit_attribute(self, node): + """Look for removed attributes""" + if node.attrname == "xreadlines": + self.add_message("xreadlines-attribute", node=node) + return + + exception_message = "message" + try: + for inferred in node.expr.infer(): + if isinstance(inferred, astroid.Instance) and utils.inherit_from_std_ex( + inferred + ): + if node.attrname == exception_message: + + # Exceptions with .message clearly defined are an exception + if exception_message in inferred.instance_attrs: + continue + self.add_message("exception-message-attribute", node=node) + if isinstance(inferred, astroid.Module): + self._warn_if_deprecated( + node, inferred.name, {node.attrname}, report_on_modules=False + ) + except astroid.InferenceError: + return + + @utils.check_messages("unpacking-in-except", "comprehension-escape") + def visit_excepthandler(self, node): + """Visit an except handler block and check for exception unpacking.""" + + def _is_used_in_except_block(node): + scope = node.scope() + current = node + while ( + current + and current != scope + and not isinstance(current, astroid.ExceptHandler) + ): + current = current.parent + return isinstance(current, astroid.ExceptHandler) and current.type != node + + if isinstance(node.name, (astroid.Tuple, astroid.List)): + self.add_message("unpacking-in-except", node=node) + return + + if not node.name: + return + + # Find any names + scope = node.parent.scope() + scope_names = scope.nodes_of_class(astroid.Name, skip_klass=astroid.FunctionDef) + scope_names = list(scope_names) + potential_leaked_names = [ + scope_name + for scope_name in scope_names + if scope_name.name == node.name.name + and scope_name.lineno > node.lineno + and not _is_used_in_except_block(scope_name) + ] + reassignments_for_same_name = { + assign_name.lineno + for assign_name in scope.nodes_of_class( + astroid.AssignName, skip_klass=astroid.FunctionDef + ) + if assign_name.name == node.name.name + } + for leaked_name in potential_leaked_names: + if any( + node.lineno < elem < leaked_name.lineno + for elem in reassignments_for_same_name + ): + continue + self.add_message("exception-escape", node=leaked_name) + + @utils.check_messages("backtick") + def visit_repr(self, node): + self.add_message("backtick", node=node) + + @utils.check_messages("raising-string", "old-raise-syntax") + def visit_raise(self, node): + """Visit a raise statement and check for raising + strings or old-raise-syntax. + """ + + # Ignore empty raise. + if node.exc is None: + return + expr = node.exc + if self._check_raise_value(node, expr): + return + try: + value = next(astroid.unpack_infer(expr)) + except astroid.InferenceError: + return + self._check_raise_value(node, value) + + def _check_raise_value(self, node, expr): + if isinstance(expr, astroid.Const): + value = expr.value + if isinstance(value, str): + self.add_message("raising-string", node=node) + return True + return None + + +class Python3TokenChecker(checkers.BaseTokenChecker): + __implements__ = interfaces.ITokenChecker + name = "python3" + enabled = False + + msgs = { + "E1606": ( + "Use of long suffix", + "long-suffix", + 'Used when "l" or "L" is used to mark a long integer. ' + "This will not work in Python 3, since `int` and `long` " + "types have merged.", + {"maxversion": (3, 0)}, + ), + "E1607": ( + "Use of the <> operator", + "old-ne-operator", + 'Used when the deprecated "<>" operator is used instead ' + 'of "!=". This is removed in Python 3.', + {"maxversion": (3, 0), "old_names": [("W0331", "old-old-ne-operator")]}, + ), + "E1608": ( + "Use of old octal literal", + "old-octal-literal", + "Used when encountering the old octal syntax, " + "removed in Python 3. To use the new syntax, " + "prepend 0o on the number.", + {"maxversion": (3, 0)}, + ), + "E1610": ( + "Non-ascii bytes literals not supported in 3.x", + "non-ascii-bytes-literal", + "Used when non-ascii bytes literals are found in a program. " + "They are no longer supported in Python 3.", + {"maxversion": (3, 0)}, + ), + } + + def process_tokens(self, tokens): + for idx, (tok_type, token, start, _, _) in enumerate(tokens): + if tok_type == tokenize.NUMBER: + if token.lower().endswith("l"): + # This has a different semantic than lowercase-l-suffix. + self.add_message("long-suffix", line=start[0]) + elif _is_old_octal(token): + self.add_message("old-octal-literal", line=start[0]) + if tokens[idx][1] == "<>": + self.add_message("old-ne-operator", line=tokens[idx][2][0]) + if tok_type == tokenize.STRING and token.startswith("b"): + if any(elem for elem in token if ord(elem) > 127): + self.add_message("non-ascii-bytes-literal", line=start[0]) + + +def register(linter): + linter.register_checker(Python3Checker(linter)) + linter.register_checker(Python3TokenChecker(linter)) |