from pychecker2.Check import Check
from pychecker2.Check import Warning
from pychecker2 import symbols
from pychecker2.util import *
from compiler.misc import mangle
from compiler import ast,walk
_ignorable = {}
for ignore in ['repr', 'dict', 'class', 'doc', 'str']:
_ignorable['__%s__' % ignore] = 1
class GetDefs(BaseVisitor):
"Record definitions of a attribute of self, who's name is provided"
def __init__(self, name):
self.selfname = name
self.result = {}
def visitAssAttr(self, node):
if isinstance(node.expr, ast.Name) and \
node.expr.name == self.selfname and \
isinstance(node.parent, (ast.Assign, ast.AssTuple)):
self.result[node.attrname] = node
def visitClass(self, node): # ignore nested classes
pass
class GetRefs(BaseVisitor):
"Record references to a attribute of self, who's name is provided"
def __init__(self, name):
self.selfname = name
self.result = {}
def visitAssAttr(self, node):
if isinstance(node.expr, ast.Name) and \
node.expr.name == self.selfname and \
not isinstance(node.parent, (ast.Assign, ast.AssTuple)):
self.result[node.attrname] = node
self.visitChildren(node)
def visitGetattr(self, node):
if isinstance(node.expr, ast.Name) and \
node.expr.name == self.selfname:
self.result[node.attrname] = node
self.visitChildren(node)
def visitClass(self, node): # ignore nested classes
pass
def _get_methods(class_scope):
return type_filter(class_scope.get_children(), symbols.FunctionScope)
class NotSimpleName(Exception): pass
# compress Getattr(Getattr(Name(x), y), z) -> "x.y.z"
def get_name(node):
if isinstance(node, ast.Getattr):
return get_name(node.expr) + (node.attrname, )
elif isinstance(node, ast.Name):
return (node.name,)
else:
raise NotSimpleName
def get_base_names(scope):
names = []
for b in scope.node.bases:
try:
names.append(get_name(b))
except NotSimpleName: # FIXME: hiding expressions
pass
return names
def find_in_module(package, remotename, names, checker):
# No other names, must be a name from the module
if not names:
f = checker.check_module(package)
if f:
return find_scope_going_down(f.root_scope, [remotename], checker)
return None
# complex name lookup
try:
# first, get the real name of the package
name = package.__name__
module = __import__(name, globals(), {}, [''])
except AttributeError:
# ok, so its a fake module... go with that
module = package
if remotename:
name += "." + remotename
# now import it, and chase down any other modules
submodule = getattr(module, names[0], None)
if type(submodule) == type(symbols):
return find_in_module(submodule, None, names[1:], checker)
# object in the module is not another module, so chase down the source
f = checker.check_module(submodule)
if f:
return find_scope_going_down(f.root_scope, names, checker)
return None
def find_scope_going_down(scope, names, checker):
"Drill down scopes to find definition of x.y.z"
for c in scope.get_children():
if getattr(c, 'name', '') == names[0]:
if len(names) == 1:
return c
return find_scope_going_down(c, names[1:], checker)
# Not defined here, check for import
return find_imported_class(scope.imports, names, checker)
def find_imported_class(imports, names, checker):
# may be defined by import
for i in range(1, len(names) + 1):
# try x, then x.y, then x.y.z as imported names
try:
name = ".".join(names[:i])
ref = imports[name]
# now look for the rest of the name
result = find_in_module(ref.module, ref.remotename, names[i:], checker)
if result:
return result
except (KeyError, ImportError):
pass
return None
def find_scope_going_up(scope, names, checker):
"Search up to find scope defining x of x.y.z"
for p in parents(scope):
if p.defs.has_key(names[0]):
return find_scope_going_down(p, names, checker)
# name imported via 'from module import *'
try:
return find_in_module(p.imports[names[0]].module, None, names, checker)
except KeyError:
return None
def get_base_classes(scope, checker):
result = []
for name in get_base_names(scope):
base = find_scope_going_up(scope, name, checker)
if base:
result.append(base)
result.extend(get_base_classes(base, checker))
return result
def conformsTo(a, b):
alen = len(a.node.argnames)
blen = len(b.node.argnames)
# f(a, *args, **kw) conforms to f(a, b, *args, **kw)
# f(a, *args) conforms to f(a, b, *args)
# f(a, *args) conforms to f(a, b, c)
# f(a, b, c, *args) does not conform to f(a, b)
if alen == blen:
if a.node.kwargs == b.node.kwargs and a.node.varargs == b.node.varargs:
return 1
if a.node.varargs and alen - 1 <= blen:
return a.node.kwargs == b.node.kwargs
return None
class AttributeCheck(Check):
"check `self.attr' expressions for attr"
unknownAttribute = Warning('Report unknown object attributes in methods',
'Class %s has no attribute %s')
unusedAttribute = Warning('Report attributes unused in methods',
'Attribute %s is not used in class %s')
methodRedefined = Warning('Report the redefinition of class methods',
'Method %s in class %s redefined')
signatureChanged = Warning('Report methods whose signatures do not '
'match base class methods',
'Signature does not match method '
'%s in base class %s')
attributeInitialized = \
Warning('Report attributes not initialized in __init__',
'Attribute %s is not initialized in __init__')
def check(self, file, checker):
def visit_with_self(Visitor, method):
if not method.node.argnames:
return {}
return walk(method.node, Visitor(method.node.argnames[0])).result
# for all class scopes
for node, scope in file.class_scopes():
init_attributes = None # attributes initilized in __init__
attributes = {} # "self.foo = " kinda things
methods = {} # methods -> scopes
# get attributes defined on self
for m in _get_methods(scope):
defs = visit_with_self(GetDefs, m)
if m.name == '__init__':
init_attributes = defs
attributes.update(defs)
methods[mangle(m.name, scope.name)] = m
# complain about attributes not initialized in __init__
if init_attributes is not None:
for name, node in dict_minus(attributes, init_attributes).items():
file.warning(node, self.attributeInitialized, name)
# collect inherited gunk: methods and attributes
# check for non-conformant methods
inherited_methods = scope.defs.copy()
inherited_attributes = attributes.copy()
for base in get_base_classes(scope, checker):
for m in _get_methods(base):
inherited_attributes.update(visit_with_self(GetDefs, m))
mname = mangle(m.name, base.name)
if m.name != "__init__" and \
methods.has_key(mname) and \
not conformsTo(methods[mname], m):
file.warning(methods[mname].node,
self.signatureChanged, m.name, base.name)
else:
methods[mname] = m
inherited_methods.update(base.defs)
# complain about attributes with the same name as methods
both = dict_intersect(attributes, inherited_methods)
for name, node in both.items():
file.warning(node, self.methodRedefined, name, scope.name)
# find refs on self
refs = {}
for m in _get_methods(scope):
refs.update(visit_with_self(GetRefs, m))
# Now complain about refs on self that aren't known
unknown = dict_minus(refs, inherited_methods)
unknown = dict_minus(unknown, _ignorable)
unknown = dict_minus(unknown, scope.defs)
unknown = dict_minus(unknown, inherited_attributes)
for name, node in unknown.items():
file.warning(node, self.unknownAttribute, scope.name, name)
unused = dict_minus(attributes, refs)
for name, node in unused.items():
if name.startswith('__'):
file.warning(node, self.unusedAttribute, name, scope.name)
class GetReturns(BaseVisitor):
def __init__(self):
self.result = []
def visitReturn(self, node):
self.result.append(node)
def visitFunction(self, node): pass
visitClass = visitFunction
class InitCheck(Check):
initReturnsValue = Warning('Report value returned from __init__',
'Method __init__ should not return a value')
def check(self, file, unused_checker):
for node, scope in file.class_scopes():
for m in _get_methods(scope):
if m.name == '__init__':
for r in walk(m.node.code, GetReturns()).result:
if isinstance(r.value, ast.Const) and \
r.value.value is None:
continue
if isinstance(r.value, ast.Name) and \
r.value.name == 'None':
continue
file.warning(r, self.initReturnsValue)
special = {
'__cmp__': 2, '__del__': 1, '__delitem__': 2, '__eq__': 2,
'__ge__': 2, '__getitem__': 2, '__gt__': 2, '__hash__': 1,
'__le__': 2, '__len__': 1, '__lt__': 2, '__ne__': 2,
'__nonzero__': 1, '__repr__': 1, '__setitem__': 3, '__str__': 1,
'__getattr__': 2, '__setattr__': 3,
'__delattr__': 2, '__len__': 1, '__delitem__': 2, '__iter__': 1,
'__contains__': 2,'__setslice__': 4,'__delslice__': 3,
'__add__': 2, '__sub__': 2, '__mul__': 2, '__floordiv__': 2,
'__mod__': 2, '__divmod__': 2, '__lshift__': 2,
'__rshift__': 2, '__and__': 2, '__xor__': 2, '__or__': 2,
'__div__': 2, '__truediv__': 2, '__radd__': 2, '__rsub__': 2,
'__rmul__': 2, '__rdiv__': 2, '__rmod__': 2, '__rdivmod__': 2,
'__rpow__': 2, '__rlshift__': 2, '__rrshift__': 2, '__rand__': 2,
'__rxor__': 2, '__ror__': 2, '__iadd__': 2, '__isub__': 2,
'__imul__': 2, '__idiv__': 2, '__imod__': 2, '__ilshift__': 2,
'__irshift__': 2, '__iand__': 2, '__ixor__': 2, '__ior__': 2,
'__neg__': 1, '__pos__': 1, '__abs__': 1, '__invert__': 1,
'__complex__': 1, '__int__': 1, '__long__': 1, '__float__': 1,
'__oct__': 1, '__hex__': 1, '__coerce__': 2,
'__new__': None,
'__getinitargs__': 1, '__reduce__': 1,
'__getstate__': 1,'__setstate__': 2,
'__copy__': 1, '__deepcopy__': 1,
'__pow__': 2, '__ipow__': 2, # 2 or 3
'__call__': None, # any number > 1
'__getslice__': 3, # deprecated
'__getattribute__': 2,
}
def check_special(scope):
try:
count = special[scope.name]
max_args = len(scope.node.argnames)
min_args = max_args - len(scope.node.defaults)
if min_args > count or max_args < count or \
scope.node.varargs or scope.node.kwargs:
return special[scope.name]
except KeyError:
pass
return None
class SpecialCheck(Check):
specialMethod = Warning('Report special methods with incorrect '
'number of arguments',
'The %s method requires %d argument%s, '
'including self')
notSpecial = Warning('Report methods with "__" prefix and suffix '
'which are not defined as special methods',
'The method %s is not a special method, '
'but is reserved.')
def check(self, file, unused_checker):
for node, scope in file.class_scopes():
for m in _get_methods(scope):
n = check_special(m)
if n:
file.warning(m.node, self.specialMethod, m.name, n,
n > 1 and "s" or "")
name = m.name
if name.startswith('__') and name.endswith('__') and \
name != '__init__' and not special.has_key(name):
file.warning(m.node, self.notSpecial, name)
class BackQuote(BaseVisitor):
def __init__(self, selfname):
self.results = []
self.selfname = selfname
def visitBackquote(self, node):
if isinstance(node.expr, ast.Name) and node.expr.name == self.selfname:
self.results.append(node)
class ReprCheck(Check):
backquoteSelf = Warning('Report use of `self` in __repr__ methods',
'Using `self` in __repr__')
def check(self, file, unused_checker):
for node, scope in file.class_scopes():
for m in _get_methods(scope):
if m.name == '__repr__' and m.node.argnames:
visitor = BackQuote(m.node.argnames[0])
for n in walk(m.node.code, visitor).results:
file.warning(n, self.backquoteSelf)
|