Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1581)

Unified Diff: third_party/pylint/checkers/classes.py

Issue 753543006: pylint: upgrade to 1.4.0 (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@master
Patch Set: Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « third_party/pylint/checkers/base.py ('k') | third_party/pylint/checkers/design_analysis.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: third_party/pylint/checkers/classes.py
diff --git a/third_party/pylint/checkers/classes.py b/third_party/pylint/checkers/classes.py
index 232e130b8b9d11e8e808cd6474a0f663dead3b89..eeaf689f440f29545512eabf2eec721090fc5237 100644
--- a/third_party/pylint/checkers/classes.py
+++ b/third_party/pylint/checkers/classes.py
@@ -18,16 +18,20 @@
from __future__ import generators
import sys
+from collections import defaultdict
import astroid
from astroid import YES, Instance, are_exclusive, AssAttr, Class
-from astroid.bases import Generator
+from astroid.bases import Generator, BUILTINS
+from astroid.inference import InferenceContext
from pylint.interfaces import IAstroidChecker
from pylint.checkers import BaseChecker
from pylint.checkers.utils import (
PYMETHODS, overrides_a_method, check_messages, is_attr_private,
- is_attr_protected, node_frame_class, safe_infer)
+ is_attr_protected, node_frame_class, safe_infer, is_builtin_object,
+ decorated_with_property)
+import six
if sys.version_info >= (3, 0):
NEXT_METHOD = '__next__'
@@ -35,6 +39,32 @@ else:
NEXT_METHOD = 'next'
ITER_METHODS = ('__iter__', '__getitem__')
+def _called_in_methods(func, klass, methods):
+ """ Check if the func was called in any of the given methods,
+ belonging to the *klass*. Returns True if so, False otherwise.
+ """
+ if not isinstance(func, astroid.Function):
+ return False
+ for method in methods:
+ try:
+ infered = klass.getattr(method)
+ except astroid.NotFoundError:
+ continue
+ for infer_method in infered:
+ for callfunc in infer_method.nodes_of_class(astroid.CallFunc):
+ try:
+ bound = next(callfunc.func.infer())
+ except (astroid.InferenceError, StopIteration):
+ continue
+ if not isinstance(bound, astroid.BoundMethod):
+ continue
+ func_obj = bound._proxied
+ if isinstance(func_obj, astroid.UnboundMethod):
+ func_obj = func_obj._proxied
+ if func_obj.name == func.name:
+ return True
+ return False
+
def class_is_abstract(node):
"""return true if the given class node should be considered as an abstract
class
@@ -45,11 +75,39 @@ def class_is_abstract(node):
return True
return False
+def _is_attribute_property(name, klass):
+ """ Check if the given attribute *name* is a property
+ in the given *klass*.
+
+ It will look for `property` calls or for functions
+ with the given name, decorated by `property` or `property`
+ subclasses.
+ Returns ``True`` if the name is a property in the given klass,
+ ``False`` otherwise.
+ """
+
+ try:
+ attributes = klass.getattr(name)
+ except astroid.NotFoundError:
+ return False
+ property_name = "{0}.property".format(BUILTINS)
+ for attr in attributes:
+ try:
+ infered = next(attr.infer())
+ except astroid.InferenceError:
+ continue
+ if (isinstance(infered, astroid.Function) and
+ decorated_with_property(infered)):
+ return True
+ if infered.pytype() == property_name:
+ return True
+ return False
+
MSGS = {
'F0202': ('Unable to check methods signature (%s / %s)',
'method-check-failed',
- 'Used when PyLint has been unable to check methods signature \
+ 'Used when Pylint has been unable to check methods signature \
compatibility for an unexpected reason. Please report this kind \
if you don\'t make sense of it.'),
@@ -136,7 +194,7 @@ MSGS = {
),
'F0220': ('failed to resolve interfaces implemented by %s (%s)', # W0224
'unresolved-interface',
- 'Used when a PyLint as failed to find interfaces implemented by \
+ 'Used when a Pylint as failed to find interfaces implemented by \
a class'),
@@ -172,7 +230,11 @@ MSGS = {
'E0238': ('Invalid __slots__ object',
'invalid-slots',
'Used when an invalid __slots__ is found in class. '
- 'Only a string, an iterable or a sequence is permitted.')
+ 'Only a string, an iterable or a sequence is permitted.'),
+ 'E0239': ('Inheriting %r, which is not a class.',
+ 'inherit-non-class',
+ 'Used when a class inherits from something which is not a '
+ 'class.'),
}
@@ -234,7 +296,16 @@ a class method.'}
'help' : 'List of valid names for the first argument in \
a metaclass class method.'}
),
- )
+ ('exclude-protected',
+ {
+ 'default': (
+ # namedtuple public API.
+ '_asdict', '_fields', '_replace', '_source', '_make'),
+ 'type': 'csv',
+ 'metavar': '<protected access exclusions>',
+ 'help': ('List of member names, which should be excluded '
+ 'from the protected access warning.')}
+ ))
def __init__(self, linter=None):
BaseChecker.__init__(self, linter)
@@ -245,7 +316,7 @@ a metaclass class method.'}
def visit_class(self, node):
"""init visit variable _accessed and check interfaces
"""
- self._accessed.append({})
+ self._accessed.append(defaultdict(list))
self._check_bases_classes(node)
self._check_interfaces(node)
# if not an interface, exception, metaclass
@@ -255,8 +326,27 @@ a metaclass class method.'}
except astroid.NotFoundError:
self.add_message('no-init', args=node, node=node)
self._check_slots(node)
+ self._check_proper_bases(node)
+
+ @check_messages('inherit-non-class')
+ def _check_proper_bases(self, node):
+ """
+ Detect that a class inherits something which is not
+ a class or a type.
+ """
+ for base in node.bases:
+ ancestor = safe_infer(base)
+ if ancestor in (YES, None):
+ continue
+ if (isinstance(ancestor, astroid.Instance) and
+ ancestor.is_subtype_of('%s.type' % (BUILTINS,))):
+ continue
+ if not isinstance(ancestor, astroid.Class):
+ self.add_message('inherit-non-class',
+ args=base.as_string(), node=node)
- @check_messages('access-member-before-definition', 'attribute-defined-outside-init')
+ @check_messages('access-member-before-definition',
+ 'attribute-defined-outside-init')
def leave_class(self, cnode):
"""close a class node:
check that instance attributes are defined in __init__ and check
@@ -271,7 +361,7 @@ a metaclass class method.'}
return
defining_methods = self.config.defining_attr_methods
current_module = cnode.root()
- for attr, nodes in cnode.instance_attrs.iteritems():
+ for attr, nodes in six.iteritems(cnode.instance_attrs):
# skip nodes which are not in the current module and it may screw up
# the output, while it's not worth it
nodes = [n for n in nodes if not
@@ -301,6 +391,12 @@ a metaclass class method.'}
except astroid.NotFoundError:
for node in nodes:
if node.frame().name not in defining_methods:
+ # If the attribute was set by a callfunc in any
+ # of the defining methods, then don't emit
+ # the warning.
+ if _called_in_methods(node.frame(), cnode,
+ defining_methods):
+ continue
self.add_message('attribute-defined-outside-init',
args=attr, node=node)
@@ -348,7 +444,7 @@ a metaclass class method.'}
and overridden_frame.type == 'method'):
overridden_frame = overridden_frame.parent.frame()
if (isinstance(overridden_frame, Class)
- and klass._is_subtype_of(overridden_frame.qname())):
+ and klass.is_subtype_of(overridden_frame.qname())):
args = (overridden.root().name, overridden.fromlineno)
self.add_message('method-hidden', args=args, node=node)
except astroid.NotFoundError:
@@ -466,7 +562,7 @@ a metaclass class method.'}
attrname = node.attrname
# Check self
if self.is_first_attr(node):
- self._accessed[-1].setdefault(attrname, []).append(node)
+ self._accessed[-1][attrname].append(node)
return
if not self.linter.is_message_enabled('protected-access'):
return
@@ -475,7 +571,7 @@ a metaclass class method.'}
def visit_assattr(self, node):
if isinstance(node.ass_type(), astroid.AugAssign) and self.is_first_attr(node):
- self._accessed[-1].setdefault(node.attrname, []).append(node)
+ self._accessed[-1][node.attrname].append(node)
self._check_in_slots(node)
def _check_in_slots(self, node):
@@ -500,6 +596,10 @@ a metaclass class method.'}
# If we have a '__dict__' in slots, then
# assigning any name is valid.
if not any(slot.value == '__dict__' for slot in slots):
+ if _is_attribute_property(node.attrname, klass):
+ # Properties circumvent the slots mechanism,
+ # so we should not emit a warning for them.
+ return
self.add_message('assigning-non-slot',
args=(node.attrname, ), node=node)
@@ -526,7 +626,8 @@ a metaclass class method.'}
'''
attrname = node.attrname
- if is_attr_protected(attrname):
+ if (is_attr_protected(attrname) and
+ attrname not in self.config.exclude_protected):
klass = node_frame_class(node)
@@ -549,6 +650,23 @@ a metaclass class method.'}
# We are in a class, one remaining valid cases, Klass._attr inside
# Klass
if not (callee == klass.name or callee in klass.basenames):
+ # Detect property assignments in the body of the class.
+ # This is acceptable:
+ #
+ # class A:
+ # b = property(lambda: self._b)
+
+ stmt = node.parent.statement()
+ try:
+ if (isinstance(stmt, astroid.Assign) and
+ (stmt in klass.body or klass.parent_of(stmt)) and
+ isinstance(stmt.value, astroid.CallFunc) and
+ isinstance(stmt.value.func, astroid.Name) and
+ stmt.value.func.name == 'property' and
+ is_builtin_object(next(stmt.value.func.infer(), None))):
+ return
+ except astroid.InferenceError:
+ pass
self.add_message('protected-access', node=node, args=attrname)
def visit_name(self, node):
@@ -562,7 +680,7 @@ a metaclass class method.'}
def _check_accessed_members(self, node, accessed):
"""check that accessed members are defined"""
# XXX refactor, probably much simpler now that E0201 is in type checker
- for attr, nodes in accessed.iteritems():
+ for attr, nodes in six.iteritems(accessed):
# deactivate "except doesn't do anything", that's expected
# pylint: disable=W0704
try:
@@ -574,7 +692,7 @@ a metaclass class method.'}
pass
# is it an instance attribute of a parent class ?
try:
- node.instance_attr_ancestors(attr).next()
+ next(node.instance_attr_ancestors(attr))
# yes, stop here
continue
except StopIteration:
@@ -606,7 +724,8 @@ a metaclass class method.'}
lno = defstmt.fromlineno
for _node in nodes:
if _node.frame() is frame and _node.fromlineno < lno \
- and not are_exclusive(_node.statement(), defstmt, ('AttributeError', 'Exception', 'BaseException')):
+ and not are_exclusive(_node.statement(), defstmt,
+ ('AttributeError', 'Exception', 'BaseException')):
self.add_message('access-member-before-definition',
node=_node, args=(attr, lno))
@@ -649,7 +768,8 @@ a metaclass class method.'}
else:
self._check_first_arg_config(
first,
- self.config.valid_classmethod_first_arg, node, 'bad-mcs-method-argument',
+ self.config.valid_classmethod_first_arg, node,
+ 'bad-mcs-method-argument',
node.name)
# regular class
else:
@@ -657,7 +777,8 @@ a metaclass class method.'}
if node.type == 'classmethod':
self._check_first_arg_config(
first,
- self.config.valid_classmethod_first_arg, node, 'bad-classmethod-argument',
+ self.config.valid_classmethod_first_arg, node,
+ 'bad-classmethod-argument',
node.name)
# regular method without self as argument
elif first != 'self':
@@ -719,7 +840,8 @@ a metaclass class method.'}
try:
method = node_method(node, name)
except astroid.NotFoundError:
- self.add_message('missing-interface-method', args=(name, iface.name),
+ self.add_message('missing-interface-method',
+ args=(name, iface.name),
node=node)
continue
# ignore inherited methods
@@ -762,17 +884,29 @@ a metaclass class method.'}
expr.expr.func.name == 'super':
return
try:
- klass = expr.expr.infer().next()
+ klass = next(expr.expr.infer())
if klass is YES:
continue
+ # The infered klass can be super(), which was
+ # assigned to a variable and the `__init__` was called later.
+ #
+ # base = super()
+ # base.__init__(...)
+
+ if (isinstance(klass, astroid.Instance) and
+ isinstance(klass._proxied, astroid.Class) and
+ is_builtin_object(klass._proxied) and
+ klass._proxied.name == 'super'):
+ return
try:
del not_called_yet[klass]
except KeyError:
if klass not in to_call:
- self.add_message('non-parent-init-called', node=expr, args=klass.name)
+ self.add_message('non-parent-init-called',
+ node=expr, args=klass.name)
except astroid.InferenceError:
continue
- for klass, method in not_called_yet.iteritems():
+ for klass, method in six.iteritems(not_called_yet):
if klass.name == 'object' or method.parent.name == 'object':
continue
self.add_message('super-init-not-called', args=klass.name, node=node)
@@ -784,7 +918,8 @@ a metaclass class method.'}
"""
if not (isinstance(method1, astroid.Function)
and isinstance(refmethod, astroid.Function)):
- self.add_message('method-check-failed', args=(method1, refmethod), node=method1)
+ self.add_message('method-check-failed',
+ args=(method1, refmethod), node=method1)
return
# don't care about functions with unknown argument (builtins)
if method1.args.args is None or refmethod.args.args is None:
@@ -813,7 +948,7 @@ def _ancestors_to_call(klass_node, method='__init__'):
to_call = {}
for base_node in klass_node.ancestors(recurs=False):
try:
- to_call[base_node] = base_node.igetattr(method).next()
+ to_call[base_node] = next(base_node.igetattr(method))
except astroid.InferenceError:
continue
return to_call
« no previous file with comments | « third_party/pylint/checkers/base.py ('k') | third_party/pylint/checkers/design_analysis.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698