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')