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)
diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py
index acf1f1e..d1fe43e 100644
--- a/tools/apilint/apilint.py
+++ b/tools/apilint/apilint.py
@@ -297,7 +297,7 @@
 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 @@
         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 @@
         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 @@
             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 @@
 
     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 @@
             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 @@
             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 @@
         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 081e98d..fde61a9 100644
--- a/tools/apilint/apilint_test.py
+++ b/tools/apilint/apilint_test.py
@@ -225,11 +225,12 @@
         self.assertEquals('pkg.SuppressLint', cls.fullname)
 
     def test_parse_method(self):
-        m = self._method("method @Deprecated public static <T> Class<T>[][] name("
+        m = self._method("method @Deprecated public static native <T> Class<T>[][] name("
                          + "Class<T[]>[][], Class<T[][][]>[][]...) 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 @@
         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 @@
         self._method("method <T> 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> T get(androidx.navigation.NavigatorProvider, kotlin.reflect.KClass<T> 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<? super Editor,kotlin.Unit> action);')
+        self._method('method <K, V> LruCache<K,V> lruCache(int maxSize, Function2<? super K,? super V,java.lang.Integer> sizeOf = { _, _ -> 1 }, Function1<? extends V> create = { (V)null }, Function4<kotlin.Unit> onEntryRemoved = { _, _, _, _ ->  });')
+        self._method('method android.Bitmap? drawToBitmap(android.View, android.Config config = android.graphics.Bitmap.Config.ARGB_8888);')
+        self._method('method void emptyLambda(Function0<kotlin.Unit> 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<java.util.Map.@NonNull Entry<K,V>> 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()