xref: /aosp_15_r20/tools/metalava/metalava/src/test/java/com/android/tools/metalava/doc/DocAnalyzerTest.kt (revision 115816f9299ab6ddd6b9673b81f34e707f6bacab)
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tools.metalava.doc
18 
19 import com.android.tools.lint.checks.infrastructure.TestFiles
20 import com.android.tools.metalava.ARG_CURRENT_CODENAME
21 import com.android.tools.metalava.ARG_CURRENT_VERSION
22 import com.android.tools.metalava.DriverTest
23 import com.android.tools.metalava.columnSource
24 import com.android.tools.metalava.lint.DefaultLintErrorMessage
25 import com.android.tools.metalava.model.provider.Capability
26 import com.android.tools.metalava.model.psi.trimDocIndent
27 import com.android.tools.metalava.model.testing.RequiresCapabilities
28 import com.android.tools.metalava.nonNullSource
29 import com.android.tools.metalava.nullableSource
30 import com.android.tools.metalava.requiresApiSource
31 import com.android.tools.metalava.requiresPermissionSource
32 import com.android.tools.metalava.systemApiSource
33 import com.android.tools.metalava.testing.java
34 import com.android.tools.metalava.testing.kotlin
35 import com.android.tools.metalava.uiThreadSource
36 import com.android.tools.metalava.workerThreadSource
37 import org.junit.Assert
38 import org.junit.Test
39 
40 /** Tests for the [DocAnalyzer] which enhances the docs */
41 class DocAnalyzerTest : DriverTest() {
42     // TODO: Test @StringDef
43 
44     @Test
Basic documentation generation testnull45     fun `Basic documentation generation test`() {
46         check(
47             sourceFiles =
48                 arrayOf(
49                     java(
50                         """
51                     package test.pkg;
52                     import android.annotation.Nullable;
53                     import android.annotation.NonNull;
54                     public class Foo {
55                         /** These are the docs for method1. */
56                         @Nullable public Double method1(@NonNull Double factor1, @NonNull Double factor2) { }
57                         /** These are the docs for method2. It can sometimes return null. */
58                         @Nullable public Double method2(@NonNull Double factor1, @NonNull Double factor2) { }
59                         @Nullable public Double method3(@NonNull Double factor1, @NonNull Double factor2) { }
60                         /**
61                          * @param factor2 Don't pass null here please.
62                          */
63                         @Nullable public Double method4(@NonNull Double factor1, @NonNull Double factor2) { }
64                     }
65                     """
66                     ),
67                     nonNullSource,
68                     nullableSource
69                 ),
70             checkCompilation = false, // needs androidx.annotations in classpath
71             docStubs = true,
72             stubFiles =
73                 arrayOf(
74                     java(
75                         """
76                     package test.pkg;
77                     @SuppressWarnings({"unchecked", "deprecation", "all"})
78                     public class Foo {
79                     public Foo() { throw new RuntimeException("Stub!"); }
80                     /**
81                      * These are the docs for method1.
82                      * @param factor1 This value must never be {@code null}.
83                      * @param factor2 This value must never be {@code null}.
84                      * @return This value may be {@code null}.
85                      */
86                     @androidx.annotation.Nullable
87                     public java.lang.Double method1(@androidx.annotation.NonNull java.lang.Double factor1, @androidx.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); }
88                     /**
89                      * These are the docs for method2. It can sometimes return null.
90                      * @param factor1 This value must never be {@code null}.
91                      * @param factor2 This value must never be {@code null}.
92                      */
93                     @androidx.annotation.Nullable
94                     public java.lang.Double method2(@androidx.annotation.NonNull java.lang.Double factor1, @androidx.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); }
95                     /**
96                      * @param factor1 This value must never be {@code null}.
97                      * @param factor2 This value must never be {@code null}.
98                      * @return This value may be {@code null}.
99                      */
100                     @androidx.annotation.Nullable
101                     public java.lang.Double method3(@androidx.annotation.NonNull java.lang.Double factor1, @androidx.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); }
102                     /**
103                      * @param factor2 Don't pass null here please.
104                      * @param factor1 This value must never be {@code null}.
105                      * @return This value may be {@code null}.
106                      */
107                     @androidx.annotation.Nullable
108                     public java.lang.Double method4(@androidx.annotation.NonNull java.lang.Double factor1, @androidx.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); }
109                     }
110                     """
111                     )
112                 )
113         )
114     }
115 
116     @Test
Check construct ApiLookup works correctlynull117     fun `Check construct ApiLookup works correctly`() {
118         check(
119             sourceFiles =
120                 arrayOf(
121                     java(
122                         """
123                             package test.pkg;
124 
125                             public class Foo {
126                                 public Foo() {}
127                                 public Foo(int i) { this.i = i; }
128 
129                                 private int i;
130                             }
131                         """
132                     ),
133                 ),
134             checkCompilation = true,
135             docStubs = true,
136             applyApiLevelsXml =
137                 """
138                     <?xml version="1.0" encoding="utf-8"?>
139                     <api version="2">
140                         <class name="test/pkg/Foo" since="17">
141                             <method name="&lt;init>()V" since="18"/>
142                             <method name="&lt;init>(I)V" since="19"/>
143                         </class>
144                     </api>
145                 """,
146             stubFiles =
147                 arrayOf(
148                     java(
149                         """
150                             package test.pkg;
151                             /** @apiSince 17 */
152                             @SuppressWarnings({"unchecked", "deprecation", "all"})
153                             public class Foo {
154                             /** @apiSince 18 */
155                             public Foo() { throw new RuntimeException("Stub!"); }
156                             /** @apiSince 19 */
157                             public Foo(int i) { throw new RuntimeException("Stub!"); }
158                             }
159                         """
160                     ),
161                 ),
162         )
163     }
164 
165     @Test
Fix first sentence handlingnull166     fun `Fix first sentence handling`() {
167         check(
168             sourceFiles =
169                 arrayOf(
170                     java(
171                         """
172                     package android.annotation;
173 
174                     import static java.lang.annotation.ElementType.*;
175                     import static java.lang.annotation.RetentionPolicy.CLASS;
176                     import java.lang.annotation.*;
177 
178                     /**
179                      * Denotes that an integer parameter, field or method return value is expected
180                      * to be a String resource reference (e.g. {@code android.R.string.ok}).
181                      */
182                     @Documented
183                     @Retention(CLASS)
184                     @Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
185                     public @interface StringRes {
186                     }
187                     """
188                     )
189                 ),
190             checkCompilation = true,
191             docStubs = true,
192             stubFiles =
193                 arrayOf(
194                     java(
195                         """
196                     package android.annotation;
197                     /**
198                      * Denotes that an integer parameter, field or method return value is expected
199                      * to be a String resource reference (e.g.&nbsp;{@code android.R.string.ok}).
200                      */
201                     @SuppressWarnings({"unchecked", "deprecation", "all"})
202                     @java.lang.annotation.Documented
203                     @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS)
204                     @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE})
205                     public @interface StringRes {
206                     }
207                     """
208                     )
209                 ),
210         )
211     }
212 
213     @Test
Document Permissionsnull214     fun `Document Permissions`() {
215         check(
216             docStubs = true,
217             sourceFiles =
218                 arrayOf(
219                     java(
220                         """
221                     package test.pkg;
222 
223                     import android.Manifest;
224                     import android.annotation.RequiresPermission;
225 
226                     public class PermissionTest {
227                         @RequiresPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
228                         public void test1() {
229                         }
230 
231                         @RequiresPermission(allOf = Manifest.permission.ACCESS_COARSE_LOCATION)
232                         public void test2() {
233                         }
234 
235                         @RequiresPermission(anyOf = {Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION})
236                         public void test3() {
237                         }
238 
239                         @RequiresPermission(allOf = {Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCOUNT_MANAGER})
240                         public void test4() {
241                         }
242 
243                         @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true) // b/73559440
244                         public void test5() {
245                         }
246 
247                         @RequiresPermission(anyOf = {Manifest.permission.ACCESS_COARSE_LOCATION, "carrier privileges"})
248                         public void test6() {
249                         }
250 
251                         // Typo in marker
252                         @RequiresPermission(anyOf = {Manifest.permission.ACCESS_COARSE_LOCATION, "carier priviliges"}) // NOTYPO
253                         public void test6() {
254                         }
255                     }
256                     """
257                     ),
258                     java(
259                         """
260                     package android;
261 
262                     public abstract class Manifest {
263                         public static final class permission {
264                             public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
265                             public static final String ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION";
266                             public static final String ACCOUNT_MANAGER = "android.permission.ACCOUNT_MANAGER";
267                             public static final String WATCH_APPOPS = "android.permission.WATCH_APPOPS";
268                         }
269                     }
270                     """
271                     ),
272                     requiresPermissionSource
273                 ),
274             checkCompilation = false, // needs androidx.annotations in classpath
275             expectedFail = DefaultLintErrorMessage,
276             expectedIssues =
277                 "src/test/pkg/PermissionTest.java:33: error: Unrecognized permission `carier priviliges`; did you mean `carrier privileges`? [MissingPermission]", // NOTYPO
278             stubFiles =
279                 arrayOf(
280                     // common_typos_disable
281                     java(
282                         """
283                     package test.pkg;
284                     import android.Manifest;
285                     @SuppressWarnings({"unchecked", "deprecation", "all"})
286                     public class PermissionTest {
287                     public PermissionTest() { throw new RuntimeException("Stub!"); }
288                     /**
289                      * Requires {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}
290                      */
291                     public void test1() { throw new RuntimeException("Stub!"); }
292                     /**
293                      * Requires {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}
294                      */
295                     public void test2() { throw new RuntimeException("Stub!"); }
296                     /**
297                      * Requires {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} or {@link android.Manifest.permission#ACCESS_FINE_LOCATION}
298                      */
299                     public void test3() { throw new RuntimeException("Stub!"); }
300                     /**
301                      * Requires {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} and {@link android.Manifest.permission#ACCOUNT_MANAGER}
302                      */
303                     public void test4() { throw new RuntimeException("Stub!"); }
304                     public void test5() { throw new RuntimeException("Stub!"); }
305                     /**
306                      * Requires {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} or {@link android.telephony.TelephonyManager#hasCarrierPrivileges carrier privileges}
307                      */
308                     public void test6() { throw new RuntimeException("Stub!"); }
309                     /**
310                      * Requires {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} or "carier priviliges"
311                      */
312                     public void test6() { throw new RuntimeException("Stub!"); }
313                     }
314                     """
315                     )
316                     // common_typos_enable
317                 )
318         )
319     }
320 
321     @Test
Conditional Permissionnull322     fun `Conditional Permission`() {
323         check(
324             sourceFiles =
325                 arrayOf(
326                     java(
327                         """
328                     package test.pkg;
329 
330                     import android.Manifest;
331                     import android.annotation.RequiresPermission;
332 
333                     // Scenario described in b/73559440
334                     public class PermissionTest {
335                         @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true)
336                         public void test1() {
337                         }
338                     }
339                     """
340                     ),
341                     java(
342                         """
343                     package android;
344 
345                     public abstract class Manifest {
346                         public static final class permission {
347                             public static final String WATCH_APPOPS = "android.permission.WATCH_APPOPS";
348                         }
349                     }
350                     """
351                     ),
352                     requiresPermissionSource
353                 ),
354             checkCompilation = false, // needs androidx.annotations in classpath
355             stubFiles =
356                 arrayOf(
357                     java(
358                         """
359                     package test.pkg;
360                     @SuppressWarnings({"unchecked", "deprecation", "all"})
361                     public class PermissionTest {
362                     public PermissionTest() { throw new RuntimeException("Stub!"); }
363                     public void test1() { throw new RuntimeException("Stub!"); }
364                     }
365                     """
366                     )
367                 )
368         )
369     }
370 
371     @Test
Merging in documentation snippets from annotation memberDoc and classDocnull372     fun `Merging in documentation snippets from annotation memberDoc and classDoc`() {
373         check(
374             sourceFiles =
375                 arrayOf(
376                     java(
377                         """
378                     package test.pkg;
379                     import androidx.annotation.UiThread;
380                     import androidx.annotation.WorkerThread;
381                     @UiThread
382                     public class RangeTest {
383                         @WorkerThread
384                         public int test1() { }
385                     }
386                     """
387                     ),
388                     uiThreadSource,
389                     workerThreadSource
390                 ),
391             checkCompilation = true,
392             docStubs = true,
393             stubFiles =
394                 arrayOf(
395                     java(
396                         """
397                     package test.pkg;
398                     /**
399                      * Methods in this class must be called on the thread that originally created
400                      * this UI element, unless otherwise noted. This is typically the
401                      * main thread of your app. *
402                      */
403                     @SuppressWarnings({"unchecked", "deprecation", "all"})
404                     public class RangeTest {
405                     public RangeTest() { throw new RuntimeException("Stub!"); }
406                     /**
407                      * This method may take several seconds to complete, so it should
408                      * only be called from a worker thread.
409                      */
410                     public int test1() { throw new RuntimeException("Stub!"); }
411                     }
412                     """
413                     )
414                 )
415         )
416     }
417 
418     @Test
Warn about multiple threading annotationsnull419     fun `Warn about multiple threading annotations`() {
420         check(
421             sourceFiles =
422                 arrayOf(
423                     java(
424                         """
425                     package test.pkg;
426                     import androidx.annotation.UiThread;
427                     import androidx.annotation.WorkerThread;
428                     public class RangeTest {
429                         @UiThread @WorkerThread
430                         public int test1() { }
431                     }
432                     """
433                     ),
434                     uiThreadSource,
435                     workerThreadSource
436                 ),
437             checkCompilation = true,
438             expectedFail = DefaultLintErrorMessage,
439             expectedIssues =
440                 "src/test/pkg/RangeTest.java:6: error: Found more than one threading annotation on method test.pkg.RangeTest.test1(); the auto-doc feature does not handle this correctly [MultipleThreadAnnotations]",
441             docStubs = true,
442             stubFiles =
443                 arrayOf(
444                     java(
445                         """
446                     package test.pkg;
447                     @SuppressWarnings({"unchecked", "deprecation", "all"})
448                     public class RangeTest {
449                     public RangeTest() { throw new RuntimeException("Stub!"); }
450                     /**
451                      * This method must be called on the thread that originally created
452                      * this UI element. This is typically the main thread of your app.
453                      * <br>
454                      * This method may take several seconds to complete, so it should
455                      * only be called from a worker thread.
456                      */
457                     public int test1() { throw new RuntimeException("Stub!"); }
458                     }
459                     """
460                     )
461                 )
462         )
463     }
464 
465     @Test
Merge Multiple sectionsnull466     fun `Merge Multiple sections`() {
467         check(
468             expectedIssues =
469                 "src/android/widget/Toolbar2.java:18: error: Documentation should not specify @apiSince manually; it's computed and injected at build time by metalava [ForbiddenTag]",
470             expectedFail = DefaultLintErrorMessage,
471             sourceFiles =
472                 arrayOf(
473                     java(
474                         """
475                     package android.widget;
476                     import androidx.annotation.UiThread;
477 
478                     public class Toolbar2 {
479                         /**
480                         * Existing documentation for {@linkplain #getCurrentContentInsetEnd()} here.
481                         * @return blah blah blah
482                         */
483                         @UiThread
484                         public int getCurrentContentInsetEnd() {
485                             return 0;
486                         }
487 
488                         /**
489                         * @apiSince 15
490                         */
491                         @UiThread
492                         public int getCurrentContentInsetRight() {
493                             return 0;
494                         }
495                     }
496                     """
497                     ),
498                     uiThreadSource
499                 ),
500             checkCompilation = true,
501             docStubs = true,
502             applyApiLevelsXml =
503                 """
504                     <?xml version="1.0" encoding="utf-8"?>
505                     <api version="2">
506                         <class name="android/widget/Toolbar2" since="21">
507                             <method name="&lt;init>(Landroid/content/Context;)V"/>
508                             <method name="collapseActionView()V"/>
509                             <method name="getContentInsetStartWithNavigation()I" since="24"/>
510                             <method name="getCurrentContentInsetEnd()I" since="24"/>
511                             <method name="getCurrentContentInsetLeft()I" since="24"/>
512                             <method name="getCurrentContentInsetRight()I" since="24"/>
513                             <method name="getCurrentContentInsetStart()I" since="24"/>
514                         </class>
515                     </api>
516                     """,
517             stubFiles =
518                 arrayOf(
519                     java(
520                         """
521                     package android.widget;
522                     /** @apiSince 21 */
523                     @SuppressWarnings({"unchecked", "deprecation", "all"})
524                     public class Toolbar2 {
525                     public Toolbar2() { throw new RuntimeException("Stub!"); }
526                     /**
527                      * Existing documentation for {@linkplain #getCurrentContentInsetEnd()} here.
528                      * <br>
529                      * This method must be called on the thread that originally created
530                      * this UI element. This is typically the main thread of your app.
531                      * @return blah blah blah
532                      * @apiSince 24
533                      */
534                     public int getCurrentContentInsetEnd() { throw new RuntimeException("Stub!"); }
535                     /**
536                      * <br>
537                      * This method must be called on the thread that originally created
538                      * this UI element. This is typically the main thread of your app.
539                      * @apiSince 15
540                      */
541                     public int getCurrentContentInsetRight() { throw new RuntimeException("Stub!"); }
542                     }
543                     """
544                     )
545                 )
546         )
547     }
548 
549     @Test
Create method documentation from nothingnull550     fun `Create method documentation from nothing`() {
551         check(
552             sourceFiles =
553                 arrayOf(
554                     java(
555                         """
556                     package test.pkg;
557                     import android.annotation.RequiresPermission;
558                     @SuppressWarnings("WeakerAccess")
559                     public class RangeTest {
560                         public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
561                         @RequiresPermission(ACCESS_COARSE_LOCATION)
562                         public void test1() {
563                         }
564                     }
565                     """
566                     ),
567                     requiresPermissionSource
568                 ),
569             checkCompilation = true,
570             docStubs = true,
571             stubFiles =
572                 arrayOf(
573                     java(
574                         """
575                     package test.pkg;
576                     @SuppressWarnings({"unchecked", "deprecation", "all"})
577                     public class RangeTest {
578                     public RangeTest() { throw new RuntimeException("Stub!"); }
579                     /**
580                      * Requires {@link test.pkg.RangeTest#ACCESS_COARSE_LOCATION}
581                      */
582                     public void test1() { throw new RuntimeException("Stub!"); }
583                     public static final java.lang.String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
584                     }
585                     """
586                     )
587                 )
588         )
589     }
590 
591     @Test
Warn about missing fieldnull592     fun `Warn about missing field`() {
593         check(
594             sourceFiles =
595                 arrayOf(
596                     java(
597                         """
598                     package test.pkg;
599                     import android.annotation.RequiresPermission;
600                     public class RangeTest {
601                         @RequiresPermission("MyPermission")
602                         public void test1() {
603                         }
604                     }
605                     """
606                     ),
607                     requiresPermissionSource
608                 ),
609             checkCompilation = true,
610             docStubs = true,
611             expectedFail = DefaultLintErrorMessage,
612             expectedIssues =
613                 "src/test/pkg/RangeTest.java:5: error: Cannot find permission field for \"MyPermission\" required by method test.pkg.RangeTest.test1() (may be hidden or removed) [MissingPermission]",
614             stubFiles =
615                 arrayOf(
616                     java(
617                         """
618                     package test.pkg;
619                     @SuppressWarnings({"unchecked", "deprecation", "all"})
620                     public class RangeTest {
621                     public RangeTest() { throw new RuntimeException("Stub!"); }
622                     /**
623                      * Requires "MyPermission"
624                      */
625                     public void test1() { throw new RuntimeException("Stub!"); }
626                     }
627                     """
628                     )
629                 )
630         )
631     }
632 
633     @Test
Add to existing single-line method documentationnull634     fun `Add to existing single-line method documentation`() {
635         check(
636             sourceFiles =
637                 arrayOf(
638                     java(
639                         """
640                     package test.pkg;
641                     import android.annotation.RequiresPermission;
642                     @SuppressWarnings("WeakerAccess")
643                     public class RangeTest {
644                         public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
645                         /** This is the existing documentation. */
646                         @RequiresPermission(ACCESS_COARSE_LOCATION)
647                         public int test1() { }
648                     }
649                     """
650                     ),
651                     requiresPermissionSource
652                 ),
653             checkCompilation = true,
654             docStubs = true,
655             stubFiles =
656                 arrayOf(
657                     java(
658                         """
659                     package test.pkg;
660                     @SuppressWarnings({"unchecked", "deprecation", "all"})
661                     public class RangeTest {
662                     public RangeTest() { throw new RuntimeException("Stub!"); }
663                     /**
664                      * This is the existing documentation.
665                      * <br>
666                      * Requires {@link test.pkg.RangeTest#ACCESS_COARSE_LOCATION}
667                      */
668                     public int test1() { throw new RuntimeException("Stub!"); }
669                     public static final java.lang.String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
670                     }
671                     """
672                     )
673                 )
674         )
675     }
676 
677     @Test
Add to existing multi-line method documentationnull678     fun `Add to existing multi-line method documentation`() {
679         check(
680             sourceFiles =
681                 arrayOf(
682                     java(
683                         """
684                     package test.pkg;
685                     import android.annotation.RequiresPermission;
686                     @SuppressWarnings("WeakerAccess")
687                     public class RangeTest {
688                         public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
689                         /**
690                          * This is the existing documentation.
691                          * Multiple lines of it.
692                          */
693                         @RequiresPermission(ACCESS_COARSE_LOCATION)
694                         public int test1() { }
695                     }
696                     """
697                     ),
698                     requiresPermissionSource
699                 ),
700             checkCompilation = true,
701             docStubs = true,
702             stubFiles =
703                 arrayOf(
704                     java(
705                         """
706                     package test.pkg;
707                     @SuppressWarnings({"unchecked", "deprecation", "all"})
708                     public class RangeTest {
709                     public RangeTest() { throw new RuntimeException("Stub!"); }
710                     /**
711                      * This is the existing documentation.
712                      * Multiple lines of it.
713                      * <br>
714                      * Requires {@link test.pkg.RangeTest#ACCESS_COARSE_LOCATION}
715                      */
716                     public int test1() { throw new RuntimeException("Stub!"); }
717                     public static final java.lang.String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
718                     }
719                     """
720                     )
721                 )
722         )
723     }
724 
725     @Test
Add to method when there are existing parameter docs and appear before thesenull726     fun `Add to method when there are existing parameter docs and appear before these`() {
727         check(
728             sourceFiles =
729                 arrayOf(
730                     java(
731                         """
732                     package test.pkg;
733                     import android.annotation.RequiresPermission;
734                     @SuppressWarnings("WeakerAccess")
735                     public class RangeTest {
736                         public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
737                         /**
738                         * This is the existing documentation.
739                         * @param parameter1 docs for parameter1
740                         * @param parameter2 docs for parameter2
741                         * @param parameter3 docs for parameter2
742                         * @return return value documented here
743                         */
744                         @RequiresPermission(ACCESS_COARSE_LOCATION)
745                         public int test1(int parameter1, int parameter2, int parameter3) { }
746                     }
747                         """
748                     ),
749                     requiresPermissionSource
750                 ),
751             docStubs = true,
752             checkCompilation = true,
753             stubFiles =
754                 arrayOf(
755                     java(
756                         """
757                     package test.pkg;
758                     @SuppressWarnings({"unchecked", "deprecation", "all"})
759                     public class RangeTest {
760                     public RangeTest() { throw new RuntimeException("Stub!"); }
761                     /**
762                      * This is the existing documentation.
763                      * <br>
764                      * Requires {@link test.pkg.RangeTest#ACCESS_COARSE_LOCATION}
765                      * @param parameter1 docs for parameter1
766                      * @param parameter2 docs for parameter2
767                      * @param parameter3 docs for parameter2
768                      * @return return value documented here
769                      */
770                     public int test1(int parameter1, int parameter2, int parameter3) { throw new RuntimeException("Stub!"); }
771                     public static final java.lang.String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
772                     }
773                     """
774                     )
775                 )
776         )
777     }
778 
779     @Test
test documentation trim utilitynull780     fun `test documentation trim utility`() {
781         Assert.assertEquals(
782             "/**\n * This is a comment\n * This is a second comment\n */",
783             trimDocIndent(
784                 """/**
785          * This is a comment
786          * This is a second comment
787          */
788                 """
789                     .trimIndent()
790             )
791         )
792     }
793 
794     @Test
Merge deprecation levelsnull795     fun `Merge deprecation levels`() {
796         check(
797             sourceFiles =
798                 arrayOf(
799                     java(
800                         """
801                     package android.hardware;
802                     /**
803                      * The Camera class is used to set image capture settings, start/stop preview.
804                      *
805                      * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
806                      *             applications.*
807                     */
808                     @Deprecated
809                     public class Camera {
810                        /** @deprecated Use something else. */
811                        public static final String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
812                     }
813                     """
814                     )
815                 ),
816             applyApiLevelsXml =
817                 """
818                     <?xml version="1.0" encoding="utf-8"?>
819                     <api version="2">
820                         <class name="android/hardware/Camera" since="1" deprecated="21">
821                             <method name="&lt;init>()V"/>
822                             <method name="addCallbackBuffer([B)V" since="8"/>
823                             <method name="getLogo()Landroid/graphics/drawable/Drawable;"/>
824                             <field name="ACTION_NEW_VIDEO" since="14" deprecated="19"/>
825                         </class>
826                     </api>
827                     """,
828             checkCompilation = true,
829             docStubs = true,
830             stubFiles =
831                 arrayOf(
832                     java(
833                         """
834                     package android.hardware;
835                     /**
836                      * The Camera class is used to set image capture settings, start/stop preview.
837                      *
838                      * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
839                      *             applications.*
840                      * @apiSince 1
841                      * @deprecatedSince 21
842                      */
843                     @SuppressWarnings({"unchecked", "deprecation", "all"})
844                     @Deprecated
845                     public class Camera {
846                     @Deprecated
847                     public Camera() { throw new RuntimeException("Stub!"); }
848                     /**
849                      * @deprecated Use something else.
850                      * @apiSince 14
851                      * @deprecatedSince 19
852                      */
853                     @Deprecated public static final java.lang.String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
854                     }
855                     """
856                     )
857                 )
858         )
859     }
860 
861     @Test
Api levels around current and previewnull862     fun `Api levels around current and preview`() {
863         check(
864             extraArguments =
865                 arrayOf(
866                     ARG_CURRENT_CODENAME,
867                     "Z",
868                     ARG_CURRENT_VERSION,
869                     "35" // not real api level of Z
870                 ),
871             includeSystemApiAnnotations = true,
872             sourceFiles =
873                 arrayOf(
874                     java(
875                         """
876                     package android.pkg;
877                     import android.annotation.SystemApi;
878                     public class Test {
879                        public static final String UNIT_TEST_1 = "unit.test.1";
880                        /**
881                          * @hide
882                          */
883                         @SystemApi
884                        public static final String UNIT_TEST_2 = "unit.test.2";
885                     }
886                     """
887                     ),
888                     systemApiSource
889                 ),
890             applyApiLevelsXml =
891                 """
892                     <?xml version="1.0" encoding="utf-8"?>
893                     <api version="2">
894                         <class name="android/pkg/Test" since="1">
895                             <field name="UNIT_TEST_1" since="35"/>
896                             <field name="UNIT_TEST_2" since="36"/>
897                         </class>
898                     </api>
899                     """,
900             checkCompilation = true,
901             docStubs = true,
902             stubFiles =
903                 arrayOf(
904                     java(
905                         """
906                     package android.pkg;
907                     /** @apiSince 1 */
908                     @SuppressWarnings({"unchecked", "deprecation", "all"})
909                     public class Test {
910                     public Test() { throw new RuntimeException("Stub!"); }
911                     /** @apiSince 35 */
912                     public static final java.lang.String UNIT_TEST_1 = "unit.test.1";
913                     /**
914                      * @hide
915                      */
916                     public static final java.lang.String UNIT_TEST_2 = "unit.test.2";
917                     }
918                     """
919                     )
920                 )
921         )
922     }
923 
924     @Test
Api levels current codename but no current versionnull925     fun `Api levels current codename but no current version`() {
926         check(
927             extraArguments =
928                 arrayOf(
929                     ARG_CURRENT_CODENAME,
930                     "Z",
931                 ),
932             includeSystemApiAnnotations = true,
933             sourceFiles =
934                 arrayOf(
935                     java(
936                         """
937                             package android.pkg;
938                             public class Test {
939                                public static final String UNIT_TEST_1 = "unit.test.1";
940                                public static final String UNIT_TEST_2 = "unit.test.2";
941                             }
942                         """
943                     ),
944                 ),
945             applyApiLevelsXml =
946                 """
947                     <?xml version="1.0" encoding="utf-8"?>
948                     <api version="2">
949                         <class name="android/pkg/Test" since="1">
950                             <field name="UNIT_TEST_1" since="24" deprecated="30"/>
951                             <field name="UNIT_TEST_2" since="36"/>
952                         </class>
953                     </api>
954                 """,
955             checkCompilation = true,
956             docStubs = true,
957             stubFiles =
958                 arrayOf(
959                     java(
960                         """
961                             package android.pkg;
962                             /** @apiSince 1 */
963                             @SuppressWarnings({"unchecked", "deprecation", "all"})
964                             public class Test {
965                             public Test() { throw new RuntimeException("Stub!"); }
966                             /**
967                              * @apiSince 24
968                              * @deprecatedSince 30
969                              */
970                             public static final java.lang.String UNIT_TEST_1 = "unit.test.1";
971                             /** @apiSince 36 */
972                             public static final java.lang.String UNIT_TEST_2 = "unit.test.2";
973                             }
974                         """
975                     )
976                 )
977         )
978     }
979 
980     @Test
No api levels on SystemApi only elementsnull981     fun `No api levels on SystemApi only elements`() {
982         // @SystemApi, @TestApi etc cannot get api versions since we don't have
983         // accurate android.jar files (or even reliable api.txt/api.xml files) for them.
984         check(
985             extraArguments =
986                 arrayOf(
987                     ARG_CURRENT_CODENAME,
988                     "Z",
989                     ARG_CURRENT_VERSION,
990                     "35" // not real api level of Z
991                 ),
992             sourceFiles =
993                 arrayOf(
994                     java(
995                         """
996                     package android.pkg;
997                     public class Test {
998                        public Test(int i) { }
999                        public static final String UNIT_TEST_1 = "unit.test.1";
1000                        public static final String UNIT_TEST_2 = "unit.test.2";
1001                     }
1002                     """
1003                     )
1004                 ),
1005             applyApiLevelsXml =
1006                 """
1007                     <?xml version="1.0" encoding="utf-8"?>
1008                     <api version="2">
1009                         <class name="android/pkg/Test" since="1">
1010                             <method name="&lt;init>(I)V"/>
1011                             <field name="UNIT_TEST_1" since="35"/>
1012                             <field name="UNIT_TEST_2" since="36"/>
1013                         </class>
1014                     </api>
1015                     """,
1016             checkCompilation = true,
1017             docStubs = true,
1018             stubFiles =
1019                 arrayOf(
1020                     java(
1021                         """
1022                     package android.pkg;
1023                     /** @apiSince 1 */
1024                     @SuppressWarnings({"unchecked", "deprecation", "all"})
1025                     public class Test {
1026                     /** @apiSince 1 */
1027                     public Test(int i) { throw new RuntimeException("Stub!"); }
1028                     /** @apiSince 35 */
1029                     public static final java.lang.String UNIT_TEST_1 = "unit.test.1";
1030                     /** @apiSince Z */
1031                     public static final java.lang.String UNIT_TEST_2 = "unit.test.2";
1032                     }
1033                     """
1034                     )
1035                 )
1036         )
1037     }
1038 
1039     @Test
Generate API level javadocsnull1040     fun `Generate API level javadocs`() {
1041         // TODO: Check package-info.java conflict
1042         // TODO: Test merging
1043         // TODO: Test non-merging
1044         check(
1045             extraArguments =
1046                 arrayOf(
1047                     ARG_CURRENT_CODENAME,
1048                     "Z",
1049                     ARG_CURRENT_VERSION,
1050                     "35" // not real api level of Z
1051                 ),
1052             sourceFiles =
1053                 arrayOf(
1054                     java(
1055                         """
1056                     package android.pkg1;
1057                     public class Test1 {
1058                     }
1059                     """
1060                     ),
1061                     java(
1062                         """
1063                     package android.pkg1;
1064                     public class Test2 {
1065                     }
1066                     """
1067                     ),
1068                     TestFiles.source(
1069                             "src/android/pkg2/package.html",
1070                             """
1071                     <body bgcolor="white">
1072                     Some existing doc here.
1073                     @deprecated
1074                     <!-- comment -->
1075                     </body>
1076                     """
1077                         )
1078                         .indented(),
1079                     java(
1080                         """
1081                     package android.pkg2;
1082                     public class Test1 {
1083                     }
1084                     """
1085                     ),
1086                     java(
1087                         """
1088                     package android.pkg2;
1089                     public class Test2 {
1090                     }
1091                     """
1092                     ),
1093                     java(
1094                         """
1095                     package android.pkg3;
1096                     public class Test1 {
1097                     }
1098                     """
1099                     )
1100                 ),
1101             applyApiLevelsXml =
1102                 """
1103                     <?xml version="1.0" encoding="utf-8"?>
1104                     <api version="2">
1105                         <class name="android/pkg1/Test1" since="15"/>
1106                         <class name="android/pkg3/Test1" since="20"/>
1107                     </api>
1108                     """,
1109             checkCompilation = true,
1110             docStubs = true,
1111             stubFiles =
1112                 arrayOf(
1113                     java(
1114                         """
1115                     package android.pkg1;
1116                     /** @apiSince 15 */
1117                     @SuppressWarnings({"unchecked", "deprecation", "all"})
1118                     public class Test1 {
1119                     public Test1() { throw new RuntimeException("Stub!"); }
1120                     }
1121                     """
1122                     ),
1123                     java(
1124                         """
1125                     /** @apiSince 15 */
1126                     package android.pkg1;
1127                     """
1128                     ),
1129                     java(
1130                         """
1131                     /**
1132                      * Some existing doc here.
1133                      * @deprecated
1134                      * <!-- comment -->
1135                      */
1136                     package android.pkg2;
1137                     """
1138                     ),
1139                     java(
1140                         """
1141                     /** @apiSince 20 */
1142                     package android.pkg3;
1143                     """
1144                     )
1145                 ),
1146         )
1147     }
1148 
1149     object SdkExtSinceConstants {
1150         val sourceFiles =
1151             arrayOf(
1152                 java(
1153                     """
1154                 package android.pkg;
1155                 public class Test {
1156                    public static final String UNIT_TEST_1 = "unit.test.1";
1157                    public static final String UNIT_TEST_2 = "unit.test.2";
1158                    public static final String UNIT_TEST_3 = "unit.test.3";
1159                    public Test() {}
1160                    public void foo() {}
1161                    public class Inner {
1162                        public Inner() {}
1163                        public static final boolean UNIT_TEST_4 = true;
1164                    }
1165                 }
1166                 """
1167                 )
1168             )
1169 
1170         const val apiVersionsXml =
1171             """
1172                 <?xml version="1.0" encoding="utf-8"?>
1173                 <api version="3">
1174                     <sdk id="30" shortname="R-ext" name="R Extensions" reference="android/os/Build${'$'}VERSION_CODES${'$'}R" />
1175                     <sdk id="31" shortname="S-ext" name="S Extensions" reference="android/os/Build${'$'}VERSION_CODES${'$'}S" />
1176                     <sdk id="33" shortname="T-ext" name="T Extensions" reference="android/os/Build${'$'}VERSION_CODES${'$'}T" />
1177                     <sdk id="1000000" shortname="standalone-ext" name="Standalone Extensions" reference="some/other/CONST" />
1178                     <class name="android/pkg/Test" since="1" sdks="0:1,30:2,31:2,33:2">
1179                         <method name="foo()V"/>
1180                         <method name="&lt;init>()V"/>
1181                         <field name="UNIT_TEST_1"/>
1182                         <field name="UNIT_TEST_2" since="2" sdks="1000000:3,31:3,33:3,0:2"/>
1183                         <!--
1184                          ! TODO(b/283062196) - This relies on an api-versions.xml structure that is
1185                          !     not yet created. If the resolution of this bug is to not support this
1186                          !     structure then this test will need updating.
1187                          !-->
1188                         <field name="UNIT_TEST_3" since="31" sdks="1000000:4,0:31"/>
1189                     </class>
1190                     <class name="android/pkg/Test${'$'}Inner" since="1" sdks="0:1,30:2,31:2,33:2">
1191                         <method name="&lt;init>()V"/>
1192                         <field name="UNIT_TEST_4"/>
1193                     </class>
1194                 </api>
1195                 """
1196     }
1197 
1198     @Test
@sdkExtSince (finalized, no codename)null1199     fun `@sdkExtSince (finalized, no codename)`() {
1200         check(
1201             extraArguments =
1202                 arrayOf(
1203                     ARG_CURRENT_VERSION,
1204                     "30",
1205                 ),
1206             sourceFiles = SdkExtSinceConstants.sourceFiles,
1207             applyApiLevelsXml = SdkExtSinceConstants.apiVersionsXml,
1208             checkCompilation = true,
1209             docStubs = true,
1210             stubFiles =
1211                 arrayOf(
1212                     java(
1213                         """
1214                     package android.pkg;
1215                     /**
1216                      * @apiSince 1
1217                      * @sdkExtSince R Extensions 2
1218                      */
1219                     @SuppressWarnings({"unchecked", "deprecation", "all"})
1220                     public class Test {
1221                     /**
1222                      * @apiSince 1
1223                      * @sdkExtSince R Extensions 2
1224                      */
1225                     public Test() { throw new RuntimeException("Stub!"); }
1226                     /**
1227                      * @apiSince 1
1228                      * @sdkExtSince R Extensions 2
1229                      */
1230                     public void foo() { throw new RuntimeException("Stub!"); }
1231                     /**
1232                      * @apiSince 1
1233                      * @sdkExtSince R Extensions 2
1234                      */
1235                     public static final java.lang.String UNIT_TEST_1 = "unit.test.1";
1236                     /**
1237                      * @apiSince 2
1238                      * @sdkExtSince Standalone Extensions 3
1239                      */
1240                     public static final java.lang.String UNIT_TEST_2 = "unit.test.2";
1241                     /** @sdkExtSince Standalone Extensions 4 */
1242                     public static final java.lang.String UNIT_TEST_3 = "unit.test.3";
1243                     /**
1244                      * @apiSince 1
1245                      * @sdkExtSince R Extensions 2
1246                      */
1247                     @SuppressWarnings({"unchecked", "deprecation", "all"})
1248                     public class Inner {
1249                     /**
1250                      * @apiSince 1
1251                      * @sdkExtSince R Extensions 2
1252                      */
1253                     public Inner() { throw new RuntimeException("Stub!"); }
1254                     /**
1255                      * @apiSince 1
1256                      * @sdkExtSince R Extensions 2
1257                      */
1258                     public static final boolean UNIT_TEST_4 = true;
1259                     }
1260                     }
1261                     """
1262                     )
1263                 )
1264         )
1265     }
1266 
1267     @Test
@sdkExtSince (not finalized)null1268     fun `@sdkExtSince (not finalized)`() {
1269         check(
1270             sourceFiles = SdkExtSinceConstants.sourceFiles,
1271             applyApiLevelsXml = SdkExtSinceConstants.apiVersionsXml,
1272             checkCompilation = true,
1273             docStubs = true,
1274             stubFiles =
1275                 arrayOf(
1276                     java(
1277                         """
1278                     package android.pkg;
1279                     /**
1280                      * @apiSince 1
1281                      * @sdkExtSince R Extensions 2
1282                      */
1283                     @SuppressWarnings({"unchecked", "deprecation", "all"})
1284                     public class Test {
1285                     /**
1286                      * @apiSince 1
1287                      * @sdkExtSince R Extensions 2
1288                      */
1289                     public Test() { throw new RuntimeException("Stub!"); }
1290                     /**
1291                      * @apiSince 1
1292                      * @sdkExtSince R Extensions 2
1293                      */
1294                     public void foo() { throw new RuntimeException("Stub!"); }
1295                     /**
1296                      * @apiSince 1
1297                      * @sdkExtSince R Extensions 2
1298                      */
1299                     public static final java.lang.String UNIT_TEST_1 = "unit.test.1";
1300                     /**
1301                      * @apiSince 2
1302                      * @sdkExtSince Standalone Extensions 3
1303                      */
1304                     public static final java.lang.String UNIT_TEST_2 = "unit.test.2";
1305                     /**
1306                      * @apiSince 31
1307                      * @sdkExtSince Standalone Extensions 4
1308                      */
1309                     public static final java.lang.String UNIT_TEST_3 = "unit.test.3";
1310                     /**
1311                      * @apiSince 1
1312                      * @sdkExtSince R Extensions 2
1313                      */
1314                     @SuppressWarnings({"unchecked", "deprecation", "all"})
1315                     public class Inner {
1316                     /**
1317                      * @apiSince 1
1318                      * @sdkExtSince R Extensions 2
1319                      */
1320                     public Inner() { throw new RuntimeException("Stub!"); }
1321                     /**
1322                      * @apiSince 1
1323                      * @sdkExtSince R Extensions 2
1324                      */
1325                     public static final boolean UNIT_TEST_4 = true;
1326                     }
1327                     }
1328                     """
1329                     )
1330                 )
1331         )
1332     }
1333 
1334     @Test
Generate overview html docsnull1335     fun `Generate overview html docs`() {
1336         // If a codebase provides overview.html files in the a public package,
1337         // make sure that we include this in the exported stubs folder as well!
1338         check(
1339             sourceFiles =
1340                 arrayOf(
1341                     TestFiles.source("src/overview.html", "<html>My overview docs</html>"),
1342                     TestFiles.source(
1343                             "src/foo/test/visible/package.html",
1344                             """
1345                     <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
1346                     <!-- not a body tag: <body> -->
1347                     <html>
1348                     <body bgcolor="white">
1349                     My package docs<br>
1350                     <!-- comment -->
1351                     Sample code: /** code here */
1352                     Another line.<br>
1353                     </BODY>
1354                     </html>
1355                     """
1356                         )
1357                         .indented(),
1358                     java(
1359                         // Note that we're *deliberately* placing the source file in the wrong
1360                         // source root here. This is to simulate the scenario where the source
1361                         // root (--source-path) points to a parent of the source folder instead
1362                         // of the source folder instead. In this case, we need to try a bit harder
1363                         // to compute the right package name; metalava has some code for that.
1364                         // This is a regression test for b/144264106.
1365                         "src/foo/test/visible/MyClass.java",
1366                         """
1367                     package test.visible;
1368                     public class MyClass {
1369                         public void test() { }
1370                     }
1371                     """
1372                     ),
1373                     // Also test hiding classes via javadoc
1374                     TestFiles.source(
1375                             "src/foo/test/hidden1/package.html",
1376                             """
1377                     <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
1378                     <html>
1379                     <body>
1380                     @hide
1381                     This is a hidden package
1382                     </body>
1383                     </html>
1384                     """
1385                         )
1386                         .indented(),
1387                     java(
1388                         "src/foo/test/hidden1/Hidden.java",
1389                         """
1390                     package test.hidden1;
1391                     public class Hidden {
1392                         public void test() { }
1393                     }
1394                     """
1395                     ),
1396                     // Also test hiding classes via package-info.java
1397                     java(
1398                             """
1399                     /**
1400                      * My package docs<br>
1401                      * @hide
1402                      */
1403                     package test.hidden2;
1404                     """
1405                         )
1406                         .indented(),
1407                     java(
1408                         """
1409                     package test.hidden2;
1410                     public class Hidden {
1411                         public void test() { }
1412                     }
1413                     """
1414                     )
1415                 ),
1416             docStubs = true,
1417             // Make sure we expose exactly what we intend (so @hide via javadocs and
1418             // via package-info.java works)
1419             api =
1420                 """
1421                 package test.visible {
1422                   public class MyClass {
1423                     ctor public MyClass();
1424                     method public void test();
1425                   }
1426                 }
1427             """,
1428             // Make sure the stubs are generated correctly; in particular, that we've
1429             // pulled docs from overview.html into javadoc on package-info.java instead
1430             // (removing all the content surrounding <body>, etc)
1431             stubFiles =
1432                 arrayOf(
1433                     TestFiles.source("overview.html", "<html>My overview docs</html>"),
1434                     java(
1435                         """
1436                     /**
1437                      * My package docs<br>
1438                      * <!-- comment -->
1439                      * Sample code: /** code here &#42;/
1440                      * Another line.<br>
1441                      */
1442                     package test.visible;
1443                     """
1444                     ),
1445                     java(
1446                         """
1447                     package test.visible;
1448                     @SuppressWarnings({"unchecked", "deprecation", "all"})
1449                     public class MyClass {
1450                     public MyClass() { throw new RuntimeException("Stub!"); }
1451                     public void test() { throw new RuntimeException("Stub!"); }
1452                     }
1453                     """
1454                     )
1455                 )
1456         )
1457     }
1458 
1459     @Test
Check RequiresApi handlingnull1460     fun `Check RequiresApi handling`() {
1461         check(
1462             sourceFiles =
1463                 arrayOf(
1464                     java(
1465                         """
1466                     package test.pkg;
1467                     import androidx.annotation.RequiresApi;
1468                     @RequiresApi(value = 21)
1469                     public class MyClass1 {
1470                     }
1471                     """
1472                     ),
1473                     requiresApiSource
1474                 ),
1475             docStubs = true,
1476             checkCompilation = false, // duplicate class: androidx.annotation.RequiresApi
1477             stubFiles =
1478                 arrayOf(
1479                     java(
1480                         """
1481                     package test.pkg;
1482                     /** @apiSince 21 */
1483                     @SuppressWarnings({"unchecked", "deprecation", "all"})
1484                     public class MyClass1 {
1485                     public MyClass1() { throw new RuntimeException("Stub!"); }
1486                     }
1487                     """
1488                     )
1489                 )
1490         )
1491     }
1492 
1493     @RequiresCapabilities(Capability.KOTLIN)
1494     @Test
Include Kotlin deprecation textnull1495     fun `Include Kotlin deprecation text`() {
1496         check(
1497             sourceFiles =
1498                 arrayOf(
1499                     kotlin(
1500                         """
1501                     package test.pkg
1502 
1503                     @Suppress("DeprecatedCallableAddReplaceWith","EqualsOrHashCode")
1504                     @Deprecated("Use Jetpack preference library", level = DeprecationLevel.ERROR)
1505                     class Foo {
1506                         fun foo()
1507 
1508                         @Deprecated("Blah blah blah 1", level = DeprecationLevel.ERROR)
1509                         override fun toString(): String = "Hello World"
1510 
1511                         /**
1512                          * My description
1513                          * @deprecated Existing deprecation message.
1514                          */
1515                         @Deprecated("Blah blah blah 2", level = DeprecationLevel.ERROR)
1516                         override fun hashCode(): Int = 0
1517                     }
1518 
1519                     """
1520                     )
1521                 ),
1522             checkCompilation = true,
1523             docStubs = true,
1524             stubFiles =
1525                 arrayOf(
1526                     java(
1527                         """
1528                     package test.pkg;
1529                     /**
1530                      * @deprecated Use Jetpack preference library
1531                      */
1532                     @SuppressWarnings({"unchecked", "deprecation", "all"})
1533                     @Deprecated
1534                     public final class Foo {
1535                     @Deprecated
1536                     public Foo() { throw new RuntimeException("Stub!"); }
1537                     @Deprecated
1538                     public void foo() { throw new RuntimeException("Stub!"); }
1539                     /**
1540                      * My description
1541                      * @deprecated Existing deprecation message.
1542                      * Blah blah blah 2
1543                      */
1544                     @Deprecated
1545                     public int hashCode() { throw new RuntimeException("Stub!"); }
1546                     /**
1547                      * {@inheritDoc}
1548                      * @deprecated Blah blah blah 1
1549                      */
1550                     @Deprecated
1551                     @androidx.annotation.NonNull
1552                     public java.lang.String toString() { throw new RuntimeException("Stub!"); }
1553                     }
1554                     """
1555                     )
1556                 )
1557         )
1558     }
1559 
1560     @Test
Annotation annotating selfnull1561     fun `Annotation annotating self`() {
1562         check(
1563             sourceFiles =
1564                 arrayOf(
1565                     java(
1566                         """
1567                         package test.pkg;
1568                         import java.lang.annotation.Retention;
1569                         import java.lang.annotation.RetentionPolicy;
1570                         /**
1571                          * Documentation here
1572                          */
1573                         @SuppressWarnings("WeakerAccess")
1574                         @MyAnnotation
1575                         @Retention(RetentionPolicy.SOURCE)
1576                         public @interface MyAnnotation {
1577                         }
1578                     """
1579                     ),
1580                     java(
1581                         """
1582                         package test.pkg;
1583 
1584                         /**
1585                          * Other documentation here
1586                          */
1587                         @SuppressWarnings("WeakerAccess")
1588                         @MyAnnotation
1589                         public class OtherClass {
1590                         }
1591                     """
1592                     )
1593                 ),
1594             checkCompilation = true,
1595             stubFiles =
1596                 arrayOf(
1597                     java(
1598                         """
1599                     package test.pkg;
1600                     /**
1601                      * Documentation here
1602                      */
1603                     @SuppressWarnings({"unchecked", "deprecation", "all"})
1604                     @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
1605                     public @interface MyAnnotation {
1606                     }
1607                     """
1608                     ),
1609                     java(
1610                         """
1611                     package test.pkg;
1612                     /**
1613                      * Other documentation here
1614                      */
1615                     @SuppressWarnings({"unchecked", "deprecation", "all"})
1616                     public class OtherClass {
1617                     public OtherClass() { throw new RuntimeException("Stub!"); }
1618                     }
1619                     """
1620                     )
1621                 )
1622         )
1623     }
1624 
1625     @Test
Annotation annotating itself indirectlynull1626     fun `Annotation annotating itself indirectly`() {
1627         check(
1628             sourceFiles =
1629                 arrayOf(
1630                     java(
1631                         """
1632                         package test.pkg;
1633 
1634                         /**
1635                          * Documentation 1 here
1636                          */
1637                         @SuppressWarnings("WeakerAccess")
1638                         @MyAnnotation2
1639                         public @interface MyAnnotation1 {
1640                         }
1641                     """
1642                     ),
1643                     java(
1644                         """
1645                         package test.pkg;
1646 
1647                         /**
1648                          * Documentation 2 here
1649                          */
1650                         @SuppressWarnings("WeakerAccess")
1651                         @MyAnnotation1
1652                         public @interface MyAnnotation2 {
1653                         }
1654                     """
1655                     )
1656                 ),
1657             checkCompilation = true,
1658             stubFiles =
1659                 arrayOf(
1660                     java(
1661                         """
1662                     package test.pkg;
1663                     /**
1664                      * Documentation 1 here
1665                      */
1666                     @SuppressWarnings({"unchecked", "deprecation", "all"})
1667                     @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS)
1668                     @test.pkg.MyAnnotation2
1669                     public @interface MyAnnotation1 {
1670                     }
1671                     """
1672                     ),
1673                     java(
1674                         """
1675                     package test.pkg;
1676                     /**
1677                      * Documentation 2 here
1678                      */
1679                     @SuppressWarnings({"unchecked", "deprecation", "all"})
1680                     @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS)
1681                     @test.pkg.MyAnnotation1
1682                     public @interface MyAnnotation2 {
1683                     }
1684                     """
1685                     )
1686                 )
1687         )
1688     }
1689 
1690     @Test
Test Column annotationnull1691     fun `Test Column annotation`() {
1692         // Bug: 120429729
1693         check(
1694             sourceFiles =
1695                 arrayOf(
1696                     java(
1697                         """
1698                     package test.pkg;
1699                     import android.provider.Column;
1700                     import android.database.Cursor;
1701                     @SuppressWarnings("WeakerAccess")
1702                     public class ColumnTest {
1703                         @Column(Cursor.FIELD_TYPE_STRING)
1704                         public static final String DATA = "_data";
1705                         @Column(value = Cursor.FIELD_TYPE_BLOB, readOnly = true)
1706                         public static final String HASH = "_hash";
1707                         @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
1708                         public static final String TITLE = "title";
1709                         @Column(value = Cursor.NONEXISTENT, readOnly = true)
1710                         public static final String BOGUS = "bogus";
1711                     }
1712                     """
1713                     ),
1714                     java(
1715                         """
1716                         package android.database;
1717                         public interface Cursor {
1718                             int FIELD_TYPE_NULL = 0;
1719                             int FIELD_TYPE_INTEGER = 1;
1720                             int FIELD_TYPE_FLOAT = 2;
1721                             int FIELD_TYPE_STRING = 3;
1722                             int FIELD_TYPE_BLOB = 4;
1723                         }
1724                     """
1725                     ),
1726                     columnSource
1727                 ),
1728             checkCompilation = false, // stubs contain Cursor.NONEXISTENT so it does not compile
1729             expectedIssues =
1730                 """
1731                 src/test/pkg/ColumnTest.java:13: warning: Cannot find feature field for Cursor.NONEXISTENT required by field ColumnTest.BOGUS (may be hidden or removed) [MissingColumn]
1732                 """,
1733             docStubs = true,
1734             stubFiles =
1735                 arrayOf(
1736                     java(
1737                         """
1738                     package test.pkg;
1739                     import android.database.Cursor;
1740                     @SuppressWarnings({"unchecked", "deprecation", "all"})
1741                     public class ColumnTest {
1742                     public ColumnTest() { throw new RuntimeException("Stub!"); }
1743                     /**
1744                      * This constant represents a column name that can be used with a {@link android.content.ContentProvider} through a {@link android.content.ContentValues} or {@link android.database.Cursor} object. The values stored in this column are {@link Cursor.NONEXISTENT}, and are read-only and cannot be mutated.
1745                      */
1746                     @android.provider.Column(value=Cursor.NONEXISTENT, readOnly=true) public static final java.lang.String BOGUS = "bogus";
1747                     /**
1748                      * This constant represents a column name that can be used with a {@link android.content.ContentProvider} through a {@link android.content.ContentValues} or {@link android.database.Cursor} object. The values stored in this column are {@link android.database.Cursor#FIELD_TYPE_STRING Cursor#FIELD_TYPE_STRING} .
1749                      */
1750                     @android.provider.Column(android.database.Cursor.FIELD_TYPE_STRING) public static final java.lang.String DATA = "_data";
1751                     /**
1752                      * This constant represents a column name that can be used with a {@link android.content.ContentProvider} through a {@link android.content.ContentValues} or {@link android.database.Cursor} object. The values stored in this column are {@link android.database.Cursor#FIELD_TYPE_BLOB Cursor#FIELD_TYPE_BLOB} , and are read-only and cannot be mutated.
1753                      */
1754                     @android.provider.Column(value=android.database.Cursor.FIELD_TYPE_BLOB, readOnly=true) public static final java.lang.String HASH = "_hash";
1755                     /**
1756                      * This constant represents a column name that can be used with a {@link android.content.ContentProvider} through a {@link android.content.ContentValues} or {@link android.database.Cursor} object. The values stored in this column are {@link android.database.Cursor#FIELD_TYPE_STRING Cursor#FIELD_TYPE_STRING} , and are read-only and cannot be mutated.
1757                      */
1758                     @android.provider.Column(value=android.database.Cursor.FIELD_TYPE_STRING, readOnly=true) public static final java.lang.String TITLE = "title";
1759                     }
1760                     """
1761                     )
1762                 )
1763         )
1764     }
1765 
1766     @Test
memberDoc crashnull1767     fun `memberDoc crash`() {
1768         check(
1769             sourceFiles =
1770                 arrayOf(
1771                     java(
1772                         """
1773                             package test.pkg;
1774                             import java.lang.annotation.ElementType;
1775                             import java.lang.annotation.Retention;
1776                             import java.lang.annotation.RetentionPolicy;
1777                             import java.lang.annotation.Target;
1778                             /**
1779                              * More text here
1780                              * @memberDoc Important {@link another.pkg.Bar#BAR}
1781                              * and here
1782                              */
1783                             @Target({ ElementType.FIELD })
1784                             @Retention(RetentionPolicy.SOURCE)
1785                             public @interface Foo { }
1786                         """
1787                     ),
1788                     java(
1789                         """
1790                             package another.pkg;
1791                             public class Bar {
1792                                 public String BAR = "BAAAAR";
1793                             }
1794                         """
1795                     ),
1796                     java(
1797                         """
1798                             package yetonemore.pkg;
1799                             public class Fun {
1800                                 @test.pkg.Foo
1801                                 public Fun() {}
1802 
1803                                 /**
1804                                  * Separate comment
1805                                  */
1806                                 @test.pkg.Foo
1807                                 public static final String FUN = "FUN";
1808                             }
1809                         """
1810                     )
1811                 ),
1812             docStubs = true,
1813             stubFiles =
1814                 arrayOf(
1815                     java(
1816                         """
1817                             package yetonemore.pkg;
1818                             @SuppressWarnings({"unchecked", "deprecation", "all"})
1819                             public class Fun {
1820                             /**
1821                              * Important {@link another.pkg.Bar#BAR}
1822                              * and here
1823                              */
1824                             public Fun() { throw new RuntimeException("Stub!"); }
1825                             /**
1826                              * Separate comment
1827                              * <br>
1828                              * Important {@link another.pkg.Bar#BAR}
1829                              * and here
1830                              */
1831                             public static final java.lang.String FUN = "FUN";
1832                             }
1833                         """
1834                     )
1835                 )
1836         )
1837     }
1838 }
1839