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