|
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043 |
- # -*- 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-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) 2016 Jakub Wilk <jwilk@jwilk.net>
- # 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) 2017 Ville Skyttä <ville.skytta@iki.fi>
- # Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com>
- # 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."""
- from __future__ import absolute_import, print_function
-
- from collections import namedtuple
- import re
- import sys
- import tokenize
-
- import astroid
- from astroid import bases
- import six
-
- from pylint import checkers, interfaces
- from pylint.interfaces import INFERENCE_FAILURE, INFERENCE
- 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(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 _check_dict_node(node):
- inferred_types = set()
- try:
- inferred = node.infer()
- if inferred is not astroid.Uninferable:
- for inferred_node in inferred:
- inferred_types.add(inferred_node)
- except astroid.InferenceError:
- pass
-
- if not inferred_types:
- return True
- return any(_inferred_value_is_dict(value) for value in inferred_types)
-
-
- 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'}
- 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):
- parent_scope = parent.func.lookup(parent.func.name)[0]
- if _is_builtin(parent_scope) and parent.func.name in _ACCEPTS_ITERATOR:
- return True
- elif isinstance(parent.func, astroid.Attribute):
- if parent.func.attrname == 'join':
- 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
- 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', '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-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', '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', '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', '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 an iterator 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', 'dbm', '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',
- 'turtle', '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'}),
- }
- }
-
- if (3, 4) <= sys.version_info < (3, 4, 4):
- # Python 3.4.0 -> 3.4.3 has a bug which breaks `repr_tree()`:
- # https://bugs.python.org/issue23572
- _python_2_tests = frozenset()
- else:
- _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
- def add_message(self, msg_id, always_warn=False, # pylint: disable=arguments-differ
- *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.statement().parent_of(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 _is_builtin(found_node):
- 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, 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):
- if isinstance(arg, astroid.Const) and isinstance(arg.value, float):
- 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, six.string_types)
- 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, six.string_types)):
- 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 _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)
- 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.
- """
- if six.PY2:
- if (node.exc is not None and
- node.inst is not 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
- 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-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)}),
- 'E1611': ('unicode raw string literals not supported in 3.x',
- 'invalid-unicode-literal',
- 'Used when raw unicode 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])
- if tok_type == tokenize.STRING and token.startswith('ur'):
- self.add_message('invalid-unicode-literal', line=start[0])
-
-
- def register(linter):
- linter.register_checker(Python3Checker(linter))
- linter.register_checker(Python3TokenChecker(linter))
|