1 /*
2  * Copyright 2024 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 androidx.build.lint
18 
19 import com.android.tools.lint.checks.infrastructure.ProjectDescription
20 import org.junit.Test
21 import org.junit.runner.RunWith
22 import org.junit.runners.JUnit4
23 
24 @RunWith(JUnit4::class)
25 class AutoValueNullnessOverrideTest :
26     AbstractLintDetectorTest(
27         useDetector = AutoValueNullnessOverride(),
28         useIssues = listOf(AutoValueNullnessOverride.ISSUE),
29         stubs = arrayOf(autovalueStub, jspecifyNonNullStub, jspecifyNullableStub)
30     ) {
31     @Test
No superclassnull32     fun `No superclass`() {
33         val input =
34             java(
35                 """
36                     package test.pkg;
37                     import com.google.auto.value.AutoValue;
38                     import org.jspecify.annotations.Nullable;
39                     @AutoValue
40                     public abstract class Foo {
41                         public abstract @Nullable String getString();
42                     }
43                 """
44                     .trimIndent()
45             )
46         check(input).expectClean()
47     }
48 
49     @Test
Superclass in same librarynull50     fun `Superclass in same library`() {
51         val input =
52             arrayOf(
53                 java(
54                     """
55                         package test.pkg;
56                         import com.google.auto.value.AutoValue;
57                         @AutoValue
58                         public abstract class Foo extends ParentClass {
59                         }
60                     """
61                         .trimIndent()
62                 ),
63                 java(
64                     """
65                         package test.pkg;
66                         import org.jspecify.annotations.Nullable;
67                         public abstract class ParentClass {
68                             public abstract @Nullable String getString();
69                         }
70                     """
71                         .trimIndent()
72                 )
73             )
74         check(*input).expectClean()
75     }
76 
77     @Test
Superclass in different librarynull78     fun `Superclass in different library`() {
79         // Files needs to be set up in project structure for the lint to understand they are from
80         // different libraries
81         val jspecify =
82             project()
83                 .files(jspecifyNonNullStub, jspecifyNullableStub)
84                 .type(ProjectDescription.Type.LIBRARY)
85 
86         val autovalue = project().files(autovalueStub).type(ProjectDescription.Type.LIBRARY)
87 
88         val parentLibrary =
89             project()
90                 .files(
91                     java(
92                         """
93                             package androidx.example;
94                             import org.jspecify.annotations.NonNull;
95                             import org.jspecify.annotations.Nullable;
96                             public abstract class SuperClass {
97                                 public abstract @Nullable String getNullableStringNotOverridden();
98                                 public abstract @NonNull String getNonNullStringNotOverridden();
99                                 public abstract String getUnannotatedStringNotOverridden();
100 
101                                 public abstract @Nullable String getNullableStringOverridden();
102 
103                                 public abstract @Nullable String getNullableStringOverrideNotAbstract();
104                             }
105                         """
106                             .trimIndent(),
107                     ),
108                     gradle(
109                         """
110                             apply plugin: 'com.android.library'
111                             group=androidx.example
112                         """
113                     )
114                 )
115                 .dependsOn(jspecify)
116                 .type(ProjectDescription.Type.LIBRARY)
117 
118         val sourceProject =
119             project()
120                 .files(
121                     java(
122                         """
123                             package test.pkg;
124                             import com.google.auto.value.AutoValue;
125                             import androidx.example.SuperClass;
126                             @AutoValue
127                             public abstract class Foo extends SuperClass {
128                                 @Override
129                                 public abstract @Nullable String getNullableStringOverridden();
130 
131                                 @Override
132                                 public abstract @Nullable String getNullableStringOverrideNotAbstract() {
133                                     return null;
134                                 }
135                             }
136                         """
137                             .trimIndent(),
138                     ),
139                     gradle(
140                         """
141                             apply plugin: 'com.android.library'
142                             group=test.pkg
143                         """
144                     )
145                 )
146                 .dependsOn(jspecify)
147                 .dependsOn(autovalue)
148                 .dependsOn(parentLibrary)
149 
150         val expected =
151             """
152                 src/main/java/test/pkg/Foo.java:5: Error: Methods need @Nullable overrides for AutoValue: getNullableStringNotOverridden() [AutoValueNullnessOverride]
153                 public abstract class Foo extends SuperClass {
154                                       ~~~
155                 1 errors, 0 warnings
156             """
157                 .trimIndent()
158         val expectedFixDiffs =
159             """
160                 Fix for src/main/java/test/pkg/Foo.java line 5: Replace with ...:
161                 @@ -6 +6
162                 + @Override
163                 + public abstract @Nullable String getNullableStringNotOverridden();
164             """
165                 .trimIndent()
166 
167         lint().projects(sourceProject).run().expect(expected).expectFixDiffs(expectedFixDiffs)
168     }
169 
170     companion object {
171         private val autovalueStub =
172             kotlin(
173                 """
174                     package com.google.auto.value
175                     annotation class AutoValue
176                 """
177                     .trimIndent()
178             )
179         private val jspecifyNullableStub =
180             kotlin(
181                 """
182                     package org.jspecify.annotations
183                     @Target(AnnotationTarget.TYPE)
184                     annotation class Nullable
185                 """
186                     .trimIndent()
187             )
188         private val jspecifyNonNullStub =
189             kotlin(
190                 """
191                     package org.jspecify.annotations
192                     @Target(AnnotationTarget.TYPE)
193                     annotation class NonNull
194                 """
195                     .trimIndent()
196             )
197     }
198 }
199