From d1e3892119a6a415fb39da5db21e06483db0ae03 Mon Sep 17 00:00:00 2001 From: Adrian Roos Date: Mon, 14 Jan 2019 15:44:15 +0100 Subject: [PATCH] ApiLint: Add operator keyword and property parsing Also fix up some issues with expression parsing, type use annotations, etc. Test: python tools/apilint/apilint_test.py Change-Id: I38145a51470ce6c3e5813a546d681489fd87fc19 (cherry picked from commit 403c8e35d8e7cc0f81a0a2c42d038c47e1b2703f) --- tools/apilint/apilint.py | 34 ++++++++++++++++++--------- tools/apilint/apilint_test.py | 43 ++++++++++++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 12 deletions(-) diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py index acf1f1e9902e8..d1fe43ea0c7c9 100644 --- a/tools/apilint/apilint.py +++ b/tools/apilint/apilint.py @@ -297,7 +297,7 @@ class V2Tokenizer(object): class V2LineParser(object): __slots__ = ["tokenized", "current", "len"] - MODIFIERS = set("public protected internal private abstract default static final transient volatile synchronized".split()) + MODIFIERS = set("public protected internal private abstract default static final transient volatile synchronized native operator sealed strictfp infix inline suspend vararg".split()) JAVA_LANG_TYPES = set("AbstractMethodError AbstractStringBuilder Appendable ArithmeticException ArrayIndexOutOfBoundsException ArrayStoreException AssertionError AutoCloseable Boolean BootstrapMethodError Byte Character CharSequence Class ClassCastException ClassCircularityError ClassFormatError ClassLoader ClassNotFoundException Cloneable CloneNotSupportedException Comparable Compiler Deprecated Double Enum EnumConstantNotPresentException Error Exception ExceptionInInitializerError Float FunctionalInterface IllegalAccessError IllegalAccessException IllegalArgumentException IllegalMonitorStateException IllegalStateException IllegalThreadStateException IncompatibleClassChangeError IndexOutOfBoundsException InheritableThreadLocal InstantiationError InstantiationException Integer InternalError InterruptedException Iterable LinkageError Long Math NegativeArraySizeException NoClassDefFoundError NoSuchFieldError NoSuchFieldException NoSuchMethodError NoSuchMethodException NullPointerException Number NumberFormatException Object OutOfMemoryError Override Package package-info.java Process ProcessBuilder ProcessEnvironment ProcessImpl Readable ReflectiveOperationException Runnable Runtime RuntimeException RuntimePermission SafeVarargs SecurityException SecurityManager Short StackOverflowError StackTraceElement StrictMath String StringBuffer StringBuilder StringIndexOutOfBoundsException SuppressWarnings System Thread ThreadDeath ThreadGroup ThreadLocal Throwable TypeNotPresentException UNIXProcess UnknownError UnsatisfiedLinkError UnsupportedClassVersionError UnsupportedOperationException VerifyError VirtualMachineError Void".split()) def __init__(self, raw): @@ -355,7 +355,7 @@ class V2LineParser(object): self.parse_eof() def parse_into_field(self, field): - kind = self.parse_token("field") + kind = self.parse_one_of("field", "property") field.split = [kind] annotations = self.parse_annotations() if "@Deprecated" in annotations: @@ -442,13 +442,19 @@ class V2LineParser(object): return None def parse_type(self): + self.parse_annotations() type = self.parse_token() + if type[-1] == '.': + self.parse_annotations() + type += self.parse_token() if type in V2LineParser.JAVA_LANG_TYPES: type = "java.lang." + type self.parse_matching_paren("<", ">") while True: t = self.lookahead() - if t == "[]": + if t == "@": + self.parse_annotation() + elif t == "[]": type += self.parse_token() elif self.parse_kotlin_nullability() is not None: pass # discard nullability for now @@ -478,17 +484,23 @@ class V2LineParser(object): self.parse_token(",") def parse_arg(self): + self.parse_if("vararg") # kotlin vararg self.parse_annotations() type = self.parse_arg_type() l = self.lookahead() if l != "," and l != ")": - self.parse_token() # kotlin argument name + if self.lookahead() != '=': + self.parse_token() # kotlin argument name if self.parse_if('='): # kotlin default value - (self.parse_matching_paren('(', ')') or - self.parse_matching_paren('{', '}') or - self.parse_token() and self.parse_matching_paren('(', ')')) + self.parse_expression() return type + def parse_expression(self): + while not self.lookahead() in [')', ',', ';']: + (self.parse_matching_paren('(', ')') or + self.parse_matching_paren('{', '}') or + self.parse_token()) + def parse_throws(self): ret = [] if self.parse_if("throws"): @@ -516,7 +528,7 @@ class V2LineParser(object): def parse_annotation_default(self): if self.parse_if("default"): - self.parse_value() + self.parse_expression() def parse_value(self): if self.lookahead() == "{": @@ -599,7 +611,7 @@ def _parse_stream_to_generator(f): clazz.ctors.append(Method(clazz, line, raw, blame, sig_format=sig_format)) elif raw.startswith(" method"): clazz.methods.append(Method(clazz, line, raw, blame, sig_format=sig_format)) - elif raw.startswith(" field"): + elif raw.startswith(" field") or raw.startswith(" property"): clazz.fields.append(Field(clazz, line, raw, blame, sig_format=sig_format)) elif raw.startswith(" }") and clazz: yield clazz @@ -942,7 +954,7 @@ def verify_fields(clazz): else: error(clazz, f, "F2", "Bare fields must be marked final, or add accessors if mutable") - if not "static" in f.split: + if "static" not in f.split and "property" not in f.split: if not re.match("[a-z]([a-zA-Z]+)?", f.name): error(clazz, f, "S1", "Non-static fields must be named using myField style") @@ -1650,7 +1662,7 @@ def verify_method_name_not_kotlin_operator(clazz): binary.add(op) for m in clazz.methods: - if 'static' in m.split: + if 'static' in m.split or 'operator' in m.split: continue # https://kotlinlang.org/docs/reference/operator-overloading.html#unary-prefix-operators diff --git a/tools/apilint/apilint_test.py b/tools/apilint/apilint_test.py index 081e98defa171..fde61a902341c 100644 --- a/tools/apilint/apilint_test.py +++ b/tools/apilint/apilint_test.py @@ -225,11 +225,12 @@ class V2ParserTests(unittest.TestCase): self.assertEquals('pkg.SuppressLint', cls.fullname) def test_parse_method(self): - m = self._method("method @Deprecated public static Class[][] name(" + m = self._method("method @Deprecated public static native Class[][] name(" + "Class[][], Class[][]...) throws Exception, T;") self.assertTrue('static' in m.split) self.assertTrue('public' in m.split) self.assertTrue('method' in m.split) + self.assertTrue('native' in m.split) self.assertTrue('deprecated' in m.split) self.assertEquals('java.lang.Class[][]', m.typ) self.assertEquals('name', m.name) @@ -248,6 +249,7 @@ class V2ParserTests(unittest.TestCase): self._method('method abstract String category() default "";', cls=cls) self._method('method abstract boolean deepExport() default false;', cls=cls) self._method('method abstract ViewDebug.FlagToString[] flagMapping() default {};', cls=cls) + self._method('method abstract ViewDebug.FlagToString[] flagMapping() default (double)java.lang.Float.NEGATIVE_INFINITY;', cls=cls) def test_parse_string_field(self): f = self._field('field @Deprecated public final String SOME_NAME = "value";') @@ -286,5 +288,44 @@ class V2ParserTests(unittest.TestCase): self._method("method T name(T a = 1, T b = A(1), Lambda f = { false }, N? n = null, " + """double c = (1/0), float d = 1.0f, String s = "heyo", char c = 'a');""") + def test_kotlin_operator(self): + self._method('method public operator void unaryPlus(androidx.navigation.NavDestination);') + self._method('method public static operator androidx.navigation.NavDestination get(androidx.navigation.NavGraph, @IdRes int id);') + self._method('method public static operator T get(androidx.navigation.NavigatorProvider, kotlin.reflect.KClass clazz);') + + def test_kotlin_property(self): + self._field('property public VM value;') + self._field('property public final String? action;') + + def test_kotlin_varargs(self): + self._method('method public void error(int p = "42", Integer int2 = "null", int p1 = "42", vararg String args);') + + def test_kotlin_default_values(self): + self._method('method public void foo(String! = null, String! = "Hello World", int = 42);') + self._method('method void method(String, String firstArg = "hello", int secondArg = "42", String thirdArg = "world");') + self._method('method void method(String, String firstArg = "hello", int secondArg = "42");') + self._method('method void method(String, String firstArg = "hello");') + self._method('method void edit(android.Type, boolean commit = false, Function1 action);') + self._method('method LruCache lruCache(int maxSize, Function2 sizeOf = { _, _ -> 1 }, Function1 create = { (V)null }, Function4 onEntryRemoved = { _, _, _, _ -> });') + self._method('method android.Bitmap? drawToBitmap(android.View, android.Config config = android.graphics.Bitmap.Config.ARGB_8888);') + self._method('method void emptyLambda(Function0 sizeOf = {});') + self._method('method void method1(int p = 42, Integer? int2 = null, int p1 = 42, String str = "hello world", java.lang.String... args);') + self._method('method void method2(int p, int int2 = (2 * int) * some.other.pkg.Constants.Misc.SIZE);') + self._method('method void method3(String str, int p, int int2 = double(int) + str.length);') + self._method('method void print(test.pkg.Foo foo = test.pkg.Foo());') + + def test_type_use_annotation(self): + self._method('method public static int codePointAt(char @NonNull [], int);') + self._method('method @NonNull public java.util.Set> entrySet();') + + m = self._method('method @NonNull public java.lang.annotation.@NonNull Annotation @NonNull [] getAnnotations();') + self.assertEquals('java.lang.annotation.Annotation[]', m.typ) + + m = self._method('method @NonNull public abstract java.lang.annotation.@NonNull Annotation @NonNull [] @NonNull [] getParameterAnnotations();') + self.assertEquals('java.lang.annotation.Annotation[][]', m.typ) + + m = self._method('method @NonNull public @NonNull String @NonNull [] split(@NonNull String, int);') + self.assertEquals('java.lang.String[]', m.typ) + if __name__ == "__main__": unittest.main()