Commit ab81b0c6 authored by vapier@chromium.org's avatar vapier@chromium.org

pylint: upgrade to 1.4.1

This is largely a bugfix release, so should be much easier to transition.

BUG=chromium:431514
TEST=ran on some code bases and checked output

Review URL: https://codereview.chromium.org/876793002

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@293806 0039d316-1c4b-4281-b951-d872f2087c98
parent 5e879a42
URL: http://www.logilab.org/project/logilab-astng URL: http://www.logilab.org/project/logilab-astng
Version: 1.3.2 Version: 1.3.4
License: GPL License: GPL
License File: LICENSE.txt License File: LICENSE.txt
......
...@@ -20,7 +20,7 @@ distname = 'astroid' ...@@ -20,7 +20,7 @@ distname = 'astroid'
modname = 'astroid' modname = 'astroid'
numversion = (1, 3, 2) numversion = (1, 3, 4)
version = '.'.join([str(num) for num in numversion]) version = '.'.join([str(num) for num in numversion])
install_requires = ['logilab-common >= 0.60.0', 'six'] install_requires = ['logilab-common >= 0.60.0', 'six']
......
"""Astroid hooks for various builtins."""
import sys
from functools import partial
from textwrap import dedent
import six
from astroid import (MANAGER, UseInferenceDefault,
inference_tip, YES, InferenceError, UnresolvableName)
from astroid import nodes
from astroid.builder import AstroidBuilder
def _extend_str(class_node, rvalue):
"""function to extend builtin str/unicode class"""
# TODO(cpopa): this approach will make astroid to believe
# that some arguments can be passed by keyword, but
# unfortunately, strings and bytes don't accept keyword arguments.
code = dedent('''
class whatever(object):
def join(self, iterable):
return {rvalue}
def replace(self, old, new, count=None):
return {rvalue}
def format(self, *args, **kwargs):
return {rvalue}
def encode(self, encoding='ascii', errors=None):
return ''
def decode(self, encoding='ascii', errors=None):
return u''
def capitalize(self):
return {rvalue}
def title(self):
return {rvalue}
def lower(self):
return {rvalue}
def upper(self):
return {rvalue}
def swapcase(self):
return {rvalue}
def index(self, sub, start=None, end=None):
return 0
def find(self, sub, start=None, end=None):
return 0
def count(self, sub, start=None, end=None):
return 0
def strip(self, chars=None):
return {rvalue}
def lstrip(self, chars=None):
return {rvalue}
def rstrip(self, chars=None):
return {rvalue}
def rjust(self, width, fillchar=None):
return {rvalue}
def center(self, width, fillchar=None):
return {rvalue}
def ljust(self, width, fillchar=None):
return {rvalue}
''')
code = code.format(rvalue=rvalue)
fake = AstroidBuilder(MANAGER).string_build(code)['whatever']
for method in fake.mymethods():
class_node.locals[method.name] = [method]
method.parent = class_node
def extend_builtins(class_transforms):
from astroid.bases import BUILTINS
builtin_ast = MANAGER.astroid_cache[BUILTINS]
for class_name, transform in class_transforms.items():
transform(builtin_ast[class_name])
if sys.version_info > (3, 0):
extend_builtins({'bytes': partial(_extend_str, rvalue="b''"),
'str': partial(_extend_str, rvalue="''")})
else:
extend_builtins({'str': partial(_extend_str, rvalue="''"),
'unicode': partial(_extend_str, rvalue="u''")})
def register_builtin_transform(transform, builtin_name):
"""Register a new transform function for the given *builtin_name*.
The transform function must accept two parameters, a node and
an optional context.
"""
def _transform_wrapper(node, context=None):
result = transform(node, context=context)
if result:
result.parent = node
result.lineno = node.lineno
result.col_offset = node.col_offset
return iter([result])
MANAGER.register_transform(nodes.CallFunc,
inference_tip(_transform_wrapper),
lambda n: (isinstance(n.func, nodes.Name) and
n.func.name == builtin_name))
def _generic_inference(node, context, node_type, transform):
args = node.args
if not args:
return node_type()
if len(node.args) > 1:
raise UseInferenceDefault()
arg, = args
transformed = transform(arg)
if not transformed:
try:
infered = next(arg.infer(context=context))
except (InferenceError, StopIteration):
raise UseInferenceDefault()
if infered is YES:
raise UseInferenceDefault()
transformed = transform(infered)
if not transformed or transformed is YES:
raise UseInferenceDefault()
return transformed
def _generic_transform(arg, klass, iterables, build_elts):
if isinstance(arg, klass):
return arg
elif isinstance(arg, iterables):
if not all(isinstance(elt, nodes.Const)
for elt in arg.elts):
# TODO(cpopa): Don't support heterogenous elements.
# Not yet, though.
raise UseInferenceDefault()
elts = [elt.value for elt in arg.elts]
elif isinstance(arg, nodes.Dict):
if not all(isinstance(elt[0], nodes.Const)
for elt in arg.items):
raise UseInferenceDefault()
elts = [item[0].value for item in arg.items]
elif (isinstance(arg, nodes.Const) and
isinstance(arg.value, (six.string_types, six.binary_type))):
elts = arg.value
else:
return
return klass(elts=build_elts(elts))
def _infer_builtin(node, context,
klass=None, iterables=None,
build_elts=None):
transform_func = partial(
_generic_transform,
klass=klass,
iterables=iterables,
build_elts=build_elts)
return _generic_inference(node, context, klass, transform_func)
# pylint: disable=invalid-name
infer_tuple = partial(
_infer_builtin,
klass=nodes.Tuple,
iterables=(nodes.List, nodes.Set),
build_elts=tuple)
infer_list = partial(
_infer_builtin,
klass=nodes.List,
iterables=(nodes.Tuple, nodes.Set),
build_elts=list)
infer_set = partial(
_infer_builtin,
klass=nodes.Set,
iterables=(nodes.List, nodes.Tuple),
build_elts=set)
def _get_elts(arg, context):
is_iterable = lambda n: isinstance(n,
(nodes.List, nodes.Tuple, nodes.Set))
try:
infered = next(arg.infer(context))
except (InferenceError, UnresolvableName):
raise UseInferenceDefault()
if isinstance(infered, nodes.Dict):
items = infered.items
elif is_iterable(infered):
items = []
for elt in infered.elts:
# If an item is not a pair of two items,
# then fallback to the default inference.
# Also, take in consideration only hashable items,
# tuples and consts. We are choosing Names as well.
if not is_iterable(elt):
raise UseInferenceDefault()
if len(elt.elts) != 2:
raise UseInferenceDefault()
if not isinstance(elt.elts[0],
(nodes.Tuple, nodes.Const, nodes.Name)):
raise UseInferenceDefault()
items.append(tuple(elt.elts))
else:
raise UseInferenceDefault()
return items
def infer_dict(node, context=None):
"""Try to infer a dict call to a Dict node.
The function treats the following cases:
* dict()
* dict(mapping)
* dict(iterable)
* dict(iterable, **kwargs)
* dict(mapping, **kwargs)
* dict(**kwargs)
If a case can't be infered, we'll fallback to default inference.
"""
has_keywords = lambda args: all(isinstance(arg, nodes.Keyword)
for arg in args)
if not node.args and not node.kwargs:
# dict()
return nodes.Dict()
elif has_keywords(node.args) and node.args:
# dict(a=1, b=2, c=4)
items = [(nodes.Const(arg.arg), arg.value) for arg in node.args]
elif (len(node.args) >= 2 and
has_keywords(node.args[1:])):
# dict(some_iterable, b=2, c=4)
elts = _get_elts(node.args[0], context)
keys = [(nodes.Const(arg.arg), arg.value) for arg in node.args[1:]]
items = elts + keys
elif len(node.args) == 1:
items = _get_elts(node.args[0], context)
else:
raise UseInferenceDefault()
empty = nodes.Dict()
empty.items = items
return empty
# Builtins inference
register_builtin_transform(infer_tuple, 'tuple')
register_builtin_transform(infer_set, 'set')
register_builtin_transform(infer_list, 'list')
register_builtin_transform(infer_dict, 'dict')
"""Astroid hooks for the Python 2 standard library. """Astroid hooks for the Python 2 standard library.
Currently help understanding of : Currently help understanding of :
...@@ -6,6 +7,7 @@ Currently help understanding of : ...@@ -6,6 +7,7 @@ Currently help understanding of :
""" """
import sys import sys
from functools import partial
from textwrap import dedent from textwrap import dedent
from astroid import ( from astroid import (
...@@ -104,6 +106,12 @@ class %(name)s(object): ...@@ -104,6 +106,12 @@ class %(name)s(object):
@property @property
def name(self): def name(self):
return %(name)r return %(name)r
@property
def block_size(self):
return 1
@property
def digest_size(self):
return 1
''' '''
algorithms = ('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512') algorithms = ('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512')
classes = "".join( classes = "".join(
...@@ -322,5 +330,5 @@ MANAGER.register_transform(nodes.CallFunc, inference_tip(infer_enum), ...@@ -322,5 +330,5 @@ MANAGER.register_transform(nodes.CallFunc, inference_tip(infer_enum),
MANAGER.register_transform(nodes.Class, infer_enum_class) MANAGER.register_transform(nodes.Class, infer_enum_class)
register_module_extender(MANAGER, 'hashlib', hashlib_transform) register_module_extender(MANAGER, 'hashlib', hashlib_transform)
register_module_extender(MANAGER, 'collections', collections_transform) register_module_extender(MANAGER, 'collections', collections_transform)
register_module_extender(MANAGER, 'pkg_resourcds', pkg_resources_transform) register_module_extender(MANAGER, 'pkg_resources', pkg_resources_transform)
register_module_extender(MANAGER, 'subprocess', subprocess_transform) register_module_extender(MANAGER, 'subprocess', subprocess_transform)
# copyright 2003-2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of astroid.
#
# astroid is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 2.1 of the License, or (at your
# option) any later version.
#
# astroid 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 Lesser General Public License
# for more details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with astroid. If not, see <http://www.gnu.org/licenses/>.
"""Hooks for nose library."""
import re
import unittest
from astroid import List, MANAGER, register_module_extender
from astroid.builder import AstroidBuilder
def _pep8(name, caps=re.compile('([A-Z])')):
return caps.sub(lambda m: '_' + m.groups()[0].lower(), name)
def nose_transform():
"""Custom transform for the nose.tools module."""
builder = AstroidBuilder(MANAGER)
stub = AstroidBuilder(MANAGER).string_build('''__all__ = []''')
unittest_module = builder.module_build(unittest.case)
case = unittest_module['TestCase']
all_entries = ['ok_', 'eq_']
for method_name, method in case.locals.items():
if method_name.startswith('assert') and '_' not in method_name:
pep8_name = _pep8(method_name)
all_entries.append(pep8_name)
stub[pep8_name] = method[0]
# Update the __all__ variable, since nose.tools
# does this manually with .append.
all_assign = stub['__all__'].parent
all_object = List(all_entries)
all_object.parent = all_assign
all_assign.value = all_object
return stub
register_module_extender(MANAGER, 'nose.tools.trivial', nose_transform)
# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of astroid.
#
# astroid is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 2.1 of the License, or (at your option) any
# later version.
#
# astroid 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with astroid. If not, see <http://www.gnu.org/licenses/>.
"""Astroid hooks for six.moves."""
import sys
from textwrap import dedent
from astroid import MANAGER, register_module_extender
from astroid.builder import AstroidBuilder
def six_moves_transform_py2():
return AstroidBuilder(MANAGER).string_build(dedent('''
import urllib as _urllib
import urllib2 as _urllib2
import urlparse as _urlparse
class Moves(object):
import BaseHTTPServer
import CGIHTTPServer
import SimpleHTTPServer
from StringIO import StringIO
from cStringIO import StringIO as cStringIO
from UserDict import UserDict
from UserList import UserList
from UserString import UserString
import __builtin__ as builtins
import thread as _thread
import dummy_thread as _dummy_thread
import ConfigParser as configparser
import copy_reg as copyreg
from itertools import (imap as map,
ifilter as filter,
ifilterfalse as filterfalse,
izip_longest as zip_longest,
izip as zip)
import htmlentitydefs as html_entities
import HTMLParser as html_parser
import httplib as http_client
import cookielib as http_cookiejar
import Cookie as http_cookies
import Queue as queue
import repr as reprlib
from pipes import quote as shlex_quote
import SocketServer as socketserver
import SimpleXMLRPCServer as xmlrpc_server
import xmlrpclib as xmlrpc_client
import _winreg as winreg
import robotparser as urllib_robotparser
input = raw_input
intern = intern
range = xrange
xrange = xrange
reduce = reduce
reload_module = reload
class UrllibParse(object):
ParseResult = _urlparse.ParseResult
SplitResult = _urlparse.SplitResult
parse_qs = _urlparse.parse_qs
parse_qsl = _urlparse.parse_qsl
urldefrag = _urlparse.urldefrag
urljoin = _urlparse.urljoin
urlparse = _urlparse.urlparse
urlsplit = _urlparse.urlsplit
urlunparse = _urlparse.urlunparse
urlunsplit = _urlparse.urlunsplit
quote = _urllib.quote
quote_plus = _urllib.quote_plus
unquote = _urllib.unquote
unquote_plus = _urllib.unquote_plus
urlencode = _urllib.urlencode
splitquery = _urllib.splitquery
splittag = _urllib.splittag
splituser = _urllib.splituser
uses_fragment = _urlparse.uses_fragment
uses_netloc = _urlparse.uses_netloc
uses_params = _urlparse.uses_params
uses_query = _urlparse.uses_query
uses_relative = _urlparse.uses_relative
class UrllibError(object):
URLError = _urllib2.URLError
HTTPError = _urllib2.HTTPError
ContentTooShortError = _urllib.ContentTooShortError
class DummyModule(object):
pass
class UrllibRequest(object):
urlopen = _urllib2.urlopen
install_opener = _urllib2.install_opener
build_opener = _urllib2.build_opener
pathname2url = _urllib.pathname2url
url2pathname = _urllib.url2pathname
getproxies = _urllib.getproxies
Request = _urllib2.Request
OpenerDirector = _urllib2.OpenerDirector
HTTPDefaultErrorHandler = _urllib2.HTTPDefaultErrorHandler
HTTPRedirectHandler = _urllib2.HTTPRedirectHandler
HTTPCookieProcessor = _urllib2.HTTPCookieProcessor
ProxyHandler = _urllib2.ProxyHandler
BaseHandler = _urllib2.BaseHandler
HTTPPasswordMgr = _urllib2.HTTPPasswordMgr
HTTPPasswordMgrWithDefaultRealm = _urllib2.HTTPPasswordMgrWithDefaultRealm
AbstractBasicAuthHandler = _urllib2.AbstractBasicAuthHandler
HTTPBasicAuthHandler = _urllib2.HTTPBasicAuthHandler
ProxyBasicAuthHandler = _urllib2.ProxyBasicAuthHandler
AbstractDigestAuthHandler = _urllib2.AbstractDigestAuthHandler
HTTPDigestAuthHandler = _urllib2.HTTPDigestAuthHandler
ProxyDigestAuthHandler = _urllib2.ProxyDigestAuthHandler
HTTPHandler = _urllib2.HTTPHandler
HTTPSHandler = _urllib2.HTTPSHandler
FileHandler = _urllib2.FileHandler
FTPHandler = _urllib2.FTPHandler
CacheFTPHandler = _urllib2.CacheFTPHandler
UnknownHandler = _urllib2.UnknownHandler
HTTPErrorProcessor = _urllib2.HTTPErrorProcessor
urlretrieve = _urllib.urlretrieve
urlcleanup = _urllib.urlcleanup
proxy_bypass = _urllib.proxy_bypass
urllib_parse = UrllibParse()
urllib_error = UrllibError()
urllib = DummyModule()
urllib.request = UrllibRequest()
urllib.parse = UrllibParse()
urllib.error = UrllibError()
moves = Moves()
'''))
def six_moves_transform_py3():
return AstroidBuilder(MANAGER).string_build(dedent('''
class Moves(object):
import _io
cStringIO = _io.StringIO
filter = filter
from itertools import filterfalse
input = input
from sys import intern
map = map
range = range
from imp import reload as reload_module
from functools import reduce
from shlex import quote as shlex_quote
from io import StringIO
from collections import UserDict, UserList, UserString
xrange = range
zip = zip
from itertools import zip_longest
import builtins
import configparser
import copyreg
import _dummy_thread
import http.cookiejar as http_cookiejar
import http.cookies as http_cookies
import html.entities as html_entities
import html.parser as html_parser
import http.client as http_client
import http.server
BaseHTTPServer = CGIHTTPServer = SimpleHTTPServer = http.server
import pickle as cPickle
import queue
import reprlib
import socketserver
import _thread
import winreg
import xmlrpc.server as xmlrpc_server
import xmlrpc.client as xmlrpc_client
import urllib.robotparser as urllib_robotparser
import email.mime.multipart as email_mime_multipart
import email.mime.nonmultipart as email_mime_nonmultipart
import email.mime.text as email_mime_text
import email.mime.base as email_mime_base
import urllib.parse as urllib_parse
import urllib.error as urllib_error
import tkinter
import tkinter.dialog as tkinter_dialog
import tkinter.filedialog as tkinter_filedialog
import tkinter.scrolledtext as tkinter_scrolledtext
import tkinter.simpledialog as tkinder_simpledialog
import tkinter.tix as tkinter_tix
import tkinter.ttk as tkinter_ttk
import tkinter.constants as tkinter_constants
import tkinter.dnd as tkinter_dnd
import tkinter.colorchooser as tkinter_colorchooser
import tkinter.commondialog as tkinter_commondialog
import tkinter.filedialog as tkinter_tkfiledialog
import tkinter.font as tkinter_font
import tkinter.messagebox as tkinter_messagebox
import urllib.request
import urllib.robotparser as urllib_robotparser
import urllib.parse as urllib_parse
import urllib.error as urllib_error
moves = Moves()
'''))
if sys.version_info[0] == 2:
TRANSFORM = six_moves_transform_py2
else:
TRANSFORM = six_moves_transform_py3
register_module_extender(MANAGER, 'six', TRANSFORM)
...@@ -311,7 +311,7 @@ class AstroidManager(OptionsProviderMixIn): ...@@ -311,7 +311,7 @@ class AstroidManager(OptionsProviderMixIn):
self.transforms[node_class].remove((transform, predicate)) self.transforms[node_class].remove((transform, predicate))
def register_failed_import_hook(self, hook): def register_failed_import_hook(self, hook):
""""Registers a hook to resolve imports that cannot be found otherwise. """Registers a hook to resolve imports that cannot be found otherwise.
`hook` must be a function that accepts a single argument `modname` which `hook` must be a function that accepts a single argument `modname` which
contains the name of the module or package that could not be imported. contains the name of the module or package that could not be imported.
...@@ -348,15 +348,16 @@ class AstroidManager(OptionsProviderMixIn): ...@@ -348,15 +348,16 @@ class AstroidManager(OptionsProviderMixIn):
"""Cache a module if no module with the same name is known yet.""" """Cache a module if no module with the same name is known yet."""
self.astroid_cache.setdefault(module.name, module) self.astroid_cache.setdefault(module.name, module)
def clear_cache(self): def clear_cache(self, astroid_builtin=None):
# XXX clear transforms # XXX clear transforms
self.astroid_cache.clear() self.astroid_cache.clear()
# force bootstrap again, else we may ends up with cache inconsistency # force bootstrap again, else we may ends up with cache inconsistency
# between the manager and CONST_PROXY, making # between the manager and CONST_PROXY, making
# unittest_lookup.LookupTC.test_builtin_lookup fail depending on the # unittest_lookup.LookupTC.test_builtin_lookup fail depending on the
# test order # test order
from astroid.raw_building import astroid_bootstrapping import astroid.raw_building
astroid_bootstrapping() astroid.raw_building._astroid_bootstrapping(
astroid_builtin=astroid_builtin)
class Project(object): class Project(object):
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
:type PY_SOURCE_EXTS: tuple(str) :type PY_SOURCE_EXTS: tuple(str)
:var PY_SOURCE_EXTS: list of possible python source file extension :var PY_SOURCE_EXTS: list of possible python source file extension
:type STD_LIB_DIRS: list of str :type STD_LIB_DIRS: set of str
:var STD_LIB_DIRS: directories where standard modules are located :var STD_LIB_DIRS: directories where standard modules are located
:type BUILTIN_MODULES: dict :type BUILTIN_MODULES: dict
...@@ -54,31 +54,33 @@ else: ...@@ -54,31 +54,33 @@ else:
PY_COMPILED_EXTS = ('so',) PY_COMPILED_EXTS = ('so',)
# Notes about STD_LIB_DIRS # Notes about STD_LIB_DIRS
# Consider arch-specific installation for STD_LIB_DIR definition # Consider arch-specific installation for STD_LIB_DIRS definition
# :mod:`distutils.sysconfig` contains to much hardcoded values to rely on # :mod:`distutils.sysconfig` contains to much hardcoded values to rely on
# #
# :see: `Problems with /usr/lib64 builds <http://bugs.python.org/issue1294959>`_ # :see: `Problems with /usr/lib64 builds <http://bugs.python.org/issue1294959>`_
# :see: `FHS <http://www.pathname.com/fhs/pub/fhs-2.3.html#LIBLTQUALGTALTERNATEFORMATESSENTIAL>`_ # :see: `FHS <http://www.pathname.com/fhs/pub/fhs-2.3.html#LIBLTQUALGTALTERNATEFORMATESSENTIAL>`_
try: try:
# The explicit prefix is to work around a patch in virtualenv that # The explicit sys.prefix is to work around a patch in virtualenv that
# replaces the 'real' sys.prefix (i.e. the location of the binary) # replaces the 'real' sys.prefix (i.e. the location of the binary)
# with the prefix from which the virtualenv was created. This throws # with the prefix from which the virtualenv was created. This throws
# off the detection logic for standard library modules, thus the # off the detection logic for standard library modules, thus the
# workaround. # workaround.
STD_LIB_DIRS = [ STD_LIB_DIRS = {
get_python_lib(standard_lib=True, prefix=sys.prefix), get_python_lib(standard_lib=True, prefix=sys.prefix),
get_python_lib(standard_lib=True)] # Take care of installations where exec_prefix != prefix.
get_python_lib(standard_lib=True, prefix=sys.exec_prefix),
get_python_lib(standard_lib=True)}
if os.name == 'nt': if os.name == 'nt':
STD_LIB_DIRS.append(os.path.join(sys.prefix, 'dlls')) STD_LIB_DIRS.add(os.path.join(sys.prefix, 'dlls'))
try: try:
# real_prefix is defined when running inside virtualenv. # real_prefix is defined when running inside virtualenv.
STD_LIB_DIRS.append(os.path.join(sys.real_prefix, 'dlls')) STD_LIB_DIRS.add(os.path.join(sys.real_prefix, 'dlls'))
except AttributeError: except AttributeError:
pass pass
# get_python_lib(standard_lib=1) is not available on pypy, set STD_LIB_DIR to # get_python_lib(standard_lib=1) is not available on pypy, set STD_LIB_DIR to
# non-valid path, see https://bugs.pypy.org/issue1164 # non-valid path, see https://bugs.pypy.org/issue1164
except DistutilsPlatformError: except DistutilsPlatformError:
STD_LIB_DIRS = [] STD_LIB_DIRS = set()
EXT_LIB_DIR = get_python_lib() EXT_LIB_DIR = get_python_lib()
......
...@@ -332,12 +332,14 @@ class InspectBuilder(object): ...@@ -332,12 +332,14 @@ class InspectBuilder(object):
Astroid_BUILDER = InspectBuilder() Astroid_BUILDER = InspectBuilder()
_CONST_PROXY = {} _CONST_PROXY = {}
def astroid_bootstrapping(): def _astroid_bootstrapping(astroid_builtin=None):
"""astroid boot strapping the builtins module""" """astroid boot strapping the builtins module"""
# this boot strapping is necessary since we need the Const nodes to # this boot strapping is necessary since we need the Const nodes to
# inspect_build builtins, and then we can proxy Const # inspect_build builtins, and then we can proxy Const
if astroid_builtin is None:
from logilab.common.compat import builtins from logilab.common.compat import builtins
astroid_builtin = Astroid_BUILDER.inspect_build(builtins) astroid_builtin = Astroid_BUILDER.inspect_build(builtins)
for cls, node_cls in CONST_CLS.items(): for cls, node_cls in CONST_CLS.items():
if cls is type(None): if cls is type(None):
proxy = build_class('NoneType') proxy = build_class('NoneType')
...@@ -349,7 +351,7 @@ def astroid_bootstrapping(): ...@@ -349,7 +351,7 @@ def astroid_bootstrapping():
else: else:
_CONST_PROXY[cls] = proxy _CONST_PROXY[cls] = proxy
astroid_bootstrapping() _astroid_bootstrapping()
# TODO : find a nicer way to handle this situation; # TODO : find a nicer way to handle this situation;
# However __proxied introduced an # However __proxied introduced an
......
This diff is collapsed.
"""Utility functions for test code that uses astroid ASTs as input."""
import functools
import sys
import textwrap
from astroid import nodes
from astroid import builder
# The name of the transient function that is used to
# wrap expressions to be extracted when calling
# extract_node.
_TRANSIENT_FUNCTION = '__'
# The comment used to select a statement to be extracted
# when calling extract_node.
_STATEMENT_SELECTOR = '#@'
def _extract_expressions(node):
"""Find expressions in a call to _TRANSIENT_FUNCTION and extract them.
The function walks the AST recursively to search for expressions that
are wrapped into a call to _TRANSIENT_FUNCTION. If it finds such an
expression, it completely removes the function call node from the tree,
replacing it by the wrapped expression inside the parent.
:param node: An astroid node.
:type node: astroid.bases.NodeNG
:yields: The sequence of wrapped expressions on the modified tree
expression can be found.
"""
if (isinstance(node, nodes.CallFunc)
and isinstance(node.func, nodes.Name)
and node.func.name == _TRANSIENT_FUNCTION):
real_expr = node.args[0]
real_expr.parent = node.parent
# Search for node in all _astng_fields (the fields checked when
# get_children is called) of its parent. Some of those fields may
# be lists or tuples, in which case the elements need to be checked.
# When we find it, replace it by real_expr, so that the AST looks
# like no call to _TRANSIENT_FUNCTION ever took place.
for name in node.parent._astroid_fields:
child = getattr(node.parent, name)
if isinstance(child, (list, tuple)):
for idx, compound_child in enumerate(child):
if compound_child is node:
child[idx] = real_expr
elif child is node:
setattr(node.parent, name, real_expr)
yield real_expr
else:
for child in node.get_children():
for result in _extract_expressions(child):
yield result
def _find_statement_by_line(node, line):
"""Extracts the statement on a specific line from an AST.
If the line number of node matches line, it will be returned;
otherwise its children are iterated and the function is called
recursively.
:param node: An astroid node.
:type node: astroid.bases.NodeNG
:param line: The line number of the statement to extract.
:type line: int
:returns: The statement on the line, or None if no statement for the line
can be found.
:rtype: astroid.bases.NodeNG or None
"""
if isinstance(node, (nodes.Class, nodes.Function)):
# This is an inaccuracy in the AST: the nodes that can be
# decorated do not carry explicit information on which line
# the actual definition (class/def), but .fromline seems to
# be close enough.
node_line = node.fromlineno
else:
node_line = node.lineno
if node_line == line:
return node
for child in node.get_children():
result = _find_statement_by_line(child, line)
if result:
return result
return None
def extract_node(code, module_name=''):
"""Parses some Python code as a module and extracts a designated AST node.
Statements:
To extract one or more statement nodes, append #@ to the end of the line
Examples:
>>> def x():
>>> def y():
>>> return 1 #@
The return statement will be extracted.
>>> class X(object):
>>> def meth(self): #@
>>> pass
The funcion object 'meth' will be extracted.
Expressions:
To extract arbitrary expressions, surround them with the fake
function call __(...). After parsing, the surrounded expression
will be returned and the whole AST (accessible via the returned
node's parent attribute) will look like the function call was
never there in the first place.
Examples:
>>> a = __(1)
The const node will be extracted.
>>> def x(d=__(foo.bar)): pass
The node containing the default argument will be extracted.
>>> def foo(a, b):
>>> return 0 < __(len(a)) < b
The node containing the function call 'len' will be extracted.
If no statements or expressions are selected, the last toplevel
statement will be returned.
If the selected statement is a discard statement, (i.e. an expression
turned into a statement), the wrapped expression is returned instead.
For convenience, singleton lists are unpacked.
:param str code: A piece of Python code that is parsed as
a module. Will be passed through textwrap.dedent first.
:param str module_name: The name of the module.
:returns: The designated node from the parse tree, or a list of nodes.
:rtype: astroid.bases.NodeNG, or a list of nodes.
"""
def _extract(node):
if isinstance(node, nodes.Discard):
return node.value
else:
return node
requested_lines = []
for idx, line in enumerate(code.splitlines()):
if line.strip().endswith(_STATEMENT_SELECTOR):
requested_lines.append(idx + 1)
tree = build_module(code, module_name=module_name)
extracted = []
if requested_lines:
for line in requested_lines:
extracted.append(_find_statement_by_line(tree, line))
# Modifies the tree.
extracted.extend(_extract_expressions(tree))
if not extracted:
extracted.append(tree.body[-1])
extracted = [_extract(node) for node in extracted]
if len(extracted) == 1:
return extracted[0]
else:
return extracted
def build_module(code, module_name='', path=None):
"""Parses a string module with a builder.
:param code: The code for the module.
:type code: str
:param module_name: The name for the module
:type module_name: str
:param path: The path for the module
:type module_name: str
:returns: The module AST.
:rtype: astroid.bases.NodeNG
"""
code = textwrap.dedent(code)
return builder.AstroidBuilder(None).string_build(code, modname=module_name, path=path)
def require_version(minver=None, maxver=None):
""" Compare version of python interpreter to the given one. Skip the test
if older.
"""
def parse(string, default=None):
string = string or default
try:
return tuple(int(v) for v in string.split('.'))
except ValueError:
raise ValueError('%s is not a correct version : should be X.Y[.Z].' % version)
def check_require_version(f):
current = sys.version_info[:3]
if parse(minver, "0") < current <= parse(maxver, "4"):
return f
else:
str_version = '.'.join(str(v) for v in sys.version_info)
@functools.wraps(f)
def new_f(self, *args, **kwargs):
if minver is not None:
self.skipTest('Needs Python > %s. Current version is %s.' % (minver, str_version))
elif maxver is not None:
self.skipTest('Needs Python <= %s. Current version is %s.' % (maxver, str_version))
return new_f
return check_require_version
def get_name_node(start_from, name, index=0):
return [n for n in start_from.nodes_of_class(nodes.Name) if n.name == name][index]
URL: http://www.pylint.org/ URL: http://www.pylint.org/
Version: 1.4.0 Version: 1.4.1
License: GPL License: GPL
License File: LICENSE.txt License File: LICENSE.txt
......
...@@ -19,10 +19,10 @@ from __future__ import absolute_import ...@@ -19,10 +19,10 @@ from __future__ import absolute_import
modname = distname = 'pylint' modname = distname = 'pylint'
numversion = (1, 4, 0) numversion = (1, 4, 1)
version = '.'.join([str(num) for num in numversion]) version = '.'.join([str(num) for num in numversion])
install_requires = ['logilab-common >= 0.53.0', 'astroid >= 1.3.2', 'six'] install_requires = ['logilab-common >= 0.53.0', 'astroid >= 1.3.3', 'six']
license = 'GPL' license = 'GPL'
description = "python code static checker" description = "python code static checker"
......
...@@ -45,6 +45,7 @@ from pylint.checkers.utils import ( ...@@ -45,6 +45,7 @@ from pylint.checkers.utils import (
has_known_bases, has_known_bases,
NoSuchArgumentError, NoSuchArgumentError,
is_import_error, is_import_error,
unimplemented_abstract_methods,
) )
...@@ -148,8 +149,7 @@ if sys.version_info < (3, 0): ...@@ -148,8 +149,7 @@ if sys.version_info < (3, 0):
PROPERTY_CLASSES = set(('__builtin__.property', 'abc.abstractproperty')) PROPERTY_CLASSES = set(('__builtin__.property', 'abc.abstractproperty'))
else: else:
PROPERTY_CLASSES = set(('builtins.property', 'abc.abstractproperty')) PROPERTY_CLASSES = set(('builtins.property', 'abc.abstractproperty'))
ABC_METHODS = set(('abc.abstractproperty', 'abc.abstractmethod',
'abc.abstractclassmethod', 'abc.abstractstaticmethod'))
def _determine_function_name_type(node): def _determine_function_name_type(node):
"""Determine the name type whose regex the a function's name should match. """Determine the name type whose regex the a function's name should match.
...@@ -179,26 +179,17 @@ def _determine_function_name_type(node): ...@@ -179,26 +179,17 @@ def _determine_function_name_type(node):
return 'attr' return 'attr'
return 'method' return 'method'
def decorated_with_abc(func):
""" Determine if the `func` node is decorated
with `abc` decorators (abstractmethod et co.)
"""
if func.decorators:
for node in func.decorators.nodes:
try:
infered = next(node.infer())
except InferenceError:
continue
if infered and infered.qname() in ABC_METHODS:
return True
def has_abstract_methods(node):
def _has_abstract_methods(node):
""" """
Determine if the given `node` has Determine if the given `node` has abstract methods.
abstract methods, defined with `abc` module.
The methods should be made abstract by decorating them
with `abc` decorators.
""" """
return any(decorated_with_abc(meth) return len(unimplemented_abstract_methods(node)) > 0
for meth in node.methods())
def report_by_type_stats(sect, stats, old_stats): def report_by_type_stats(sect, stats, old_stats):
"""make a report of """make a report of
...@@ -298,7 +289,7 @@ class BasicErrorChecker(_BasicChecker): ...@@ -298,7 +289,7 @@ class BasicErrorChecker(_BasicChecker):
'duplicate-argument-name', 'duplicate-argument-name',
'Duplicate argument names in function definitions are syntax' 'Duplicate argument names in function definitions are syntax'
' errors.'), ' errors.'),
'E0110': ('Abstract class with abstract methods instantiated', 'E0110': ('Abstract class %r with abstract methods instantiated',
'abstract-class-instantiated', 'abstract-class-instantiated',
'Used when an abstract class with `abc.ABCMeta` as metaclass ' 'Used when an abstract class with `abc.ABCMeta` as metaclass '
'has abstract methods and is instantiated.'), 'has abstract methods and is instantiated.'),
...@@ -398,17 +389,21 @@ class BasicErrorChecker(_BasicChecker): ...@@ -398,17 +389,21 @@ class BasicErrorChecker(_BasicChecker):
return return
# __init__ was called # __init__ was called
metaclass = infered.metaclass() metaclass = infered.metaclass()
abstract_methods = has_abstract_methods(infered) abstract_methods = _has_abstract_methods(infered)
if metaclass is None: if metaclass is None:
# Python 3.4 has `abc.ABC`, which won't be detected # Python 3.4 has `abc.ABC`, which won't be detected
# by ClassNode.metaclass() # by ClassNode.metaclass()
for ancestor in infered.ancestors(): for ancestor in infered.ancestors():
if ancestor.qname() == 'abc.ABC' and abstract_methods: if ancestor.qname() == 'abc.ABC' and abstract_methods:
self.add_message('abstract-class-instantiated', node=node) self.add_message('abstract-class-instantiated',
args=(infered.name, ),
node=node)
break break
return return
if metaclass.qname() == 'abc.ABCMeta' and abstract_methods: if metaclass.qname() == 'abc.ABCMeta' and abstract_methods:
self.add_message('abstract-class-instantiated', node=node) self.add_message('abstract-class-instantiated',
args=(infered.name, ),
node=node)
def _check_else_on_loop(self, node): def _check_else_on_loop(self, node):
"""Check that any loop with an else clause has a break statement.""" """Check that any loop with an else clause has a break statement."""
...@@ -676,7 +671,13 @@ functions, methods ...@@ -676,7 +671,13 @@ functions, methods
variable names, max locals variable names, max locals
""" """
self.stats[node.is_method() and 'method' or 'function'] += 1 self.stats[node.is_method() and 'method' or 'function'] += 1
self._check_dangerous_default(node)
def _check_dangerous_default(self, node):
# check for dangerous default values as arguments # check for dangerous default values as arguments
is_iterable = lambda n: isinstance(n, (astroid.List,
astroid.Set,
astroid.Dict))
for default in node.args.defaults: for default in node.args.defaults:
try: try:
value = next(default.infer()) value = next(default.infer())
...@@ -685,21 +686,30 @@ functions, methods ...@@ -685,21 +686,30 @@ functions, methods
if (isinstance(value, astroid.Instance) and if (isinstance(value, astroid.Instance) and
value.qname() in DEFAULT_ARGUMENT_SYMBOLS): value.qname() in DEFAULT_ARGUMENT_SYMBOLS):
if value is default: if value is default:
msg = DEFAULT_ARGUMENT_SYMBOLS[value.qname()] msg = DEFAULT_ARGUMENT_SYMBOLS[value.qname()]
elif type(value) is astroid.Instance: elif type(value) is astroid.Instance or is_iterable(value):
if isinstance(default, astroid.CallFunc): # We are here in the following situation(s):
# this argument is direct call to list() or dict() etc # * a dict/set/list/tuple call which wasn't inferred
# to a syntax node ({}, () etc.). This can happen
# when the arguments are invalid or unknown to
# the inference.
# * a variable from somewhere else, which turns out to be a list
# or a dict.
if is_iterable(default):
msg = value.pytype()
elif isinstance(default, astroid.CallFunc):
msg = '%s() (%s)' % (value.name, value.qname()) msg = '%s() (%s)' % (value.name, value.qname())
else: else:
# this argument is a variable from somewhere else which turns
# out to be a list or dict
msg = '%s (%s)' % (default.as_string(), value.qname()) msg = '%s (%s)' % (default.as_string(), value.qname())
else: else:
# this argument is a name # this argument is a name
msg = '%s (%s)' % (default.as_string(), msg = '%s (%s)' % (default.as_string(),
DEFAULT_ARGUMENT_SYMBOLS[value.qname()]) DEFAULT_ARGUMENT_SYMBOLS[value.qname()])
self.add_message('dangerous-default-value', node=node, args=(msg,)) self.add_message('dangerous-default-value',
node=node,
args=(msg, ))
@check_messages('unreachable', 'lost-exception') @check_messages('unreachable', 'lost-exception')
def visit_return(self, node): def visit_return(self, node):
......
...@@ -30,7 +30,7 @@ from pylint.checkers import BaseChecker ...@@ -30,7 +30,7 @@ from pylint.checkers import BaseChecker
from pylint.checkers.utils import ( from pylint.checkers.utils import (
PYMETHODS, overrides_a_method, check_messages, is_attr_private, PYMETHODS, overrides_a_method, check_messages, is_attr_private,
is_attr_protected, node_frame_class, safe_infer, is_builtin_object, is_attr_protected, node_frame_class, safe_infer, is_builtin_object,
decorated_with_property) decorated_with_property, unimplemented_abstract_methods)
import six import six
if sys.version_info >= (3, 0): if sys.version_info >= (3, 0):
...@@ -179,11 +179,11 @@ MSGS = { ...@@ -179,11 +179,11 @@ MSGS = {
'missing-interface-method', 'missing-interface-method',
'Used when a method declared in an interface is missing from a \ 'Used when a method declared in an interface is missing from a \
class implementing this interface'), class implementing this interface'),
'W0221': ('Arguments number differs from %s method', 'W0221': ('Arguments number differs from %s %r method',
'arguments-differ', 'arguments-differ',
'Used when a method has a different number of arguments than in \ 'Used when a method has a different number of arguments than in \
the implemented interface or in an overridden method.'), the implemented interface or in an overridden method.'),
'W0222': ('Signature differs from %s method', 'W0222': ('Signature differs from %s %r method',
'signature-differs', 'signature-differs',
'Used when a method signature is different than in the \ 'Used when a method signature is different than in the \
implemented interface or in an overridden method.'), implemented interface or in an overridden method.'),
...@@ -496,7 +496,7 @@ a metaclass class method.'} ...@@ -496,7 +496,7 @@ a metaclass class method.'}
if infered is YES: if infered is YES:
continue continue
if (not isinstance(infered, astroid.Const) or if (not isinstance(infered, astroid.Const) or
not isinstance(infered.value, str)): not isinstance(infered.value, six.string_types)):
self.add_message('invalid-slots-object', self.add_message('invalid-slots-object',
args=infered.as_string(), args=infered.as_string(),
node=elt) node=elt)
...@@ -585,6 +585,8 @@ a metaclass class method.'} ...@@ -585,6 +585,8 @@ a metaclass class method.'}
return return
slots = klass.slots() slots = klass.slots()
if slots is None:
return
# If any ancestor doesn't use slots, the slots # If any ancestor doesn't use slots, the slots
# defined for this class are superfluous. # defined for this class are superfluous.
if any('__slots__' not in ancestor.locals and if any('__slots__' not in ancestor.locals and
...@@ -798,21 +800,28 @@ a metaclass class method.'} ...@@ -798,21 +800,28 @@ a metaclass class method.'}
"""check that the given class node implements abstract methods from """check that the given class node implements abstract methods from
base classes base classes
""" """
def is_abstract(method):
return method.is_abstract(pass_is_abstract=False)
# check if this class abstract # check if this class abstract
if class_is_abstract(node): if class_is_abstract(node):
return return
for method in node.methods():
methods = sorted(
unimplemented_abstract_methods(node, is_abstract).items(),
key=lambda item: item[0],
)
for name, method in methods:
owner = method.parent.frame() owner = method.parent.frame()
if owner is node: if owner is node:
continue continue
# owner is not this class, it must be a parent class # owner is not this class, it must be a parent class
# check that the ancestor's method is not abstract # check that the ancestor's method is not abstract
if method.name in node.locals: if name in node.locals:
# it is redefined as an attribute or with a descriptor # it is redefined as an attribute or with a descriptor
continue continue
if method.is_abstract(pass_is_abstract=False):
self.add_message('abstract-method', node=node, self.add_message('abstract-method', node=node,
args=(method.name, owner.name)) args=(name, owner.name))
def _check_interfaces(self, node): def _check_interfaces(self, node):
"""check that the given class node really implements declared """check that the given class node really implements declared
...@@ -930,9 +939,13 @@ a metaclass class method.'} ...@@ -930,9 +939,13 @@ a metaclass class method.'}
if is_attr_private(method1.name): if is_attr_private(method1.name):
return return
if len(method1.args.args) != len(refmethod.args.args): if len(method1.args.args) != len(refmethod.args.args):
self.add_message('arguments-differ', args=class_type, node=method1) self.add_message('arguments-differ',
args=(class_type, method1.name),
node=method1)
elif len(method1.args.defaults) < len(refmethod.args.defaults): elif len(method1.args.defaults) < len(refmethod.args.defaults):
self.add_message('signature-differs', args=class_type, node=method1) self.add_message('signature-differs',
args=(class_type, method1.name),
node=method1)
def is_first_attr(self, node): def is_first_attr(self, node):
"""Check that attribute lookup name use first attribute variable name """Check that attribute lookup name use first attribute variable name
......
This diff is collapsed.
...@@ -82,8 +82,6 @@ class EncodingChecker(BaseChecker): ...@@ -82,8 +82,6 @@ class EncodingChecker(BaseChecker):
"""inspect the source file to find encoding problem or fixmes like """inspect the source file to find encoding problem or fixmes like
notes notes
""" """
stream = module.file_stream
stream.seek(0) # XXX may be removed with astroid > 0.23
if self.config.notes: if self.config.notes:
notes = re.compile( notes = re.compile(
r'.*?#\s*(%s)(:*\s*.+)' % "|".join(self.config.notes)) r'.*?#\s*(%s)(:*\s*.+)' % "|".join(self.config.notes))
...@@ -94,6 +92,7 @@ class EncodingChecker(BaseChecker): ...@@ -94,6 +92,7 @@ class EncodingChecker(BaseChecker):
else: else:
encoding = 'ascii' encoding = 'ascii'
with module.stream() as stream:
for lineno, line in enumerate(stream): for lineno, line in enumerate(stream):
line = self._check_encoding(lineno + 1, line, encoding) line = self._check_encoding(lineno + 1, line, encoding)
if line is not None and notes: if line is not None and notes:
......
...@@ -252,9 +252,19 @@ class Python3Checker(checkers.BaseChecker): ...@@ -252,9 +252,19 @@ class Python3Checker(checkers.BaseChecker):
'map is a generator and must be evaluated. ' 'map is a generator and must be evaluated. '
'Prefer a for-loop as alternative.', 'Prefer a for-loop as alternative.',
{'maxversion': (3, 0)}), {'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)}),
} }
_missing_builtins = frozenset([ _bad_builtins = frozenset([
'apply', 'apply',
'basestring', 'basestring',
'buffer', 'buffer',
...@@ -262,9 +272,11 @@ class Python3Checker(checkers.BaseChecker): ...@@ -262,9 +272,11 @@ class Python3Checker(checkers.BaseChecker):
'coerce', 'coerce',
'execfile', 'execfile',
'file', 'file',
'input', # Not missing, but incompatible semantics
'long', 'long',
'raw_input', 'raw_input',
'reduce', 'reduce',
'round', # Not missing, but incompatible semantics
'StandardError', 'StandardError',
'unicode', 'unicode',
'xrange', 'xrange',
...@@ -310,10 +322,10 @@ class Python3Checker(checkers.BaseChecker): ...@@ -310,10 +322,10 @@ class Python3Checker(checkers.BaseChecker):
self.add_message('implicit-map-evaluation', node=node) self.add_message('implicit-map-evaluation', node=node)
def visit_name(self, node): def visit_name(self, node):
"""Detect when a built-in that is missing in Python 3 is referenced.""" """Detect when a "bad" built-in is referenced."""
found_node = node.lookup(node.name)[0] found_node = node.lookup(node.name)[0]
if getattr(found_node, 'name', None) == '__builtin__': if getattr(found_node, 'name', None) == '__builtin__':
if node.name in self._missing_builtins: if node.name in self._bad_builtins:
message = node.name.lower() + '-builtin' message = node.name.lower() + '-builtin'
self.add_message(message, node=node) self.add_message(message, node=node)
......
...@@ -42,7 +42,6 @@ class Similar(object): ...@@ -42,7 +42,6 @@ class Similar(object):
def append_stream(self, streamid, stream, encoding=None): def append_stream(self, streamid, stream, encoding=None):
"""append a file to search for similarities""" """append a file to search for similarities"""
stream.seek(0) # XXX may be removed with astroid > 0.23
if encoding is None: if encoding is None:
readlines = stream.readlines readlines = stream.readlines
else: else:
...@@ -300,7 +299,10 @@ class SimilarChecker(BaseChecker, Similar): ...@@ -300,7 +299,10 @@ class SimilarChecker(BaseChecker, Similar):
stream must implement the readlines method stream must implement the readlines method
""" """
self.append_stream(self.linter.current_name, node.file_stream, node.file_encoding) with node.stream() as stream:
self.append_stream(self.linter.current_name,
stream,
node.file_encoding)
def close(self): def close(self):
"""compute and display similarities on closing (i.e. end of parsing)""" """compute and display similarities on closing (i.e. end of parsing)"""
...@@ -361,7 +363,8 @@ def Run(argv=None): ...@@ -361,7 +363,8 @@ def Run(argv=None):
usage(1) usage(1)
sim = Similar(min_lines, ignore_comments, ignore_docstrings, ignore_imports) sim = Similar(min_lines, ignore_comments, ignore_docstrings, ignore_imports)
for filename in args: for filename in args:
sim.append_stream(filename, open(filename)) with open(filename) as stream:
sim.append_stream(filename, stream)
sim.run() sim.run()
sys.exit(0) sys.exit(0)
......
...@@ -97,7 +97,7 @@ class SpellingChecker(BaseTokenChecker): ...@@ -97,7 +97,7 @@ class SpellingChecker(BaseTokenChecker):
if not dict_name: if not dict_name:
return return
self.ignore_list = self.config.spelling_ignore_words.split(",") self.ignore_list = [w.strip() for w in self.config.spelling_ignore_words.split(",")]
# "param" appears in docstring in param description and # "param" appears in docstring in param description and
# "pylint" appears in comments in pylint pragmas. # "pylint" appears in comments in pylint pragmas.
self.ignore_list.extend(["param", "pylint"]) self.ignore_list.extend(["param", "pylint"])
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
"""Checkers for various standard library functions.""" """Checkers for various standard library functions."""
import re import re
import six
import sys import sys
import astroid import astroid
...@@ -25,13 +26,56 @@ from pylint.interfaces import IAstroidChecker ...@@ -25,13 +26,56 @@ from pylint.interfaces import IAstroidChecker
from pylint.checkers import BaseChecker from pylint.checkers import BaseChecker
from pylint.checkers import utils from pylint.checkers import utils
_VALID_OPEN_MODE_REGEX = re.compile(r'^(r?U|[rwa]\+?b?)$')
if sys.version_info >= (3, 0): if sys.version_info >= (3, 0):
OPEN_MODULE = '_io' OPEN_MODULE = '_io'
else: else:
OPEN_MODULE = '__builtin__' OPEN_MODULE = '__builtin__'
def _check_mode_str(mode):
# check type
if not isinstance(mode, six.string_types):
return False
# check syntax
modes = set(mode)
_mode = "rwatb+U"
creating = False
if six.PY3:
_mode += "x"
creating = "x" in modes
if modes - set(_mode) or len(mode) > len(modes):
return False
# check logic
reading = "r" in modes
writing = "w" in modes
appending = "a" in modes
updating = "+" in modes
text = "t" in modes
binary = "b" in modes
if "U" in modes:
if writing or appending or creating and six.PY3:
return False
reading = True
if not six.PY3:
binary = True
if text and binary:
return False
total = reading + writing + appending + (creating if six.PY3 else 0)
if total > 1:
return False
if not (reading or writing or appending or creating and six.PY3):
return False
# other 2.x constraints
if not six.PY3:
if "U" in mode:
mode = mode.replace("U", "")
if "r" not in mode:
mode = "r" + mode
return mode[0] in ("r", "w", "a", "U")
return True
class StdlibChecker(BaseChecker): class StdlibChecker(BaseChecker):
__implements__ = (IAstroidChecker,) __implements__ = (IAstroidChecker,)
name = 'stdlib' name = 'stdlib'
...@@ -39,7 +83,8 @@ class StdlibChecker(BaseChecker): ...@@ -39,7 +83,8 @@ class StdlibChecker(BaseChecker):
msgs = { msgs = {
'W1501': ('"%s" is not a valid mode for open.', 'W1501': ('"%s" is not a valid mode for open.',
'bad-open-mode', 'bad-open-mode',
'Python supports: r, w, a modes with b, +, and U options. ' 'Python supports: r, w, a[, x] modes with b, +, '
'and U (only with r) options. '
'See http://docs.python.org/2/library/functions.html#open'), 'See http://docs.python.org/2/library/functions.html#open'),
'W1502': ('Using datetime.time in a boolean context.', 'W1502': ('Using datetime.time in a boolean context.',
'boolean-datetime', 'boolean-datetime',
...@@ -48,16 +93,26 @@ class StdlibChecker(BaseChecker): ...@@ -48,16 +93,26 @@ class StdlibChecker(BaseChecker):
'midnight UTC. This behaviour was fixed in Python 3.5. ' 'midnight UTC. This behaviour was fixed in Python 3.5. '
'See http://bugs.python.org/issue13936 for reference.', 'See http://bugs.python.org/issue13936 for reference.',
{'maxversion': (3, 5)}), {'maxversion': (3, 5)}),
'W1503': ('Redundant use of %s with constant '
'value %r',
'redundant-unittest-assert',
'The first argument of assertTrue and assertFalse is'
'a condition. If a constant is passed as parameter, that'
'condition will be always true. In this case a warning '
'should be emitted.')
} }
@utils.check_messages('bad-open-mode') @utils.check_messages('bad-open-mode', 'redundant-unittest-assert')
def visit_callfunc(self, node): def visit_callfunc(self, node):
"""Visit a CallFunc node.""" """Visit a CallFunc node."""
if hasattr(node, 'func'): if hasattr(node, 'func'):
infer = utils.safe_infer(node.func) infer = utils.safe_infer(node.func)
if infer and infer.root().name == OPEN_MODULE: if infer:
if infer.root().name == OPEN_MODULE:
if getattr(node.func, 'name', None) in ('open', 'file'): if getattr(node.func, 'name', None) in ('open', 'file'):
self._check_open_mode(node) self._check_open_mode(node)
if infer.root().name == 'unittest.case':
self._check_redundant_assert(node, infer)
@utils.check_messages('boolean-datetime') @utils.check_messages('boolean-datetime')
def visit_unaryop(self, node): def visit_unaryop(self, node):
...@@ -77,6 +132,14 @@ class StdlibChecker(BaseChecker): ...@@ -77,6 +132,14 @@ class StdlibChecker(BaseChecker):
for value in node.values: for value in node.values:
self._check_datetime(value) self._check_datetime(value)
def _check_redundant_assert(self, node, infer):
if (isinstance(infer, astroid.BoundMethod) and
node.args and isinstance(node.args[0], astroid.Const) and
infer.name in ['assertTrue', 'assertFalse']):
self.add_message('redundant-unittest-assert',
args=(infer.name, node.args[0].value, ),
node=node)
def _check_datetime(self, node): def _check_datetime(self, node):
""" Check that a datetime was infered. """ Check that a datetime was infered.
If so, emit boolean-datetime warning. If so, emit boolean-datetime warning.
...@@ -89,20 +152,22 @@ class StdlibChecker(BaseChecker): ...@@ -89,20 +152,22 @@ class StdlibChecker(BaseChecker):
infered.qname() == 'datetime.time'): infered.qname() == 'datetime.time'):
self.add_message('boolean-datetime', node=node) self.add_message('boolean-datetime', node=node)
def _check_open_mode(self, node): def _check_open_mode(self, node):
"""Check that the mode argument of an open or file call is valid.""" """Check that the mode argument of an open or file call is valid."""
try: try:
mode_arg = utils.get_argument_from_call(node, position=1, keyword='mode') mode_arg = utils.get_argument_from_call(node, position=1,
keyword='mode')
except utils.NoSuchArgumentError:
return
if mode_arg: if mode_arg:
mode_arg = utils.safe_infer(mode_arg) mode_arg = utils.safe_infer(mode_arg)
if (isinstance(mode_arg, astroid.Const) if (isinstance(mode_arg, astroid.Const)
and not _VALID_OPEN_MODE_REGEX.match(mode_arg.value)): and not _check_mode_str(mode_arg.value)):
self.add_message('bad-open-mode', node=node, self.add_message('bad-open-mode', node=node,
args=(mode_arg.value)) args=mode_arg.value)
except (utils.NoSuchArgumentError, TypeError):
pass
def register(linter): def register(linter):
"""required method to auto register this checker """ """required method to auto register this checker """
linter.register_checker(StdlibChecker(linter)) linter.register_checker(StdlibChecker(linter))
...@@ -344,8 +344,13 @@ accessed. Python regular expressions are accepted.'} ...@@ -344,8 +344,13 @@ accessed. Python regular expressions are accepted.'}
if not isinstance(attr, astroid.Function): if not isinstance(attr, astroid.Function):
continue continue
# Decorated, see if it is decorated with a property # Decorated, see if it is decorated with a property.
# Also, check the returns and see if they are callable.
if decorated_with_property(attr): if decorated_with_property(attr):
if all(return_node.callable()
for return_node in attr.infer_call_result(node)):
continue
else:
self.add_message('not-callable', node=node, self.add_message('not-callable', node=node,
args=node.func.as_string()) args=node.func.as_string())
break break
......
...@@ -34,6 +34,8 @@ if not PY3K: ...@@ -34,6 +34,8 @@ if not PY3K:
EXCEPTIONS_MODULE = "exceptions" EXCEPTIONS_MODULE = "exceptions"
else: else:
EXCEPTIONS_MODULE = "builtins" EXCEPTIONS_MODULE = "builtins"
ABC_METHODS = set(('abc.abstractproperty', 'abc.abstractmethod',
'abc.abstractclassmethod', 'abc.abstractstaticmethod'))
class NoSuchArgumentError(Exception): class NoSuchArgumentError(Exception):
...@@ -499,3 +501,64 @@ def decorated_with_property(node): ...@@ -499,3 +501,64 @@ def decorated_with_property(node):
return True return True
except astroid.InferenceError: except astroid.InferenceError:
pass pass
def decorated_with_abc(func):
"""Determine if the `func` node is decorated with `abc` decorators."""
if func.decorators:
for node in func.decorators.nodes:
try:
infered = next(node.infer())
except astroid.InferenceError:
continue
if infered and infered.qname() in ABC_METHODS:
return True
def unimplemented_abstract_methods(node, is_abstract_cb=decorated_with_abc):
"""
Get the unimplemented abstract methods for the given *node*.
A method can be considered abstract if the callback *is_abstract_cb*
returns a ``True`` value. The check defaults to verifying that
a method is decorated with abstract methods.
The function will work only for new-style classes. For old-style
classes, it will simply return an empty dictionary.
For the rest of them, it will return a dictionary of abstract method
names and their inferred objects.
"""
visited = {}
try:
mro = reversed(node.mro())
except NotImplementedError:
# Old style class, it will not have a mro.
return {}
except astroid.ResolveError:
# Probably inconsistent hierarchy, don'try
# to figure this out here.
return {}
for ancestor in mro:
for obj in ancestor.values():
infered = obj
if isinstance(obj, astroid.AssName):
infered = safe_infer(obj)
if not infered:
continue
if not isinstance(infered, astroid.Function):
if obj.name in visited:
del visited[obj.name]
if isinstance(infered, astroid.Function):
# It's critical to use the original name,
# since after inferring, an object can be something
# else than expected, as in the case of the
# following assignment.
#
# class A:
# def keys(self): pass
# __iter__ = keys
abstract = is_abstract_cb(infered)
if abstract:
visited[obj.name] = infered
elif not abstract and obj.name in visited:
del visited[obj.name]
return visited
...@@ -25,7 +25,7 @@ import six ...@@ -25,7 +25,7 @@ import six
from six.moves.tkinter import ( from six.moves.tkinter import (
Tk, Frame, Listbox, Entry, Label, Button, Scrollbar, Tk, Frame, Listbox, Entry, Label, Button, Scrollbar,
Checkbutton, Radiobutton, IntVar, StringVar, Checkbutton, Radiobutton, IntVar, StringVar, PanedWindow,
TOP, LEFT, RIGHT, BOTTOM, END, X, Y, BOTH, SUNKEN, W, TOP, LEFT, RIGHT, BOTTOM, END, X, Y, BOTH, SUNKEN, W,
HORIZONTAL, DISABLED, NORMAL, W, HORIZONTAL, DISABLED, NORMAL, W,
) )
...@@ -150,23 +150,34 @@ class LintGui(object): ...@@ -150,23 +150,34 @@ class LintGui(object):
def init_gui(self): def init_gui(self):
"""init helper""" """init helper"""
window = PanedWindow(self.root, orient="vertical")
window.pack(side=TOP, fill=BOTH, expand=True)
top_pane = Frame(window)
window.add(top_pane)
mid_pane = Frame(window)
window.add(mid_pane)
bottom_pane = Frame(window)
window.add(bottom_pane)
#setting up frames #setting up frames
top_frame = Frame(self.root) top_frame = Frame(top_pane)
mid_frame = Frame(self.root) mid_frame = Frame(top_pane)
radio_frame = Frame(self.root) history_frame = Frame(top_pane)
res_frame = Frame(self.root) radio_frame = Frame(mid_pane)
msg_frame = Frame(self.root) rating_frame = Frame(mid_pane)
check_frame = Frame(self.root) res_frame = Frame(mid_pane)
history_frame = Frame(self.root) check_frame = Frame(bottom_pane)
btn_frame = Frame(self.root) msg_frame = Frame(bottom_pane)
rating_frame = Frame(self.root) btn_frame = Frame(bottom_pane)
top_frame.pack(side=TOP, fill=X) top_frame.pack(side=TOP, fill=X)
mid_frame.pack(side=TOP, fill=X) mid_frame.pack(side=TOP, fill=X)
history_frame.pack(side=TOP, fill=BOTH, expand=True) history_frame.pack(side=TOP, fill=BOTH, expand=True)
radio_frame.pack(side=TOP, fill=BOTH, expand=True) radio_frame.pack(side=TOP, fill=X)
rating_frame.pack(side=TOP, fill=BOTH, expand=True) rating_frame.pack(side=TOP, fill=X)
res_frame.pack(side=TOP, fill=BOTH, expand=True) res_frame.pack(side=TOP, fill=BOTH, expand=True)
check_frame.pack(side=TOP, fill=BOTH, expand=True) check_frame.pack(side=TOP, fill=X)
msg_frame.pack(side=TOP, fill=BOTH, expand=True) msg_frame.pack(side=TOP, fill=BOTH, expand=True)
btn_frame.pack(side=TOP, fill=X) btn_frame.pack(side=TOP, fill=X)
......
...@@ -46,7 +46,7 @@ class IRawChecker(IChecker): ...@@ -46,7 +46,7 @@ class IRawChecker(IChecker):
def process_module(self, astroid): def process_module(self, astroid):
""" process a module """ process a module
the module's content is accessible via astroid.file_stream the module's content is accessible via astroid.stream
""" """
......
This diff is collapsed.
# Copyright (c) 2003-2014 LOGILAB S.A. (Paris, FRANCE).
# 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.
"""JSON reporter"""
from __future__ import absolute_import, print_function
import json
import sys
from cgi import escape
from pylint.interfaces import IReporter
from pylint.reporters import BaseReporter
class JSONReporter(BaseReporter):
"""Report messages and layouts in JSON."""
__implements__ = IReporter
name = 'json'
extension = 'json'
def __init__(self, output=sys.stdout):
BaseReporter.__init__(self, output)
self.messages = []
def handle_message(self, message):
"""Manage message of different type and in the context of path."""
self.messages.append({
'type': message.category,
'module': message.module,
'obj': message.obj,
'line': message.line,
'column': message.column,
'path': message.path,
'symbol': message.symbol,
'message': escape(message.msg or ''),
})
def _display(self, layout):
"""Launch layouts display"""
if self.messages:
print(json.dumps(self.messages, indent=4), file=self.out)
def register(linter):
"""Register the reporter classes with the linter."""
linter.register_reporter(JSONReporter)
...@@ -77,7 +77,8 @@ class ParseableTextReporter(TextReporter): ...@@ -77,7 +77,8 @@ class ParseableTextReporter(TextReporter):
def __init__(self, output=None): def __init__(self, output=None):
warnings.warn('%s output format is deprecated. This is equivalent ' warnings.warn('%s output format is deprecated. This is equivalent '
'to --msg-template=%s' % (self.name, self.line_format)) 'to --msg-template=%s' % (self.name, self.line_format),
DeprecationWarning)
TextReporter.__init__(self, output) TextReporter.__init__(self, output)
......
...@@ -128,14 +128,17 @@ def category_id(cid): ...@@ -128,14 +128,17 @@ def category_id(cid):
return MSG_TYPES_LONG.get(cid) return MSG_TYPES_LONG.get(cid)
def _decoding_readline(stream, module):
return lambda: stream.readline().decode(module.file_encoding,
'replace')
def tokenize_module(module): def tokenize_module(module):
stream = module.file_stream with module.stream() as stream:
stream.seek(0)
readline = stream.readline readline = stream.readline
if sys.version_info < (3, 0): if sys.version_info < (3, 0):
if module.file_encoding is not None: if module.file_encoding is not None:
readline = lambda: stream.readline().decode(module.file_encoding, readline = _decoding_readline(stream, module)
'replace')
return list(tokenize.generate_tokens(readline)) return list(tokenize.generate_tokens(readline))
return list(tokenize.tokenize(readline)) return list(tokenize.tokenize(readline))
...@@ -267,7 +270,7 @@ class MessagesHandlerMixIn(object): ...@@ -267,7 +270,7 @@ class MessagesHandlerMixIn(object):
msgs = self._msgs_state msgs = self._msgs_state
msgs[msg.msgid] = False msgs[msg.msgid] = False
# sync configuration object # sync configuration object
self.config.disable_msg = [mid for mid, val in six.iteritems(msgs) self.config.disable = [mid for mid, val in six.iteritems(msgs)
if not val] if not val]
def enable(self, msgid, scope='package', line=None, ignore_unknown=False): def enable(self, msgid, scope='package', line=None, ignore_unknown=False):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment