ClassChecks.py :  » Development » PyChecker » pychecker-0.8.18 » pychecker2 » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Development » PyChecker 
PyChecker » pychecker 0.8.18 » pychecker2 » ClassChecks.py
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)

www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.