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

pylint: update to 1.4.5

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

Bug: None
Test: `./pylint *.py` shows no regressions
Change-Id: I494379e18da002d479cb193412fcc49f8c66f376
Reviewed-on: https://chromium-review.googlesource.com/1103763Reviewed-by: 's avatarRobbie Iannucci <iannucci@chromium.org>
Commit-Queue: Mike Frysinger <vapier@chromium.org>
parent 1cabdc46
URL: http://www.logilab.org/project/logilab-astng URL: https://github.com/PyCQA/astroid
Version: 1.3.4 Version: 1.3.8
License: GPL License: GPL
License File: LICENSE.txt License File: LICENSE.txt
Description: Description:
This directory contains the logilab-astng module, required for pylint. This directory contains the astroid module, required for pylint.
Local Modifications: Local Modifications:
None None
...@@ -20,10 +20,10 @@ distname = 'astroid' ...@@ -20,10 +20,10 @@ distname = 'astroid'
modname = 'astroid' modname = 'astroid'
numversion = (1, 3, 4) numversion = (1, 3, 8)
version = '.'.join([str(num) for num in numversion]) version = '.'.join([str(num) for num in numversion])
install_requires = ['logilab-common >= 0.60.0', 'six'] install_requires = ['logilab-common>=0.63.0', 'six']
license = 'LGPL' license = 'LGPL'
......
# copyright 2003-2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of astroid.
#
# astroid is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 2.1 of the License, or (at your
# option) any later version.
#
# astroid is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with astroid. If not, see <http://www.gnu.org/licenses/>.
"""Small AST optimizations."""
import _ast
from astroid import nodes
__all__ = ('ASTPeepholeOptimizer', )
try:
_TYPES = (_ast.Str, _ast.Bytes)
except AttributeError:
_TYPES = (_ast.Str, )
class ASTPeepholeOptimizer(object):
"""Class for applying small optimizations to generate new AST."""
def optimize_binop(self, node):
"""Optimize BinOps with string Const nodes on the lhs.
This fixes an infinite recursion crash, where multiple
strings are joined using the addition operator. With a
sufficient number of such strings, astroid will fail
with a maximum recursion limit exceeded. The
function will return a Const node with all the strings
already joined.
Return ``None`` if no AST node can be obtained
through optimization.
"""
ast_nodes = []
current = node
while isinstance(current, _ast.BinOp):
# lhs must be a BinOp with the addition operand.
if not isinstance(current.left, _ast.BinOp):
return
if (not isinstance(current.left.op, _ast.Add)
or not isinstance(current.op, _ast.Add)):
return
# rhs must a str / bytes.
if not isinstance(current.right, _TYPES):
return
ast_nodes.append(current.right.s)
current = current.left
if (isinstance(current, _ast.BinOp)
and isinstance(current.left, _TYPES)
and isinstance(current.right, _TYPES)):
# Stop early if we are at the last BinOp in
# the operation
ast_nodes.append(current.right.s)
ast_nodes.append(current.left.s)
break
if not ast_nodes:
return
# If we have inconsistent types, bail out.
known = type(ast_nodes[0])
if any(type(element) is not known
for element in ast_nodes[1:]):
return
value = known().join(reversed(ast_nodes))
newnode = nodes.Const(value)
return newnode
...@@ -58,52 +58,28 @@ class Proxy(object): ...@@ -58,52 +58,28 @@ class Proxy(object):
# Inference ################################################################## # Inference ##################################################################
MISSING = object()
class InferenceContext(object): class InferenceContext(object):
__slots__ = ('path', 'callcontext', 'boundnode', 'infered') __slots__ = ('path', 'lookupname', 'callcontext', 'boundnode', 'infered')
def __init__(self, def __init__(self, path=None, infered=None):
path=None, callcontext=None, boundnode=None, infered=None): self.path = path or set()
if path is None: self.lookupname = None
self.path = frozenset() self.callcontext = None
else: self.boundnode = None
self.path = path self.infered = infered or {}
self.callcontext = callcontext
self.boundnode = boundnode def push(self, node):
if infered is None: name = self.lookupname
self.infered = {} if (node, name) in self.path:
else: raise StopIteration()
self.infered = infered self.path.add((node, name))
def push(self, key): def clone(self):
# This returns a NEW context with the same attributes, but a new key # XXX copy lookupname/callcontext ?
# added to `path`. The intention is that it's only passed to callees clone = InferenceContext(self.path, infered=self.infered)
# and then destroyed; otherwise scope() may not work correctly. clone.callcontext = self.callcontext
# The cache will be shared, since it's the same exact dict. clone.boundnode = self.boundnode
if key in self.path: return clone
# End the containing generator
raise StopIteration
return InferenceContext(
self.path.union([key]),
self.callcontext,
self.boundnode,
self.infered,
)
@contextmanager
def scope(self, callcontext=MISSING, boundnode=MISSING):
try:
orig = self.callcontext, self.boundnode
if callcontext is not MISSING:
self.callcontext = callcontext
if boundnode is not MISSING:
self.boundnode = boundnode
yield
finally:
self.callcontext, self.boundnode = orig
def cache_generator(self, key, generator): def cache_generator(self, key, generator):
results = [] results = []
...@@ -114,28 +90,38 @@ class InferenceContext(object): ...@@ -114,28 +90,38 @@ class InferenceContext(object):
self.infered[key] = tuple(results) self.infered[key] = tuple(results)
return return
@contextmanager
def restore_path(self):
path = set(self.path)
yield
self.path = path
def _infer_stmts(stmts, context, frame=None, lookupname=None): def copy_context(context):
if context is not None:
return context.clone()
else:
return InferenceContext()
def _infer_stmts(stmts, context, frame=None):
"""return an iterator on statements inferred by each statement in <stmts> """return an iterator on statements inferred by each statement in <stmts>
""" """
stmt = None stmt = None
infered = False infered = False
if context is None: if context is not None:
name = context.lookupname
context = context.clone()
else:
name = None
context = InferenceContext() context = InferenceContext()
for stmt in stmts: for stmt in stmts:
if stmt is YES: if stmt is YES:
yield stmt yield stmt
infered = True infered = True
continue continue
context.lookupname = stmt._infer_name(frame, name)
kw = {}
infered_name = stmt._infer_name(frame, lookupname)
if infered_name is not None:
# only returns not None if .infer() accepts a lookupname kwarg
kw['lookupname'] = infered_name
try: try:
for infered in stmt.infer(context, **kw): for infered in stmt.infer(context):
yield infered yield infered
infered = True infered = True
except UnresolvableName: except UnresolvableName:
...@@ -197,12 +183,13 @@ class Instance(Proxy): ...@@ -197,12 +183,13 @@ class Instance(Proxy):
context = InferenceContext() context = InferenceContext()
try: try:
# avoid recursively inferring the same attr on the same class # avoid recursively inferring the same attr on the same class
new_context = context.push((self._proxied, name))
context.push((self._proxied, name))
# XXX frame should be self._proxied, or not ? # XXX frame should be self._proxied, or not ?
get_attr = self.getattr(name, new_context, lookupclass=False) get_attr = self.getattr(name, context, lookupclass=False)
return _infer_stmts( return _infer_stmts(
self._wrap_attr(get_attr, new_context), self._wrap_attr(get_attr, context),
new_context, context,
frame=self, frame=self,
) )
except NotFoundError: except NotFoundError:
...@@ -210,7 +197,7 @@ class Instance(Proxy): ...@@ -210,7 +197,7 @@ class Instance(Proxy):
# fallback to class'igetattr since it has some logic to handle # fallback to class'igetattr since it has some logic to handle
# descriptors # descriptors
return self._wrap_attr(self._proxied.igetattr(name, context), return self._wrap_attr(self._proxied.igetattr(name, context),
context) context)
except NotFoundError: except NotFoundError:
raise InferenceError(name) raise InferenceError(name)
...@@ -301,9 +288,9 @@ class BoundMethod(UnboundMethod): ...@@ -301,9 +288,9 @@ class BoundMethod(UnboundMethod):
return True return True
def infer_call_result(self, caller, context): def infer_call_result(self, caller, context):
with context.scope(boundnode=self.bound): context = context.clone()
for infered in self._proxied.infer_call_result(caller, context): context.boundnode = self.bound
yield infered return self._proxied.infer_call_result(caller, context)
class Generator(Instance): class Generator(Instance):
...@@ -335,8 +322,7 @@ def path_wrapper(func): ...@@ -335,8 +322,7 @@ def path_wrapper(func):
"""wrapper function handling context""" """wrapper function handling context"""
if context is None: if context is None:
context = InferenceContext() context = InferenceContext()
context = context.push((node, kwargs.get('lookupname'))) context.push(node)
yielded = set() yielded = set()
for res in _func(node, context, **kwargs): for res in _func(node, context, **kwargs):
# unproxy only true instance, not const, tuple, dict... # unproxy only true instance, not const, tuple, dict...
...@@ -409,7 +395,8 @@ class NodeNG(object): ...@@ -409,7 +395,8 @@ class NodeNG(object):
if not context: if not context:
return self._infer(context, **kwargs) return self._infer(context, **kwargs)
key = (self, kwargs.get('lookupname'), context.callcontext, context.boundnode) key = (self, context.lookupname,
context.callcontext, context.boundnode)
if key in context.infered: if key in context.infered:
return iter(context.infered[key]) return iter(context.infered[key])
......
"""Astroid hooks for pytest.""" """Astroid hooks for pytest."""
from astroid import MANAGER, register_module_extender from astroid import MANAGER, register_module_extender
from astroid.builder import AstroidBuilder from astroid.builder import AstroidBuilder
def pytest_transform(): def pytest_transform():
return AstroidBuilder(MANAGER).string_build(''' return AstroidBuilder(MANAGER).string_build('''
try: try:
import _pytest.mark import _pytest.mark
import _pytest.recwarn import _pytest.recwarn
import _pytest.runner import _pytest.runner
import _pytest.python import _pytest.python
except ImportError: except ImportError:
pass pass
else: else:
deprecated_call = _pytest.recwarn.deprecated_call deprecated_call = _pytest.recwarn.deprecated_call
exit = _pytest.runner.exit exit = _pytest.runner.exit
fail = _pytest.runner.fail fail = _pytest.runner.fail
fixture = _pytest.python.fixture fixture = _pytest.python.fixture
importorskip = _pytest.runner.importorskip importorskip = _pytest.runner.importorskip
mark = _pytest.mark.MarkGenerator() mark = _pytest.mark.MarkGenerator()
raises = _pytest.python.raises raises = _pytest.python.raises
skip = _pytest.runner.skip skip = _pytest.runner.skip
yield_fixture = _pytest.python.yield_fixture yield_fixture = _pytest.python.yield_fixture
''') ''')
register_module_extender(MANAGER, 'pytest', pytest_transform) register_module_extender(MANAGER, 'pytest', pytest_transform)
register_module_extender(MANAGER, 'py.test', pytest_transform) register_module_extender(MANAGER, 'py.test', pytest_transform)
...@@ -19,38 +19,61 @@ ...@@ -19,38 +19,61 @@
"""Hooks for nose library.""" """Hooks for nose library."""
import re import re
import unittest import textwrap
from astroid import List, MANAGER, register_module_extender import astroid
from astroid.builder import AstroidBuilder import astroid.builder
_BUILDER = astroid.builder.AstroidBuilder(astroid.MANAGER)
def _pep8(name, caps=re.compile('([A-Z])')): def _pep8(name, caps=re.compile('([A-Z])')):
return caps.sub(lambda m: '_' + m.groups()[0].lower(), name) return caps.sub(lambda m: '_' + m.groups()[0].lower(), name)
def nose_transform(): def _nose_tools_functions():
"""Custom transform for the nose.tools module.""" """Get an iterator of names and bound methods."""
module = _BUILDER.string_build(textwrap.dedent('''
import unittest
class Test(unittest.TestCase):
pass
a = Test()
'''))
try:
case = next(module['a'].infer())
except astroid.InferenceError:
return
for method in case.methods():
if method.name.startswith('assert') and '_' not in method.name:
pep8_name = _pep8(method.name)
yield pep8_name, astroid.BoundMethod(method, case)
builder = AstroidBuilder(MANAGER) def _nose_tools_transform(node):
stub = AstroidBuilder(MANAGER).string_build('''__all__ = []''') for method_name, method in _nose_tools_functions():
unittest_module = builder.module_build(unittest.case) node.locals[method_name] = [method]
case = unittest_module['TestCase']
def _nose_tools_trivial_transform():
"""Custom transform for the nose.tools module."""
stub = _BUILDER.string_build('''__all__ = []''')
all_entries = ['ok_', 'eq_'] all_entries = ['ok_', 'eq_']
for method_name, method in case.locals.items(): for pep8_name, method in _nose_tools_functions():
if method_name.startswith('assert') and '_' not in method_name: all_entries.append(pep8_name)
pep8_name = _pep8(method_name) stub[pep8_name] = method
all_entries.append(pep8_name)
stub[pep8_name] = method[0]
# Update the __all__ variable, since nose.tools # Update the __all__ variable, since nose.tools
# does this manually with .append. # does this manually with .append.
all_assign = stub['__all__'].parent all_assign = stub['__all__'].parent
all_object = List(all_entries) all_object = astroid.List(all_entries)
all_object.parent = all_assign all_object.parent = all_assign
all_assign.value = all_object all_assign.value = all_object
return stub return stub
register_module_extender(MANAGER, 'nose.tools.trivial', nose_transform) astroid.register_module_extender(astroid.MANAGER, 'nose.tools.trivial',
_nose_tools_trivial_transform)
astroid.MANAGER.register_transform(astroid.Module, _nose_tools_transform,
lambda n: n.name == 'nose.tools')
...@@ -28,7 +28,7 @@ from astroid.manager import AstroidManager ...@@ -28,7 +28,7 @@ from astroid.manager import AstroidManager
from astroid.exceptions import (AstroidError, InferenceError, NoDefault, from astroid.exceptions import (AstroidError, InferenceError, NoDefault,
NotFoundError, UnresolvableName) NotFoundError, UnresolvableName)
from astroid.bases import (YES, Instance, InferenceContext, from astroid.bases import (YES, Instance, InferenceContext,
_infer_stmts, path_wrapper, _infer_stmts, copy_context, path_wrapper,
raise_if_nothing_infered) raise_if_nothing_infered)
from astroid.protocols import ( from astroid.protocols import (
_arguments_infer_argname, _arguments_infer_argname,
...@@ -175,89 +175,92 @@ def infer_name(self, context=None): ...@@ -175,89 +175,92 @@ def infer_name(self, context=None):
if not stmts: if not stmts:
raise UnresolvableName(self.name) raise UnresolvableName(self.name)
return _infer_stmts(stmts, context, frame, self.name) context = context.clone()
context.lookupname = self.name
return _infer_stmts(stmts, context, frame)
nodes.Name._infer = path_wrapper(infer_name) nodes.Name._infer = path_wrapper(infer_name)
nodes.AssName.infer_lhs = infer_name # won't work with a path wrapper nodes.AssName.infer_lhs = infer_name # won't work with a path wrapper
def infer_callfunc(self, context=None): def infer_callfunc(self, context=None):
"""infer a CallFunc node by trying to guess what the function returns""" """infer a CallFunc node by trying to guess what the function returns"""
if context is None: callcontext = context.clone()
context = InferenceContext() callcontext.callcontext = CallContext(self.args, self.starargs, self.kwargs)
callcontext.boundnode = None
for callee in self.func.infer(context): for callee in self.func.infer(context):
with context.scope( if callee is YES:
callcontext=CallContext(self.args, self.starargs, self.kwargs), yield callee
boundnode=None, continue
): try:
if callee is YES: if hasattr(callee, 'infer_call_result'):
yield callee for infered in callee.infer_call_result(self, callcontext):
continue yield infered
try: except InferenceError:
if hasattr(callee, 'infer_call_result'): ## XXX log error ?
for infered in callee.infer_call_result(self, context): continue
yield infered
except InferenceError:
## XXX log error ?
continue
nodes.CallFunc._infer = path_wrapper(raise_if_nothing_infered(infer_callfunc)) nodes.CallFunc._infer = path_wrapper(raise_if_nothing_infered(infer_callfunc))
def infer_import(self, context=None, asname=True, lookupname=None): def infer_import(self, context=None, asname=True):
"""infer an Import node: return the imported module/object""" """infer an Import node: return the imported module/object"""
if lookupname is None: name = context.lookupname
if name is None:
raise InferenceError() raise InferenceError()
if asname: if asname:
yield self.do_import_module(self.real_name(lookupname)) yield self.do_import_module(self.real_name(name))
else: else:
yield self.do_import_module(lookupname) yield self.do_import_module(name)
nodes.Import._infer = path_wrapper(infer_import) nodes.Import._infer = path_wrapper(infer_import)
def infer_name_module(self, name): def infer_name_module(self, name):
context = InferenceContext() context = InferenceContext()
return self.infer(context, asname=False, lookupname=name) context.lookupname = name
return self.infer(context, asname=False)
nodes.Import.infer_name_module = infer_name_module nodes.Import.infer_name_module = infer_name_module
def infer_from(self, context=None, asname=True, lookupname=None): def infer_from(self, context=None, asname=True):
"""infer a From nodes: return the imported module/object""" """infer a From nodes: return the imported module/object"""
if lookupname is None: name = context.lookupname
if name is None:
raise InferenceError() raise InferenceError()
if asname: if asname:
lookupname = self.real_name(lookupname) name = self.real_name(name)
module = self.do_import_module() module = self.do_import_module()
try: try:
return _infer_stmts(module.getattr(lookupname, ignore_locals=module is self.root()), context, lookupname=lookupname) context = copy_context(context)
context.lookupname = name
return _infer_stmts(module.getattr(name, ignore_locals=module is self.root()), context)
except NotFoundError: except NotFoundError:
raise InferenceError(lookupname) raise InferenceError(name)
nodes.From._infer = path_wrapper(infer_from) nodes.From._infer = path_wrapper(infer_from)
def infer_getattr(self, context=None): def infer_getattr(self, context=None):
"""infer a Getattr node by using getattr on the associated object""" """infer a Getattr node by using getattr on the associated object"""
if not context:
context = InferenceContext()
for owner in self.expr.infer(context): for owner in self.expr.infer(context):
if owner is YES: if owner is YES:
yield owner yield owner
continue continue
try: try:
with context.scope(boundnode=owner): context.boundnode = owner
for obj in owner.igetattr(self.attrname, context): for obj in owner.igetattr(self.attrname, context):
yield obj yield obj
context.boundnode = None
except (NotFoundError, InferenceError): except (NotFoundError, InferenceError):
pass context.boundnode = None
except AttributeError: except AttributeError:
# XXX method / function # XXX method / function
pass context.boundnode = None
nodes.Getattr._infer = path_wrapper(raise_if_nothing_infered(infer_getattr)) nodes.Getattr._infer = path_wrapper(raise_if_nothing_infered(infer_getattr))
nodes.AssAttr.infer_lhs = raise_if_nothing_infered(infer_getattr) # # won't work with a path wrapper nodes.AssAttr.infer_lhs = raise_if_nothing_infered(infer_getattr) # # won't work with a path wrapper
def infer_global(self, context=None, lookupname=None): def infer_global(self, context=None):
if lookupname is None: if context.lookupname is None:
raise InferenceError() raise InferenceError()
try: try:
return _infer_stmts(self.root().getattr(lookupname), context) return _infer_stmts(self.root().getattr(context.lookupname), context)
except NotFoundError: except NotFoundError:
raise InferenceError() raise InferenceError()
nodes.Global._infer = path_wrapper(infer_global) nodes.Global._infer = path_wrapper(infer_global)
...@@ -349,10 +352,11 @@ def infer_binop(self, context=None): ...@@ -349,10 +352,11 @@ def infer_binop(self, context=None):
nodes.BinOp._infer = path_wrapper(infer_binop) nodes.BinOp._infer = path_wrapper(infer_binop)
def infer_arguments(self, context=None, lookupname=None): def infer_arguments(self, context=None):
if lookupname is None: name = context.lookupname
if name is None:
raise InferenceError() raise InferenceError()
return _arguments_infer_argname(self, lookupname, context) return _arguments_infer_argname(self, name, context)
nodes.Arguments._infer = infer_arguments nodes.Arguments._infer = infer_arguments
......
...@@ -90,6 +90,7 @@ class AstroidManager(OptionsProviderMixIn): ...@@ -90,6 +90,7 @@ class AstroidManager(OptionsProviderMixIn):
self.transforms = collections.defaultdict(list) self.transforms = collections.defaultdict(list)
self._failed_import_hooks = [] self._failed_import_hooks = []
self.always_load_extensions = False self.always_load_extensions = False
self.optimize_ast = False
self.extension_package_whitelist = set() self.extension_package_whitelist = set()
def ast_from_file(self, filepath, modname=None, fallback=True, source=False): def ast_from_file(self, filepath, modname=None, fallback=True, source=False):
......
...@@ -65,11 +65,11 @@ try: ...@@ -65,11 +65,11 @@ try:
# with the prefix from which the virtualenv was created. This throws # with the prefix from which the virtualenv was created. This throws
# off the detection logic for standard library modules, thus the # off the detection logic for standard library modules, thus the
# workaround. # workaround.
STD_LIB_DIRS = { STD_LIB_DIRS = set([
get_python_lib(standard_lib=True, prefix=sys.prefix), get_python_lib(standard_lib=True, prefix=sys.prefix),
# Take care of installations where exec_prefix != prefix. # Take care of installations where exec_prefix != prefix.
get_python_lib(standard_lib=True, prefix=sys.exec_prefix), get_python_lib(standard_lib=True, prefix=sys.exec_prefix),
get_python_lib(standard_lib=True)} get_python_lib(standard_lib=True)])
if os.name == 'nt': if os.name == 'nt':
STD_LIB_DIRS.add(os.path.join(sys.prefix, 'dlls')) STD_LIB_DIRS.add(os.path.join(sys.prefix, 'dlls'))
try: try:
......
...@@ -130,7 +130,8 @@ class LookupMixIn(object): ...@@ -130,7 +130,8 @@ class LookupMixIn(object):
the lookup method the lookup method
""" """
frame, stmts = self.lookup(name) frame, stmts = self.lookup(name)
return _infer_stmts(stmts, None, frame) context = InferenceContext()
return _infer_stmts(stmts, context, frame)
def _filter_stmts(self, stmts, frame, offset): def _filter_stmts(self, stmts, frame, offset):
"""filter statements to remove ignorable statements. """filter statements to remove ignorable statements.
...@@ -308,7 +309,7 @@ class Arguments(NodeNG, AssignTypeMixin): ...@@ -308,7 +309,7 @@ class Arguments(NodeNG, AssignTypeMixin):
@cachedproperty @cachedproperty
def fromlineno(self): def fromlineno(self):
lineno = super(Arguments, self).fromlineno lineno = super(Arguments, self).fromlineno
return max(lineno, self.parent.fromlineno) return max(lineno, self.parent.fromlineno or 0)
def format_args(self): def format_args(self):
"""return arguments formatted as string""" """return arguments formatted as string"""
......
...@@ -34,6 +34,7 @@ on From and Import : ...@@ -34,6 +34,7 @@ on From and Import :
""" """
# pylint: disable=unused-import
__docformat__ = "restructuredtext en" __docformat__ = "restructuredtext en"
......
...@@ -20,10 +20,11 @@ where it makes sense. ...@@ -20,10 +20,11 @@ where it makes sense.
""" """
__doctype__ = "restructuredtext en" __doctype__ = "restructuredtext en"
import collections
from astroid.exceptions import InferenceError, NoDefault, NotFoundError from astroid.exceptions import InferenceError, NoDefault, NotFoundError
from astroid.node_classes import unpack_infer from astroid.node_classes import unpack_infer
from astroid.bases import InferenceContext, \ from astroid.bases import InferenceContext, copy_context, \
raise_if_nothing_infered, yes_if_nothing_infered, Instance, YES raise_if_nothing_infered, yes_if_nothing_infered, Instance, YES
from astroid.nodes import const_factory from astroid.nodes import const_factory
from astroid import nodes from astroid import nodes
...@@ -282,8 +283,7 @@ def _arguments_infer_argname(self, name, context): ...@@ -282,8 +283,7 @@ def _arguments_infer_argname(self, name, context):
# if there is a default value, yield it. And then yield YES to reflect # if there is a default value, yield it. And then yield YES to reflect
# we can't guess given argument value # we can't guess given argument value
try: try:
if context is None: context = copy_context(context)
context = InferenceContext()
for infered in self.default_value(name).infer(context): for infered in self.default_value(name).infer(context):
yield infered yield infered
yield YES yield YES
...@@ -295,6 +295,8 @@ def arguments_assigned_stmts(self, node, context, asspath=None): ...@@ -295,6 +295,8 @@ def arguments_assigned_stmts(self, node, context, asspath=None):
if context.callcontext: if context.callcontext:
# reset call context/name # reset call context/name
callcontext = context.callcontext callcontext = context.callcontext
context = copy_context(context)
context.callcontext = None
return callcontext.infer_argument(self.parent, node.name, context) return callcontext.infer_argument(self.parent, node.name, context)
return _arguments_infer_argname(self, node.name, context) return _arguments_infer_argname(self, node.name, context)
nodes.Arguments.assigned_stmts = arguments_assigned_stmts nodes.Arguments.assigned_stmts = arguments_assigned_stmts
...@@ -359,3 +361,55 @@ def with_assigned_stmts(self, node, context=None, asspath=None): ...@@ -359,3 +361,55 @@ def with_assigned_stmts(self, node, context=None, asspath=None):
nodes.With.assigned_stmts = raise_if_nothing_infered(with_assigned_stmts) nodes.With.assigned_stmts = raise_if_nothing_infered(with_assigned_stmts)
def starred_assigned_stmts(self, node=None, context=None, asspath=None):
stmt = self.statement()
if not isinstance(stmt, (nodes.Assign, nodes.For)):
raise InferenceError()
if isinstance(stmt, nodes.Assign):
value = stmt.value
lhs = stmt.targets[0]
if sum(1 for node in lhs.nodes_of_class(nodes.Starred)) > 1:
# Too many starred arguments in the expression.
raise InferenceError()
if context is None:
context = InferenceContext()
try:
rhs = next(value.infer(context))
except InferenceError:
yield YES
return
if rhs is YES or not hasattr(rhs, 'elts'):
# Not interested in inferred values without elts.
yield YES
return
elts = collections.deque(rhs.elts[:])
if len(lhs.elts) > len(rhs.elts):
# a, *b, c = (1, 2)
raise InferenceError()
# Unpack iteratively the values from the rhs of the assignment,
# until the find the starred node. What will remain will
# be the list of values which the Starred node will represent
# This is done in two steps, from left to right to remove
# anything before the starred node and from right to left
# to remvoe anything after the starred node.
for index, node in enumerate(lhs.elts):
if not isinstance(node, nodes.Starred):
elts.popleft()
continue
lhs_elts = collections.deque(reversed(lhs.elts[index:]))
for node in lhs_elts:
if not isinstance(node, nodes.Starred):
elts.pop()
continue
# We're done
for elt in elts:
yield elt
break
nodes.Starred.assigned_stmts = starred_assigned_stmts
...@@ -154,7 +154,7 @@ def object_build_function(node, member, localname): ...@@ -154,7 +154,7 @@ def object_build_function(node, member, localname):
if varkw is not None: if varkw is not None:
args.append(varkw) args.append(varkw)
func = build_function(getattr(member, '__name__', None) or localname, args, func = build_function(getattr(member, '__name__', None) or localname, args,
defaults, member.func_code.co_flags, member.__doc__) defaults, six.get_function_code(member).co_flags, member.__doc__)
node.add_local_node(func, localname) node.add_local_node(func, localname)
def object_build_datadescriptor(node, member, name): def object_build_datadescriptor(node, member, name):
......
...@@ -23,7 +23,7 @@ import sys ...@@ -23,7 +23,7 @@ import sys
from _ast import ( from _ast import (
Expr as Discard, Str, Expr as Discard, Str,
# binary operators # binary operators
Add, Div, FloorDiv, Mod, Mult, Pow, Sub, BitAnd, BitOr, BitXor, Add, BinOp, Div, FloorDiv, Mod, Mult, Pow, Sub, BitAnd, BitOr, BitXor,
LShift, RShift, LShift, RShift,
# logical operators # logical operators
And, Or, And, Or,
...@@ -34,6 +34,7 @@ from _ast import ( ...@@ -34,6 +34,7 @@ from _ast import (
) )
from astroid import nodes as new from astroid import nodes as new
from astroid import astpeephole
_BIN_OP_CLASSES = {Add: '+', _BIN_OP_CLASSES = {Add: '+',
...@@ -136,6 +137,7 @@ class TreeRebuilder(object): ...@@ -136,6 +137,7 @@ class TreeRebuilder(object):
self._delayed_assattr = [] self._delayed_assattr = []
self._visit_meths = {} self._visit_meths = {}
self._transform = manager.transform self._transform = manager.transform
self._peepholer = astpeephole.ASTPeepholeOptimizer()
def visit_module(self, node, modname, modpath, package): def visit_module(self, node, modname, modpath, package):
"""visit a Module node by returning a fresh instance of it""" """visit a Module node by returning a fresh instance of it"""
...@@ -281,6 +283,24 @@ class TreeRebuilder(object): ...@@ -281,6 +283,24 @@ class TreeRebuilder(object):
def visit_binop(self, node, parent): def visit_binop(self, node, parent):
"""visit a BinOp node by returning a fresh instance of it""" """visit a BinOp node by returning a fresh instance of it"""
if isinstance(node.left, BinOp) and self._manager.optimize_ast:
# Optimize BinOp operations in order to remove
# redundant recursion. For instance, if the
# following code is parsed in order to obtain
# its ast, then the rebuilder will fail with an
# infinite recursion, the same will happen with the
# inference engine as well. There's no need to hold
# so many objects for the BinOp if they can be reduced
# to something else (also, the optimization
# might handle only Const binops, which isn't a big
# problem for the correctness of the program).
#
# ("a" + "b" + # one thousand more + "c")
newnode = self._peepholer.optimize_binop(node)
if newnode:
_lineno_parent(node, newnode, parent)
return newnode
newnode = new.BinOp() newnode = new.BinOp()
_lineno_parent(node, newnode, parent) _lineno_parent(node, newnode, parent)
newnode.left = self.visit(node.left, newnode) newnode.left = self.visit(node.left, newnode)
......
...@@ -39,8 +39,8 @@ from astroid.exceptions import NotFoundError, \ ...@@ -39,8 +39,8 @@ from astroid.exceptions import NotFoundError, \
AstroidBuildingException, InferenceError, ResolveError AstroidBuildingException, InferenceError, ResolveError
from astroid.node_classes import Const, DelName, DelAttr, \ from astroid.node_classes import Const, DelName, DelAttr, \
Dict, From, List, Pass, Raise, Return, Tuple, Yield, YieldFrom, \ Dict, From, List, Pass, Raise, Return, Tuple, Yield, YieldFrom, \
LookupMixIn, const_factory as cf, unpack_infer, Name, CallFunc LookupMixIn, const_factory as cf, unpack_infer, CallFunc
from astroid.bases import NodeNG, InferenceContext, Instance,\ from astroid.bases import NodeNG, InferenceContext, Instance, copy_context, \
YES, Generator, UnboundMethod, BoundMethod, _infer_stmts, \ YES, Generator, UnboundMethod, BoundMethod, _infer_stmts, \
BUILTINS BUILTINS
from astroid.mixins import FilterStmtsMixin from astroid.mixins import FilterStmtsMixin
...@@ -376,10 +376,10 @@ class Module(LocalsDictNodeNG): ...@@ -376,10 +376,10 @@ class Module(LocalsDictNodeNG):
"""inferred getattr""" """inferred getattr"""
# set lookup name since this is necessary to infer on import nodes for # set lookup name since this is necessary to infer on import nodes for
# instance # instance
if not context: context = copy_context(context)
context = InferenceContext() context.lookupname = name
try: try:
return _infer_stmts(self.getattr(name, context), context, frame=self, lookupname=name) return _infer_stmts(self.getattr(name, context), context, frame=self)
except NotFoundError: except NotFoundError:
raise InferenceError(name) raise InferenceError(name)
...@@ -822,7 +822,8 @@ class Function(Statement, Lambda): ...@@ -822,7 +822,8 @@ class Function(Statement, Lambda):
c = Class('temporary_class', None) c = Class('temporary_class', None)
c.hide = True c.hide = True
c.parent = self c.parent = self
c.bases = [next(b.infer(context)) for b in caller.args[1:]] bases = [next(b.infer(context)) for b in caller.args[1:]]
c.bases = [base for base in bases if base != YES]
c._metaclass = metaclass c._metaclass = metaclass
yield c yield c
return return
...@@ -1036,7 +1037,21 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin): ...@@ -1036,7 +1037,21 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin):
yield Instance(self) yield Instance(self)
def scope_lookup(self, node, name, offset=0): def scope_lookup(self, node, name, offset=0):
if node in self.bases: if any(node == base or base.parent_of(node)
for base in self.bases):
# Handle the case where we have either a name
# in the bases of a class, which exists before
# the actual definition or the case where we have
# a Getattr node, with that name.
#
# name = ...
# class A(name):
# def name(self): ...
#
# import name
# class A(name.Name):
# def name(self): ...
frame = self.parent.frame() frame = self.parent.frame()
# line offset to avoid that class A(A) resolve the ancestor to # line offset to avoid that class A(A) resolve the ancestor to
# the defined class # the defined class
...@@ -1070,29 +1085,33 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin): ...@@ -1070,29 +1085,33 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin):
return return
for stmt in self.bases: for stmt in self.bases:
try: with context.restore_path():
for baseobj in stmt.infer(context): try:
if not isinstance(baseobj, Class): for baseobj in stmt.infer(context):
if isinstance(baseobj, Instance): if not isinstance(baseobj, Class):
baseobj = baseobj._proxied if isinstance(baseobj, Instance):
else: baseobj = baseobj._proxied
# duh ? else:
continue # duh ?
if not baseobj.hide: continue
if baseobj in yielded: if not baseobj.hide:
continue # cf xxx above if baseobj in yielded:
yielded.add(baseobj)
yield baseobj
if recurs:
for grandpa in baseobj.ancestors(recurs=True,
context=context):
if grandpa in yielded:
continue # cf xxx above continue # cf xxx above
yielded.add(grandpa) yielded.add(baseobj)
yield grandpa yield baseobj
except InferenceError: if recurs:
# XXX log error ? for grandpa in baseobj.ancestors(recurs=True,
continue context=context):
if grandpa is self:
# This class is the ancestor of itself.
break
if grandpa in yielded:
continue # cf xxx above
yielded.add(grandpa)
yield grandpa
except InferenceError:
# XXX log error ?
continue
def local_attr_ancestors(self, name, context=None): def local_attr_ancestors(self, name, context=None):
"""return an iterator on astroid representation of parent classes """return an iterator on astroid representation of parent classes
...@@ -1187,11 +1206,11 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin): ...@@ -1187,11 +1206,11 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin):
""" """
# set lookup name since this is necessary to infer on import nodes for # set lookup name since this is necessary to infer on import nodes for
# instance # instance
if not context: context = copy_context(context)
context = InferenceContext() context.lookupname = name
try: try:
for infered in _infer_stmts(self.getattr(name, context), context, for infered in _infer_stmts(self.getattr(name, context), context,
frame=self, lookupname=name): frame=self):
# yield YES object instead of descriptors when necessary # yield YES object instead of descriptors when necessary
if not isinstance(infered, Const) and isinstance(infered, Instance): if not isinstance(infered, Const) and isinstance(infered, Instance):
try: try:
...@@ -1398,6 +1417,10 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin): ...@@ -1398,6 +1417,10 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin):
Also, it will return None in the case the slots weren't inferred. Also, it will return None in the case the slots weren't inferred.
Otherwise, it will return a list of slot names. Otherwise, it will return a list of slot names.
""" """
if not self.newstyle:
raise NotImplementedError(
"The concept of slots is undefined for old-style classes.")
slots = self._islots() slots = self._islots()
try: try:
first = next(slots) first = next(slots)
...@@ -1453,7 +1476,9 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin): ...@@ -1453,7 +1476,9 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin):
"Could not obtain mro for old-style classes.") "Could not obtain mro for old-style classes.")
bases = list(self._inferred_bases(context=context)) bases = list(self._inferred_bases(context=context))
unmerged_mro = [[self]] + [base.mro() for base in bases] + [bases] unmerged_mro = ([[self]] +
[base.mro() for base in bases if base is not self] +
[bases])
_verify_duplicates_mro(unmerged_mro) _verify_duplicates_mro(unmerged_mro)
return _c3_merge(unmerged_mro) return _c3_merge(unmerged_mro)
URL: http://www.pylint.org/ URL: https://www.pylint.org/
Version: 1.4.1 Version: 1.4.5
License: GPL License: GPL
License File: LICENSE.txt License File: LICENSE.txt
......
...@@ -19,10 +19,10 @@ from __future__ import absolute_import ...@@ -19,10 +19,10 @@ from __future__ import absolute_import
modname = distname = 'pylint' modname = distname = 'pylint'
numversion = (1, 4, 1) numversion = (1, 4, 5)
version = '.'.join([str(num) for num in numversion]) version = '.'.join([str(num) for num in numversion])
install_requires = ['logilab-common >= 0.53.0', 'astroid >= 1.3.3', 'six'] install_requires = ['logilab-common >= 0.53.0', 'astroid >= 1.3.6,<1.4.0', 'six']
license = 'GPL' license = 'GPL'
description = "python code static checker" description = "python code static checker"
...@@ -67,4 +67,4 @@ scripts = [join('bin', filename) ...@@ -67,4 +67,4 @@ scripts = [join('bin', filename)
for filename in ('pylint', 'pylint-gui', "symilar", "epylint", for filename in ('pylint', 'pylint-gui', "symilar", "epylint",
"pyreverse")] "pyreverse")]
include_dirs = ['test'] include_dirs = [join('pylint', 'test')]
...@@ -497,11 +497,6 @@ functions, methods ...@@ -497,11 +497,6 @@ functions, methods
'bad-function option). Usual black listed functions are the ones ' 'bad-function option). Usual black listed functions are the ones '
'like map, or filter , where Python offers now some cleaner ' 'like map, or filter , where Python offers now some cleaner '
'alternative like list comprehension.'), 'alternative like list comprehension.'),
'W0142': ('Used * or ** magic',
'star-args',
'Used when a function or method is called using `*args` or '
'`**kwargs` to dispatch arguments. This doesn\'t improve '
'readability and should be used with care.'),
'W0150': ("%s statement in finally block may swallow exception", 'W0150': ("%s statement in finally block may swallow exception",
'lost-exception', 'lost-exception',
'Used when a break or a return statement is found inside the ' 'Used when a break or a return statement is found inside the '
...@@ -753,7 +748,7 @@ functions, methods ...@@ -753,7 +748,7 @@ functions, methods
"""just print a warning on exec statements""" """just print a warning on exec statements"""
self.add_message('exec-used', node=node) self.add_message('exec-used', node=node)
@check_messages('bad-builtin', 'star-args', 'eval-used', @check_messages('bad-builtin', 'eval-used',
'exec-used', 'missing-reversed-argument', 'exec-used', 'missing-reversed-argument',
'bad-reversed-sequence') 'bad-reversed-sequence')
def visit_callfunc(self, node): def visit_callfunc(self, node):
...@@ -774,18 +769,6 @@ functions, methods ...@@ -774,18 +769,6 @@ functions, methods
self.add_message('eval-used', node=node) self.add_message('eval-used', node=node)
if name in self.config.bad_functions: if name in self.config.bad_functions:
self.add_message('bad-builtin', node=node, args=name) self.add_message('bad-builtin', node=node, args=name)
if node.starargs or node.kwargs:
scope = node.scope()
if isinstance(scope, astroid.Function):
toprocess = [(n, vn) for (n, vn) in ((node.starargs, scope.args.vararg),
(node.kwargs, scope.args.kwarg)) if n]
if toprocess:
for cfnode, fargname in toprocess[:]:
if getattr(cfnode, 'name', None) == fargname:
toprocess.remove((cfnode, fargname))
if not toprocess:
return # star-args can be skipped
self.add_message('star-args', node=node.func)
@check_messages('assert-on-tuple') @check_messages('assert-on-tuple')
def visit_assert(self, node): def visit_assert(self, node):
...@@ -1171,6 +1154,11 @@ class DocStringChecker(_BasicChecker): ...@@ -1171,6 +1154,11 @@ class DocStringChecker(_BasicChecker):
lines = node.body[-1].lineno - node.body[0].lineno + 1 lines = node.body[-1].lineno - node.body[0].lineno + 1
else: else:
lines = 0 lines = 0
if node_type == 'module' and not lines:
# If the module has no body, there's no reason
# to require a docstring.
return
max_lines = self.config.docstring_min_length max_lines = self.config.docstring_min_length
if node_type != 'module' and max_lines > -1 and lines < max_lines: if node_type != 'module' and max_lines > -1 and lines < max_lines:
......
This diff is collapsed.
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
import re import re
from collections import defaultdict from collections import defaultdict
from astroid import Function, If, InferenceError from astroid import If, InferenceError
from pylint.interfaces import IAstroidChecker from pylint.interfaces import IAstroidChecker
from pylint.checkers import BaseChecker from pylint.checkers import BaseChecker
...@@ -28,17 +28,6 @@ from pylint.checkers.utils import check_messages ...@@ -28,17 +28,6 @@ from pylint.checkers.utils import check_messages
IGNORED_ARGUMENT_NAMES = re.compile('_.*') IGNORED_ARGUMENT_NAMES = re.compile('_.*')
def class_is_abstract(klass):
"""return true if the given class node should be considered as an abstract
class
"""
for attr in klass.values():
if isinstance(attr, Function):
if attr.is_abstract(pass_is_abstract=False):
return True
return False
MSGS = { MSGS = {
'R0901': ('Too many ancestors (%s/%s)', 'R0901': ('Too many ancestors (%s/%s)',
'too-many-ancestors', 'too-many-ancestors',
...@@ -75,14 +64,6 @@ MSGS = { ...@@ -75,14 +64,6 @@ MSGS = {
'too-many-statements', 'too-many-statements',
'Used when a function or method has too many statements. You \ 'Used when a function or method has too many statements. You \
should then split it in smaller functions / methods.'), should then split it in smaller functions / methods.'),
'R0921': ('Abstract class not referenced',
'abstract-class-not-used',
'Used when an abstract class is not used as ancestor anywhere.'),
'R0922': ('Abstract class is only referenced %s times',
'abstract-class-little-used',
'Used when an abstract class is used less than X times as \
ancestor.'),
'R0923': ('Interface not implemented', 'R0923': ('Interface not implemented',
'interface-not-implemented', 'interface-not-implemented',
'Used when an interface class is not implemented anywhere.'), 'Used when an interface class is not implemented anywhere.'),
...@@ -165,9 +146,7 @@ class MisdesignChecker(BaseChecker): ...@@ -165,9 +146,7 @@ class MisdesignChecker(BaseChecker):
self.stats = None self.stats = None
self._returns = None self._returns = None
self._branches = None self._branches = None
self._used_abstracts = None
self._used_ifaces = None self._used_ifaces = None
self._abstracts = None
self._ifaces = None self._ifaces = None
self._stmts = 0 self._stmts = 0
...@@ -176,27 +155,17 @@ class MisdesignChecker(BaseChecker): ...@@ -176,27 +155,17 @@ class MisdesignChecker(BaseChecker):
self.stats = self.linter.add_stats() self.stats = self.linter.add_stats()
self._returns = [] self._returns = []
self._branches = defaultdict(int) self._branches = defaultdict(int)
self._used_abstracts = {}
self._used_ifaces = {} self._used_ifaces = {}
self._abstracts = []
self._ifaces = [] self._ifaces = []
# Check 'R0921', 'R0922', 'R0923'
def close(self): def close(self):
"""check that abstract/interface classes are used""" """check that interface classes are used"""
for abstract in self._abstracts:
if not abstract in self._used_abstracts:
self.add_message('abstract-class-not-used', node=abstract)
elif self._used_abstracts[abstract] < 2:
self.add_message('abstract-class-little-used', node=abstract,
args=self._used_abstracts[abstract])
for iface in self._ifaces: for iface in self._ifaces:
if not iface in self._used_ifaces: if not iface in self._used_ifaces:
self.add_message('interface-not-implemented', node=iface) self.add_message('interface-not-implemented', node=iface)
@check_messages('too-many-ancestors', 'too-many-instance-attributes', @check_messages('too-many-ancestors', 'too-many-instance-attributes',
'too-few-public-methods', 'too-many-public-methods', 'too-few-public-methods', 'too-many-public-methods',
'abstract-class-not-used', 'abstract-class-little-used',
'interface-not-implemented') 'interface-not-implemented')
def visit_class(self, node): def visit_class(self, node):
"""check size of inheritance hierarchy and number of instance attributes """check size of inheritance hierarchy and number of instance attributes
...@@ -213,10 +182,8 @@ class MisdesignChecker(BaseChecker): ...@@ -213,10 +182,8 @@ class MisdesignChecker(BaseChecker):
self.add_message('too-many-instance-attributes', node=node, self.add_message('too-many-instance-attributes', node=node,
args=(len(node.instance_attrs), args=(len(node.instance_attrs),
self.config.max_attributes)) self.config.max_attributes))
# update abstract / interface classes structures # update interface classes structures
if class_is_abstract(node): if node.type == 'interface' and node.name != 'Interface':
self._abstracts.append(node)
elif node.type == 'interface' and node.name != 'Interface':
self._ifaces.append(node) self._ifaces.append(node)
for parent in node.ancestors(False): for parent in node.ancestors(False):
if parent.name == 'Interface': if parent.name == 'Interface':
...@@ -228,34 +195,36 @@ class MisdesignChecker(BaseChecker): ...@@ -228,34 +195,36 @@ class MisdesignChecker(BaseChecker):
except InferenceError: except InferenceError:
# XXX log ? # XXX log ?
pass pass
for parent in node.ancestors():
try:
self._used_abstracts[parent] += 1
except KeyError:
self._used_abstracts[parent] = 1
@check_messages('too-many-ancestors', 'too-many-instance-attributes', @check_messages('too-few-public-methods', 'too-many-public-methods')
'too-few-public-methods', 'too-many-public-methods',
'abstract-class-not-used', 'abstract-class-little-used',
'interface-not-implemented')
def leave_class(self, node): def leave_class(self, node):
"""check number of public methods""" """check number of public methods"""
nb_public_methods = 0 my_methods = sum(1 for method in node.mymethods()
for method in node.mymethods(): if not method.name.startswith('_'))
if not method.name.startswith('_'): all_methods = sum(1 for method in node.methods()
nb_public_methods += 1 if not method.name.startswith('_'))
# Does the class contain less than 20 public methods ?
if nb_public_methods > self.config.max_public_methods: # Does the class contain less than n public methods ?
# This checks only the methods defined in the current class,
# since the user might not have control over the classes
# from the ancestors. It avoids some false positives
# for classes such as unittest.TestCase, which provides
# a lot of assert methods. It doesn't make sense to warn
# when the user subclasses TestCase to add his own tests.
if my_methods > self.config.max_public_methods:
self.add_message('too-many-public-methods', node=node, self.add_message('too-many-public-methods', node=node,
args=(nb_public_methods, args=(my_methods,
self.config.max_public_methods)) self.config.max_public_methods))
# stop here for exception, metaclass and interface classes # stop here for exception, metaclass and interface classes
if node.type != 'class': if node.type != 'class':
return return
# Does the class contain more than 5 public methods ?
if nb_public_methods < self.config.min_public_methods: # Does the class contain more than n public methods ?
# This checks all the methods defined by ancestors and
# by the current class.
if all_methods < self.config.min_public_methods:
self.add_message('too-few-public-methods', node=node, self.add_message('too-few-public-methods', node=node,
args=(nb_public_methods, args=(all_methods,
self.config.min_public_methods)) self.config.min_public_methods))
@check_messages('too-many-return-statements', 'too-many-branches', @check_messages('too-many-return-statements', 'too-many-branches',
...@@ -356,7 +325,6 @@ class MisdesignChecker(BaseChecker): ...@@ -356,7 +325,6 @@ class MisdesignChecker(BaseChecker):
"""increments the branches counter""" """increments the branches counter"""
self._branches[node.scope()] += branchesnum self._branches[node.scope()] += branchesnum
# FIXME: make a nice report...
def register(linter): def register(linter):
"""required method to auto register this checker """ """required method to auto register this checker """
......
...@@ -303,7 +303,7 @@ class ContinuedLineState(object): ...@@ -303,7 +303,7 @@ class ContinuedLineState(object):
self.retained_warnings.append((token_position, state, valid_offsets)) self.retained_warnings.append((token_position, state, valid_offsets))
def get_valid_offsets(self, idx): def get_valid_offsets(self, idx):
""""Returns the valid offsets for the token at the given position.""" """Returns the valid offsets for the token at the given position."""
# The closing brace on a dict or the 'for' in a dict comprehension may # The closing brace on a dict or the 'for' in a dict comprehension may
# reset two indent levels because the dict value is ended implicitly # reset two indent levels because the dict value is ended implicitly
stack_top = -1 stack_top = -1
...@@ -353,21 +353,22 @@ class ContinuedLineState(object): ...@@ -353,21 +353,22 @@ class ContinuedLineState(object):
def _continuation_inside_bracket(self, bracket, pos): def _continuation_inside_bracket(self, bracket, pos):
"""Extracts indentation information for a continued indent.""" """Extracts indentation information for a continued indent."""
indentation = _get_indent_length(self._tokens.line(pos)) indentation = _get_indent_length(self._tokens.line(pos))
if self._is_block_opener and self._tokens.start_col(pos+1) - indentation == self._block_indent_size: token_start = self._tokens.start_col(pos)
next_token_start = self._tokens.start_col(pos + 1)
if self._is_block_opener and next_token_start - indentation == self._block_indent_size:
return _ContinuedIndent( return _ContinuedIndent(
CONTINUED_BLOCK, CONTINUED_BLOCK,
bracket, bracket,
pos, pos,
_Offsets(self._tokens.start_col(pos)), _Offsets(token_start),
_BeforeBlockOffsets(self._tokens.start_col(pos+1), _BeforeBlockOffsets(next_token_start, next_token_start + self._continuation_size))
self._tokens.start_col(pos+1) + self._continuation_size))
else: else:
return _ContinuedIndent( return _ContinuedIndent(
CONTINUED, CONTINUED,
bracket, bracket,
pos, pos,
_Offsets(self._tokens.start_col(pos)), _Offsets(token_start),
_Offsets(self._tokens.start_col(pos+1))) _Offsets(next_token_start))
def pop_token(self): def pop_token(self):
self._cont_stack.pop() self._cont_stack.pop()
...@@ -442,7 +443,8 @@ class FormatChecker(BaseTokenChecker): ...@@ -442,7 +443,8 @@ class FormatChecker(BaseTokenChecker):
('expected-line-ending-format', ('expected-line-ending-format',
{'type': 'choice', 'metavar': '<empty or LF or CRLF>', 'default': '', {'type': 'choice', 'metavar': '<empty or LF or CRLF>', 'default': '',
'choices': ['', 'LF', 'CRLF'], 'choices': ['', 'LF', 'CRLF'],
'help': 'Expected format of line ending, e.g. empty (any line ending), LF or CRLF.'}), 'help': ('Expected format of line ending, '
'e.g. empty (any line ending), LF or CRLF.')}),
) )
def __init__(self, linter=None): def __init__(self, linter=None):
...@@ -796,10 +798,12 @@ class FormatChecker(BaseTokenChecker): ...@@ -796,10 +798,12 @@ class FormatChecker(BaseTokenChecker):
# check if line ending is as expected # check if line ending is as expected
expected = self.config.expected_line_ending_format expected = self.config.expected_line_ending_format
if expected: if expected:
line_ending = reduce(lambda x, y: x + y if x != y else x, line_ending, "") # reduce multiple \n\n\n\n to one \n # reduce multiple \n\n\n\n to one \n
line_ending = reduce(lambda x, y: x + y if x != y else x, line_ending, "")
line_ending = 'LF' if line_ending == '\n' else 'CRLF' line_ending = 'LF' if line_ending == '\n' else 'CRLF'
if line_ending != expected: if line_ending != expected:
self.add_message('unexpected-line-ending-format', args=(line_ending, expected), line=line_num) self.add_message('unexpected-line-ending-format', args=(line_ending, expected),
line=line_num)
def _process_retained_warnings(self, tokens, current_pos): def _process_retained_warnings(self, tokens, current_pos):
......
This diff is collapsed.
...@@ -61,6 +61,9 @@ class SpellingChecker(BaseTokenChecker): ...@@ -61,6 +61,9 @@ class SpellingChecker(BaseTokenChecker):
'%s\nDid you mean: \'%s\'?', '%s\nDid you mean: \'%s\'?',
'wrong-spelling-in-docstring', 'wrong-spelling-in-docstring',
'Used when a word in docstring is not spelled correctly.'), 'Used when a word in docstring is not spelled correctly.'),
'C0403': ('Invalid characters %r in a docstring',
'invalid-characters-in-docstring',
'Used when a word in docstring cannot be checked by enchant.'),
} }
options = (('spelling-dict', options = (('spelling-dict',
{'default' : '', 'type' : 'choice', 'metavar' : '<dict name>', {'default' : '', 'type' : 'choice', 'metavar' : '<dict name>',
...@@ -168,7 +171,13 @@ class SpellingChecker(BaseTokenChecker): ...@@ -168,7 +171,13 @@ class SpellingChecker(BaseTokenChecker):
word = word[2:] word = word[2:]
# If it is a known word, then continue. # If it is a known word, then continue.
if self.spelling_dict.check(word): try:
if self.spelling_dict.check(word):
continue
except enchant.errors.Error:
# this can only happen in docstrings, not comments
self.add_message('invalid-characters-in-docstring',
line=line_num, args=(word,))
continue continue
# Store word to private dict or raise a message. # Store word to private dict or raise a message.
......
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""Checkers for various standard library functions.""" """Checkers for various standard library functions."""
import re
import six import six
import sys import sys
...@@ -27,10 +26,15 @@ from pylint.checkers import BaseChecker ...@@ -27,10 +26,15 @@ from pylint.checkers import BaseChecker
from pylint.checkers import utils from pylint.checkers import utils
TYPECHECK_COMPARISON_OPERATORS = frozenset(('is', 'is not', '==', '!=', 'in', 'not in'))
LITERAL_NODE_TYPES = (astroid.Const, astroid.Dict, astroid.List, astroid.Set)
if sys.version_info >= (3, 0): if sys.version_info >= (3, 0):
OPEN_MODULE = '_io' OPEN_MODULE = '_io'
TYPE_QNAME = 'builtins.type'
else: else:
OPEN_MODULE = '__builtin__' OPEN_MODULE = '__builtin__'
TYPE_QNAME = '__builtin__.type'
def _check_mode_str(mode): def _check_mode_str(mode):
...@@ -50,7 +54,6 @@ def _check_mode_str(mode): ...@@ -50,7 +54,6 @@ def _check_mode_str(mode):
reading = "r" in modes reading = "r" in modes
writing = "w" in modes writing = "w" in modes
appending = "a" in modes appending = "a" in modes
updating = "+" in modes
text = "t" in modes text = "t" in modes
binary = "b" in modes binary = "b" in modes
if "U" in modes: if "U" in modes:
...@@ -76,6 +79,15 @@ def _check_mode_str(mode): ...@@ -76,6 +79,15 @@ def _check_mode_str(mode):
return True return True
def _is_one_arg_pos_call(call):
"""Is this a call with exactly 1 argument,
where that argument is positional?
"""
return (isinstance(call, astroid.CallFunc)
and len(call.args) == 1
and not isinstance(call.args[0], astroid.Keyword))
class StdlibChecker(BaseChecker): class StdlibChecker(BaseChecker):
__implements__ = (IAstroidChecker,) __implements__ = (IAstroidChecker,)
name = 'stdlib' name = 'stdlib'
...@@ -88,7 +100,7 @@ class StdlibChecker(BaseChecker): ...@@ -88,7 +100,7 @@ class StdlibChecker(BaseChecker):
'See http://docs.python.org/2/library/functions.html#open'), 'See http://docs.python.org/2/library/functions.html#open'),
'W1502': ('Using datetime.time in a boolean context.', 'W1502': ('Using datetime.time in a boolean context.',
'boolean-datetime', 'boolean-datetime',
'Using datetetime.time in a boolean context can hide ' 'Using datetime.time in a boolean context can hide '
'subtle bugs when the time they represent matches ' 'subtle bugs when the time they represent matches '
'midnight UTC. This behaviour was fixed in Python 3.5. ' 'midnight UTC. This behaviour was fixed in Python 3.5. '
'See http://bugs.python.org/issue13936 for reference.', 'See http://bugs.python.org/issue13936 for reference.',
...@@ -96,10 +108,16 @@ class StdlibChecker(BaseChecker): ...@@ -96,10 +108,16 @@ class StdlibChecker(BaseChecker):
'W1503': ('Redundant use of %s with constant ' 'W1503': ('Redundant use of %s with constant '
'value %r', 'value %r',
'redundant-unittest-assert', 'redundant-unittest-assert',
'The first argument of assertTrue and assertFalse is' 'The first argument of assertTrue and assertFalse is '
'a condition. If a constant is passed as parameter, that' 'a condition. If a constant is passed as parameter, that '
'condition will be always true. In this case a warning ' 'condition will be always true. In this case a warning '
'should be emitted.') 'should be emitted.'),
'W1504': ('Using type() instead of isinstance() for a typecheck.',
'unidiomatic-typecheck',
'The idiomatic way to perform an explicit typecheck in '
'Python is to use isinstance(x, Y) rather than '
'type(x) == Y, type(x) is Y. Though there are unusual '
'situations where these give different results.')
} }
@utils.check_messages('bad-open-mode', 'redundant-unittest-assert') @utils.check_messages('bad-open-mode', 'redundant-unittest-assert')
...@@ -132,6 +150,14 @@ class StdlibChecker(BaseChecker): ...@@ -132,6 +150,14 @@ class StdlibChecker(BaseChecker):
for value in node.values: for value in node.values:
self._check_datetime(value) self._check_datetime(value)
@utils.check_messages('unidiomatic-typecheck')
def visit_compare(self, node):
operator, right = node.ops[0]
if operator in TYPECHECK_COMPARISON_OPERATORS:
left = node.left
if _is_one_arg_pos_call(left):
self._check_type_x_is_y(node, left, operator, right)
def _check_redundant_assert(self, node, infer): def _check_redundant_assert(self, node, infer):
if (isinstance(infer, astroid.BoundMethod) and if (isinstance(infer, astroid.BoundMethod) and
node.args and isinstance(node.args[0], astroid.Const) and node.args and isinstance(node.args[0], astroid.Const) and
...@@ -152,7 +178,6 @@ class StdlibChecker(BaseChecker): ...@@ -152,7 +178,6 @@ class StdlibChecker(BaseChecker):
infered.qname() == 'datetime.time'): infered.qname() == 'datetime.time'):
self.add_message('boolean-datetime', node=node) self.add_message('boolean-datetime', node=node)
def _check_open_mode(self, node): def _check_open_mode(self, node):
"""Check that the mode argument of an open or file call is valid.""" """Check that the mode argument of an open or file call is valid."""
try: try:
...@@ -167,6 +192,24 @@ class StdlibChecker(BaseChecker): ...@@ -167,6 +192,24 @@ class StdlibChecker(BaseChecker):
self.add_message('bad-open-mode', node=node, self.add_message('bad-open-mode', node=node,
args=mode_arg.value) args=mode_arg.value)
def _check_type_x_is_y(self, node, left, operator, right):
"""Check for expressions like type(x) == Y."""
left_func = utils.safe_infer(left.func)
if not (isinstance(left_func, astroid.Class)
and left_func.qname() == TYPE_QNAME):
return
if operator in ('is', 'is not') and _is_one_arg_pos_call(right):
right_func = utils.safe_infer(right.func)
if (isinstance(right_func, astroid.Class)
and right_func.qname() == TYPE_QNAME):
# type(x) == type(a)
right_arg = utils.safe_infer(right.args[0])
if not isinstance(right_arg, LITERAL_NODE_TYPES):
# not e.g. type(x) == type([])
return
self.add_message('unidiomatic-typecheck', node=node)
def register(linter): def register(linter):
"""required method to auto register this checker """ """required method to auto register this checker """
......
...@@ -181,7 +181,7 @@ def parse_format_method_string(format_string): ...@@ -181,7 +181,7 @@ def parse_format_method_string(format_string):
if isinstance(keyname, numbers.Number): if isinstance(keyname, numbers.Number):
# In Python 2 it will return long which will lead # In Python 2 it will return long which will lead
# to different output between 2 and 3 # to different output between 2 and 3
manual_pos_arg.add(keyname) manual_pos_arg.add(str(keyname))
keyname = int(keyname) keyname = int(keyname)
keys.append((keyname, list(fielditerator))) keys.append((keyname, list(fielditerator)))
else: else:
......
# -*- coding: utf-8; mode: python; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:ft=python:et:sw=4:ts=4:sts=4 # -*- coding: utf-8; mode: python; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4
# -*- vim:fenc=utf-8:ft=python:et:sw=4:ts=4:sts=4
# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). # Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE).
# http://www.logilab.fr/ -- mailto:contact@logilab.fr # http://www.logilab.fr/ -- mailto:contact@logilab.fr
# #
......
...@@ -38,7 +38,7 @@ from pylint.reporters.guireporter import GUIReporter ...@@ -38,7 +38,7 @@ from pylint.reporters.guireporter import GUIReporter
HOME = os.path.expanduser('~/') HOME = os.path.expanduser('~/')
HISTORY = '.pylint-gui-history' HISTORY = '.pylint-gui-history'
COLORS = {'(I)':'lightblue', COLORS = {'(I)':'green',
'(C)':'blue', '(R)':'darkblue', '(C)':'blue', '(R)':'darkblue',
'(W)':'black', '(E)':'darkred', '(W)':'black', '(E)':'darkred',
'(F)':'red'} '(F)':'red'}
......
This diff is collapsed.
...@@ -13,8 +13,9 @@ ...@@ -13,8 +13,9 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""HTML reporter""" """HTML reporter"""
import itertools
import string
import sys import sys
from cgi import escape
from logilab.common.ureports import HTMLWriter, Section, Table from logilab.common.ureports import HTMLWriter, Section, Table
...@@ -32,11 +33,41 @@ class HTMLReporter(BaseReporter): ...@@ -32,11 +33,41 @@ class HTMLReporter(BaseReporter):
def __init__(self, output=sys.stdout): def __init__(self, output=sys.stdout):
BaseReporter.__init__(self, output) BaseReporter.__init__(self, output)
self.msgs = [] self.msgs = []
# Add placeholders for title and parsed messages
self.header = None
self.msgargs = []
@staticmethod
def _parse_msg_template(msg_template):
formatter = string.Formatter()
parsed = formatter.parse(msg_template)
for item in parsed:
if item[1]:
yield item[1]
def _parse_template(self):
"""Helper function to parse the message template"""
self.header = []
if self.linter.config.msg_template:
msg_template = self.linter.config.msg_template
else:
msg_template = '{category}{module}{obj}{line}{column}{msg}'
_header, _msgs = itertools.tee(self._parse_msg_template(msg_template))
self.header = list(_header)
self.msgargs = list(_msgs)
def handle_message(self, msg): def handle_message(self, msg):
"""manage message of different type and in the context of path""" """manage message of different type and in the context of path"""
self.msgs += (msg.category, msg.module, msg.obj,
str(msg.line), str(msg.column), escape(msg.msg)) # It would be better to do this in init, but currently we do not
# have access to the linter (as it is setup in lint.set_reporter()
# Therefore we try to parse just the once.
if self.header is None:
self._parse_template()
# We want to add the lines given by the template
self.msgs += [str(getattr(msg, field)) for field in self.msgargs]
def set_output(self, output=None): def set_output(self, output=None):
"""set output stream """set output stream
...@@ -55,11 +86,12 @@ class HTMLReporter(BaseReporter): ...@@ -55,11 +86,12 @@ class HTMLReporter(BaseReporter):
""" """
if self.msgs: if self.msgs:
# add stored messages to the layout # add stored messages to the layout
msgs = ['type', 'module', 'object', 'line', 'col_offset', 'message'] msgs = self.header
cols = len(self.header)
msgs += self.msgs msgs += self.msgs
sect = Section('Messages') sect = Section('Messages')
layout.append(sect) layout.append(sect)
sect.append(Table(cols=6, children=msgs, rheaders=1)) sect.append(Table(cols=cols, children=msgs, rheaders=1))
self.msgs = [] self.msgs = []
HTMLWriter().format(layout, self.out) HTMLWriter().format(layout, self.out)
......
...@@ -93,7 +93,7 @@ def get_tests_info(input_dir, msg_dir, prefix, suffix): ...@@ -93,7 +93,7 @@ def get_tests_info(input_dir, msg_dir, prefix, suffix):
class TestReporter(BaseReporter): class TestReporter(BaseReporter):
"""reporter storing plain text messages""" """reporter storing plain text messages"""
__implements____ = IReporter __implements__ = IReporter
def __init__(self): # pylint: disable=super-init-not-called def __init__(self): # pylint: disable=super-init-not-called
......
...@@ -225,6 +225,11 @@ class MessagesHandlerMixIn(object): ...@@ -225,6 +225,11 @@ class MessagesHandlerMixIn(object):
self._msgs_state = {} self._msgs_state = {}
self.msg_status = 0 self.msg_status = 0
def _checker_messages(self, checker):
for checker in self._checkers[checker.lower()]:
for msgid in checker.msgs:
yield msgid
def disable(self, msgid, scope='package', line=None, ignore_unknown=False): def disable(self, msgid, scope='package', line=None, ignore_unknown=False):
"""don't output message of the given id""" """don't output message of the given id"""
assert scope in ('package', 'module') assert scope in ('package', 'module')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment