Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b46a9690 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

More lint tests.

When overloading methods, verify that common arguments always come
first and have consistent ordering.

When methods register listeners or callbacks (outside of the UI
toolkit), verify that an overload exists that takes a Handler to
deliver events through.

When a method accepts a Context argument, it must be the first
argument.

Change-Id: I6a6f94a3a0a8c48987835e47eb87564e569db2af
parent 9f64d5c6
Loading
Loading
Loading
Loading
+118 −6
Original line number Diff line number Diff line
@@ -409,7 +409,7 @@ def verify_parcelable(clazz):


def verify_protected(clazz):
    """Verify that no protected methods are allowed."""
    """Verify that no protected methods or fields are allowed."""
    for m in clazz.methods:
        if "protected" in m.split:
            error(clazz, m, "M7", "No protected methods; must be public")
@@ -554,7 +554,7 @@ def verify_helper_classes(clazz):
            if "final" in m.split: continue
            if not re.match("on[A-Z]", m.name):
                if "abstract" in m.split:
                    error(clazz, m, None, "Methods implemented by developers must be named onFoo()")
                    warn(clazz, m, None, "Methods implemented by developers should be named onFoo()")
                else:
                    warn(clazz, m, None, "If implemented by developer, should be named onFoo(); otherwise consider marking final")

@@ -578,7 +578,7 @@ def verify_builder(clazz):
        if m.name.startswith("clear"): continue

        if m.name.startswith("with"):
            error(clazz, m, None, "Builder methods names must follow setFoo() style")
            warn(clazz, m, None, "Builder methods names should follow setFoo() style")

        if m.name.startswith("set"):
            if not m.typ.endswith(clazz.fullname):
@@ -692,7 +692,7 @@ def verify_flags(clazz):

            scope = f.name[0:f.name.index("FLAG_")]
            if val & known[scope]:
                error(clazz, f, "C1", "Found overlapping flag constant value")
                warn(clazz, f, "C1", "Found overlapping flag constant value")
            known[scope] |= val


@@ -740,7 +740,7 @@ def verify_manager(clazz):
    if not clazz.name.endswith("Manager"): return

    for c in clazz.ctors:
        error(clazz, c, None, "Managers must always be obtained from Context")
        error(clazz, c, None, "Managers should always be obtained from Context")


def verify_boxed(clazz):
@@ -782,6 +782,115 @@ def verify_static_utils(clazz):
        error(clazz, None, None, "Fully-static utility classes must not have constructor")


def verify_overload_args(clazz):
    """Verifies that method overloads add new arguments at the end."""
    if clazz.fullname.startswith("android.opengl"): return

    overloads = collections.defaultdict(list)
    for m in clazz.methods:
        if "deprecated" in m.split: continue
        overloads[m.name].append(m)

    for name, methods in overloads.iteritems():
        if len(methods) <= 1: continue

        # Look for arguments common across all overloads
        def cluster(args):
            count = collections.defaultdict(int)
            res = set()
            for i in range(len(args)):
                a = args[i]
                res.add("%s#%d" % (a, count[a]))
                count[a] += 1
            return res

        common_args = cluster(methods[0].args)
        for m in methods:
            common_args = common_args & cluster(m.args)

        if len(common_args) == 0: continue

        # Require that all common arguments are present at start of signature
        locked_sig = None
        for m in methods:
            sig = m.args[0:len(common_args)]
            if not common_args.issubset(cluster(sig)):
                warn(clazz, m, "M2", "Expected common arguments [%s] at beginning of overloaded method" % (", ".join(common_args)))
            elif not locked_sig:
                locked_sig = sig
            elif locked_sig != sig:
                error(clazz, m, "M2", "Expected consistent argument ordering between overloads: %s..." % (", ".join(locked_sig)))


def verify_callback_handlers(clazz):
    """Verifies that methods adding listener/callback have overload
    for specifying delivery thread."""

    # Ignore UI components which deliver things on main thread
    skip = [
        "android.animation",
        "android.view",
        "android.graphics",
        "android.transition",
        "android.widget",
        "android.webkit",
    ]
    for s in skip:
        if clazz.fullname.startswith(s): return
        if clazz.extends and clazz.extends.startswith(s): return

    skip = [
        "android.app.ActionBar",
        "android.app.AlertDialog",
        "android.app.AlertDialog.Builder",
        "android.app.Application",
        "android.app.Activity",
        "android.app.Dialog",
        "android.app.Fragment",
        "android.app.FragmentManager",
        "android.app.LoaderManager",
        "android.app.ListActivity",
        "android.app.AlertDialog.Builder"
        "android.content.Loader",
    ]
    for s in skip:
        if clazz.fullname == s or clazz.extends == s: return

    found = {}
    by_name = collections.defaultdict(list)
    for m in clazz.methods:
        if m.name.startswith("unregister"): continue
        if m.name.startswith("remove"): continue
        if re.match("on[A-Z]+", m.name): continue

        by_name[m.name].append(m)

        for a in m.args:
            if a.endswith("Listener") or a.endswith("Callback") or a.endswith("Callbacks"):
                found[m.name] = m

    for f in found.values():
        takes_handler = False
        for m in by_name[f.name]:
            if "android.os.Handler" in m.args:
                takes_handler = True
        if not takes_handler:
            error(clazz, f, "L1", "Registration methods should have overload that accepts delivery Handler")


def verify_context_first(clazz):
    """Verifies that methods accepting a Context keep it the first argument."""
    for m in clazz.ctors:
        if len(m.args) > 1:
            if "android.content.Context" in m.args[1:]:
                error(clazz, m, "M3", "Context is distinct, so it must be the first argument")

    for m in clazz.methods:
        if len(m.args) > 1:
            if "android.content.Context" in m.args[1:]:
                error(clazz, m, "M3", "Context is distinct, so it must be the first argument")


def verify_style(api):
    """Find all style issues in the given API level."""
    global failures
@@ -826,6 +935,9 @@ def verify_style(api):
        verify_manager(clazz)
        verify_boxed(clazz)
        verify_static_utils(clazz)
        verify_overload_args(clazz)
        verify_callback_handlers(clazz)
        verify_context_first(clazz)

    return failures