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 URL: https://github.com/PyCQA/astroid
Version: 1.3.4 Version: 1.3.8
License: GPL License: GPL
License File: LICENSE.txt License File: LICENSE.txt
Description: Description:
This directory contains the logilab-astng module, required for pylint. This directory contains the astroid module, required for pylint.
Local Modifications: Local Modifications:
None None
...@@ -20,10 +20,10 @@ distname = 'astroid' ...@@ -20,10 +20,10 @@ distname = 'astroid'
modname = 'astroid' modname = 'astroid'
numversion = (1, 3, 4) numversion = (1, 3, 8)
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.63.0', 'six']
license = 'LGPL' 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): ...@@ -58,52 +58,28 @@ class Proxy(object):
# Inference ################################################################## # Inference ##################################################################
MISSING = object()
class InferenceContext(object): class InferenceContext(object):
__slots__ = ('path', 'callcontext', 'boundnode', 'infered') __slots__ = ('path', 'lookupname', 'callcontext', 'boundnode', 'infered')
def __init__(self, def __init__(self, path=None, infered=None):
path=None, callcontext=None, boundnode=None, infered=None): self.path = path or set()
if path is None: self.lookupname = None
self.path = frozenset() self.callcontext = None
else: self.boundnode = None
self.path = path self.infered = infered or {}
self.callcontext = callcontext
self.boundnode = boundnode def push(self, node):
if infered is None: name = self.lookupname
self.infered = {} if (node, name) in self.path:
else: raise StopIteration()
self.infered = infered self.path.add((node, name))
def push(self, key): def clone(self):
# This returns a NEW context with the same attributes, but a new key # XXX copy lookupname/callcontext ?
# added to `path`. The intention is that it's only passed to callees clone = InferenceContext(self.path, infered=self.infered)
# and then destroyed; otherwise scope() may not work correctly. clone.callcontext = self.callcontext
# The cache will be shared, since it's the same exact dict. clone.boundnode = self.boundnode
if key in self.path: return clone
# 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
def cache_generator(self, key, generator): def cache_generator(self, key, generator):
results = [] results = []
...@@ -114,28 +90,38 @@ class InferenceContext(object): ...@@ -114,28 +90,38 @@ class InferenceContext(object):
self.infered[key] = tuple(results) self.infered[key] = tuple(results)
return 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> """return an iterator on statements inferred by each statement in <stmts>
""" """
stmt = None stmt = None
infered = False infered = False
if context is None: if context is not None:
name = context.lookupname
context = context.clone()
else:
name = None
context = InferenceContext() context = InferenceContext()
for stmt in stmts: for stmt in stmts:
if stmt is YES: if stmt is YES:
yield stmt yield stmt
infered = True infered = True
continue continue
context.lookupname = stmt._infer_name(frame, name)
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
try: try:
for infered in stmt.infer(context, **kw): for infered in stmt.infer(context):
yield infered yield infered
infered = True infered = True
except UnresolvableName: except UnresolvableName:
...@@ -197,12 +183,13 @@ class Instance(Proxy): ...@@ -197,12 +183,13 @@ class Instance(Proxy):
context = InferenceContext() context = InferenceContext()
try: try:
# avoid recursively inferring the same attr on the same class # 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 ? # 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( return _infer_stmts(
self._wrap_attr(get_attr, new_context), self._wrap_attr(get_attr, context),
new_context, context,
frame=self, frame=self,
) )
except NotFoundError: except NotFoundError:
...@@ -210,7 +197,7 @@ class Instance(Proxy): ...@@ -210,7 +197,7 @@ class Instance(Proxy):
# fallback to class'igetattr since it has some logic to handle # fallback to class'igetattr since it has some logic to handle
# descriptors # descriptors
return self._wrap_attr(self._proxied.igetattr(name, context), return self._wrap_attr(self._proxied.igetattr(name, context),
context) context)
except NotFoundError: except NotFoundError:
raise InferenceError(name) raise InferenceError(name)
...@@ -301,9 +288,9 @@ class BoundMethod(UnboundMethod): ...@@ -301,9 +288,9 @@ class BoundMethod(UnboundMethod):
return True return True
def infer_call_result(self, caller, context): def infer_call_result(self, caller, context):
with context.scope(boundnode=self.bound): context = context.clone()
for infered in self._proxied.infer_call_result(caller, context): context.boundnode = self.bound
yield infered return self._proxied.infer_call_result(caller, context)
class Generator(Instance): class Generator(Instance):
...@@ -335,8 +322,7 @@ def path_wrapper(func): ...@@ -335,8 +322,7 @@ def path_wrapper(func):
"""wrapper function handling context""" """wrapper function handling context"""
if context is None: if context is None:
context = InferenceContext() context = InferenceContext()
context = context.push((node, kwargs.get('lookupname'))) context.push(node)
yielded = set() yielded = set()
for res in _func(node, context, **kwargs): for res in _func(node, context, **kwargs):
# unproxy only true instance, not const, tuple, dict... # unproxy only true instance, not const, tuple, dict...
...@@ -409,7 +395,8 @@ class NodeNG(object): ...@@ -409,7 +395,8 @@ class NodeNG(object):
if not context: if not context:
return self._infer(context, **kwargs) 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: if key in context.infered:
return iter(context.infered[key]) return iter(context.infered[key])
......
"""Astroid hooks for pytest.""" """Astroid hooks for pytest."""
from astroid import MANAGER, register_module_extender from astroid import MANAGER, register_module_extender
from astroid.builder import AstroidBuilder from astroid.builder import AstroidBuilder
def pytest_transform(): def pytest_transform():
return AstroidBuilder(MANAGER).string_build(''' return AstroidBuilder(MANAGER).string_build('''
try: try:
import _pytest.mark import _pytest.mark
import _pytest.recwarn import _pytest.recwarn
import _pytest.runner import _pytest.runner
import _pytest.python import _pytest.python
except ImportError: except ImportError:
pass pass
else: else:
deprecated_call = _pytest.recwarn.deprecated_call deprecated_call = _pytest.recwarn.deprecated_call
exit = _pytest.runner.exit exit = _pytest.runner.exit
fail = _pytest.runner.fail fail = _pytest.runner.fail
fixture = _pytest.python.fixture fixture = _pytest.python.fixture
importorskip = _pytest.runner.importorskip importorskip = _pytest.runner.importorskip
mark = _pytest.mark.MarkGenerator() mark = _pytest.mark.MarkGenerator()
raises = _pytest.python.raises raises = _pytest.python.raises
skip = _pytest.runner.skip skip = _pytest.runner.skip
yield_fixture = _pytest.python.yield_fixture yield_fixture = _pytest.python.yield_fixture
''') ''')
register_module_extender(MANAGER, 'pytest', pytest_transform) register_module_extender(MANAGER, 'pytest', pytest_transform)
register_module_extender(MANAGER, 'py.test', pytest_transform) register_module_extender(MANAGER, 'py.test', pytest_transform)
...@@ -19,38 +19,61 @@ ...@@ -19,38 +19,61 @@
"""Hooks for nose library.""" """Hooks for nose library."""
import re import re
import unittest import textwrap
from astroid import List, MANAGER, register_module_extender import astroid
from astroid.builder import AstroidBuilder import astroid.builder
_BUILDER = astroid.builder.AstroidBuilder(astroid.MANAGER)
def _pep8(name, caps=re.compile('([A-Z])')): def _pep8(name, caps=re.compile('([A-Z])')):
return caps.sub(lambda m: '_' + m.groups()[0].lower(), name) return caps.sub(lambda m: '_' + m.groups()[0].lower(), name)
def nose_transform(): def _nose_tools_functions():
"""Custom transform for the nose.tools module.""" """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) def _nose_tools_transform(node):
stub = AstroidBuilder(MANAGER).string_build('''__all__ = []''') for method_name, method in _nose_tools_functions():
unittest_module = builder.module_build(unittest.case) node.locals[method_name] = [method]
case = unittest_module['TestCase']
def _nose_tools_trivial_transform():
"""Custom transform for the nose.tools module."""
stub = _BUILDER.string_build('''__all__ = []''')
all_entries = ['ok_', 'eq_'] all_entries = ['ok_', 'eq_']
for method_name, method in case.locals.items(): for pep8_name, method in _nose_tools_functions():
if method_name.startswith('assert') and '_' not in method_name: all_entries.append(pep8_name)
pep8_name = _pep8(method_name) stub[pep8_name] = method
all_entries.append(pep8_name)
stub[pep8_name] = method[0]
# Update the __all__ variable, since nose.tools # Update the __all__ variable, since nose.tools
# does this manually with .append. # does this manually with .append.
all_assign = stub['__all__'].parent all_assign = stub['__all__'].parent
all_object = List(all_entries) all_object = astroid.List(all_entries)
all_object.parent = all_assign all_object.parent = all_assign
all_assign.value = all_object all_assign.value = all_object
return stub 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 ...@@ -23,203 +23,239 @@ from textwrap import dedent
from astroid import MANAGER, register_module_extender from astroid import MANAGER, register_module_extender
from astroid.builder import AstroidBuilder 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(): If 'predicate' is provided, 'prefix' will only be added to the lines
return AstroidBuilder(MANAGER).string_build(dedent(''' where 'predicate(line)' is True. If 'predicate' is not provided,
import urllib as _urllib it will default to adding 'prefix' to all non-empty lines that do not
import urllib2 as _urllib2 consist solely of whitespace characters.
import urlparse as _urlparse """
if predicate is None:
predicate = lambda line: line.strip()
class Moves(object): def prefixed_lines():
import BaseHTTPServer for line in text.splitlines(True):
import CGIHTTPServer yield prefix + line if predicate(line) else line
import SimpleHTTPServer return ''.join(prefixed_lines())
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()
''')) 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(): import __builtin__ as builtins
return AstroidBuilder(MANAGER).string_build(dedent(''' import thread as _thread
class Moves(object): import dummy_thread as _dummy_thread
import _io import ConfigParser as configparser
cStringIO = _io.StringIO import copy_reg as copyreg
filter = filter from itertools import (imap as map,
from itertools import filterfalse ifilter as filter,
input = input ifilterfalse as filterfalse,
from sys import intern izip_longest as zip_longest,
map = map izip as zip)
range = range import htmlentitydefs as html_entities
from imp import reload as reload_module import HTMLParser as html_parser
from functools import reduce import httplib as http_client
from shlex import quote as shlex_quote import cookielib as http_cookiejar
from io import StringIO import Cookie as http_cookies
from collections import UserDict, UserList, UserString import Queue as queue
xrange = range import repr as reprlib
zip = zip from pipes import quote as shlex_quote
from itertools import zip_longest import SocketServer as socketserver
import builtins import SimpleXMLRPCServer as xmlrpc_server
import configparser import xmlrpclib as xmlrpc_client
import copyreg import _winreg as winreg
import _dummy_thread import robotparser as urllib_robotparser
import http.cookiejar as http_cookiejar import Tkinter as tkinter
import http.cookies as http_cookies import tkFileDialog as tkinter_tkfiledialog
import html.entities as html_entities
import html.parser as html_parser input = raw_input
import http.client as http_client intern = intern
import http.server range = xrange
BaseHTTPServer = CGIHTTPServer = SimpleHTTPServer = http.server xrange = xrange
import pickle as cPickle reduce = reduce
import queue reload_module = reload
import reprlib
import socketserver class UrllibParse(object):
import _thread import urlparse as _urlparse
import winreg import urllib as _urllib
import xmlrpc.server as xmlrpc_server ParseResult = _urlparse.ParseResult
import xmlrpc.client as xmlrpc_client SplitResult = _urlparse.SplitResult
import urllib.robotparser as urllib_robotparser parse_qs = _urlparse.parse_qs
import email.mime.multipart as email_mime_multipart parse_qsl = _urlparse.parse_qsl
import email.mime.nonmultipart as email_mime_nonmultipart urldefrag = _urlparse.urldefrag
import email.mime.text as email_mime_text urljoin = _urlparse.urljoin
import email.mime.base as email_mime_base urlparse = _urlparse.urlparse
import urllib.parse as urllib_parse urlsplit = _urlparse.urlsplit
import urllib.error as urllib_error urlunparse = _urlparse.urlunparse
import tkinter urlunsplit = _urlparse.urlunsplit
import tkinter.dialog as tkinter_dialog quote = _urllib.quote
import tkinter.filedialog as tkinter_filedialog quote_plus = _urllib.quote_plus
import tkinter.scrolledtext as tkinter_scrolledtext unquote = _urllib.unquote
import tkinter.simpledialog as tkinder_simpledialog unquote_plus = _urllib.unquote_plus
import tkinter.tix as tkinter_tix urlencode = _urllib.urlencode
import tkinter.ttk as tkinter_ttk splitquery = _urllib.splitquery
import tkinter.constants as tkinter_constants splittag = _urllib.splittag
import tkinter.dnd as tkinter_dnd splituser = _urllib.splituser
import tkinter.colorchooser as tkinter_colorchooser uses_fragment = _urlparse.uses_fragment
import tkinter.commondialog as tkinter_commondialog uses_netloc = _urlparse.uses_netloc
import tkinter.filedialog as tkinter_tkfiledialog uses_params = _urlparse.uses_params
import tkinter.font as tkinter_font uses_query = _urlparse.uses_query
import tkinter.messagebox as tkinter_messagebox uses_relative = _urlparse.uses_relative
import urllib.request
import urllib.robotparser as urllib_robotparser class UrllibError(object):
import urllib.parse as urllib_parse import urllib2 as _urllib2
import urllib.error as urllib_error import urllib as _urllib
moves = Moves() 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: if sys.version_info[0] == 2:
TRANSFORM = six_moves_transform_py2 _IMPORTS = dedent(_IMPORTS_2)
else: 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 ...@@ -28,7 +28,7 @@ from astroid.manager import AstroidManager
from astroid.exceptions import (AstroidError, InferenceError, NoDefault, from astroid.exceptions import (AstroidError, InferenceError, NoDefault,
NotFoundError, UnresolvableName) NotFoundError, UnresolvableName)
from astroid.bases import (YES, Instance, InferenceContext, from astroid.bases import (YES, Instance, InferenceContext,
_infer_stmts, path_wrapper, _infer_stmts, copy_context, path_wrapper,
raise_if_nothing_infered) raise_if_nothing_infered)
from astroid.protocols import ( from astroid.protocols import (
_arguments_infer_argname, _arguments_infer_argname,
...@@ -175,89 +175,92 @@ def infer_name(self, context=None): ...@@ -175,89 +175,92 @@ def infer_name(self, context=None):
if not stmts: if not stmts:
raise UnresolvableName(self.name) 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.Name._infer = path_wrapper(infer_name)
nodes.AssName.infer_lhs = infer_name # won't work with a path wrapper nodes.AssName.infer_lhs = infer_name # won't work with a path wrapper
def infer_callfunc(self, context=None): def infer_callfunc(self, context=None):
"""infer a CallFunc node by trying to guess what the function returns""" """infer a CallFunc node by trying to guess what the function returns"""
if context is None: callcontext = context.clone()
context = InferenceContext() callcontext.callcontext = CallContext(self.args, self.starargs, self.kwargs)
callcontext.boundnode = None
for callee in self.func.infer(context): for callee in self.func.infer(context):
with context.scope( if callee is YES:
callcontext=CallContext(self.args, self.starargs, self.kwargs), yield callee
boundnode=None, continue
): try:
if callee is YES: if hasattr(callee, 'infer_call_result'):
yield callee for infered in callee.infer_call_result(self, callcontext):
continue yield infered
try: except InferenceError:
if hasattr(callee, 'infer_call_result'): ## XXX log error ?
for infered in callee.infer_call_result(self, context): continue
yield infered
except InferenceError:
## XXX log error ?
continue
nodes.CallFunc._infer = path_wrapper(raise_if_nothing_infered(infer_callfunc)) 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""" """infer an Import node: return the imported module/object"""
if lookupname is None: name = context.lookupname
if name is None:
raise InferenceError() raise InferenceError()
if asname: if asname:
yield self.do_import_module(self.real_name(lookupname)) yield self.do_import_module(self.real_name(name))
else: else:
yield self.do_import_module(lookupname) yield self.do_import_module(name)
nodes.Import._infer = path_wrapper(infer_import) nodes.Import._infer = path_wrapper(infer_import)
def infer_name_module(self, name): def infer_name_module(self, name):
context = InferenceContext() 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 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""" """infer a From nodes: return the imported module/object"""
if lookupname is None: name = context.lookupname
if name is None:
raise InferenceError() raise InferenceError()
if asname: if asname:
lookupname = self.real_name(lookupname) name = self.real_name(name)
module = self.do_import_module() module = self.do_import_module()
try: 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: except NotFoundError:
raise InferenceError(lookupname) raise InferenceError(name)
nodes.From._infer = path_wrapper(infer_from) nodes.From._infer = path_wrapper(infer_from)
def infer_getattr(self, context=None): def infer_getattr(self, context=None):
"""infer a Getattr node by using getattr on the associated object""" """infer a Getattr node by using getattr on the associated object"""
if not context:
context = InferenceContext()
for owner in self.expr.infer(context): for owner in self.expr.infer(context):
if owner is YES: if owner is YES:
yield owner yield owner
continue continue
try: try:
with context.scope(boundnode=owner): context.boundnode = owner
for obj in owner.igetattr(self.attrname, context): for obj in owner.igetattr(self.attrname, context):
yield obj yield obj
context.boundnode = None
except (NotFoundError, InferenceError): except (NotFoundError, InferenceError):
pass context.boundnode = None
except AttributeError: except AttributeError:
# XXX method / function # XXX method / function
pass context.boundnode = None
nodes.Getattr._infer = path_wrapper(raise_if_nothing_infered(infer_getattr)) 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 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): def infer_global(self, context=None):
if lookupname is None: if context.lookupname is None:
raise InferenceError() raise InferenceError()
try: try:
return _infer_stmts(self.root().getattr(lookupname), context) return _infer_stmts(self.root().getattr(context.lookupname), context)
except NotFoundError: except NotFoundError:
raise InferenceError() raise InferenceError()
nodes.Global._infer = path_wrapper(infer_global) nodes.Global._infer = path_wrapper(infer_global)
...@@ -349,10 +352,11 @@ def infer_binop(self, context=None): ...@@ -349,10 +352,11 @@ def infer_binop(self, context=None):
nodes.BinOp._infer = path_wrapper(infer_binop) nodes.BinOp._infer = path_wrapper(infer_binop)
def infer_arguments(self, context=None, lookupname=None): def infer_arguments(self, context=None):
if lookupname is None: name = context.lookupname
if name is None:
raise InferenceError() raise InferenceError()
return _arguments_infer_argname(self, lookupname, context) return _arguments_infer_argname(self, name, context)
nodes.Arguments._infer = infer_arguments nodes.Arguments._infer = infer_arguments
......
...@@ -90,6 +90,7 @@ class AstroidManager(OptionsProviderMixIn): ...@@ -90,6 +90,7 @@ class AstroidManager(OptionsProviderMixIn):
self.transforms = collections.defaultdict(list) self.transforms = collections.defaultdict(list)
self._failed_import_hooks = [] self._failed_import_hooks = []
self.always_load_extensions = False self.always_load_extensions = False
self.optimize_ast = False
self.extension_package_whitelist = set() self.extension_package_whitelist = set()
def ast_from_file(self, filepath, modname=None, fallback=True, source=False): def ast_from_file(self, filepath, modname=None, fallback=True, source=False):
......
...@@ -65,11 +65,11 @@ try: ...@@ -65,11 +65,11 @@ try:
# 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 = set([
get_python_lib(standard_lib=True, prefix=sys.prefix), get_python_lib(standard_lib=True, prefix=sys.prefix),
# Take care of installations where exec_prefix != 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, prefix=sys.exec_prefix),
get_python_lib(standard_lib=True)} get_python_lib(standard_lib=True)])
if os.name == 'nt': if os.name == 'nt':
STD_LIB_DIRS.add(os.path.join(sys.prefix, 'dlls')) STD_LIB_DIRS.add(os.path.join(sys.prefix, 'dlls'))
try: try:
......
...@@ -130,7 +130,8 @@ class LookupMixIn(object): ...@@ -130,7 +130,8 @@ class LookupMixIn(object):
the lookup method the lookup method
""" """
frame, stmts = self.lookup(name) 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): def _filter_stmts(self, stmts, frame, offset):
"""filter statements to remove ignorable statements. """filter statements to remove ignorable statements.
...@@ -308,7 +309,7 @@ class Arguments(NodeNG, AssignTypeMixin): ...@@ -308,7 +309,7 @@ class Arguments(NodeNG, AssignTypeMixin):
@cachedproperty @cachedproperty
def fromlineno(self): def fromlineno(self):
lineno = super(Arguments, self).fromlineno lineno = super(Arguments, self).fromlineno
return max(lineno, self.parent.fromlineno) return max(lineno, self.parent.fromlineno or 0)
def format_args(self): def format_args(self):
"""return arguments formatted as string""" """return arguments formatted as string"""
......
...@@ -34,6 +34,7 @@ on From and Import : ...@@ -34,6 +34,7 @@ on From and Import :
""" """
# pylint: disable=unused-import
__docformat__ = "restructuredtext en" __docformat__ = "restructuredtext en"
......
...@@ -20,10 +20,11 @@ where it makes sense. ...@@ -20,10 +20,11 @@ where it makes sense.
""" """
__doctype__ = "restructuredtext en" __doctype__ = "restructuredtext en"
import collections
from astroid.exceptions import InferenceError, NoDefault, NotFoundError from astroid.exceptions import InferenceError, NoDefault, NotFoundError
from astroid.node_classes import unpack_infer 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 raise_if_nothing_infered, yes_if_nothing_infered, Instance, YES
from astroid.nodes import const_factory from astroid.nodes import const_factory
from astroid import nodes from astroid import nodes
...@@ -282,8 +283,7 @@ def _arguments_infer_argname(self, name, context): ...@@ -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 # if there is a default value, yield it. And then yield YES to reflect
# we can't guess given argument value # we can't guess given argument value
try: try:
if context is None: context = copy_context(context)
context = InferenceContext()
for infered in self.default_value(name).infer(context): for infered in self.default_value(name).infer(context):
yield infered yield infered
yield YES yield YES
...@@ -295,6 +295,8 @@ def arguments_assigned_stmts(self, node, context, asspath=None): ...@@ -295,6 +295,8 @@ def arguments_assigned_stmts(self, node, context, asspath=None):
if context.callcontext: if context.callcontext:
# reset call context/name # reset call context/name
callcontext = context.callcontext callcontext = context.callcontext
context = copy_context(context)
context.callcontext = None
return callcontext.infer_argument(self.parent, node.name, context) return callcontext.infer_argument(self.parent, node.name, context)
return _arguments_infer_argname(self, node.name, context) return _arguments_infer_argname(self, node.name, context)
nodes.Arguments.assigned_stmts = arguments_assigned_stmts nodes.Arguments.assigned_stmts = arguments_assigned_stmts
...@@ -359,3 +361,55 @@ def with_assigned_stmts(self, node, context=None, asspath=None): ...@@ -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) 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): ...@@ -154,7 +154,7 @@ def object_build_function(node, member, localname):
if varkw is not None: if varkw is not None:
args.append(varkw) args.append(varkw)
func = build_function(getattr(member, '__name__', None) or localname, args, 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) node.add_local_node(func, localname)
def object_build_datadescriptor(node, member, name): def object_build_datadescriptor(node, member, name):
......
...@@ -23,7 +23,7 @@ import sys ...@@ -23,7 +23,7 @@ import sys
from _ast import ( from _ast import (
Expr as Discard, Str, Expr as Discard, Str,
# binary operators # 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, LShift, RShift,
# logical operators # logical operators
And, Or, And, Or,
...@@ -34,6 +34,7 @@ from _ast import ( ...@@ -34,6 +34,7 @@ from _ast import (
) )
from astroid import nodes as new from astroid import nodes as new
from astroid import astpeephole
_BIN_OP_CLASSES = {Add: '+', _BIN_OP_CLASSES = {Add: '+',
...@@ -136,6 +137,7 @@ class TreeRebuilder(object): ...@@ -136,6 +137,7 @@ class TreeRebuilder(object):
self._delayed_assattr = [] self._delayed_assattr = []
self._visit_meths = {} self._visit_meths = {}
self._transform = manager.transform self._transform = manager.transform
self._peepholer = astpeephole.ASTPeepholeOptimizer()
def visit_module(self, node, modname, modpath, package): def visit_module(self, node, modname, modpath, package):
"""visit a Module node by returning a fresh instance of it""" """visit a Module node by returning a fresh instance of it"""
...@@ -281,6 +283,24 @@ class TreeRebuilder(object): ...@@ -281,6 +283,24 @@ class TreeRebuilder(object):
def visit_binop(self, node, parent): def visit_binop(self, node, parent):
"""visit a BinOp node by returning a fresh instance of it""" """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() newnode = new.BinOp()
_lineno_parent(node, newnode, parent) _lineno_parent(node, newnode, parent)
newnode.left = self.visit(node.left, newnode) newnode.left = self.visit(node.left, newnode)
......
...@@ -39,8 +39,8 @@ from astroid.exceptions import NotFoundError, \ ...@@ -39,8 +39,8 @@ from astroid.exceptions import NotFoundError, \
AstroidBuildingException, InferenceError, ResolveError AstroidBuildingException, InferenceError, ResolveError
from astroid.node_classes import Const, DelName, DelAttr, \ from astroid.node_classes import Const, DelName, DelAttr, \
Dict, From, List, Pass, Raise, Return, Tuple, Yield, YieldFrom, \ Dict, From, List, Pass, Raise, Return, Tuple, Yield, YieldFrom, \
LookupMixIn, const_factory as cf, unpack_infer, Name, CallFunc LookupMixIn, const_factory as cf, unpack_infer, CallFunc
from astroid.bases import NodeNG, InferenceContext, Instance,\ from astroid.bases import NodeNG, InferenceContext, Instance, copy_context, \
YES, Generator, UnboundMethod, BoundMethod, _infer_stmts, \ YES, Generator, UnboundMethod, BoundMethod, _infer_stmts, \
BUILTINS BUILTINS
from astroid.mixins import FilterStmtsMixin from astroid.mixins import FilterStmtsMixin
...@@ -376,10 +376,10 @@ class Module(LocalsDictNodeNG): ...@@ -376,10 +376,10 @@ class Module(LocalsDictNodeNG):
"""inferred getattr""" """inferred getattr"""
# set lookup name since this is necessary to infer on import nodes for # set lookup name since this is necessary to infer on import nodes for
# instance # instance
if not context: context = copy_context(context)
context = InferenceContext() context.lookupname = name
try: 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: except NotFoundError:
raise InferenceError(name) raise InferenceError(name)
...@@ -822,7 +822,8 @@ class Function(Statement, Lambda): ...@@ -822,7 +822,8 @@ class Function(Statement, Lambda):
c = Class('temporary_class', None) c = Class('temporary_class', None)
c.hide = True c.hide = True
c.parent = self 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 c._metaclass = metaclass
yield c yield c
return return
...@@ -1036,7 +1037,21 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin): ...@@ -1036,7 +1037,21 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin):
yield Instance(self) yield Instance(self)
def scope_lookup(self, node, name, offset=0): 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() frame = self.parent.frame()
# line offset to avoid that class A(A) resolve the ancestor to # line offset to avoid that class A(A) resolve the ancestor to
# the defined class # the defined class
...@@ -1070,29 +1085,33 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin): ...@@ -1070,29 +1085,33 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin):
return return
for stmt in self.bases: for stmt in self.bases:
try: with context.restore_path():
for baseobj in stmt.infer(context): try:
if not isinstance(baseobj, Class): for baseobj in stmt.infer(context):
if isinstance(baseobj, Instance): if not isinstance(baseobj, Class):
baseobj = baseobj._proxied if isinstance(baseobj, Instance):
else: baseobj = baseobj._proxied
# duh ? else:
continue # duh ?
if not baseobj.hide: continue
if baseobj in yielded: if not baseobj.hide:
continue # cf xxx above if baseobj in yielded:
yielded.add(baseobj)
yield baseobj
if recurs:
for grandpa in baseobj.ancestors(recurs=True,
context=context):
if grandpa in yielded:
continue # cf xxx above continue # cf xxx above
yielded.add(grandpa) yielded.add(baseobj)
yield grandpa yield baseobj
except InferenceError: if recurs:
# XXX log error ? for grandpa in baseobj.ancestors(recurs=True,
continue 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): def local_attr_ancestors(self, name, context=None):
"""return an iterator on astroid representation of parent classes """return an iterator on astroid representation of parent classes
...@@ -1187,11 +1206,11 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin): ...@@ -1187,11 +1206,11 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin):
""" """
# set lookup name since this is necessary to infer on import nodes for # set lookup name since this is necessary to infer on import nodes for
# instance # instance
if not context: context = copy_context(context)
context = InferenceContext() context.lookupname = name
try: try:
for infered in _infer_stmts(self.getattr(name, context), context, for infered in _infer_stmts(self.getattr(name, context), context,
frame=self, lookupname=name): frame=self):
# yield YES object instead of descriptors when necessary # yield YES object instead of descriptors when necessary
if not isinstance(infered, Const) and isinstance(infered, Instance): if not isinstance(infered, Const) and isinstance(infered, Instance):
try: try:
...@@ -1398,6 +1417,10 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin): ...@@ -1398,6 +1417,10 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin):
Also, it will return None in the case the slots weren't inferred. Also, it will return None in the case the slots weren't inferred.
Otherwise, it will return a list of slot names. 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() slots = self._islots()
try: try:
first = next(slots) first = next(slots)
...@@ -1453,7 +1476,9 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin): ...@@ -1453,7 +1476,9 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin):
"Could not obtain mro for old-style classes.") "Could not obtain mro for old-style classes.")
bases = list(self._inferred_bases(context=context)) 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) _verify_duplicates_mro(unmerged_mro)
return _c3_merge(unmerged_mro) return _c3_merge(unmerged_mro)
URL: http://www.pylint.org/ URL: https://www.pylint.org/
Version: 1.4.1 Version: 1.4.5
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, 1) numversion = (1, 4, 5)
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.3', 'six'] install_requires = ['logilab-common >= 0.53.0', 'astroid >= 1.3.6,<1.4.0', 'six']
license = 'GPL' license = 'GPL'
description = "python code static checker" description = "python code static checker"
...@@ -67,4 +67,4 @@ scripts = [join('bin', filename) ...@@ -67,4 +67,4 @@ scripts = [join('bin', filename)
for filename in ('pylint', 'pylint-gui', "symilar", "epylint", for filename in ('pylint', 'pylint-gui', "symilar", "epylint",
"pyreverse")] "pyreverse")]
include_dirs = ['test'] include_dirs = [join('pylint', 'test')]
...@@ -497,11 +497,6 @@ functions, methods ...@@ -497,11 +497,6 @@ functions, methods
'bad-function option). Usual black listed functions are the ones ' 'bad-function option). Usual black listed functions are the ones '
'like map, or filter , where Python offers now some cleaner ' 'like map, or filter , where Python offers now some cleaner '
'alternative like list comprehension.'), '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", 'W0150': ("%s statement in finally block may swallow exception",
'lost-exception', 'lost-exception',
'Used when a break or a return statement is found inside the ' 'Used when a break or a return statement is found inside the '
...@@ -753,7 +748,7 @@ functions, methods ...@@ -753,7 +748,7 @@ functions, methods
"""just print a warning on exec statements""" """just print a warning on exec statements"""
self.add_message('exec-used', node=node) 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', 'exec-used', 'missing-reversed-argument',
'bad-reversed-sequence') 'bad-reversed-sequence')
def visit_callfunc(self, node): def visit_callfunc(self, node):
...@@ -774,18 +769,6 @@ functions, methods ...@@ -774,18 +769,6 @@ functions, methods
self.add_message('eval-used', node=node) self.add_message('eval-used', node=node)
if name in self.config.bad_functions: if name in self.config.bad_functions:
self.add_message('bad-builtin', node=node, args=name) 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') @check_messages('assert-on-tuple')
def visit_assert(self, node): def visit_assert(self, node):
...@@ -1171,6 +1154,11 @@ class DocStringChecker(_BasicChecker): ...@@ -1171,6 +1154,11 @@ class DocStringChecker(_BasicChecker):
lines = node.body[-1].lineno - node.body[0].lineno + 1 lines = node.body[-1].lineno - node.body[0].lineno + 1
else: else:
lines = 0 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 max_lines = self.config.docstring_min_length
if node_type != 'module' and max_lines > -1 and lines < max_lines: if node_type != 'module' and max_lines > -1 and lines < max_lines:
......
...@@ -107,9 +107,9 @@ def _is_attribute_property(name, klass): ...@@ -107,9 +107,9 @@ def _is_attribute_property(name, klass):
MSGS = { MSGS = {
'F0202': ('Unable to check methods signature (%s / %s)', 'F0202': ('Unable to check methods signature (%s / %s)',
'method-check-failed', 'method-check-failed',
'Used when Pylint has been unable to check methods signature \ 'Used when Pylint has been unable to check methods signature '
compatibility for an unexpected reason. Please report this kind \ 'compatibility for an unexpected reason. Please report this kind '
if you don\'t make sense of it.'), 'if you don\'t make sense of it.'),
'E0202': ('An attribute defined in %s line %s hides this method', 'E0202': ('An attribute defined in %s line %s hides this method',
'method-hidden', 'method-hidden',
...@@ -118,35 +118,35 @@ MSGS = { ...@@ -118,35 +118,35 @@ MSGS = {
'client code.'), 'client code.'),
'E0203': ('Access to member %r before its definition line %s', 'E0203': ('Access to member %r before its definition line %s',
'access-member-before-definition', 'access-member-before-definition',
'Used when an instance member is accessed before it\'s actually\ 'Used when an instance member is accessed before it\'s actually '
assigned.'), 'assigned.'),
'W0201': ('Attribute %r defined outside __init__', 'W0201': ('Attribute %r defined outside __init__',
'attribute-defined-outside-init', 'attribute-defined-outside-init',
'Used when an instance attribute is defined outside the __init__\ 'Used when an instance attribute is defined outside the __init__ '
method.'), 'method.'),
'W0212': ('Access to a protected member %s of a client class', # E0214 'W0212': ('Access to a protected member %s of a client class', # E0214
'protected-access', 'protected-access',
'Used when a protected member (i.e. class member with a name \ 'Used when a protected member (i.e. class member with a name '
beginning with an underscore) is access outside the class or a \ 'beginning with an underscore) is access outside the class or a '
descendant of the class where it\'s defined.'), 'descendant of the class where it\'s defined.'),
'E0211': ('Method has no argument', 'E0211': ('Method has no argument',
'no-method-argument', 'no-method-argument',
'Used when a method which should have the bound instance as \ 'Used when a method which should have the bound instance as '
first argument has no argument defined.'), 'first argument has no argument defined.'),
'E0213': ('Method should have "self" as first argument', 'E0213': ('Method should have "self" as first argument',
'no-self-argument', 'no-self-argument',
'Used when a method has an attribute different the "self" as\ 'Used when a method has an attribute different the "self" as '
first argument. This is considered as an error since this is\ 'first argument. This is considered as an error since this is '
a so common convention that you shouldn\'t break it!'), 'a so common convention that you shouldn\'t break it!'),
'C0202': ('Class method %s should have %s as first argument', # E0212 'C0202': ('Class method %s should have %s as first argument',
'bad-classmethod-argument', 'bad-classmethod-argument',
'Used when a class method has a first argument named differently ' 'Used when a class method has a first argument named differently '
'than the value specified in valid-classmethod-first-arg option ' 'than the value specified in valid-classmethod-first-arg option '
'(default to "cls"), recommended to easily differentiate them ' '(default to "cls"), recommended to easily differentiate them '
'from regular instance methods.'), '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', 'bad-mcs-method-argument',
'Used when a metaclass method has a first agument named ' 'Used when a metaclass method has a first agument named '
'differently than the value specified in valid-classmethod-first' 'differently than the value specified in valid-classmethod-first'
...@@ -167,58 +167,58 @@ MSGS = { ...@@ -167,58 +167,58 @@ MSGS = {
), ),
'R0201': ('Method could be a function', 'R0201': ('Method could be a function',
'no-self-use', 'no-self-use',
'Used when a method doesn\'t use its bound instance, and so could\ 'Used when a method doesn\'t use its bound instance, and so could '
be written as a function.' 'be written as a function.'
), ),
'E0221': ('Interface resolved to %s is not a class', 'E0221': ('Interface resolved to %s is not a class',
'interface-is-not-class', 'interface-is-not-class',
'Used when a class claims to implement an interface which is not \ 'Used when a class claims to implement an interface which is not '
a class.'), 'a class.'),
'E0222': ('Missing method %r from %s interface', 'E0222': ('Missing method %r from %s interface',
'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 %r 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 %r 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.'),
'W0223': ('Method %r is abstract in class %r but is not overridden', 'W0223': ('Method %r is abstract in class %r but is not overridden',
'abstract-method', 'abstract-method',
'Used when an abstract method (i.e. raise NotImplementedError) is \ 'Used when an abstract method (i.e. raise NotImplementedError) is '
not overridden in concrete class.' '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', 'unresolved-interface',
'Used when a Pylint as failed to find interfaces implemented by \ 'Used when a Pylint as failed to find interfaces implemented by '
a class'), ' a class'),
'W0231': ('__init__ method from base class %r is not called', 'W0231': ('__init__ method from base class %r is not called',
'super-init-not-called', 'super-init-not-called',
'Used when an ancestor class method has an __init__ method \ 'Used when an ancestor class method has an __init__ method '
which is not called by a derived class.'), 'which is not called by a derived class.'),
'W0232': ('Class has no __init__ method', 'W0232': ('Class has no __init__ method',
'no-init', 'no-init',
'Used when a class has no __init__ method, neither its parent \ 'Used when a class has no __init__ method, neither its parent '
classes.'), 'classes.'),
'W0233': ('__init__ method from a non direct base class %r is called', 'W0233': ('__init__ method from a non direct base class %r is called',
'non-parent-init-called', 'non-parent-init-called',
'Used when an __init__ method is called on a class which is not \ 'Used when an __init__ method is called on a class which is not '
in the direct ancestors for the analysed class.'), 'in the direct ancestors for the analysed class.'),
'W0234': ('__iter__ returns non-iterator', 'W0234': ('__iter__ returns non-iterator',
'non-iterator-returned', 'non-iterator-returned',
'Used when an __iter__ method returns something which is not an \ 'Used when an __iter__ method returns something which is not an '
iterable (i.e. has no `%s` method)' % NEXT_METHOD), 'iterable (i.e. has no `%s` method)' % NEXT_METHOD),
'E0235': ('__exit__ must accept 3 arguments: type, value, traceback', 'E0235': ('__exit__ must accept 3 arguments: type, value, traceback',
'bad-context-manager', 'bad-context-manager',
'Used when the __exit__ special method, belonging to a \ 'Used when the __exit__ special method, belonging to a '
context manager, does not accept 3 arguments \ 'context manager, does not accept 3 arguments '
(type, value, traceback).'), '(type, value, traceback).'),
'E0236': ('Invalid object %r in __slots__, must contain ' 'E0236': ('Invalid object %r in __slots__, must contain '
'only non empty strings', 'only non empty strings',
'invalid-slots-object', 'invalid-slots-object',
...@@ -893,26 +893,27 @@ a metaclass class method.'} ...@@ -893,26 +893,27 @@ a metaclass class method.'}
expr.expr.func.name == 'super': expr.expr.func.name == 'super':
return return
try: try:
klass = next(expr.expr.infer()) for klass in expr.expr.infer():
if klass is YES: if klass is YES:
continue continue
# The infered klass can be super(), which was # The infered klass can be super(), which was
# assigned to a variable and the `__init__` was called later. # assigned to a variable and the `__init__`
# # was called later.
# base = super() #
# base.__init__(...) # base = super()
# base.__init__(...)
if (isinstance(klass, astroid.Instance) and
isinstance(klass._proxied, astroid.Class) and if (isinstance(klass, astroid.Instance) and
is_builtin_object(klass._proxied) and isinstance(klass._proxied, astroid.Class) and
klass._proxied.name == 'super'): is_builtin_object(klass._proxied) and
return klass._proxied.name == 'super'):
try: return
del not_called_yet[klass] try:
except KeyError: del not_called_yet[klass]
if klass not in to_call: except KeyError:
self.add_message('non-parent-init-called', if klass not in to_call:
node=expr, args=klass.name) self.add_message('non-parent-init-called',
node=expr, args=klass.name)
except astroid.InferenceError: except astroid.InferenceError:
continue continue
for klass, method in six.iteritems(not_called_yet): for klass, method in six.iteritems(not_called_yet):
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
import re import re
from collections import defaultdict from collections import defaultdict
from astroid import Function, If, InferenceError from astroid import If, InferenceError
from pylint.interfaces import IAstroidChecker from pylint.interfaces import IAstroidChecker
from pylint.checkers import BaseChecker from pylint.checkers import BaseChecker
...@@ -28,17 +28,6 @@ from pylint.checkers.utils import check_messages ...@@ -28,17 +28,6 @@ from pylint.checkers.utils import check_messages
IGNORED_ARGUMENT_NAMES = re.compile('_.*') 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 = { MSGS = {
'R0901': ('Too many ancestors (%s/%s)', 'R0901': ('Too many ancestors (%s/%s)',
'too-many-ancestors', 'too-many-ancestors',
...@@ -75,14 +64,6 @@ MSGS = { ...@@ -75,14 +64,6 @@ MSGS = {
'too-many-statements', 'too-many-statements',
'Used when a function or method has too many statements. You \ 'Used when a function or method has too many statements. You \
should then split it in smaller functions / methods.'), 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', 'R0923': ('Interface not implemented',
'interface-not-implemented', 'interface-not-implemented',
'Used when an interface class is not implemented anywhere.'), 'Used when an interface class is not implemented anywhere.'),
...@@ -165,9 +146,7 @@ class MisdesignChecker(BaseChecker): ...@@ -165,9 +146,7 @@ class MisdesignChecker(BaseChecker):
self.stats = None self.stats = None
self._returns = None self._returns = None
self._branches = None self._branches = None
self._used_abstracts = None
self._used_ifaces = None self._used_ifaces = None
self._abstracts = None
self._ifaces = None self._ifaces = None
self._stmts = 0 self._stmts = 0
...@@ -176,27 +155,17 @@ class MisdesignChecker(BaseChecker): ...@@ -176,27 +155,17 @@ class MisdesignChecker(BaseChecker):
self.stats = self.linter.add_stats() self.stats = self.linter.add_stats()
self._returns = [] self._returns = []
self._branches = defaultdict(int) self._branches = defaultdict(int)
self._used_abstracts = {}
self._used_ifaces = {} self._used_ifaces = {}
self._abstracts = []
self._ifaces = [] self._ifaces = []
# Check 'R0921', 'R0922', 'R0923'
def close(self): def close(self):
"""check that abstract/interface classes are used""" """check that 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])
for iface in self._ifaces: for iface in self._ifaces:
if not iface in self._used_ifaces: if not iface in self._used_ifaces:
self.add_message('interface-not-implemented', node=iface) self.add_message('interface-not-implemented', node=iface)
@check_messages('too-many-ancestors', 'too-many-instance-attributes', @check_messages('too-many-ancestors', 'too-many-instance-attributes',
'too-few-public-methods', 'too-many-public-methods', 'too-few-public-methods', 'too-many-public-methods',
'abstract-class-not-used', 'abstract-class-little-used',
'interface-not-implemented') 'interface-not-implemented')
def visit_class(self, node): def visit_class(self, node):
"""check size of inheritance hierarchy and number of instance attributes """check size of inheritance hierarchy and number of instance attributes
...@@ -213,10 +182,8 @@ class MisdesignChecker(BaseChecker): ...@@ -213,10 +182,8 @@ class MisdesignChecker(BaseChecker):
self.add_message('too-many-instance-attributes', node=node, self.add_message('too-many-instance-attributes', node=node,
args=(len(node.instance_attrs), args=(len(node.instance_attrs),
self.config.max_attributes)) self.config.max_attributes))
# update abstract / interface classes structures # update interface classes structures
if class_is_abstract(node): if node.type == 'interface' and node.name != 'Interface':
self._abstracts.append(node)
elif node.type == 'interface' and node.name != 'Interface':
self._ifaces.append(node) self._ifaces.append(node)
for parent in node.ancestors(False): for parent in node.ancestors(False):
if parent.name == 'Interface': if parent.name == 'Interface':
...@@ -228,34 +195,36 @@ class MisdesignChecker(BaseChecker): ...@@ -228,34 +195,36 @@ class MisdesignChecker(BaseChecker):
except InferenceError: except InferenceError:
# XXX log ? # XXX log ?
pass 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', @check_messages('too-few-public-methods', 'too-many-public-methods')
'too-few-public-methods', 'too-many-public-methods',
'abstract-class-not-used', 'abstract-class-little-used',
'interface-not-implemented')
def leave_class(self, node): def leave_class(self, node):
"""check number of public methods""" """check number of public methods"""
nb_public_methods = 0 my_methods = sum(1 for method in node.mymethods()
for method in node.mymethods(): if not method.name.startswith('_'))
if not method.name.startswith('_'): all_methods = sum(1 for method in node.methods()
nb_public_methods += 1 if not method.name.startswith('_'))
# Does the class contain less than 20 public methods ?
if nb_public_methods > self.config.max_public_methods: # 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, self.add_message('too-many-public-methods', node=node,
args=(nb_public_methods, args=(my_methods,
self.config.max_public_methods)) self.config.max_public_methods))
# stop here for exception, metaclass and interface classes # stop here for exception, metaclass and interface classes
if node.type != 'class': if node.type != 'class':
return 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, self.add_message('too-few-public-methods', node=node,
args=(nb_public_methods, args=(all_methods,
self.config.min_public_methods)) self.config.min_public_methods))
@check_messages('too-many-return-statements', 'too-many-branches', @check_messages('too-many-return-statements', 'too-many-branches',
...@@ -356,7 +325,6 @@ class MisdesignChecker(BaseChecker): ...@@ -356,7 +325,6 @@ class MisdesignChecker(BaseChecker):
"""increments the branches counter""" """increments the branches counter"""
self._branches[node.scope()] += branchesnum self._branches[node.scope()] += branchesnum
# FIXME: make a nice report...
def register(linter): def register(linter):
"""required method to auto register this checker """ """required method to auto register this checker """
......
...@@ -303,7 +303,7 @@ class ContinuedLineState(object): ...@@ -303,7 +303,7 @@ class ContinuedLineState(object):
self.retained_warnings.append((token_position, state, valid_offsets)) self.retained_warnings.append((token_position, state, valid_offsets))
def get_valid_offsets(self, idx): 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 # 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 # reset two indent levels because the dict value is ended implicitly
stack_top = -1 stack_top = -1
...@@ -353,21 +353,22 @@ class ContinuedLineState(object): ...@@ -353,21 +353,22 @@ class ContinuedLineState(object):
def _continuation_inside_bracket(self, bracket, pos): def _continuation_inside_bracket(self, bracket, pos):
"""Extracts indentation information for a continued indent.""" """Extracts indentation information for a continued indent."""
indentation = _get_indent_length(self._tokens.line(pos)) 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( return _ContinuedIndent(
CONTINUED_BLOCK, CONTINUED_BLOCK,
bracket, bracket,
pos, pos,
_Offsets(self._tokens.start_col(pos)), _Offsets(token_start),
_BeforeBlockOffsets(self._tokens.start_col(pos+1), _BeforeBlockOffsets(next_token_start, next_token_start + self._continuation_size))
self._tokens.start_col(pos+1) + self._continuation_size))
else: else:
return _ContinuedIndent( return _ContinuedIndent(
CONTINUED, CONTINUED,
bracket, bracket,
pos, pos,
_Offsets(self._tokens.start_col(pos)), _Offsets(token_start),
_Offsets(self._tokens.start_col(pos+1))) _Offsets(next_token_start))
def pop_token(self): def pop_token(self):
self._cont_stack.pop() self._cont_stack.pop()
...@@ -442,7 +443,8 @@ class FormatChecker(BaseTokenChecker): ...@@ -442,7 +443,8 @@ class FormatChecker(BaseTokenChecker):
('expected-line-ending-format', ('expected-line-ending-format',
{'type': 'choice', 'metavar': '<empty or LF or CRLF>', 'default': '', {'type': 'choice', 'metavar': '<empty or LF or CRLF>', 'default': '',
'choices': ['', 'LF', 'CRLF'], '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): def __init__(self, linter=None):
...@@ -796,10 +798,12 @@ class FormatChecker(BaseTokenChecker): ...@@ -796,10 +798,12 @@ class FormatChecker(BaseTokenChecker):
# check if line ending is as expected # check if line ending is as expected
expected = self.config.expected_line_ending_format expected = self.config.expected_line_ending_format
if expected: 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' line_ending = 'LF' if line_ending == '\n' else 'CRLF'
if line_ending != expected: 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): def _process_retained_warnings(self, tokens, current_pos):
......
...@@ -12,12 +12,13 @@ ...@@ -12,12 +12,13 @@
# this program; if not, write to the Free Software Foundation, Inc., # this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""Check Python 2 code for Python 2/3 source-compatible issues.""" """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 re
import tokenize import tokenize
import astroid import astroid
from astroid import bases
from pylint import checkers, interfaces from pylint import checkers, interfaces
from pylint.utils import WarningScope from pylint.utils import WarningScope
from pylint.checkers import utils from pylint.checkers import utils
...@@ -46,6 +47,46 @@ def _check_dict_node(node): ...@@ -46,6 +47,46 @@ def _check_dict_node(node):
return (not inferred_types return (not inferred_types
or any(isinstance(x, astroid.Dict) for x in 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): class Python3Checker(checkers.BaseChecker):
...@@ -177,13 +218,13 @@ class Python3Checker(checkers.BaseChecker): ...@@ -177,13 +218,13 @@ class Python3Checker(checkers.BaseChecker):
'W1618': ('import missing `from __future__ import absolute_import`', 'W1618': ('import missing `from __future__ import absolute_import`',
'no-absolute-import', 'no-absolute-import',
'Used when an import is not accompanied by ' 'Used when an import is not accompanied by '
'`from __future__ import absolute_import`' '``from __future__ import absolute_import`` '
' (default behaviour in Python 3)', '(default behaviour in Python 3)',
{'maxversion': (3, 0)}), {'maxversion': (3, 0)}),
'W1619': ('division w/o __future__ statement', 'W1619': ('division w/o __future__ statement',
'old-division', 'old-division',
'Used for non-floor division w/o a float literal or ' '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)', '(Python 3 returns a float for int division unconditionally)',
{'maxversion': (3, 0)}), {'maxversion': (3, 0)}),
'W1620': ('Calling a dict.iter*() method', 'W1620': ('Calling a dict.iter*() method',
...@@ -244,14 +285,7 @@ class Python3Checker(checkers.BaseChecker): ...@@ -244,14 +285,7 @@ class Python3Checker(checkers.BaseChecker):
'Used when a __cmp__ method is defined ' 'Used when a __cmp__ method is defined '
'(method is not used by Python 3)', '(method is not used by Python 3)',
{'maxversion': (3, 0)}), {'maxversion': (3, 0)}),
'W1631': ('map is used as implicitly evaluated call', # 'W1631': replaced by W1636
'implicit-map-evaluation',
'Used when the map builtin is used as implicitly '
'evaluated call, as in "map(func, args)" on a single line. '
'This behaviour will not work in Python 3, where '
'map is a generator and must be evaluated. '
'Prefer a for-loop as alternative.',
{'maxversion': (3, 0)}),
'W1632': ('input built-in referenced', 'W1632': ('input built-in referenced',
'input-builtin', 'input-builtin',
'Used when the input built-in is referenced ' 'Used when the input built-in is referenced '
...@@ -262,6 +296,44 @@ class Python3Checker(checkers.BaseChecker): ...@@ -262,6 +296,44 @@ class Python3Checker(checkers.BaseChecker):
'Used when the round built-in is referenced ' 'Used when the round built-in is referenced '
'(backwards-incompatible semantics in Python 3)', '(backwards-incompatible semantics in Python 3)',
{'maxversion': (3, 0)}), {'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([ _bad_builtins = frozenset([
...@@ -273,11 +345,13 @@ class Python3Checker(checkers.BaseChecker): ...@@ -273,11 +345,13 @@ class Python3Checker(checkers.BaseChecker):
'execfile', 'execfile',
'file', 'file',
'input', # Not missing, but incompatible semantics 'input', # Not missing, but incompatible semantics
'intern',
'long', 'long',
'raw_input', 'raw_input',
'reduce', 'reduce',
'round', # Not missing, but incompatible semantics 'round', # Not missing, but incompatible semantics
'StandardError', 'StandardError',
'unichr',
'unicode', 'unicode',
'xrange', 'xrange',
'reload', 'reload',
...@@ -299,6 +373,11 @@ class Python3Checker(checkers.BaseChecker): ...@@ -299,6 +373,11 @@ class Python3Checker(checkers.BaseChecker):
self._future_absolute_import = False self._future_absolute_import = False
super(Python3Checker, self).__init__(*args, **kwargs) 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): def visit_function(self, node):
if node.is_method() and node.name in self._unused_magic_methods: if node.is_method() and node.name in self._unused_magic_methods:
method_name = node.name method_name = node.name
...@@ -312,19 +391,10 @@ class Python3Checker(checkers.BaseChecker): ...@@ -312,19 +391,10 @@ class Python3Checker(checkers.BaseChecker):
if isinstance(arg, astroid.Tuple): if isinstance(arg, astroid.Tuple):
self.add_message('parameter-unpacking', node=arg) 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): def visit_name(self, node):
"""Detect when a "bad" built-in 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 _is_builtin(found_node):
if node.name in self._bad_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)
...@@ -363,22 +433,57 @@ class Python3Checker(checkers.BaseChecker): ...@@ -363,22 +433,57 @@ class Python3Checker(checkers.BaseChecker):
else: else:
self.add_message('old-division', node=node) self.add_message('old-division', node=node)
@utils.check_messages('next-method-called', def _check_cmp_argument(self, node):
'dict-iter-method', # Check that the `cmp` argument is used
'dict-view-method') 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): def visit_callfunc(self, node):
if not isinstance(node.func, astroid.Getattr): self._check_cmp_argument(node)
return
if any([node.args, node.starargs, node.kwargs]): if isinstance(node.func, astroid.Getattr):
return if any([node.args, node.starargs, node.kwargs]):
if node.func.attrname == 'next': return
self.add_message('next-method-called', node=node) if node.func.attrname == 'next':
else: self.add_message('next-method-called', node=node)
if _check_dict_node(node.func.expr): else:
if node.func.attrname in ('iterkeys', 'itervalues', 'iteritems'): if _check_dict_node(node.func.expr):
self.add_message('dict-iter-method', node=node) if node.func.attrname in ('iterkeys', 'itervalues', 'iteritems'):
elif node.func.attrname in ('viewkeys', 'viewvalues', 'viewitems'): self.add_message('dict-iter-method', node=node)
self.add_message('dict-view-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') @utils.check_messages('indexing-exception')
def visit_subscript(self, node): def visit_subscript(self, node):
......
...@@ -61,6 +61,9 @@ class SpellingChecker(BaseTokenChecker): ...@@ -61,6 +61,9 @@ class SpellingChecker(BaseTokenChecker):
'%s\nDid you mean: \'%s\'?', '%s\nDid you mean: \'%s\'?',
'wrong-spelling-in-docstring', 'wrong-spelling-in-docstring',
'Used when a word in docstring is not spelled correctly.'), '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', options = (('spelling-dict',
{'default' : '', 'type' : 'choice', 'metavar' : '<dict name>', {'default' : '', 'type' : 'choice', 'metavar' : '<dict name>',
...@@ -168,7 +171,13 @@ class SpellingChecker(BaseTokenChecker): ...@@ -168,7 +171,13 @@ class SpellingChecker(BaseTokenChecker):
word = word[2:] word = word[2:]
# If it is a known word, then continue. # 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 continue
# Store word to private dict or raise a message. # Store word to private dict or raise a message.
......
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""Checkers for various standard library functions.""" """Checkers for various standard library functions."""
import re
import six import six
import sys import sys
...@@ -27,10 +26,15 @@ from pylint.checkers import BaseChecker ...@@ -27,10 +26,15 @@ from pylint.checkers import BaseChecker
from pylint.checkers import utils 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): if sys.version_info >= (3, 0):
OPEN_MODULE = '_io' OPEN_MODULE = '_io'
TYPE_QNAME = 'builtins.type'
else: else:
OPEN_MODULE = '__builtin__' OPEN_MODULE = '__builtin__'
TYPE_QNAME = '__builtin__.type'
def _check_mode_str(mode): def _check_mode_str(mode):
...@@ -50,7 +54,6 @@ def _check_mode_str(mode): ...@@ -50,7 +54,6 @@ def _check_mode_str(mode):
reading = "r" in modes reading = "r" in modes
writing = "w" in modes writing = "w" in modes
appending = "a" in modes appending = "a" in modes
updating = "+" in modes
text = "t" in modes text = "t" in modes
binary = "b" in modes binary = "b" in modes
if "U" in modes: if "U" in modes:
...@@ -76,6 +79,15 @@ def _check_mode_str(mode): ...@@ -76,6 +79,15 @@ def _check_mode_str(mode):
return True 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): class StdlibChecker(BaseChecker):
__implements__ = (IAstroidChecker,) __implements__ = (IAstroidChecker,)
name = 'stdlib' name = 'stdlib'
...@@ -88,7 +100,7 @@ class StdlibChecker(BaseChecker): ...@@ -88,7 +100,7 @@ class StdlibChecker(BaseChecker):
'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',
'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 ' 'subtle bugs when the time they represent matches '
'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.',
...@@ -96,10 +108,16 @@ class StdlibChecker(BaseChecker): ...@@ -96,10 +108,16 @@ class StdlibChecker(BaseChecker):
'W1503': ('Redundant use of %s with constant ' 'W1503': ('Redundant use of %s with constant '
'value %r', 'value %r',
'redundant-unittest-assert', 'redundant-unittest-assert',
'The first argument of assertTrue and assertFalse is' 'The first argument of assertTrue and assertFalse is '
'a condition. If a constant is passed as parameter, that' 'a condition. If a constant is passed as parameter, that '
'condition will be always true. In this case a warning ' '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') @utils.check_messages('bad-open-mode', 'redundant-unittest-assert')
...@@ -132,6 +150,14 @@ class StdlibChecker(BaseChecker): ...@@ -132,6 +150,14 @@ class StdlibChecker(BaseChecker):
for value in node.values: for value in node.values:
self._check_datetime(value) 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): def _check_redundant_assert(self, node, infer):
if (isinstance(infer, astroid.BoundMethod) and if (isinstance(infer, astroid.BoundMethod) and
node.args and isinstance(node.args[0], astroid.Const) and node.args and isinstance(node.args[0], astroid.Const) and
...@@ -152,7 +178,6 @@ class StdlibChecker(BaseChecker): ...@@ -152,7 +178,6 @@ 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:
...@@ -167,6 +192,24 @@ class StdlibChecker(BaseChecker): ...@@ -167,6 +192,24 @@ class StdlibChecker(BaseChecker):
self.add_message('bad-open-mode', node=node, self.add_message('bad-open-mode', node=node,
args=mode_arg.value) 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): def register(linter):
"""required method to auto register this checker """ """required method to auto register this checker """
......
...@@ -181,7 +181,7 @@ def parse_format_method_string(format_string): ...@@ -181,7 +181,7 @@ def parse_format_method_string(format_string):
if isinstance(keyname, numbers.Number): if isinstance(keyname, numbers.Number):
# In Python 2 it will return long which will lead # In Python 2 it will return long which will lead
# to different output between 2 and 3 # to different output between 2 and 3
manual_pos_arg.add(keyname) manual_pos_arg.add(str(keyname))
keyname = int(keyname) keyname = int(keyname)
keys.append((keyname, list(fielditerator))) keys.append((keyname, list(fielditerator)))
else: 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). # Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE).
# http://www.logilab.fr/ -- mailto:contact@logilab.fr # http://www.logilab.fr/ -- mailto:contact@logilab.fr
# #
......
...@@ -38,7 +38,7 @@ from pylint.reporters.guireporter import GUIReporter ...@@ -38,7 +38,7 @@ from pylint.reporters.guireporter import GUIReporter
HOME = os.path.expanduser('~/') HOME = os.path.expanduser('~/')
HISTORY = '.pylint-gui-history' HISTORY = '.pylint-gui-history'
COLORS = {'(I)':'lightblue', COLORS = {'(I)':'green',
'(C)':'blue', '(R)':'darkblue', '(C)':'blue', '(R)':'darkblue',
'(W)':'black', '(E)':'darkred', '(W)':'black', '(E)':'darkred',
'(F)':'red'} '(F)':'red'}
......
...@@ -48,7 +48,7 @@ from logilab.common import optik_ext ...@@ -48,7 +48,7 @@ from logilab.common import optik_ext
from logilab.common import interface from logilab.common import interface
from logilab.common import textutils from logilab.common import textutils
from logilab.common import ureports from logilab.common import ureports
from logilab.common.__pkginfo__ import version as common_version from logilab.common import __version__ as common_version
import six import six
from pylint import checkers from pylint import checkers
...@@ -60,6 +60,10 @@ from pylint.__pkginfo__ import version ...@@ -60,6 +60,10 @@ from pylint.__pkginfo__ import version
MANAGER = astroid.MANAGER 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): def _get_new_args(message):
location = ( location = (
...@@ -105,6 +109,24 @@ def _merge_stats(stats): ...@@ -105,6 +109,24 @@ def _merge_stats(stats):
return merged 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 ######################################################### # Python Linter class #########################################################
MSGS = { MSGS = {
...@@ -177,10 +199,10 @@ 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 def _warn_deprecated(option, optname, *args): # pylint: disable=unused-argument
sys.stderr.write('Warning: option %s is deprecated and ignored.\n' % (optname,)) 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} 'type': opt_type, 'action': 'callback', 'callback': _warn_deprecated}
...@@ -190,6 +212,8 @@ if multiprocessing is not None: ...@@ -190,6 +212,8 @@ if multiprocessing is not None:
tasks_queue, results_queue, self._config = self._args # pylint: disable=no-member tasks_queue, results_queue, self._config = self._args # pylint: disable=no-member
self._config["jobs"] = 1 # Child does not parallelize any further. 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. # Run linter for received files/modules.
for file_or_module in iter(tasks_queue.get, 'STOP'): for file_or_module in iter(tasks_queue.get, 'STOP'):
...@@ -197,7 +221,8 @@ if multiprocessing is not None: ...@@ -197,7 +221,8 @@ if multiprocessing is not None:
try: try:
results_queue.put(result) results_queue.put(result)
except Exception as ex: 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) print(ex, file=sys.stderr)
results_queue.put({}) results_queue.put({})
...@@ -212,6 +237,13 @@ if multiprocessing is not None: ...@@ -212,6 +237,13 @@ if multiprocessing is not None:
linter.load_configuration(**self._config) linter.load_configuration(**self._config)
linter.set_reporter(reporters.CollectingReporter()) 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. # Run the checks.
linter.check(file_or_module) linter.check(file_or_module)
...@@ -350,8 +382,9 @@ class PyLinter(configuration.OptionsManagerMixIn, ...@@ -350,8 +382,9 @@ class PyLinter(configuration.OptionsManagerMixIn,
'See doc for all details') 'See doc for all details')
}), }),
('include-ids', _deprecated_option('i', 'yn')), ('include-ids', _deprecated_option('i', 'yn',
('symbols', _deprecated_option('s', 'yn')), INCLUDE_IDS_HELP)),
('symbols', _deprecated_option('s', 'yn', SYMBOLS_HELP)),
('jobs', ('jobs',
{'type' : 'int', 'metavar': '<n-processes>', {'type' : 'int', 'metavar': '<n-processes>',
...@@ -373,6 +406,19 @@ class PyLinter(configuration.OptionsManagerMixIn, ...@@ -373,6 +406,19 @@ class PyLinter(configuration.OptionsManagerMixIn,
' loading into the active Python interpreter and may run' ' loading into the active Python interpreter and may run'
' arbitrary code')} ' 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 = ( option_groups = (
...@@ -427,6 +473,8 @@ class PyLinter(configuration.OptionsManagerMixIn, ...@@ -427,6 +473,8 @@ class PyLinter(configuration.OptionsManagerMixIn,
) )
self.register_checker(self) self.register_checker(self)
self._dynamic_plugins = set() self._dynamic_plugins = set()
self._python3_porting_mode = False
self._error_mode = False
self.load_provider_defaults() self.load_provider_defaults()
if reporter: if reporter:
self.set_reporter(reporter) self.set_reporter(reporter)
...@@ -555,11 +603,34 @@ class PyLinter(configuration.OptionsManagerMixIn, ...@@ -555,11 +603,34 @@ class PyLinter(configuration.OptionsManagerMixIn,
def error_mode(self): def error_mode(self):
"""error mode: enable only errors; no reports, no persistent""" """error mode: enable only errors; no reports, no persistent"""
self._error_mode = True
self.disable_noerror_messages() self.disable_noerror_messages()
self.disable('miscellaneous') 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('reports', False)
self.set_option('persistent', 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 ############################################# # block level option handling #############################################
# #
# see func_block_disable_msg.py test case for expected behaviour # see func_block_disable_msg.py test case for expected behaviour
...@@ -596,7 +667,8 @@ class PyLinter(configuration.OptionsManagerMixIn, ...@@ -596,7 +667,8 @@ class PyLinter(configuration.OptionsManagerMixIn,
except KeyError: except KeyError:
meth = self._bw_options_methods[opt] meth = self._bw_options_methods[opt]
# found a "(dis|en)able-msg" pragma deprecated suppresssion # 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): for msgid in textutils.splitstrip(value):
# Add the line where a control pragma was encountered. # Add the line where a control pragma was encountered.
if opt in control_pragmas: if opt in control_pragmas:
...@@ -604,7 +676,8 @@ class PyLinter(configuration.OptionsManagerMixIn, ...@@ -604,7 +676,8 @@ class PyLinter(configuration.OptionsManagerMixIn,
try: try:
if (opt, msgid) == ('disable', 'all'): 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.add_message('file-ignored', line=start[0])
self._ignore_file = True self._ignore_file = True
return return
...@@ -674,19 +747,9 @@ class PyLinter(configuration.OptionsManagerMixIn, ...@@ -674,19 +747,9 @@ class PyLinter(configuration.OptionsManagerMixIn,
with fix_import_path(files_or_modules): with fix_import_path(files_or_modules):
self._do_check(files_or_modules) self._do_check(files_or_modules)
else: else:
# Hack that permits running pylint, on Windows, with -m switch with _patch_sysmodules():
# 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:
self._parallel_check(files_or_modules) self._parallel_check(files_or_modules)
finally:
if mock_main:
sys.modules.pop('__main__')
def _parallel_task(self, files_or_modules): def _parallel_task(self, files_or_modules):
# Prepare configuration for child linters. # Prepare configuration for child linters.
...@@ -697,6 +760,7 @@ class PyLinter(configuration.OptionsManagerMixIn, ...@@ -697,6 +760,7 @@ class PyLinter(configuration.OptionsManagerMixIn,
for optname, optdict, val in opt_providers.options_and_values(): for optname, optdict, val in opt_providers.options_and_values():
if optname not in filter_options: if optname not in filter_options:
config[optname] = configuration.format_option_value(optdict, val) config[optname] = configuration.format_option_value(optdict, val)
config['python3_porting_mode'] = self._python3_porting_mode
childs = [] childs = []
manager = multiprocessing.Manager() # pylint: disable=no-member manager = multiprocessing.Manager() # pylint: disable=no-member
...@@ -805,7 +869,8 @@ class PyLinter(configuration.OptionsManagerMixIn, ...@@ -805,7 +869,8 @@ class PyLinter(configuration.OptionsManagerMixIn,
self.current_file = ast_node.file # pylint: disable=maybe-no-member self.current_file = ast_node.file # pylint: disable=maybe-no-member
self.check_astroid_module(ast_node, walker, rawcheckers, tokencheckers) self.check_astroid_module(ast_node, walker, rawcheckers, tokencheckers)
# warn about spurious inline messages handling # 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) self.add_message(msgid, line, None, args)
# notify global end # notify global end
self.stats['statement'] = walker.nbstatements self.stats['statement'] = walker.nbstatements
...@@ -889,6 +954,7 @@ class PyLinter(configuration.OptionsManagerMixIn, ...@@ -889,6 +954,7 @@ class PyLinter(configuration.OptionsManagerMixIn,
self.stats = {'by_module' : {}, self.stats = {'by_module' : {},
'by_msg' : {}, 'by_msg' : {},
} }
MANAGER.optimize_ast = self.config.optimize_ast
MANAGER.always_load_extensions = self.config.unsafe_load_any_extension MANAGER.always_load_extensions = self.config.unsafe_load_any_extension
MANAGER.extension_package_whitelist.update( MANAGER.extension_package_whitelist.update(
self.config.extension_pkg_whitelist) self.config.extension_pkg_whitelist)
...@@ -1315,8 +1381,7 @@ group are mutually exclusive.'), ...@@ -1315,8 +1381,7 @@ group are mutually exclusive.'),
def cb_python3_porting_mode(self, *args, **kwargs): def cb_python3_porting_mode(self, *args, **kwargs):
"""Activate only the python3 porting checker.""" """Activate only the python3 porting checker."""
self.linter.disable('all') self.linter.python3_porting_mode()
self.linter.enable('python3')
def cb_list_confidence_levels(option, optname, value, parser): def cb_list_confidence_levels(option, optname, value, parser):
......
...@@ -13,8 +13,9 @@ ...@@ -13,8 +13,9 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""HTML reporter""" """HTML reporter"""
import itertools
import string
import sys import sys
from cgi import escape
from logilab.common.ureports import HTMLWriter, Section, Table from logilab.common.ureports import HTMLWriter, Section, Table
...@@ -32,11 +33,41 @@ class HTMLReporter(BaseReporter): ...@@ -32,11 +33,41 @@ class HTMLReporter(BaseReporter):
def __init__(self, output=sys.stdout): def __init__(self, output=sys.stdout):
BaseReporter.__init__(self, output) BaseReporter.__init__(self, output)
self.msgs = [] 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): def handle_message(self, msg):
"""manage message of different type and in the context of path""" """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): def set_output(self, output=None):
"""set output stream """set output stream
...@@ -55,11 +86,12 @@ class HTMLReporter(BaseReporter): ...@@ -55,11 +86,12 @@ class HTMLReporter(BaseReporter):
""" """
if self.msgs: if self.msgs:
# add stored messages to the layout # add stored messages to the layout
msgs = ['type', 'module', 'object', 'line', 'col_offset', 'message'] msgs = self.header
cols = len(self.header)
msgs += self.msgs msgs += self.msgs
sect = Section('Messages') sect = Section('Messages')
layout.append(sect) layout.append(sect)
sect.append(Table(cols=6, children=msgs, rheaders=1)) sect.append(Table(cols=cols, children=msgs, rheaders=1))
self.msgs = [] self.msgs = []
HTMLWriter().format(layout, self.out) HTMLWriter().format(layout, self.out)
......
...@@ -93,7 +93,7 @@ def get_tests_info(input_dir, msg_dir, prefix, suffix): ...@@ -93,7 +93,7 @@ def get_tests_info(input_dir, msg_dir, prefix, suffix):
class TestReporter(BaseReporter): class TestReporter(BaseReporter):
"""reporter storing plain text messages""" """reporter storing plain text messages"""
__implements____ = IReporter __implements__ = IReporter
def __init__(self): # pylint: disable=super-init-not-called def __init__(self): # pylint: disable=super-init-not-called
......
...@@ -225,6 +225,11 @@ class MessagesHandlerMixIn(object): ...@@ -225,6 +225,11 @@ class MessagesHandlerMixIn(object):
self._msgs_state = {} self._msgs_state = {}
self.msg_status = 0 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): def disable(self, msgid, scope='package', line=None, ignore_unknown=False):
"""don't output message of the given id""" """don't output message of the given id"""
assert scope in ('package', 'module') 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