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

pylint: update to 1.4.5

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

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