Commit 49083eaa authored by Mike Frysinger's avatar Mike Frysinger Committed by Commit Bot

pylint: update to 1.4.5

Grab a few random fixes like deleting old abstract-class-little-used,
abstract-class-not-used, and star-args.

Bug: None
Test: `./pylint *.py` shows no regressions
Change-Id: I494379e18da002d479cb193412fcc49f8c66f376
Reviewed-on: https://chromium-review.googlesource.com/1103763Reviewed-by: 's avatarRobbie Iannucci <iannucci@chromium.org>
Commit-Queue: Mike Frysinger <vapier@chromium.org>
parent 1cabdc46
URL: http://www.logilab.org/project/logilab-astng
Version: 1.3.4
URL: https://github.com/PyCQA/astroid
Version: 1.3.8
License: GPL
License File: LICENSE.txt
Description:
This directory contains the logilab-astng module, required for pylint.
This directory contains the astroid module, required for pylint.
Local Modifications:
None
......@@ -20,10 +20,10 @@ distname = 'astroid'
modname = 'astroid'
numversion = (1, 3, 4)
numversion = (1, 3, 8)
version = '.'.join([str(num) for num in numversion])
install_requires = ['logilab-common >= 0.60.0', 'six']
install_requires = ['logilab-common>=0.63.0', 'six']
license = 'LGPL'
......
# 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/>.
"""Small AST optimizations."""
import _ast
from astroid import nodes
__all__ = ('ASTPeepholeOptimizer', )
try:
_TYPES = (_ast.Str, _ast.Bytes)
except AttributeError:
_TYPES = (_ast.Str, )
class ASTPeepholeOptimizer(object):
"""Class for applying small optimizations to generate new AST."""
def optimize_binop(self, node):
"""Optimize BinOps with string Const nodes on the lhs.
This fixes an infinite recursion crash, where multiple
strings are joined using the addition operator. With a
sufficient number of such strings, astroid will fail
with a maximum recursion limit exceeded. The
function will return a Const node with all the strings
already joined.
Return ``None`` if no AST node can be obtained
through optimization.
"""
ast_nodes = []
current = node
while isinstance(current, _ast.BinOp):
# lhs must be a BinOp with the addition operand.
if not isinstance(current.left, _ast.BinOp):
return
if (not isinstance(current.left.op, _ast.Add)
or not isinstance(current.op, _ast.Add)):
return
# rhs must a str / bytes.
if not isinstance(current.right, _TYPES):
return
ast_nodes.append(current.right.s)
current = current.left
if (isinstance(current, _ast.BinOp)
and isinstance(current.left, _TYPES)
and isinstance(current.right, _TYPES)):
# Stop early if we are at the last BinOp in
# the operation
ast_nodes.append(current.right.s)
ast_nodes.append(current.left.s)
break
if not ast_nodes:
return
# If we have inconsistent types, bail out.
known = type(ast_nodes[0])
if any(type(element) is not known
for element in ast_nodes[1:]):
return
value = known().join(reversed(ast_nodes))
newnode = nodes.Const(value)
return newnode
......@@ -58,52 +58,28 @@ class Proxy(object):
# Inference ##################################################################
MISSING = object()
class InferenceContext(object):
__slots__ = ('path', 'callcontext', 'boundnode', 'infered')
def __init__(self,
path=None, callcontext=None, boundnode=None, infered=None):
if path is None:
self.path = frozenset()
else:
self.path = path
self.callcontext = callcontext
self.boundnode = boundnode
if infered is None:
self.infered = {}
else:
self.infered = infered
def push(self, key):
# This returns a NEW context with the same attributes, but a new key
# added to `path`. The intention is that it's only passed to callees
# and then destroyed; otherwise scope() may not work correctly.
# The cache will be shared, since it's the same exact dict.
if key in self.path:
# End the containing generator
raise StopIteration
return InferenceContext(
self.path.union([key]),
self.callcontext,
self.boundnode,
self.infered,
)
@contextmanager
def scope(self, callcontext=MISSING, boundnode=MISSING):
try:
orig = self.callcontext, self.boundnode
if callcontext is not MISSING:
self.callcontext = callcontext
if boundnode is not MISSING:
self.boundnode = boundnode
yield
finally:
self.callcontext, self.boundnode = orig
__slots__ = ('path', 'lookupname', 'callcontext', 'boundnode', 'infered')
def __init__(self, path=None, infered=None):
self.path = path or set()
self.lookupname = None
self.callcontext = None
self.boundnode = None
self.infered = infered or {}
def push(self, node):
name = self.lookupname
if (node, name) in self.path:
raise StopIteration()
self.path.add((node, name))
def clone(self):
# XXX copy lookupname/callcontext ?
clone = InferenceContext(self.path, infered=self.infered)
clone.callcontext = self.callcontext
clone.boundnode = self.boundnode
return clone
def cache_generator(self, key, generator):
results = []
......@@ -114,28 +90,38 @@ class InferenceContext(object):
self.infered[key] = tuple(results)
return
@contextmanager
def restore_path(self):
path = set(self.path)
yield
self.path = path
def _infer_stmts(stmts, context, frame=None, lookupname=None):
def copy_context(context):
if context is not None:
return context.clone()
else:
return InferenceContext()
def _infer_stmts(stmts, context, frame=None):
"""return an iterator on statements inferred by each statement in <stmts>
"""
stmt = None
infered = False
if context is None:
if context is not None:
name = context.lookupname
context = context.clone()
else:
name = None
context = InferenceContext()
for stmt in stmts:
if stmt is YES:
yield stmt
infered = True
continue
kw = {}
infered_name = stmt._infer_name(frame, lookupname)
if infered_name is not None:
# only returns not None if .infer() accepts a lookupname kwarg
kw['lookupname'] = infered_name
context.lookupname = stmt._infer_name(frame, name)
try:
for infered in stmt.infer(context, **kw):
for infered in stmt.infer(context):
yield infered
infered = True
except UnresolvableName:
......@@ -197,12 +183,13 @@ class Instance(Proxy):
context = InferenceContext()
try:
# avoid recursively inferring the same attr on the same class
new_context = context.push((self._proxied, name))
context.push((self._proxied, name))
# XXX frame should be self._proxied, or not ?
get_attr = self.getattr(name, new_context, lookupclass=False)
get_attr = self.getattr(name, context, lookupclass=False)
return _infer_stmts(
self._wrap_attr(get_attr, new_context),
new_context,
self._wrap_attr(get_attr, context),
context,
frame=self,
)
except NotFoundError:
......@@ -210,7 +197,7 @@ class Instance(Proxy):
# fallback to class'igetattr since it has some logic to handle
# descriptors
return self._wrap_attr(self._proxied.igetattr(name, context),
context)
context)
except NotFoundError:
raise InferenceError(name)
......@@ -301,9 +288,9 @@ class BoundMethod(UnboundMethod):
return True
def infer_call_result(self, caller, context):
with context.scope(boundnode=self.bound):
for infered in self._proxied.infer_call_result(caller, context):
yield infered
context = context.clone()
context.boundnode = self.bound
return self._proxied.infer_call_result(caller, context)
class Generator(Instance):
......@@ -335,8 +322,7 @@ def path_wrapper(func):
"""wrapper function handling context"""
if context is None:
context = InferenceContext()
context = context.push((node, kwargs.get('lookupname')))
context.push(node)
yielded = set()
for res in _func(node, context, **kwargs):
# unproxy only true instance, not const, tuple, dict...
......@@ -409,7 +395,8 @@ class NodeNG(object):
if not context:
return self._infer(context, **kwargs)
key = (self, kwargs.get('lookupname'), context.callcontext, context.boundnode)
key = (self, context.lookupname,
context.callcontext, context.boundnode)
if key in context.infered:
return iter(context.infered[key])
......
"""Astroid hooks for pytest."""
from astroid import MANAGER, register_module_extender
from astroid.builder import AstroidBuilder
def pytest_transform():
return AstroidBuilder(MANAGER).string_build('''
try:
import _pytest.mark
import _pytest.recwarn
import _pytest.runner
import _pytest.python
except ImportError:
pass
else:
deprecated_call = _pytest.recwarn.deprecated_call
exit = _pytest.runner.exit
fail = _pytest.runner.fail
fixture = _pytest.python.fixture
importorskip = _pytest.runner.importorskip
mark = _pytest.mark.MarkGenerator()
raises = _pytest.python.raises
skip = _pytest.runner.skip
yield_fixture = _pytest.python.yield_fixture
''')
register_module_extender(MANAGER, 'pytest', pytest_transform)
register_module_extender(MANAGER, 'py.test', pytest_transform)
"""Astroid hooks for pytest."""
from astroid import MANAGER, register_module_extender
from astroid.builder import AstroidBuilder
def pytest_transform():
return AstroidBuilder(MANAGER).string_build('''
try:
import _pytest.mark
import _pytest.recwarn
import _pytest.runner
import _pytest.python
except ImportError:
pass
else:
deprecated_call = _pytest.recwarn.deprecated_call
exit = _pytest.runner.exit
fail = _pytest.runner.fail
fixture = _pytest.python.fixture
importorskip = _pytest.runner.importorskip
mark = _pytest.mark.MarkGenerator()
raises = _pytest.python.raises
skip = _pytest.runner.skip
yield_fixture = _pytest.python.yield_fixture
''')
register_module_extender(MANAGER, 'pytest', pytest_transform)
register_module_extender(MANAGER, 'py.test', pytest_transform)
......@@ -19,38 +19,61 @@
"""Hooks for nose library."""
import re
import unittest
import textwrap
from astroid import List, MANAGER, register_module_extender
from astroid.builder import AstroidBuilder
import astroid
import astroid.builder
_BUILDER = astroid.builder.AstroidBuilder(astroid.MANAGER)
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."""
def _nose_tools_functions():
"""Get an iterator of names and bound methods."""
module = _BUILDER.string_build(textwrap.dedent('''
import unittest
class Test(unittest.TestCase):
pass
a = Test()
'''))
try:
case = next(module['a'].infer())
except astroid.InferenceError:
return
for method in case.methods():
if method.name.startswith('assert') and '_' not in method.name:
pep8_name = _pep8(method.name)
yield pep8_name, astroid.BoundMethod(method, case)
builder = AstroidBuilder(MANAGER)
stub = AstroidBuilder(MANAGER).string_build('''__all__ = []''')
unittest_module = builder.module_build(unittest.case)
case = unittest_module['TestCase']
def _nose_tools_transform(node):
for method_name, method in _nose_tools_functions():
node.locals[method_name] = [method]
def _nose_tools_trivial_transform():
"""Custom transform for the nose.tools module."""
stub = _BUILDER.string_build('''__all__ = []''')
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]
for pep8_name, method in _nose_tools_functions():
all_entries.append(pep8_name)
stub[pep8_name] = method
# Update the __all__ variable, since nose.tools
# does this manually with .append.
all_assign = stub['__all__'].parent
all_object = List(all_entries)
all_object = astroid.List(all_entries)
all_object.parent = all_assign
all_assign.value = all_object
return stub
register_module_extender(MANAGER, 'nose.tools.trivial', nose_transform)
astroid.register_module_extender(astroid.MANAGER, 'nose.tools.trivial',
_nose_tools_trivial_transform)
astroid.MANAGER.register_transform(astroid.Module, _nose_tools_transform,
lambda n: n.name == 'nose.tools')
......@@ -23,203 +23,239 @@ from textwrap import dedent
from astroid import MANAGER, register_module_extender
from astroid.builder import AstroidBuilder
from astroid.exceptions import AstroidBuildingException
def _indent(text, prefix, predicate=None):
"""Adds 'prefix' to the beginning of selected lines in 'text'.
def six_moves_transform_py2():
return AstroidBuilder(MANAGER).string_build(dedent('''
import urllib as _urllib
import urllib2 as _urllib2
import urlparse as _urlparse
If 'predicate' is provided, 'prefix' will only be added to the lines
where 'predicate(line)' is True. If 'predicate' is not provided,
it will default to adding 'prefix' to all non-empty lines that do not
consist solely of whitespace characters.
"""
if predicate is None:
predicate = lambda line: line.strip()
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()
def prefixed_lines():
for line in text.splitlines(True):
yield prefix + line if predicate(line) else line
return ''.join(prefixed_lines())
moves = Moves()
'''))
if sys.version_info[0] == 2:
_IMPORTS_2 = """
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
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()
'''))
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
import Tkinter as tkinter
import tkFileDialog as tkinter_tkfiledialog
input = raw_input
intern = intern
range = xrange
xrange = xrange
reduce = reduce
reload_module = reload
class UrllibParse(object):
import urlparse as _urlparse
import urllib as _urllib
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):
import urllib2 as _urllib2
import urllib as _urllib
URLError = _urllib2.URLError
HTTPError = _urllib2.HTTPError
ContentTooShortError = _urllib.ContentTooShortError
class DummyModule(object):
pass
class UrllibRequest(object):
import urlparse as _urlparse
import urllib2 as _urllib2
import urllib as _urllib
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()
"""
else:
_IMPORTS_3 = """
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
"""
if sys.version_info[0] == 2:
TRANSFORM = six_moves_transform_py2
_IMPORTS = dedent(_IMPORTS_2)
else:
TRANSFORM = six_moves_transform_py3
_IMPORTS = dedent(_IMPORTS_3)
def six_moves_transform():
code = dedent('''
class Moves(object):
{}
moves = Moves()
''').format(_indent(_IMPORTS, " "))
module = AstroidBuilder(MANAGER).string_build(code)
module.name = 'six.moves'
return module
def _six_fail_hook(modname):
if modname != 'six.moves':
raise AstroidBuildingException
module = AstroidBuilder(MANAGER).string_build(_IMPORTS)
module.name = 'six.moves'
return module
register_module_extender(MANAGER, 'six', TRANSFORM)
register_module_extender(MANAGER, 'six', six_moves_transform)
register_module_extender(MANAGER, 'requests.packages.urllib3.packages.six',
six_moves_transform)
MANAGER.register_failed_import_hook(_six_fail_hook)
......@@ -28,7 +28,7 @@ from astroid.manager import AstroidManager
from astroid.exceptions import (AstroidError, InferenceError, NoDefault,
NotFoundError, UnresolvableName)
from astroid.bases import (YES, Instance, InferenceContext,
_infer_stmts, path_wrapper,
_infer_stmts, copy_context, path_wrapper,
raise_if_nothing_infered)
from astroid.protocols import (
_arguments_infer_argname,
......@@ -175,89 +175,92 @@ def infer_name(self, context=None):
if not stmts:
raise UnresolvableName(self.name)
return _infer_stmts(stmts, context, frame, self.name)
context = context.clone()
context.lookupname = self.name
return _infer_stmts(stmts, context, frame)
nodes.Name._infer = path_wrapper(infer_name)
nodes.AssName.infer_lhs = infer_name # won't work with a path wrapper
def infer_callfunc(self, context=None):
"""infer a CallFunc node by trying to guess what the function returns"""
if context is None:
context = InferenceContext()
callcontext = context.clone()
callcontext.callcontext = CallContext(self.args, self.starargs, self.kwargs)
callcontext.boundnode = None
for callee in self.func.infer(context):
with context.scope(
callcontext=CallContext(self.args, self.starargs, self.kwargs),
boundnode=None,
):
if callee is YES:
yield callee
continue
try:
if hasattr(callee, 'infer_call_result'):
for infered in callee.infer_call_result(self, context):
yield infered
except InferenceError:
## XXX log error ?
continue
if callee is YES:
yield callee
continue
try:
if hasattr(callee, 'infer_call_result'):
for infered in callee.infer_call_result(self, callcontext):
yield infered
except InferenceError:
## XXX log error ?
continue
nodes.CallFunc._infer = path_wrapper(raise_if_nothing_infered(infer_callfunc))
def infer_import(self, context=None, asname=True, lookupname=None):
def infer_import(self, context=None, asname=True):
"""infer an Import node: return the imported module/object"""
if lookupname is None:
name = context.lookupname
if name is None:
raise InferenceError()
if asname:
yield self.do_import_module(self.real_name(lookupname))
yield self.do_import_module(self.real_name(name))
else:
yield self.do_import_module(lookupname)
yield self.do_import_module(name)
nodes.Import._infer = path_wrapper(infer_import)
def infer_name_module(self, name):
context = InferenceContext()
return self.infer(context, asname=False, lookupname=name)
context.lookupname = name
return self.infer(context, asname=False)
nodes.Import.infer_name_module = infer_name_module
def infer_from(self, context=None, asname=True, lookupname=None):
def infer_from(self, context=None, asname=True):
"""infer a From nodes: return the imported module/object"""
if lookupname is None:
name = context.lookupname
if name is None:
raise InferenceError()
if asname:
lookupname = self.real_name(lookupname)
name = self.real_name(name)
module = self.do_import_module()
try:
return _infer_stmts(module.getattr(lookupname, ignore_locals=module is self.root()), context, lookupname=lookupname)
context = copy_context(context)
context.lookupname = name
return _infer_stmts(module.getattr(name, ignore_locals=module is self.root()), context)
except NotFoundError:
raise InferenceError(lookupname)
raise InferenceError(name)
nodes.From._infer = path_wrapper(infer_from)
def infer_getattr(self, context=None):
"""infer a Getattr node by using getattr on the associated object"""
if not context:
context = InferenceContext()
for owner in self.expr.infer(context):
if owner is YES:
yield owner
continue
try:
with context.scope(boundnode=owner):
for obj in owner.igetattr(self.attrname, context):
yield obj
context.boundnode = owner
for obj in owner.igetattr(self.attrname, context):
yield obj
context.boundnode = None
except (NotFoundError, InferenceError):
pass
context.boundnode = None
except AttributeError:
# XXX method / function
pass
context.boundnode = None
nodes.Getattr._infer = path_wrapper(raise_if_nothing_infered(infer_getattr))
nodes.AssAttr.infer_lhs = raise_if_nothing_infered(infer_getattr) # # won't work with a path wrapper
def infer_global(self, context=None, lookupname=None):
if lookupname is None:
def infer_global(self, context=None):
if context.lookupname is None:
raise InferenceError()
try:
return _infer_stmts(self.root().getattr(lookupname), context)
return _infer_stmts(self.root().getattr(context.lookupname), context)
except NotFoundError:
raise InferenceError()
nodes.Global._infer = path_wrapper(infer_global)
......@@ -349,10 +352,11 @@ def infer_binop(self, context=None):
nodes.BinOp._infer = path_wrapper(infer_binop)
def infer_arguments(self, context=None, lookupname=None):
if lookupname is None:
def infer_arguments(self, context=None):
name = context.lookupname
if name is None:
raise InferenceError()
return _arguments_infer_argname(self, lookupname, context)
return _arguments_infer_argname(self, name, context)
nodes.Arguments._infer = infer_arguments
......
......@@ -90,6 +90,7 @@ class AstroidManager(OptionsProviderMixIn):
self.transforms = collections.defaultdict(list)
self._failed_import_hooks = []
self.always_load_extensions = False
self.optimize_ast = False
self.extension_package_whitelist = set()
def ast_from_file(self, filepath, modname=None, fallback=True, source=False):
......
......@@ -65,11 +65,11 @@ try:
# with the prefix from which the virtualenv was created. This throws
# off the detection logic for standard library modules, thus the
# workaround.
STD_LIB_DIRS = {
STD_LIB_DIRS = set([
get_python_lib(standard_lib=True, prefix=sys.prefix),
# Take care of installations where exec_prefix != prefix.
get_python_lib(standard_lib=True, prefix=sys.exec_prefix),
get_python_lib(standard_lib=True)}
get_python_lib(standard_lib=True)])
if os.name == 'nt':
STD_LIB_DIRS.add(os.path.join(sys.prefix, 'dlls'))
try:
......
......@@ -130,7 +130,8 @@ class LookupMixIn(object):
the lookup method
"""
frame, stmts = self.lookup(name)
return _infer_stmts(stmts, None, frame)
context = InferenceContext()
return _infer_stmts(stmts, context, frame)
def _filter_stmts(self, stmts, frame, offset):
"""filter statements to remove ignorable statements.
......@@ -308,7 +309,7 @@ class Arguments(NodeNG, AssignTypeMixin):
@cachedproperty
def fromlineno(self):
lineno = super(Arguments, self).fromlineno
return max(lineno, self.parent.fromlineno)
return max(lineno, self.parent.fromlineno or 0)
def format_args(self):
"""return arguments formatted as string"""
......
......@@ -34,6 +34,7 @@ on From and Import :
"""
# pylint: disable=unused-import
__docformat__ = "restructuredtext en"
......
......@@ -20,10 +20,11 @@ where it makes sense.
"""
__doctype__ = "restructuredtext en"
import collections
from astroid.exceptions import InferenceError, NoDefault, NotFoundError
from astroid.node_classes import unpack_infer
from astroid.bases import InferenceContext, \
from astroid.bases import InferenceContext, copy_context, \
raise_if_nothing_infered, yes_if_nothing_infered, Instance, YES
from astroid.nodes import const_factory
from astroid import nodes
......@@ -282,8 +283,7 @@ def _arguments_infer_argname(self, name, context):
# if there is a default value, yield it. And then yield YES to reflect
# we can't guess given argument value
try:
if context is None:
context = InferenceContext()
context = copy_context(context)
for infered in self.default_value(name).infer(context):
yield infered
yield YES
......@@ -295,6 +295,8 @@ def arguments_assigned_stmts(self, node, context, asspath=None):
if context.callcontext:
# reset call context/name
callcontext = context.callcontext
context = copy_context(context)
context.callcontext = None
return callcontext.infer_argument(self.parent, node.name, context)
return _arguments_infer_argname(self, node.name, context)
nodes.Arguments.assigned_stmts = arguments_assigned_stmts
......@@ -359,3 +361,55 @@ def with_assigned_stmts(self, node, context=None, asspath=None):
nodes.With.assigned_stmts = raise_if_nothing_infered(with_assigned_stmts)
def starred_assigned_stmts(self, node=None, context=None, asspath=None):
stmt = self.statement()
if not isinstance(stmt, (nodes.Assign, nodes.For)):
raise InferenceError()
if isinstance(stmt, nodes.Assign):
value = stmt.value
lhs = stmt.targets[0]
if sum(1 for node in lhs.nodes_of_class(nodes.Starred)) > 1:
# Too many starred arguments in the expression.
raise InferenceError()
if context is None:
context = InferenceContext()
try:
rhs = next(value.infer(context))
except InferenceError:
yield YES
return
if rhs is YES or not hasattr(rhs, 'elts'):
# Not interested in inferred values without elts.
yield YES
return
elts = collections.deque(rhs.elts[:])
if len(lhs.elts) > len(rhs.elts):
# a, *b, c = (1, 2)
raise InferenceError()
# Unpack iteratively the values from the rhs of the assignment,
# until the find the starred node. What will remain will
# be the list of values which the Starred node will represent
# This is done in two steps, from left to right to remove
# anything before the starred node and from right to left
# to remvoe anything after the starred node.
for index, node in enumerate(lhs.elts):
if not isinstance(node, nodes.Starred):
elts.popleft()
continue
lhs_elts = collections.deque(reversed(lhs.elts[index:]))
for node in lhs_elts:
if not isinstance(node, nodes.Starred):
elts.pop()
continue
# We're done
for elt in elts:
yield elt
break
nodes.Starred.assigned_stmts = starred_assigned_stmts
......@@ -154,7 +154,7 @@ def object_build_function(node, member, localname):
if varkw is not None:
args.append(varkw)
func = build_function(getattr(member, '__name__', None) or localname, args,
defaults, member.func_code.co_flags, member.__doc__)
defaults, six.get_function_code(member).co_flags, member.__doc__)
node.add_local_node(func, localname)
def object_build_datadescriptor(node, member, name):
......
......@@ -23,7 +23,7 @@ import sys
from _ast import (
Expr as Discard, Str,
# binary operators
Add, Div, FloorDiv, Mod, Mult, Pow, Sub, BitAnd, BitOr, BitXor,
Add, BinOp, Div, FloorDiv, Mod, Mult, Pow, Sub, BitAnd, BitOr, BitXor,
LShift, RShift,
# logical operators
And, Or,
......@@ -34,6 +34,7 @@ from _ast import (
)
from astroid import nodes as new
from astroid import astpeephole
_BIN_OP_CLASSES = {Add: '+',
......@@ -136,6 +137,7 @@ class TreeRebuilder(object):
self._delayed_assattr = []
self._visit_meths = {}
self._transform = manager.transform
self._peepholer = astpeephole.ASTPeepholeOptimizer()
def visit_module(self, node, modname, modpath, package):
"""visit a Module node by returning a fresh instance of it"""
......@@ -281,6 +283,24 @@ class TreeRebuilder(object):
def visit_binop(self, node, parent):
"""visit a BinOp node by returning a fresh instance of it"""
if isinstance(node.left, BinOp) and self._manager.optimize_ast:
# Optimize BinOp operations in order to remove
# redundant recursion. For instance, if the
# following code is parsed in order to obtain
# its ast, then the rebuilder will fail with an
# infinite recursion, the same will happen with the
# inference engine as well. There's no need to hold
# so many objects for the BinOp if they can be reduced
# to something else (also, the optimization
# might handle only Const binops, which isn't a big
# problem for the correctness of the program).
#
# ("a" + "b" + # one thousand more + "c")
newnode = self._peepholer.optimize_binop(node)
if newnode:
_lineno_parent(node, newnode, parent)
return newnode
newnode = new.BinOp()
_lineno_parent(node, newnode, parent)
newnode.left = self.visit(node.left, newnode)
......
......@@ -39,8 +39,8 @@ from astroid.exceptions import NotFoundError, \
AstroidBuildingException, InferenceError, ResolveError
from astroid.node_classes import Const, DelName, DelAttr, \
Dict, From, List, Pass, Raise, Return, Tuple, Yield, YieldFrom, \
LookupMixIn, const_factory as cf, unpack_infer, Name, CallFunc
from astroid.bases import NodeNG, InferenceContext, Instance,\
LookupMixIn, const_factory as cf, unpack_infer, CallFunc
from astroid.bases import NodeNG, InferenceContext, Instance, copy_context, \
YES, Generator, UnboundMethod, BoundMethod, _infer_stmts, \
BUILTINS
from astroid.mixins import FilterStmtsMixin
......@@ -376,10 +376,10 @@ class Module(LocalsDictNodeNG):
"""inferred getattr"""
# set lookup name since this is necessary to infer on import nodes for
# instance
if not context:
context = InferenceContext()
context = copy_context(context)
context.lookupname = name
try:
return _infer_stmts(self.getattr(name, context), context, frame=self, lookupname=name)
return _infer_stmts(self.getattr(name, context), context, frame=self)
except NotFoundError:
raise InferenceError(name)
......@@ -822,7 +822,8 @@ class Function(Statement, Lambda):
c = Class('temporary_class', None)
c.hide = True
c.parent = self
c.bases = [next(b.infer(context)) for b in caller.args[1:]]
bases = [next(b.infer(context)) for b in caller.args[1:]]
c.bases = [base for base in bases if base != YES]
c._metaclass = metaclass
yield c
return
......@@ -1036,7 +1037,21 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin):
yield Instance(self)
def scope_lookup(self, node, name, offset=0):
if node in self.bases:
if any(node == base or base.parent_of(node)
for base in self.bases):
# Handle the case where we have either a name
# in the bases of a class, which exists before
# the actual definition or the case where we have
# a Getattr node, with that name.
#
# name = ...
# class A(name):
# def name(self): ...
#
# import name
# class A(name.Name):
# def name(self): ...
frame = self.parent.frame()
# line offset to avoid that class A(A) resolve the ancestor to
# the defined class
......@@ -1070,29 +1085,33 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin):
return
for stmt in self.bases:
try:
for baseobj in stmt.infer(context):
if not isinstance(baseobj, Class):
if isinstance(baseobj, Instance):
baseobj = baseobj._proxied
else:
# duh ?
continue
if not baseobj.hide:
if baseobj in yielded:
continue # cf xxx above
yielded.add(baseobj)
yield baseobj
if recurs:
for grandpa in baseobj.ancestors(recurs=True,
context=context):
if grandpa in yielded:
with context.restore_path():
try:
for baseobj in stmt.infer(context):
if not isinstance(baseobj, Class):
if isinstance(baseobj, Instance):
baseobj = baseobj._proxied
else:
# duh ?
continue
if not baseobj.hide:
if baseobj in yielded:
continue # cf xxx above
yielded.add(grandpa)
yield grandpa
except InferenceError:
# XXX log error ?
continue
yielded.add(baseobj)
yield baseobj
if recurs:
for grandpa in baseobj.ancestors(recurs=True,
context=context):
if grandpa is self:
# This class is the ancestor of itself.
break
if grandpa in yielded:
continue # cf xxx above
yielded.add(grandpa)
yield grandpa
except InferenceError:
# XXX log error ?
continue
def local_attr_ancestors(self, name, context=None):
"""return an iterator on astroid representation of parent classes
......@@ -1187,11 +1206,11 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin):
"""
# set lookup name since this is necessary to infer on import nodes for
# instance
if not context:
context = InferenceContext()
context = copy_context(context)
context.lookupname = name
try:
for infered in _infer_stmts(self.getattr(name, context), context,
frame=self, lookupname=name):
frame=self):
# yield YES object instead of descriptors when necessary
if not isinstance(infered, Const) and isinstance(infered, Instance):
try:
......@@ -1398,6 +1417,10 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin):
Also, it will return None in the case the slots weren't inferred.
Otherwise, it will return a list of slot names.
"""
if not self.newstyle:
raise NotImplementedError(
"The concept of slots is undefined for old-style classes.")
slots = self._islots()
try:
first = next(slots)
......@@ -1453,7 +1476,9 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin):
"Could not obtain mro for old-style classes.")
bases = list(self._inferred_bases(context=context))
unmerged_mro = [[self]] + [base.mro() for base in bases] + [bases]
unmerged_mro = ([[self]] +
[base.mro() for base in bases if base is not self] +
[bases])
_verify_duplicates_mro(unmerged_mro)
return _c3_merge(unmerged_mro)
URL: http://www.pylint.org/
Version: 1.4.1
URL: https://www.pylint.org/
Version: 1.4.5
License: GPL
License File: LICENSE.txt
......
......@@ -19,10 +19,10 @@ from __future__ import absolute_import
modname = distname = 'pylint'
numversion = (1, 4, 1)
numversion = (1, 4, 5)
version = '.'.join([str(num) for num in numversion])
install_requires = ['logilab-common >= 0.53.0', 'astroid >= 1.3.3', 'six']
install_requires = ['logilab-common >= 0.53.0', 'astroid >= 1.3.6,<1.4.0', 'six']
license = 'GPL'
description = "python code static checker"
......@@ -67,4 +67,4 @@ scripts = [join('bin', filename)
for filename in ('pylint', 'pylint-gui', "symilar", "epylint",
"pyreverse")]
include_dirs = ['test']
include_dirs = [join('pylint', 'test')]
......@@ -497,11 +497,6 @@ functions, methods
'bad-function option). Usual black listed functions are the ones '
'like map, or filter , where Python offers now some cleaner '
'alternative like list comprehension.'),
'W0142': ('Used * or ** magic',
'star-args',
'Used when a function or method is called using `*args` or '
'`**kwargs` to dispatch arguments. This doesn\'t improve '
'readability and should be used with care.'),
'W0150': ("%s statement in finally block may swallow exception",
'lost-exception',
'Used when a break or a return statement is found inside the '
......@@ -753,7 +748,7 @@ functions, methods
"""just print a warning on exec statements"""
self.add_message('exec-used', node=node)
@check_messages('bad-builtin', 'star-args', 'eval-used',
@check_messages('bad-builtin', 'eval-used',
'exec-used', 'missing-reversed-argument',
'bad-reversed-sequence')
def visit_callfunc(self, node):
......@@ -774,18 +769,6 @@ functions, methods
self.add_message('eval-used', node=node)
if name in self.config.bad_functions:
self.add_message('bad-builtin', node=node, args=name)
if node.starargs or node.kwargs:
scope = node.scope()
if isinstance(scope, astroid.Function):
toprocess = [(n, vn) for (n, vn) in ((node.starargs, scope.args.vararg),
(node.kwargs, scope.args.kwarg)) if n]
if toprocess:
for cfnode, fargname in toprocess[:]:
if getattr(cfnode, 'name', None) == fargname:
toprocess.remove((cfnode, fargname))
if not toprocess:
return # star-args can be skipped
self.add_message('star-args', node=node.func)
@check_messages('assert-on-tuple')
def visit_assert(self, node):
......@@ -1171,6 +1154,11 @@ class DocStringChecker(_BasicChecker):
lines = node.body[-1].lineno - node.body[0].lineno + 1
else:
lines = 0
if node_type == 'module' and not lines:
# If the module has no body, there's no reason
# to require a docstring.
return
max_lines = self.config.docstring_min_length
if node_type != 'module' and max_lines > -1 and lines < max_lines:
......
......@@ -107,9 +107,9 @@ def _is_attribute_property(name, klass):
MSGS = {
'F0202': ('Unable to check methods signature (%s / %s)',
'method-check-failed',
'Used when Pylint has been unable to check methods signature \
compatibility for an unexpected reason. Please report this kind \
if you don\'t make sense of it.'),
'Used when Pylint has been unable to check methods signature '
'compatibility for an unexpected reason. Please report this kind '
'if you don\'t make sense of it.'),
'E0202': ('An attribute defined in %s line %s hides this method',
'method-hidden',
......@@ -118,35 +118,35 @@ MSGS = {
'client code.'),
'E0203': ('Access to member %r before its definition line %s',
'access-member-before-definition',
'Used when an instance member is accessed before it\'s actually\
assigned.'),
'Used when an instance member is accessed before it\'s actually '
'assigned.'),
'W0201': ('Attribute %r defined outside __init__',
'attribute-defined-outside-init',
'Used when an instance attribute is defined outside the __init__\
method.'),
'Used when an instance attribute is defined outside the __init__ '
'method.'),
'W0212': ('Access to a protected member %s of a client class', # E0214
'protected-access',
'Used when a protected member (i.e. class member with a name \
beginning with an underscore) is access outside the class or a \
descendant of the class where it\'s defined.'),
'Used when a protected member (i.e. class member with a name '
'beginning with an underscore) is access outside the class or a '
'descendant of the class where it\'s defined.'),
'E0211': ('Method has no argument',
'no-method-argument',
'Used when a method which should have the bound instance as \
first argument has no argument defined.'),
'Used when a method which should have the bound instance as '
'first argument has no argument defined.'),
'E0213': ('Method should have "self" as first argument',
'no-self-argument',
'Used when a method has an attribute different the "self" as\
first argument. This is considered as an error since this is\
a so common convention that you shouldn\'t break it!'),
'C0202': ('Class method %s should have %s as first argument', # E0212
'Used when a method has an attribute different the "self" as '
'first argument. This is considered as an error since this is '
'a so common convention that you shouldn\'t break it!'),
'C0202': ('Class method %s should have %s as first argument',
'bad-classmethod-argument',
'Used when a class method has a first argument named differently '
'than the value specified in valid-classmethod-first-arg option '
'(default to "cls"), recommended to easily differentiate them '
'from regular instance methods.'),
'C0203': ('Metaclass method %s should have %s as first argument', # E0214
'C0203': ('Metaclass method %s should have %s as first argument',
'bad-mcs-method-argument',
'Used when a metaclass method has a first agument named '
'differently than the value specified in valid-classmethod-first'
......@@ -167,58 +167,58 @@ MSGS = {
),
'R0201': ('Method could be a function',
'no-self-use',
'Used when a method doesn\'t use its bound instance, and so could\
be written as a function.'
'Used when a method doesn\'t use its bound instance, and so could '
'be written as a function.'
),
'E0221': ('Interface resolved to %s is not a class',
'interface-is-not-class',
'Used when a class claims to implement an interface which is not \
a class.'),
'Used when a class claims to implement an interface which is not '
'a class.'),
'E0222': ('Missing method %r from %s interface',
'missing-interface-method',
'Used when a method declared in an interface is missing from a \
class implementing this interface'),
'Used when a method declared in an interface is missing from a '
'class implementing this interface'),
'W0221': ('Arguments number differs from %s %r method',
'arguments-differ',
'Used when a method has a different number of arguments than in \
the implemented interface or in an overridden method.'),
'Used when a method has a different number of arguments than in '
'the implemented interface or in an overridden method.'),
'W0222': ('Signature differs from %s %r method',
'signature-differs',
'Used when a method signature is different than in the \
implemented interface or in an overridden method.'),
'Used when a method signature is different than in the '
'implemented interface or in an overridden method.'),
'W0223': ('Method %r is abstract in class %r but is not overridden',
'abstract-method',
'Used when an abstract method (i.e. raise NotImplementedError) is \
not overridden in concrete class.'
'Used when an abstract method (i.e. raise NotImplementedError) is '
'not overridden in concrete class.'
),
'F0220': ('failed to resolve interfaces implemented by %s (%s)', # W0224
'F0220': ('failed to resolve interfaces implemented by %s (%s)',
'unresolved-interface',
'Used when a Pylint as failed to find interfaces implemented by \
a class'),
'Used when a Pylint as failed to find interfaces implemented by '
' a class'),
'W0231': ('__init__ method from base class %r is not called',
'super-init-not-called',
'Used when an ancestor class method has an __init__ method \
which is not called by a derived class.'),
'Used when an ancestor class method has an __init__ method '
'which is not called by a derived class.'),
'W0232': ('Class has no __init__ method',
'no-init',
'Used when a class has no __init__ method, neither its parent \
classes.'),
'Used when a class has no __init__ method, neither its parent '
'classes.'),
'W0233': ('__init__ method from a non direct base class %r is called',
'non-parent-init-called',
'Used when an __init__ method is called on a class which is not \
in the direct ancestors for the analysed class.'),
'Used when an __init__ method is called on a class which is not '
'in the direct ancestors for the analysed class.'),
'W0234': ('__iter__ returns non-iterator',
'non-iterator-returned',
'Used when an __iter__ method returns something which is not an \
iterable (i.e. has no `%s` method)' % NEXT_METHOD),
'Used when an __iter__ method returns something which is not an '
'iterable (i.e. has no `%s` method)' % NEXT_METHOD),
'E0235': ('__exit__ must accept 3 arguments: type, value, traceback',
'bad-context-manager',
'Used when the __exit__ special method, belonging to a \
context manager, does not accept 3 arguments \
(type, value, traceback).'),
'Used when the __exit__ special method, belonging to a '
'context manager, does not accept 3 arguments '
'(type, value, traceback).'),
'E0236': ('Invalid object %r in __slots__, must contain '
'only non empty strings',
'invalid-slots-object',
......@@ -893,26 +893,27 @@ a metaclass class method.'}
expr.expr.func.name == 'super':
return
try:
klass = next(expr.expr.infer())
if klass is YES:
continue
# The infered klass can be super(), which was
# assigned to a variable and the `__init__` was called later.
#
# base = super()
# base.__init__(...)
if (isinstance(klass, astroid.Instance) and
isinstance(klass._proxied, astroid.Class) and
is_builtin_object(klass._proxied) and
klass._proxied.name == 'super'):
return
try:
del not_called_yet[klass]
except KeyError:
if klass not in to_call:
self.add_message('non-parent-init-called',
node=expr, args=klass.name)
for klass in expr.expr.infer():
if klass is YES:
continue
# The infered klass can be super(), which was
# assigned to a variable and the `__init__`
# was called later.
#
# base = super()
# base.__init__(...)
if (isinstance(klass, astroid.Instance) and
isinstance(klass._proxied, astroid.Class) and
is_builtin_object(klass._proxied) and
klass._proxied.name == 'super'):
return
try:
del not_called_yet[klass]
except KeyError:
if klass not in to_call:
self.add_message('non-parent-init-called',
node=expr, args=klass.name)
except astroid.InferenceError:
continue
for klass, method in six.iteritems(not_called_yet):
......
......@@ -18,7 +18,7 @@
import re
from collections import defaultdict
from astroid import Function, If, InferenceError
from astroid import If, InferenceError
from pylint.interfaces import IAstroidChecker
from pylint.checkers import BaseChecker
......@@ -28,17 +28,6 @@ from pylint.checkers.utils import check_messages
IGNORED_ARGUMENT_NAMES = re.compile('_.*')
def class_is_abstract(klass):
"""return true if the given class node should be considered as an abstract
class
"""
for attr in klass.values():
if isinstance(attr, Function):
if attr.is_abstract(pass_is_abstract=False):
return True
return False
MSGS = {
'R0901': ('Too many ancestors (%s/%s)',
'too-many-ancestors',
......@@ -75,14 +64,6 @@ MSGS = {
'too-many-statements',
'Used when a function or method has too many statements. You \
should then split it in smaller functions / methods.'),
'R0921': ('Abstract class not referenced',
'abstract-class-not-used',
'Used when an abstract class is not used as ancestor anywhere.'),
'R0922': ('Abstract class is only referenced %s times',
'abstract-class-little-used',
'Used when an abstract class is used less than X times as \
ancestor.'),
'R0923': ('Interface not implemented',
'interface-not-implemented',
'Used when an interface class is not implemented anywhere.'),
......@@ -165,9 +146,7 @@ class MisdesignChecker(BaseChecker):
self.stats = None
self._returns = None
self._branches = None
self._used_abstracts = None
self._used_ifaces = None
self._abstracts = None
self._ifaces = None
self._stmts = 0
......@@ -176,27 +155,17 @@ class MisdesignChecker(BaseChecker):
self.stats = self.linter.add_stats()
self._returns = []
self._branches = defaultdict(int)
self._used_abstracts = {}
self._used_ifaces = {}
self._abstracts = []
self._ifaces = []
# Check 'R0921', 'R0922', 'R0923'
def close(self):
"""check that abstract/interface classes are used"""
for abstract in self._abstracts:
if not abstract in self._used_abstracts:
self.add_message('abstract-class-not-used', node=abstract)
elif self._used_abstracts[abstract] < 2:
self.add_message('abstract-class-little-used', node=abstract,
args=self._used_abstracts[abstract])
"""check that interface classes are used"""
for iface in self._ifaces:
if not iface in self._used_ifaces:
self.add_message('interface-not-implemented', node=iface)
@check_messages('too-many-ancestors', 'too-many-instance-attributes',
'too-few-public-methods', 'too-many-public-methods',
'abstract-class-not-used', 'abstract-class-little-used',
'interface-not-implemented')
def visit_class(self, node):
"""check size of inheritance hierarchy and number of instance attributes
......@@ -213,10 +182,8 @@ class MisdesignChecker(BaseChecker):
self.add_message('too-many-instance-attributes', node=node,
args=(len(node.instance_attrs),
self.config.max_attributes))
# update abstract / interface classes structures
if class_is_abstract(node):
self._abstracts.append(node)
elif node.type == 'interface' and node.name != 'Interface':
# update interface classes structures
if node.type == 'interface' and node.name != 'Interface':
self._ifaces.append(node)
for parent in node.ancestors(False):
if parent.name == 'Interface':
......@@ -228,34 +195,36 @@ class MisdesignChecker(BaseChecker):
except InferenceError:
# XXX log ?
pass
for parent in node.ancestors():
try:
self._used_abstracts[parent] += 1
except KeyError:
self._used_abstracts[parent] = 1
@check_messages('too-many-ancestors', 'too-many-instance-attributes',
'too-few-public-methods', 'too-many-public-methods',
'abstract-class-not-used', 'abstract-class-little-used',
'interface-not-implemented')
@check_messages('too-few-public-methods', 'too-many-public-methods')
def leave_class(self, node):
"""check number of public methods"""
nb_public_methods = 0
for method in node.mymethods():
if not method.name.startswith('_'):
nb_public_methods += 1
# Does the class contain less than 20 public methods ?
if nb_public_methods > self.config.max_public_methods:
my_methods = sum(1 for method in node.mymethods()
if not method.name.startswith('_'))
all_methods = sum(1 for method in node.methods()
if not method.name.startswith('_'))
# Does the class contain less than n public methods ?
# This checks only the methods defined in the current class,
# since the user might not have control over the classes
# from the ancestors. It avoids some false positives
# for classes such as unittest.TestCase, which provides
# a lot of assert methods. It doesn't make sense to warn
# when the user subclasses TestCase to add his own tests.
if my_methods > self.config.max_public_methods:
self.add_message('too-many-public-methods', node=node,
args=(nb_public_methods,
args=(my_methods,
self.config.max_public_methods))
# stop here for exception, metaclass and interface classes
if node.type != 'class':
return
# Does the class contain more than 5 public methods ?
if nb_public_methods < self.config.min_public_methods:
# Does the class contain more than n public methods ?
# This checks all the methods defined by ancestors and
# by the current class.
if all_methods < self.config.min_public_methods:
self.add_message('too-few-public-methods', node=node,
args=(nb_public_methods,
args=(all_methods,
self.config.min_public_methods))
@check_messages('too-many-return-statements', 'too-many-branches',
......@@ -356,7 +325,6 @@ class MisdesignChecker(BaseChecker):
"""increments the branches counter"""
self._branches[node.scope()] += branchesnum
# FIXME: make a nice report...
def register(linter):
"""required method to auto register this checker """
......
......@@ -303,7 +303,7 @@ class ContinuedLineState(object):
self.retained_warnings.append((token_position, state, valid_offsets))
def get_valid_offsets(self, idx):
""""Returns the valid offsets for the token at the given position."""
"""Returns the valid offsets for the token at the given position."""
# The closing brace on a dict or the 'for' in a dict comprehension may
# reset two indent levels because the dict value is ended implicitly
stack_top = -1
......@@ -353,21 +353,22 @@ class ContinuedLineState(object):
def _continuation_inside_bracket(self, bracket, pos):
"""Extracts indentation information for a continued indent."""
indentation = _get_indent_length(self._tokens.line(pos))
if self._is_block_opener and self._tokens.start_col(pos+1) - indentation == self._block_indent_size:
token_start = self._tokens.start_col(pos)
next_token_start = self._tokens.start_col(pos + 1)
if self._is_block_opener and next_token_start - indentation == self._block_indent_size:
return _ContinuedIndent(
CONTINUED_BLOCK,
bracket,
pos,
_Offsets(self._tokens.start_col(pos)),
_BeforeBlockOffsets(self._tokens.start_col(pos+1),
self._tokens.start_col(pos+1) + self._continuation_size))
_Offsets(token_start),
_BeforeBlockOffsets(next_token_start, next_token_start + self._continuation_size))
else:
return _ContinuedIndent(
CONTINUED,
bracket,
pos,
_Offsets(self._tokens.start_col(pos)),
_Offsets(self._tokens.start_col(pos+1)))
_Offsets(token_start),
_Offsets(next_token_start))
def pop_token(self):
self._cont_stack.pop()
......@@ -442,7 +443,8 @@ class FormatChecker(BaseTokenChecker):
('expected-line-ending-format',
{'type': 'choice', 'metavar': '<empty or LF or CRLF>', 'default': '',
'choices': ['', 'LF', 'CRLF'],
'help': 'Expected format of line ending, e.g. empty (any line ending), LF or CRLF.'}),
'help': ('Expected format of line ending, '
'e.g. empty (any line ending), LF or CRLF.')}),
)
def __init__(self, linter=None):
......@@ -796,10 +798,12 @@ class FormatChecker(BaseTokenChecker):
# check if line ending is as expected
expected = self.config.expected_line_ending_format
if expected:
line_ending = reduce(lambda x, y: x + y if x != y else x, line_ending, "") # reduce multiple \n\n\n\n to one \n
# reduce multiple \n\n\n\n to one \n
line_ending = reduce(lambda x, y: x + y if x != y else x, line_ending, "")
line_ending = 'LF' if line_ending == '\n' else 'CRLF'
if line_ending != expected:
self.add_message('unexpected-line-ending-format', args=(line_ending, expected), line=line_num)
self.add_message('unexpected-line-ending-format', args=(line_ending, expected),
line=line_num)
def _process_retained_warnings(self, tokens, current_pos):
......
......@@ -12,12 +12,13 @@
# this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""Check Python 2 code for Python 2/3 source-compatible issues."""
from __future__ import absolute_import
from __future__ import absolute_import, print_function
import re
import tokenize
import astroid
from astroid import bases
from pylint import checkers, interfaces
from pylint.utils import WarningScope
from pylint.checkers import utils
......@@ -46,6 +47,46 @@ def _check_dict_node(node):
return (not inferred_types
or any(isinstance(x, astroid.Dict) for x 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'}
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.
elif 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.CallFunc):
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.Getattr):
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
class Python3Checker(checkers.BaseChecker):
......@@ -177,13 +218,13 @@ class Python3Checker(checkers.BaseChecker):
'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)',
'``from __future__ import absolute_import`` '
'(default behaviour in Python 3)',
{'maxversion': (3, 0)}),
'W1619': ('division w/o __future__ statement',
'old-division',
'Used for non-floor division w/o a float literal or '
'``from __future__ import division``'
'``from __future__ import division`` '
'(Python 3 returns a float for int division unconditionally)',
{'maxversion': (3, 0)}),
'W1620': ('Calling a dict.iter*() method',
......@@ -244,14 +285,7 @@ class Python3Checker(checkers.BaseChecker):
'Used when a __cmp__ method is defined '
'(method is not used by Python 3)',
{'maxversion': (3, 0)}),
'W1631': ('map is used as implicitly evaluated call',
'implicit-map-evaluation',
'Used when the map builtin is used as implicitly '
'evaluated call, as in "map(func, args)" on a single line. '
'This behaviour will not work in Python 3, where '
'map is a generator and must be evaluated. '
'Prefer a for-loop as alternative.',
{'maxversion': (3, 0)}),
# 'W1631': replaced by W1636
'W1632': ('input built-in referenced',
'input-builtin',
'Used when the input built-in is referenced '
......@@ -262,6 +296,44 @@ class Python3Checker(checkers.BaseChecker):
'Used when the round built-in is referenced '
'(backwards-incompatible semantics in Python 3)',
{'maxversion': (3, 0)}),
'W1634': ('intern built-in referenced',
'intern-builtin',
'Used when the intern built-in is referenced '
'(Moved to sys.intern in Python 3)',
{'maxversion': (3, 0)}),
'W1635': ('unichr built-in referenced',
'unichr-builtin',
'Used when the unichr built-in is referenced '
'(Use chr in Python 3)',
{'maxversion': (3, 0)}),
'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)',
{'maxversion': (3, 0),
'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)',
{'maxversion': (3, 0)}),
'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)',
{'maxversion': (3, 0)}),
'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)',
{'maxversion': (3, 0)}),
'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.',
{'maxversion': (3, 0)}),
}
_bad_builtins = frozenset([
......@@ -273,11 +345,13 @@ class Python3Checker(checkers.BaseChecker):
'execfile',
'file',
'input', # Not missing, but incompatible semantics
'intern',
'long',
'raw_input',
'reduce',
'round', # Not missing, but incompatible semantics
'StandardError',
'unichr',
'unicode',
'xrange',
'reload',
......@@ -299,6 +373,11 @@ class Python3Checker(checkers.BaseChecker):
self._future_absolute_import = False
super(Python3Checker, self).__init__(*args, **kwargs)
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_function(self, node):
if node.is_method() and node.name in self._unused_magic_methods:
method_name = node.name
......@@ -312,19 +391,10 @@ class Python3Checker(checkers.BaseChecker):
if isinstance(arg, astroid.Tuple):
self.add_message('parameter-unpacking', node=arg)
@utils.check_messages('implicit-map-evaluation')
def visit_discard(self, node):
if (isinstance(node.value, astroid.CallFunc) and
isinstance(node.value.func, astroid.Name) and
node.value.func.name == 'map'):
module = node.value.func.lookup('map')[0]
if getattr(module, 'name', None) == '__builtin__':
self.add_message('implicit-map-evaluation', node=node)
def visit_name(self, node):
"""Detect when a "bad" built-in is referenced."""
found_node = node.lookup(node.name)[0]
if getattr(found_node, 'name', None) == '__builtin__':
if _is_builtin(found_node):
if node.name in self._bad_builtins:
message = node.name.lower() + '-builtin'
self.add_message(message, node=node)
......@@ -363,22 +433,57 @@ class Python3Checker(checkers.BaseChecker):
else:
self.add_message('old-division', node=node)
@utils.check_messages('next-method-called',
'dict-iter-method',
'dict-view-method')
def _check_cmp_argument(self, node):
# Check that the `cmp` argument is used
args = []
if (isinstance(node.func, astroid.Getattr)
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):
args = node.args
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:
args = node.args
for arg in args:
if isinstance(arg, astroid.Keyword) and arg.arg == 'cmp':
self.add_message('using-cmp-argument', node=node)
return
def visit_callfunc(self, node):
if not isinstance(node.func, astroid.Getattr):
return
if any([node.args, node.starargs, node.kwargs]):
return
if node.func.attrname == 'next':
self.add_message('next-method-called', node=node)
else:
if _check_dict_node(node.func.expr):
if node.func.attrname in ('iterkeys', 'itervalues', 'iteritems'):
self.add_message('dict-iter-method', node=node)
elif node.func.attrname in ('viewkeys', 'viewvalues', 'viewitems'):
self.add_message('dict-view-method', node=node)
self._check_cmp_argument(node)
if isinstance(node.func, astroid.Getattr):
if any([node.args, node.starargs, node.kwargs]):
return
if node.func.attrname == 'next':
self.add_message('next-method-called', node=node)
else:
if _check_dict_node(node.func.expr):
if node.func.attrname in ('iterkeys', 'itervalues', 'iteritems'):
self.add_message('dict-iter-method', node=node)
elif node.func.attrname in ('viewkeys', 'viewvalues', 'viewitems'):
self.add_message('dict-view-method', node=node)
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)
@utils.check_messages('indexing-exception')
def visit_subscript(self, node):
......
......@@ -61,6 +61,9 @@ class SpellingChecker(BaseTokenChecker):
'%s\nDid you mean: \'%s\'?',
'wrong-spelling-in-docstring',
'Used when a word in docstring is not spelled correctly.'),
'C0403': ('Invalid characters %r in a docstring',
'invalid-characters-in-docstring',
'Used when a word in docstring cannot be checked by enchant.'),
}
options = (('spelling-dict',
{'default' : '', 'type' : 'choice', 'metavar' : '<dict name>',
......@@ -168,7 +171,13 @@ class SpellingChecker(BaseTokenChecker):
word = word[2:]
# If it is a known word, then continue.
if self.spelling_dict.check(word):
try:
if self.spelling_dict.check(word):
continue
except enchant.errors.Error:
# this can only happen in docstrings, not comments
self.add_message('invalid-characters-in-docstring',
line=line_num, args=(word,))
continue
# Store word to private dict or raise a message.
......
......@@ -15,7 +15,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""Checkers for various standard library functions."""
import re
import six
import sys
......@@ -27,10 +26,15 @@ from pylint.checkers import BaseChecker
from pylint.checkers import utils
TYPECHECK_COMPARISON_OPERATORS = frozenset(('is', 'is not', '==', '!=', 'in', 'not in'))
LITERAL_NODE_TYPES = (astroid.Const, astroid.Dict, astroid.List, astroid.Set)
if sys.version_info >= (3, 0):
OPEN_MODULE = '_io'
TYPE_QNAME = 'builtins.type'
else:
OPEN_MODULE = '__builtin__'
TYPE_QNAME = '__builtin__.type'
def _check_mode_str(mode):
......@@ -50,7 +54,6 @@ def _check_mode_str(mode):
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:
......@@ -76,6 +79,15 @@ def _check_mode_str(mode):
return True
def _is_one_arg_pos_call(call):
"""Is this a call with exactly 1 argument,
where that argument is positional?
"""
return (isinstance(call, astroid.CallFunc)
and len(call.args) == 1
and not isinstance(call.args[0], astroid.Keyword))
class StdlibChecker(BaseChecker):
__implements__ = (IAstroidChecker,)
name = 'stdlib'
......@@ -88,7 +100,7 @@ class StdlibChecker(BaseChecker):
'See http://docs.python.org/2/library/functions.html#open'),
'W1502': ('Using datetime.time in a boolean context.',
'boolean-datetime',
'Using datetetime.time in a boolean context can hide '
'Using datetime.time in a boolean context can hide '
'subtle bugs when the time they represent matches '
'midnight UTC. This behaviour was fixed in Python 3.5. '
'See http://bugs.python.org/issue13936 for reference.',
......@@ -96,10 +108,16 @@ class StdlibChecker(BaseChecker):
'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'
'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.')
'should be emitted.'),
'W1504': ('Using type() instead of isinstance() for a typecheck.',
'unidiomatic-typecheck',
'The idiomatic way to perform an explicit typecheck in '
'Python is to use isinstance(x, Y) rather than '
'type(x) == Y, type(x) is Y. Though there are unusual '
'situations where these give different results.')
}
@utils.check_messages('bad-open-mode', 'redundant-unittest-assert')
......@@ -132,6 +150,14 @@ class StdlibChecker(BaseChecker):
for value in node.values:
self._check_datetime(value)
@utils.check_messages('unidiomatic-typecheck')
def visit_compare(self, node):
operator, right = node.ops[0]
if operator in TYPECHECK_COMPARISON_OPERATORS:
left = node.left
if _is_one_arg_pos_call(left):
self._check_type_x_is_y(node, left, operator, right)
def _check_redundant_assert(self, node, infer):
if (isinstance(infer, astroid.BoundMethod) and
node.args and isinstance(node.args[0], astroid.Const) and
......@@ -152,7 +178,6 @@ class StdlibChecker(BaseChecker):
infered.qname() == 'datetime.time'):
self.add_message('boolean-datetime', node=node)
def _check_open_mode(self, node):
"""Check that the mode argument of an open or file call is valid."""
try:
......@@ -167,6 +192,24 @@ class StdlibChecker(BaseChecker):
self.add_message('bad-open-mode', node=node,
args=mode_arg.value)
def _check_type_x_is_y(self, node, left, operator, right):
"""Check for expressions like type(x) == Y."""
left_func = utils.safe_infer(left.func)
if not (isinstance(left_func, astroid.Class)
and left_func.qname() == TYPE_QNAME):
return
if operator in ('is', 'is not') and _is_one_arg_pos_call(right):
right_func = utils.safe_infer(right.func)
if (isinstance(right_func, astroid.Class)
and right_func.qname() == TYPE_QNAME):
# type(x) == type(a)
right_arg = utils.safe_infer(right.args[0])
if not isinstance(right_arg, LITERAL_NODE_TYPES):
# not e.g. type(x) == type([])
return
self.add_message('unidiomatic-typecheck', node=node)
def register(linter):
"""required method to auto register this checker """
......
......@@ -181,7 +181,7 @@ def parse_format_method_string(format_string):
if isinstance(keyname, numbers.Number):
# In Python 2 it will return long which will lead
# to different output between 2 and 3
manual_pos_arg.add(keyname)
manual_pos_arg.add(str(keyname))
keyname = int(keyname)
keys.append((keyname, list(fielditerator)))
else:
......
# -*- coding: utf-8; mode: python; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:ft=python:et:sw=4:ts=4:sts=4
# -*- coding: utf-8; mode: python; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4
# -*- vim:fenc=utf-8:ft=python:et:sw=4:ts=4:sts=4
# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE).
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
......
......@@ -38,7 +38,7 @@ from pylint.reporters.guireporter import GUIReporter
HOME = os.path.expanduser('~/')
HISTORY = '.pylint-gui-history'
COLORS = {'(I)':'lightblue',
COLORS = {'(I)':'green',
'(C)':'blue', '(R)':'darkblue',
'(W)':'black', '(E)':'darkred',
'(F)':'red'}
......
......@@ -48,7 +48,7 @@ from logilab.common import optik_ext
from logilab.common import interface
from logilab.common import textutils
from logilab.common import ureports
from logilab.common.__pkginfo__ import version as common_version
from logilab.common import __version__ as common_version
import six
from pylint import checkers
......@@ -60,6 +60,10 @@ from pylint.__pkginfo__ import version
MANAGER = astroid.MANAGER
INCLUDE_IDS_HELP = ("Deprecated. It was used to include message\'s "
"id in output. Use --msg-template instead.")
SYMBOLS_HELP = ("Deprecated. It was used to include symbolic ids of "
"messages in output. Use --msg-template instead.")
def _get_new_args(message):
location = (
......@@ -105,6 +109,24 @@ def _merge_stats(stats):
return merged
@contextlib.contextmanager
def _patch_sysmodules():
# Context manager that permits running pylint, on Windows, with -m switch
# and with --jobs, as in 'python -2 -m pylint .. --jobs'.
# For more details why this is needed,
# see Python issue http://bugs.python.org/issue10845.
mock_main = __name__ != '__main__' # -m switch
if mock_main:
sys.modules['__main__'] = sys.modules[__name__]
try:
yield
finally:
if mock_main:
sys.modules.pop('__main__')
# Python Linter class #########################################################
MSGS = {
......@@ -177,10 +199,10 @@ MSGS = {
}
def _deprecated_option(shortname, opt_type):
def _deprecated_option(shortname, opt_type, help_msg):
def _warn_deprecated(option, optname, *args): # pylint: disable=unused-argument
sys.stderr.write('Warning: option %s is deprecated and ignored.\n' % (optname,))
return {'short': shortname, 'help': 'DEPRECATED', 'hide': True,
return {'short': shortname, 'help': help_msg, 'hide': True,
'type': opt_type, 'action': 'callback', 'callback': _warn_deprecated}
......@@ -190,6 +212,8 @@ if multiprocessing is not None:
tasks_queue, results_queue, self._config = self._args # pylint: disable=no-member
self._config["jobs"] = 1 # Child does not parallelize any further.
self._python3_porting_mode = self._config.pop(
'python3_porting_mode', None)
# Run linter for received files/modules.
for file_or_module in iter(tasks_queue.get, 'STOP'):
......@@ -197,7 +221,8 @@ if multiprocessing is not None:
try:
results_queue.put(result)
except Exception as ex:
print("internal error with sending report for module %s" % file_or_module, file=sys.stderr)
print("internal error with sending report for module %s" %
file_or_module, file=sys.stderr)
print(ex, file=sys.stderr)
results_queue.put({})
......@@ -212,6 +237,13 @@ if multiprocessing is not None:
linter.load_configuration(**self._config)
linter.set_reporter(reporters.CollectingReporter())
# Enable the Python 3 checker mode. This option is
# passed down from the parent linter up to here, since
# the Python 3 porting flag belongs to the Run class,
# instead of the Linter class.
if self._python3_porting_mode:
linter.python3_porting_mode()
# Run the checks.
linter.check(file_or_module)
......@@ -350,8 +382,9 @@ class PyLinter(configuration.OptionsManagerMixIn,
'See doc for all details')
}),
('include-ids', _deprecated_option('i', 'yn')),
('symbols', _deprecated_option('s', 'yn')),
('include-ids', _deprecated_option('i', 'yn',
INCLUDE_IDS_HELP)),
('symbols', _deprecated_option('s', 'yn', SYMBOLS_HELP)),
('jobs',
{'type' : 'int', 'metavar': '<n-processes>',
......@@ -373,6 +406,19 @@ class PyLinter(configuration.OptionsManagerMixIn,
' loading into the active Python interpreter and may run'
' arbitrary code')}
),
('optimize-ast',
{'type': 'yn', 'metavar': '<yn>', 'default': False,
'help': ('Allow optimization of some AST trees. This will '
'activate a peephole AST optimizer, which will '
'apply various small optimizations. For instance, '
'it can be used to obtain the result of joining '
'multiple strings with the addition operator. '
'Joining a lot of strings can lead to a maximum '
'recursion error in Pylint and this flag can prevent '
'that. It has one side effect, the resulting AST '
'will be different than the one from reality.')}
),
)
option_groups = (
......@@ -427,6 +473,8 @@ class PyLinter(configuration.OptionsManagerMixIn,
)
self.register_checker(self)
self._dynamic_plugins = set()
self._python3_porting_mode = False
self._error_mode = False
self.load_provider_defaults()
if reporter:
self.set_reporter(reporter)
......@@ -555,11 +603,34 @@ class PyLinter(configuration.OptionsManagerMixIn,
def error_mode(self):
"""error mode: enable only errors; no reports, no persistent"""
self._error_mode = True
self.disable_noerror_messages()
self.disable('miscellaneous')
if self._python3_porting_mode:
self.disable('all')
for msg_id in self._checker_messages('python3'):
if msg_id.startswith('E'):
self.enable(msg_id)
else:
self.disable('python3')
self.set_option('reports', False)
self.set_option('persistent', False)
def python3_porting_mode(self):
"""Disable all other checkers and enable Python 3 warnings."""
self.disable('all')
self.enable('python3')
if self._error_mode:
# The error mode was activated, using the -E flag.
# So we'll need to enable only the errors from the
# Python 3 porting checker.
for msg_id in self._checker_messages('python3'):
if msg_id.startswith('E'):
self.enable(msg_id)
else:
self.disable(msg_id)
self._python3_porting_mode = True
# block level option handling #############################################
#
# see func_block_disable_msg.py test case for expected behaviour
......@@ -596,7 +667,8 @@ class PyLinter(configuration.OptionsManagerMixIn,
except KeyError:
meth = self._bw_options_methods[opt]
# found a "(dis|en)able-msg" pragma deprecated suppresssion
self.add_message('deprecated-pragma', line=start[0], args=(opt, opt.replace('-msg', '')))
self.add_message('deprecated-pragma', line=start[0],
args=(opt, opt.replace('-msg', '')))
for msgid in textutils.splitstrip(value):
# Add the line where a control pragma was encountered.
if opt in control_pragmas:
......@@ -604,7 +676,8 @@ class PyLinter(configuration.OptionsManagerMixIn,
try:
if (opt, msgid) == ('disable', 'all'):
self.add_message('deprecated-pragma', line=start[0], args=('disable=all', 'skip-file'))
self.add_message('deprecated-pragma', line=start[0],
args=('disable=all', 'skip-file'))
self.add_message('file-ignored', line=start[0])
self._ignore_file = True
return
......@@ -674,19 +747,9 @@ class PyLinter(configuration.OptionsManagerMixIn,
with fix_import_path(files_or_modules):
self._do_check(files_or_modules)
else:
# Hack that permits running pylint, on Windows, with -m switch
# and with --jobs, as in 'python -2 -m pylint .. --jobs'.
# For more details why this is needed,
# see Python issue http://bugs.python.org/issue10845.
mock_main = __name__ != '__main__' # -m switch
if mock_main:
sys.modules['__main__'] = sys.modules[__name__]
try:
with _patch_sysmodules():
self._parallel_check(files_or_modules)
finally:
if mock_main:
sys.modules.pop('__main__')
def _parallel_task(self, files_or_modules):
# Prepare configuration for child linters.
......@@ -697,6 +760,7 @@ class PyLinter(configuration.OptionsManagerMixIn,
for optname, optdict, val in opt_providers.options_and_values():
if optname not in filter_options:
config[optname] = configuration.format_option_value(optdict, val)
config['python3_porting_mode'] = self._python3_porting_mode
childs = []
manager = multiprocessing.Manager() # pylint: disable=no-member
......@@ -805,7 +869,8 @@ class PyLinter(configuration.OptionsManagerMixIn,
self.current_file = ast_node.file # pylint: disable=maybe-no-member
self.check_astroid_module(ast_node, walker, rawcheckers, tokencheckers)
# warn about spurious inline messages handling
for msgid, line, args in self.file_state.iter_spurious_suppression_messages(self.msgs_store):
spurious_messages = self.file_state.iter_spurious_suppression_messages(self.msgs_store)
for msgid, line, args in spurious_messages:
self.add_message(msgid, line, None, args)
# notify global end
self.stats['statement'] = walker.nbstatements
......@@ -889,6 +954,7 @@ class PyLinter(configuration.OptionsManagerMixIn,
self.stats = {'by_module' : {},
'by_msg' : {},
}
MANAGER.optimize_ast = self.config.optimize_ast
MANAGER.always_load_extensions = self.config.unsafe_load_any_extension
MANAGER.extension_package_whitelist.update(
self.config.extension_pkg_whitelist)
......@@ -1315,8 +1381,7 @@ group are mutually exclusive.'),
def cb_python3_porting_mode(self, *args, **kwargs):
"""Activate only the python3 porting checker."""
self.linter.disable('all')
self.linter.enable('python3')
self.linter.python3_porting_mode()
def cb_list_confidence_levels(option, optname, value, parser):
......
......@@ -13,8 +13,9 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""HTML reporter"""
import itertools
import string
import sys
from cgi import escape
from logilab.common.ureports import HTMLWriter, Section, Table
......@@ -32,11 +33,41 @@ class HTMLReporter(BaseReporter):
def __init__(self, output=sys.stdout):
BaseReporter.__init__(self, output)
self.msgs = []
# Add placeholders for title and parsed messages
self.header = None
self.msgargs = []
@staticmethod
def _parse_msg_template(msg_template):
formatter = string.Formatter()
parsed = formatter.parse(msg_template)
for item in parsed:
if item[1]:
yield item[1]
def _parse_template(self):
"""Helper function to parse the message template"""
self.header = []
if self.linter.config.msg_template:
msg_template = self.linter.config.msg_template
else:
msg_template = '{category}{module}{obj}{line}{column}{msg}'
_header, _msgs = itertools.tee(self._parse_msg_template(msg_template))
self.header = list(_header)
self.msgargs = list(_msgs)
def handle_message(self, msg):
"""manage message of different type and in the context of path"""
self.msgs += (msg.category, msg.module, msg.obj,
str(msg.line), str(msg.column), escape(msg.msg))
# It would be better to do this in init, but currently we do not
# have access to the linter (as it is setup in lint.set_reporter()
# Therefore we try to parse just the once.
if self.header is None:
self._parse_template()
# We want to add the lines given by the template
self.msgs += [str(getattr(msg, field)) for field in self.msgargs]
def set_output(self, output=None):
"""set output stream
......@@ -55,11 +86,12 @@ class HTMLReporter(BaseReporter):
"""
if self.msgs:
# add stored messages to the layout
msgs = ['type', 'module', 'object', 'line', 'col_offset', 'message']
msgs = self.header
cols = len(self.header)
msgs += self.msgs
sect = Section('Messages')
layout.append(sect)
sect.append(Table(cols=6, children=msgs, rheaders=1))
sect.append(Table(cols=cols, children=msgs, rheaders=1))
self.msgs = []
HTMLWriter().format(layout, self.out)
......
......@@ -93,7 +93,7 @@ def get_tests_info(input_dir, msg_dir, prefix, suffix):
class TestReporter(BaseReporter):
"""reporter storing plain text messages"""
__implements____ = IReporter
__implements__ = IReporter
def __init__(self): # pylint: disable=super-init-not-called
......
......@@ -225,6 +225,11 @@ class MessagesHandlerMixIn(object):
self._msgs_state = {}
self.msg_status = 0
def _checker_messages(self, checker):
for checker in self._checkers[checker.lower()]:
for msgid in checker.msgs:
yield msgid
def disable(self, msgid, scope='package', line=None, ignore_unknown=False):
"""don't output message of the given id"""
assert scope in ('package', 'module')
......
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