summaryrefslogtreecommitdiffstats
path: root/tools/apilint
diff options
context:
space:
mode:
Diffstat (limited to 'tools/apilint')
-rw-r--r--tools/apilint/apilint.py124
1 files changed, 118 insertions, 6 deletions
diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py
index fefea6b..b8eaa2d 100644
--- a/tools/apilint/apilint.py
+++ b/tools/apilint/apilint.py
@@ -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,13 +740,13 @@ 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):
"""Verifies that methods avoid boxed primitives."""
- boxed = ["java.lang.Number", "java.lang.Byte","java.lang.Double","java.lang.Float","java.lang.Integer","java.lang.Long","java.lang.Short"]
+ boxed = ["java.lang.Number","java.lang.Byte","java.lang.Double","java.lang.Float","java.lang.Integer","java.lang.Long","java.lang.Short"]
for c in clazz.ctors:
for arg in c.args:
@@ -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