mirror of
https://github.com/PiBrewing/craftbeerpi4.git
synced 2024-11-27 09:18:02 +01:00
1427 lines
52 KiB
Python
1427 lines
52 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright (c) 2014-2020 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-2018 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-2020 Anthony Sottile <asottile@umich.edu>
|
|
# Copyright (c) 2018 sbagan <pnlbagan@gmail.com>
|
|
# Copyright (c) 2018 Lucas Cimon <lucas.cimon@gmail.com>
|
|
# Copyright (c) 2018 Aivar Annamaa <aivarannamaa@users.noreply.github.com>
|
|
# Copyright (c) 2018 ssolanki <sushobhitsolanki@gmail.com>
|
|
# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com>
|
|
# Copyright (c) 2018 Ashley Whetter <ashley@awhetter.co.uk>
|
|
# Copyright (c) 2018 gaurikholkar <f2013002@goa.bits-pilani.ac.in>
|
|
# Copyright (c) 2019 Nick Drozd <nicholasdrozd@gmail.com>
|
|
# Copyright (c) 2019 Hugues Bruant <hugues.bruant@affirm.com>
|
|
# Copyright (c) 2019 Gabriel R Sezefredo <gabriel@sezefredo.com.br>
|
|
# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
|
|
# Copyright (c) 2019 Pierre Sassoulas <pierre.sassoulas@gmail.com>
|
|
# Copyright (c) 2019 bluesheeptoken <louis.fruleux1@gmail.com>
|
|
# Copyright (c) 2020 谭九鼎 <109224573@qq.com>
|
|
# Copyright (c) 2020 Federico Bond <federicobond@gmail.com>
|
|
# Copyright (c) 2020 Athos Ribeiro <athoscr@fedoraproject.org>
|
|
# 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 itertools
|
|
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 _infer_if_relevant_attr(node, whitelist):
|
|
return node.expr.infer() if node.attrname in whitelist else []
|
|
|
|
|
|
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 ["in", "not 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 https://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"})},
|
|
}
|
|
|
|
_deprecated_attrs = frozenset(
|
|
itertools.chain.from_iterable(
|
|
attr
|
|
for module_map in _bad_python3_module_map.values()
|
|
if isinstance(module_map, dict)
|
|
for attr in module_map.values()
|
|
)
|
|
)
|
|
|
|
_relevant_call_attrs = (
|
|
DICT_METHODS | _deprecated_attrs | {"encode", "decode", "translate"}
|
|
)
|
|
|
|
_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().__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().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 _infer_if_relevant_attr(
|
|
node.func, self._relevant_call_attrs
|
|
):
|
|
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)
|
|
elif 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)
|
|
elif 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 _infer_if_relevant_attr(
|
|
node, self._deprecated_attrs | {exception_message}
|
|
):
|
|
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, block):
|
|
current = node
|
|
while current and current is not block:
|
|
current = current.parent
|
|
return current is not None
|
|
|
|
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, node)
|
|
]
|
|
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))
|