You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
477 lines
20 KiB
Python
477 lines
20 KiB
Python
# Copyright 2014 Google Inc.
|
|
# This program is free software; you can redistribute it and/or modify it under
|
|
# the terms of the GNU General Public License as published by the Free Software
|
|
# Foundation; either version 2 of the License, or (at your option) any later
|
|
# version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License along with
|
|
# this program; if not, write to the Free Software Foundation, Inc.,
|
|
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
"""Check Python 2 code for Python 2/3 source-compatible issues."""
|
|
from __future__ import absolute_import
|
|
|
|
import re
|
|
import tokenize
|
|
|
|
import astroid
|
|
from pylint import checkers, interfaces
|
|
from pylint.utils import WarningScope
|
|
from pylint.checkers import utils
|
|
|
|
|
|
_ZERO = re.compile("^0+$")
|
|
|
|
def _is_old_octal(literal):
|
|
if _ZERO.match(literal):
|
|
return False
|
|
if re.match('0\d+', literal):
|
|
try:
|
|
int(literal, 8)
|
|
except ValueError:
|
|
return False
|
|
return True
|
|
|
|
def _check_dict_node(node):
|
|
inferred_types = set()
|
|
try:
|
|
inferred = node.infer()
|
|
for inferred_node in inferred:
|
|
inferred_types.add(inferred_node)
|
|
except (astroid.InferenceError, astroid.UnresolvableName):
|
|
pass
|
|
return (not inferred_types
|
|
or any(isinstance(x, astroid.Dict) for x in inferred_types))
|
|
|
|
|
|
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)',
|
|
{'maxversion': (3, 0)}),
|
|
'E1602': ('Parameter unpacking specified',
|
|
'parameter-unpacking',
|
|
'Used when parameter unpacking is specified for a function'
|
|
"(Python 3 doesn't allow it)",
|
|
{'maxversion': (3, 0)}),
|
|
'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/',
|
|
{'maxversion': (3, 0),
|
|
'old_names': [('W0712', '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)'.",
|
|
{'maxversion': (3, 0),
|
|
'old_names': [('W0121', '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,
|
|
'maxversion': (3, 0),
|
|
'old_names': [('W0333', 'backtick')]}),
|
|
'W1601': ('apply built-in referenced',
|
|
'apply-builtin',
|
|
'Used when the apply built-in function is referenced '
|
|
'(missing from Python 3)',
|
|
{'maxversion': (3, 0)}),
|
|
'W1602': ('basestring built-in referenced',
|
|
'basestring-builtin',
|
|
'Used when the basestring built-in function is referenced '
|
|
'(missing from Python 3)',
|
|
{'maxversion': (3, 0)}),
|
|
'W1603': ('buffer built-in referenced',
|
|
'buffer-builtin',
|
|
'Used when the buffer built-in function is referenced '
|
|
'(missing from Python 3)',
|
|
{'maxversion': (3, 0)}),
|
|
'W1604': ('cmp built-in referenced',
|
|
'cmp-builtin',
|
|
'Used when the cmp built-in function is referenced '
|
|
'(missing from Python 3)',
|
|
{'maxversion': (3, 0)}),
|
|
'W1605': ('coerce built-in referenced',
|
|
'coerce-builtin',
|
|
'Used when the coerce built-in function is referenced '
|
|
'(missing from Python 3)',
|
|
{'maxversion': (3, 0)}),
|
|
'W1606': ('execfile built-in referenced',
|
|
'execfile-builtin',
|
|
'Used when the execfile built-in function is referenced '
|
|
'(missing from Python 3)',
|
|
{'maxversion': (3, 0)}),
|
|
'W1607': ('file built-in referenced',
|
|
'file-builtin',
|
|
'Used when the file built-in function is referenced '
|
|
'(missing from Python 3)',
|
|
{'maxversion': (3, 0)}),
|
|
'W1608': ('long built-in referenced',
|
|
'long-builtin',
|
|
'Used when the long built-in function is referenced '
|
|
'(missing from Python 3)',
|
|
{'maxversion': (3, 0)}),
|
|
'W1609': ('raw_input built-in referenced',
|
|
'raw_input-builtin',
|
|
'Used when the raw_input built-in function is referenced '
|
|
'(missing from Python 3)',
|
|
{'maxversion': (3, 0)}),
|
|
'W1610': ('reduce built-in referenced',
|
|
'reduce-builtin',
|
|
'Used when the reduce built-in function is referenced '
|
|
'(missing from Python 3)',
|
|
{'maxversion': (3, 0)}),
|
|
'W1611': ('StandardError built-in referenced',
|
|
'standarderror-builtin',
|
|
'Used when the StandardError built-in function is referenced '
|
|
'(missing from Python 3)',
|
|
{'maxversion': (3, 0)}),
|
|
'W1612': ('unicode built-in referenced',
|
|
'unicode-builtin',
|
|
'Used when the unicode built-in function is referenced '
|
|
'(missing from Python 3)',
|
|
{'maxversion': (3, 0)}),
|
|
'W1613': ('xrange built-in referenced',
|
|
'xrange-builtin',
|
|
'Used when the xrange built-in function is referenced '
|
|
'(missing from Python 3)',
|
|
{'maxversion': (3, 0)}),
|
|
'W1614': ('__coerce__ method defined',
|
|
'coerce-method',
|
|
'Used when a __coerce__ method is defined '
|
|
'(method is not used by Python 3)',
|
|
{'maxversion': (3, 0)}),
|
|
'W1615': ('__delslice__ method defined',
|
|
'delslice-method',
|
|
'Used when a __delslice__ method is defined '
|
|
'(method is not used by Python 3)',
|
|
{'maxversion': (3, 0)}),
|
|
'W1616': ('__getslice__ method defined',
|
|
'getslice-method',
|
|
'Used when a __getslice__ method is defined '
|
|
'(method is not used by Python 3)',
|
|
{'maxversion': (3, 0)}),
|
|
'W1617': ('__setslice__ method defined',
|
|
'setslice-method',
|
|
'Used when a __setslice__ method is defined '
|
|
'(method is not used by Python 3)',
|
|
{'maxversion': (3, 0)}),
|
|
'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)',
|
|
{'maxversion': (3, 0)}),
|
|
'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)',
|
|
{'maxversion': (3, 0)}),
|
|
'W1620': ('Calling a dict.iter*() method',
|
|
'dict-iter-method',
|
|
'Used for calls to dict.iterkeys(), itervalues() or iteritems() '
|
|
'(Python 3 lacks these methods)',
|
|
{'maxversion': (3, 0)}),
|
|
'W1621': ('Calling a dict.view*() method',
|
|
'dict-view-method',
|
|
'Used for calls to dict.viewkeys(), viewvalues() or viewitems() '
|
|
'(Python 3 lacks these methods)',
|
|
{'maxversion': (3, 0)}),
|
|
'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)',
|
|
{'maxversion': (3, 0)}),
|
|
'W1623': ("Assigning to a class' __metaclass__ attribute",
|
|
'metaclass-assignment',
|
|
"Used when a metaclass is specified by assigning to __metaclass__ "
|
|
'(Python 3 specifies the metaclass as a class statement argument)',
|
|
{'maxversion': (3, 0)}),
|
|
'W1624': ('Indexing exceptions will not work on Python 3',
|
|
'indexing-exception',
|
|
'Indexing exceptions will not work on Python 3. Use '
|
|
'`exception.args[index]` instead.',
|
|
{'maxversion': (3, 0),
|
|
'old_names': [('W0713', 'indexing-exception')]}),
|
|
'W1625': ('Raising a string exception',
|
|
'raising-string',
|
|
'Used when a string exception is raised. This will not '
|
|
'work on Python 3.',
|
|
{'maxversion': (3, 0),
|
|
'old_names': [('W0701', '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.',
|
|
{'maxversion': (3, 0)}),
|
|
'W1627': ('__oct__ method defined',
|
|
'oct-method',
|
|
'Used when a __oct__ method is defined '
|
|
'(method is not used by Python 3)',
|
|
{'maxversion': (3, 0)}),
|
|
'W1628': ('__hex__ method defined',
|
|
'hex-method',
|
|
'Used when a __hex__ method is defined '
|
|
'(method is not used by Python 3)',
|
|
{'maxversion': (3, 0)}),
|
|
'W1629': ('__nonzero__ method defined',
|
|
'nonzero-method',
|
|
'Used when a __nonzero__ method is defined '
|
|
'(method is not used by Python 3)',
|
|
{'maxversion': (3, 0)}),
|
|
'W1630': ('__cmp__ method defined',
|
|
'cmp-method',
|
|
'Used when a __cmp__ method is defined '
|
|
'(method is not used by Python 3)',
|
|
{'maxversion': (3, 0)}),
|
|
'W1631': ('map is used as implicitly evaluated call',
|
|
'implicit-map-evaluation',
|
|
'Used when the map builtin is used as implicitly '
|
|
'evaluated call, as in "map(func, args)" on a single line. '
|
|
'This behaviour will not work in Python 3, where '
|
|
'map is a generator and must be evaluated. '
|
|
'Prefer a for-loop as alternative.',
|
|
{'maxversion': (3, 0)}),
|
|
'W1632': ('input built-in referenced',
|
|
'input-builtin',
|
|
'Used when the input built-in is referenced '
|
|
'(backwards-incompatible semantics in Python 3)',
|
|
{'maxversion': (3, 0)}),
|
|
'W1633': ('round built-in referenced',
|
|
'round-builtin',
|
|
'Used when the round built-in is referenced '
|
|
'(backwards-incompatible semantics in Python 3)',
|
|
{'maxversion': (3, 0)}),
|
|
}
|
|
|
|
_bad_builtins = frozenset([
|
|
'apply',
|
|
'basestring',
|
|
'buffer',
|
|
'cmp',
|
|
'coerce',
|
|
'execfile',
|
|
'file',
|
|
'input', # Not missing, but incompatible semantics
|
|
'long',
|
|
'raw_input',
|
|
'reduce',
|
|
'round', # Not missing, but incompatible semantics
|
|
'StandardError',
|
|
'unicode',
|
|
'xrange',
|
|
'reload',
|
|
])
|
|
|
|
_unused_magic_methods = frozenset([
|
|
'__coerce__',
|
|
'__delslice__',
|
|
'__getslice__',
|
|
'__setslice__',
|
|
'__oct__',
|
|
'__hex__',
|
|
'__nonzero__',
|
|
'__cmp__',
|
|
])
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self._future_division = False
|
|
self._future_absolute_import = False
|
|
super(Python3Checker, self).__init__(*args, **kwargs)
|
|
|
|
def visit_function(self, node):
|
|
if node.is_method() and 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)
|
|
|
|
@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('implicit-map-evaluation')
|
|
def visit_discard(self, node):
|
|
if (isinstance(node.value, astroid.CallFunc) and
|
|
isinstance(node.value.func, astroid.Name) and
|
|
node.value.func.name == 'map'):
|
|
module = node.value.func.lookup('map')[0]
|
|
if getattr(module, 'name', None) == '__builtin__':
|
|
self.add_message('implicit-map-evaluation', node=node)
|
|
|
|
def visit_name(self, node):
|
|
"""Detect when a "bad" built-in is referenced."""
|
|
found_node = node.lookup(node.name)[0]
|
|
if getattr(found_node, 'name', None) == '__builtin__':
|
|
if node.name in self._bad_builtins:
|
|
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)
|
|
|
|
@utils.check_messages('no-absolute-import')
|
|
def visit_from(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
|
|
elif not self._future_absolute_import:
|
|
self.add_message('no-absolute-import', node=node)
|
|
|
|
@utils.check_messages('no-absolute-import')
|
|
def visit_import(self, node):
|
|
if not self._future_absolute_import:
|
|
self.add_message('no-absolute-import', node=node)
|
|
|
|
@utils.check_messages('metaclass-assignment')
|
|
def visit_class(self, node):
|
|
if '__metaclass__' in node.locals:
|
|
self.add_message('metaclass-assignment', 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):
|
|
if isinstance(arg, astroid.Const) and isinstance(arg.value, float):
|
|
break
|
|
else:
|
|
self.add_message('old-division', node=node)
|
|
|
|
@utils.check_messages('next-method-called',
|
|
'dict-iter-method',
|
|
'dict-view-method')
|
|
def visit_callfunc(self, node):
|
|
if not isinstance(node.func, astroid.Getattr):
|
|
return
|
|
if any([node.args, node.starargs, node.kwargs]):
|
|
return
|
|
if node.func.attrname == 'next':
|
|
self.add_message('next-method-called', node=node)
|
|
else:
|
|
if _check_dict_node(node.func.expr):
|
|
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)
|
|
|
|
@utils.check_messages('indexing-exception')
|
|
def visit_subscript(self, node):
|
|
""" Look for indexing exceptions. """
|
|
try:
|
|
for infered in node.value.infer():
|
|
if not isinstance(infered, astroid.Instance):
|
|
continue
|
|
if utils.inherit_from_std_ex(infered):
|
|
self.add_message('indexing-exception', node=node)
|
|
except astroid.InferenceError:
|
|
return
|
|
|
|
@utils.check_messages('unpacking-in-except')
|
|
def visit_excepthandler(self, node):
|
|
"""Visit an except handler block and check for exception unpacking."""
|
|
if isinstance(node.name, (astroid.Tuple, astroid.List)):
|
|
self.add_message('unpacking-in-except', node=node)
|
|
|
|
@utils.check_messages('backtick')
|
|
def visit_backquote(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.
|
|
"""
|
|
if (node.exc is not None and
|
|
node.inst is not None and
|
|
node.tback is None):
|
|
self.add_message('old-raise-syntax', node=node)
|
|
|
|
# Ignore empty raise.
|
|
if node.exc is None:
|
|
return
|
|
expr = node.exc
|
|
if self._check_raise_value(node, expr):
|
|
return
|
|
else:
|
|
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
|
|
|
|
|
|
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-ne-operator')]}),
|
|
'E1608': ('Use of old octal literal',
|
|
'old-octal-literal',
|
|
'Usen when encountering the old octal syntax, '
|
|
'removed in Python 3. To use the new syntax, '
|
|
'prepend 0o on the number.',
|
|
{'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])
|
|
|
|
|
|
def register(linter):
|
|
linter.register_checker(Python3Checker(linter))
|
|
linter.register_checker(Python3TokenChecker(linter))
|