diff options
Diffstat (limited to 'tools/apilint/apilint.py')
-rw-r--r-- | tools/apilint/apilint.py | 126 |
1 files changed, 70 insertions, 56 deletions
diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py index ddfee622699e..75aeb5e2b288 100644 --- a/tools/apilint/apilint.py +++ b/tools/apilint/apilint.py @@ -763,6 +763,14 @@ def notice(clazz): noticed[clazz.fullname] = hash(clazz) +verifiers = {} + +def verifier(f): + verifiers[f.__name__] = f + return f + + +@verifier def verify_constants(clazz): """All static final constants must be FOO_NAME style.""" if re.match("android\.R\.[a-z]+", clazz.fullname): return @@ -780,13 +788,13 @@ def verify_constants(clazz): if f.typ in req and f.value is None: error(clazz, f, None, "All constants must be defined at compile time") - +@verifier def verify_enums(clazz): """Enums are bad, mmkay?""" if clazz.extends == "java.lang.Enum" or "enum" in clazz.split: error(clazz, None, "F5", "Enums are not allowed") - +@verifier def verify_class_names(clazz): """Try catching malformed class names like myMtp or MTPUser.""" if clazz.fullname.startswith("android.opengl"): return @@ -801,6 +809,7 @@ def verify_class_names(clazz): error(clazz, None, None, "Don't expose your implementation details") +@verifier def verify_method_names(clazz): """Try catching malformed method names, like Foo() or getMTU().""" if clazz.fullname.startswith("android.opengl"): return @@ -814,6 +823,7 @@ def verify_method_names(clazz): error(clazz, m, "S1", "Method name must start with lowercase char") +@verifier def verify_callbacks(clazz): """Verify Callback classes. All callback classes must be abstract. @@ -834,6 +844,7 @@ def verify_callbacks(clazz): error(clazz, m, "L1", "Callback method names must be onFoo() style") +@verifier def verify_listeners(clazz): """Verify Listener classes. All Listener classes must be interface. @@ -855,6 +866,7 @@ def verify_listeners(clazz): error(clazz, m, "L1", "Single listener method name must match class name") +@verifier def verify_actions(clazz): """Verify intent actions. All action names must be named ACTION_FOO. @@ -886,6 +898,7 @@ def verify_actions(clazz): error(clazz, f, "C4", "Inconsistent action value; expected '%s'" % (expected)) +@verifier def verify_extras(clazz): """Verify intent extras. All extra names must be named EXTRA_FOO. @@ -916,6 +929,7 @@ def verify_extras(clazz): error(clazz, f, "C4", "Inconsistent extra value; expected '%s'" % (expected)) +@verifier def verify_equals(clazz): """Verify that equals() and hashCode() must be overridden together.""" eq = False @@ -928,6 +942,7 @@ def verify_equals(clazz): error(clazz, None, "M8", "Must override both equals and hashCode; missing one") +@verifier def verify_parcelable(clazz): """Verify that Parcelable objects aren't hiding required bits.""" if clazz.implements == "android.os.Parcelable": @@ -946,6 +961,7 @@ def verify_parcelable(clazz): error(clazz, c, "FW3", "Parcelable inflation is exposed through CREATOR, not raw constructors") +@verifier def verify_protected(clazz): """Verify that no protected methods or fields are allowed.""" for m in clazz.methods: @@ -957,6 +973,7 @@ def verify_protected(clazz): error(clazz, f, "M7", "Protected fields not allowed; must be public") +@verifier def verify_fields(clazz): """Verify that all exposed fields are final. Exposed fields must follow myName style. @@ -1002,6 +1019,7 @@ def verify_fields(clazz): error(clazz, f, "C2", "Constants must be marked static final") +@verifier def verify_register(clazz): """Verify parity of registration methods. Callback objects use register/unregister methods. @@ -1035,6 +1053,7 @@ def verify_register(clazz): error(clazz, m, "L3", "Listener methods should be named add/remove") +@verifier def verify_sync(clazz): """Verify synchronized methods aren't exposed.""" for m in clazz.methods: @@ -1042,6 +1061,7 @@ def verify_sync(clazz): error(clazz, m, "M5", "Internal locks must not be exposed") +@verifier def verify_intent_builder(clazz): """Verify that Intent builders are createFooIntent() style.""" if clazz.name == "Intent": return @@ -1054,6 +1074,7 @@ def verify_intent_builder(clazz): warn(clazz, m, "FW1", "Methods creating an Intent should be named createFooIntent()") +@verifier def verify_helper_classes(clazz): """Verify that helper classes are named consistently with what they extend. All developer extendable methods should be named onFoo().""" @@ -1102,6 +1123,7 @@ def verify_helper_classes(clazz): warn(clazz, m, None, "If implemented by developer, should be named onFoo(); otherwise consider marking final") +@verifier def verify_builder(clazz): """Verify builder classes. Methods should return the builder to enable chaining.""" @@ -1134,12 +1156,14 @@ def verify_builder(clazz): error(clazz, None, None, "Builder should be final") +@verifier def verify_aidl(clazz): """Catch people exposing raw AIDL.""" if clazz.extends == "android.os.Binder" or clazz.implements == "android.os.IInterface": error(clazz, None, None, "Raw AIDL interfaces must not be exposed") +@verifier def verify_internal(clazz): """Catch people exposing internal classes.""" if clazz.pkg.name.startswith("com.android"): @@ -1174,6 +1198,7 @@ LAYERING_PACKAGE_RANKING = layering_build_ranking([ "android.util" ]) +@verifier def verify_layering(clazz): """Catch package layering violations. For example, something in android.os depending on android.app.""" @@ -1208,6 +1233,7 @@ def verify_layering(clazz): warn(clazz, m, "FW6", "Method argument type violates package layering") +@verifier def verify_boolean(clazz): """Verifies that boolean accessors are named correctly. For example, hasFoo() and setHasFoo().""" @@ -1248,6 +1274,7 @@ def verify_boolean(clazz): error_if_exists(sets, m.name, expected, "has" + target) +@verifier def verify_collections(clazz): """Verifies that collection types are interfaces.""" if clazz.fullname == "android.os.Bundle": return @@ -1262,6 +1289,8 @@ def verify_collections(clazz): if arg in bad: error(clazz, m, "CL2", "Argument is concrete collection; must be higher-level interface") + +@verifier def verify_uris(clazz): bad = ["java.net.URL", "java.net.URI", "android.net.URL"] @@ -1276,6 +1305,8 @@ def verify_uris(clazz): if arg in bad: error(clazz, m, None, "Argument must take android.net.Uri instead of " + arg) + +@verifier def verify_flags(clazz): """Verifies that flags are non-overlapping.""" known = collections.defaultdict(int) @@ -1292,6 +1323,7 @@ def verify_flags(clazz): known[scope] |= val +@verifier def verify_exception(clazz): """Verifies that methods don't throw generic exceptions.""" for m in clazz.methods: @@ -1309,8 +1341,10 @@ def verify_exception(clazz): if len(m.args) == 0 and t in ["java.lang.IllegalArgumentException", "java.lang.NullPointerException"]: warn(clazz, m, "S1", "Methods taking no arguments should throw IllegalStateException") + GOOGLE_IGNORECASE = re.compile("google", re.IGNORECASE) +# Not marked as @verifier, because it is only conditionally applied. def verify_google(clazz): """Verifies that APIs never reference Google.""" @@ -1323,6 +1357,7 @@ def verify_google(clazz): error(clazz, t, None, "Must never reference Google") +@verifier def verify_bitset(clazz): """Verifies that we avoid using heavy BitSet.""" @@ -1338,6 +1373,7 @@ def verify_bitset(clazz): error(clazz, m, None, "Argument type must not be heavy BitSet") +@verifier def verify_manager(clazz): """Verifies that FooManager is only obtained from Context.""" @@ -1351,6 +1387,7 @@ def verify_manager(clazz): error(clazz, m, None, "Managers must always be obtained from Context") +@verifier def verify_boxed(clazz): """Verifies that methods avoid boxed primitives.""" @@ -1373,6 +1410,7 @@ def verify_boxed(clazz): error(clazz, m, "M11", "Must avoid boxed primitives") +@verifier def verify_static_utils(clazz): """Verifies that helper classes can't be constructed.""" if clazz.fullname.startswith("android.opengl"): return @@ -1392,6 +1430,7 @@ def verify_static_utils(clazz): error(clazz, None, None, "Fully-static utility classes must not have constructor") +# @verifier # Disabled for now def verify_overload_args(clazz): """Verifies that method overloads add new arguments at the end.""" if clazz.fullname.startswith("android.opengl"): return @@ -1432,6 +1471,7 @@ def verify_overload_args(clazz): error(clazz, m, "M2", "Expected consistent argument ordering between overloads: %s..." % (", ".join(locked_sig))) +@verifier def verify_callback_handlers(clazz): """Verifies that methods adding listener/callback have overload for specifying delivery thread.""" @@ -1483,6 +1523,7 @@ def verify_callback_handlers(clazz): warn(clazz, f, "L1", "Registration methods should have overload that accepts delivery Executor") +@verifier def verify_context_first(clazz): """Verifies that methods accepting a Context keep it the first argument.""" examine = clazz.ctors + clazz.methods @@ -1495,6 +1536,7 @@ def verify_context_first(clazz): error(clazz, m, "M3", "ContentResolver is distinct, so it must be the first argument") +@verifier def verify_listener_last(clazz): """Verifies that methods accepting a Listener or Callback keep them as last arguments.""" examine = clazz.ctors + clazz.methods @@ -1508,6 +1550,7 @@ def verify_listener_last(clazz): warn(clazz, m, "M3", "Listeners should always be at end of argument list") +@verifier def verify_resource_names(clazz): """Verifies that resource names have consistent case.""" if not re.match("android\.R\.[a-z]+", clazz.fullname): return @@ -1539,6 +1582,7 @@ def verify_resource_names(clazz): error(clazz, f, "C7", "Expected resource name in this class to be FooBar_Baz style") +@verifier def verify_files(clazz): """Verifies that methods accepting File also accept streams.""" @@ -1560,6 +1604,7 @@ def verify_files(clazz): warn(clazz, m, "M10", "Methods accepting File should also accept FileDescriptor or streams") +@verifier def verify_manager_list(clazz): """Verifies that managers return List<? extends Parcelable> instead of arrays.""" @@ -1570,6 +1615,7 @@ def verify_manager_list(clazz): warn(clazz, m, None, "Methods should return List<? extends Parcelable> instead of Parcelable[] to support ParceledListSlice under the hood") +@verifier def verify_abstract_inner(clazz): """Verifies that abstract inner classes are static.""" @@ -1578,6 +1624,7 @@ def verify_abstract_inner(clazz): warn(clazz, None, None, "Abstract inner classes should be static to improve testability") +@verifier def verify_runtime_exceptions(clazz): """Verifies that runtime exceptions aren't listed in throws.""" @@ -1622,6 +1669,7 @@ def verify_runtime_exceptions(clazz): error(clazz, m, None, "Methods must not mention RuntimeException subclasses in throws clauses") +@verifier def verify_error(clazz): """Verifies that we always use Exception instead of Error.""" if not clazz.extends: return @@ -1631,6 +1679,7 @@ def verify_error(clazz): error(clazz, None, None, "Exceptions must be named FooException") +@verifier def verify_units(clazz): """Verifies that we use consistent naming for units.""" @@ -1667,6 +1716,7 @@ def verify_units(clazz): error(clazz, m, None, "Percentage must use ints") +@verifier def verify_closable(clazz): """Verifies that classes are AutoClosable.""" if "java.lang.AutoCloseable" in clazz.implements_all: return @@ -1679,6 +1729,7 @@ def verify_closable(clazz): return +@verifier def verify_member_name_not_kotlin_keyword(clazz): """Prevent method names which are keywords in Kotlin.""" @@ -1704,6 +1755,7 @@ def verify_member_name_not_kotlin_keyword(clazz): error(clazz, f, None, "Field name must not be a Kotlin keyword") +@verifier def verify_method_name_not_kotlin_operator(clazz): """Warn about method names which become operators in Kotlin.""" @@ -1753,6 +1805,7 @@ def verify_method_name_not_kotlin_operator(clazz): unique_binary_op(m, m.name[:-6]) # Remove 'Assign' suffix +@verifier def verify_collections_over_arrays(clazz): """Warn that [] should be Collections.""" @@ -1768,6 +1821,7 @@ def verify_collections_over_arrays(clazz): warn(clazz, m, None, "Method argument should be Collection<> (or subclass) instead of raw array") +@verifier def verify_user_handle(clazz): """Methods taking UserHandle should be ForUser or AsUser.""" if clazz.name.endswith("Listener") or clazz.name.endswith("Callback") or clazz.name.endswith("Callbacks"): return @@ -1792,6 +1846,7 @@ def verify_user_handle(clazz): "or 'queryFooForUser'") +@verifier def verify_params(clazz): """Parameter classes should be 'Params'.""" if clazz.name.endswith("Params"): return @@ -1807,6 +1862,7 @@ def verify_params(clazz): error(clazz, None, None, "Classes holding a set of parameters should be called 'FooParams'") +@verifier def verify_services(clazz): """Service name should be FOO_BAR_SERVICE = 'foo_bar'.""" if clazz.fullname != "android.content.Context": return @@ -1820,6 +1876,7 @@ def verify_services(clazz): error(clazz, f, "C4", "Inconsistent service value; expected '%s'" % (expected)) +@verifier def verify_tense(clazz): """Verify tenses of method names.""" if clazz.fullname.startswith("android.opengl"): return @@ -1829,6 +1886,7 @@ def verify_tense(clazz): warn(clazz, m, None, "Unexpected tense; probably meant 'enabled'") +@verifier def verify_icu(clazz): """Verifies that richer ICU replacements are used.""" better = { @@ -1860,6 +1918,7 @@ def verify_icu(clazz): warn(clazz, m, None, "Type %s should be replaced with richer ICU type %s" % (arg, better[arg])) +@verifier def verify_clone(clazz): """Verify that clone() isn't implemented; see EJ page 61.""" for m in clazz.methods: @@ -1867,6 +1926,7 @@ def verify_clone(clazz): error(clazz, m, None, "Provide an explicit copy constructor instead of implementing clone()") +@verifier def verify_pfd(clazz): """Verify that android APIs use PFD over FD.""" if clazz.fullname == "android.os.FileUtils" or clazz.fullname == "android.system.Os": @@ -1888,6 +1948,7 @@ def verify_pfd(clazz): error(clazz, f, "FW11", "Must use ParcelFileDescriptor") +@verifier def verify_numbers(clazz): """Discourage small numbers types like short and byte.""" @@ -1909,8 +1970,10 @@ def verify_numbers(clazz): if arg in discouraged: warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead") + PRIMITIVES = {"void", "int", "float", "boolean", "short", "char", "byte", "long", "double"} +@verifier def verify_nullability(clazz): """Catches missing nullability annotations""" @@ -1939,6 +2002,8 @@ def verify_nullability_args(clazz, m): def has_nullability(annotations): return "@NonNull" in annotations or "@Nullable" in annotations + +@verifier def verify_singleton(clazz): """Catch singleton objects with constructors.""" @@ -1973,61 +2038,10 @@ def examine_clazz(clazz): if not is_interesting(clazz): return - verify_constants(clazz) - verify_enums(clazz) - verify_class_names(clazz) - verify_method_names(clazz) - verify_callbacks(clazz) - verify_listeners(clazz) - verify_actions(clazz) - verify_extras(clazz) - verify_equals(clazz) - verify_parcelable(clazz) - verify_protected(clazz) - verify_fields(clazz) - verify_register(clazz) - verify_sync(clazz) - verify_intent_builder(clazz) - verify_helper_classes(clazz) - verify_builder(clazz) - verify_aidl(clazz) - verify_internal(clazz) - verify_layering(clazz) - verify_boolean(clazz) - verify_collections(clazz) - verify_uris(clazz) - verify_flags(clazz) - verify_exception(clazz) + for v in verifiers.itervalues(): + v(clazz) + if not ALLOW_GOOGLE: verify_google(clazz) - verify_bitset(clazz) - verify_manager(clazz) - verify_boxed(clazz) - verify_static_utils(clazz) - # verify_overload_args(clazz) - verify_callback_handlers(clazz) - verify_context_first(clazz) - verify_listener_last(clazz) - verify_resource_names(clazz) - verify_files(clazz) - verify_manager_list(clazz) - verify_abstract_inner(clazz) - verify_runtime_exceptions(clazz) - verify_error(clazz) - verify_units(clazz) - verify_closable(clazz) - verify_member_name_not_kotlin_keyword(clazz) - verify_method_name_not_kotlin_operator(clazz) - verify_collections_over_arrays(clazz) - verify_user_handle(clazz) - verify_params(clazz) - verify_services(clazz) - verify_tense(clazz) - verify_icu(clazz) - verify_clone(clazz) - verify_pfd(clazz) - verify_numbers(clazz) - verify_singleton(clazz) - verify_nullability(clazz) def examine_stream(stream, base_stream=None, in_classes_with_base=[], out_classes_with_base=None): |