1 /*
2  * Copyright (C) 2021 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.model.psi
18 
19 import com.android.tools.metalava.model.AnnotationItem
20 import com.android.tools.metalava.model.PropertyItem
21 import com.android.tools.metalava.model.testsuite.BaseModelTest
22 import com.android.tools.metalava.testing.kotlin
23 import kotlin.test.Test
24 import kotlin.test.assertContains
25 import kotlin.test.assertEquals
26 import kotlin.test.assertNotNull
27 import kotlin.test.assertNull
28 import kotlin.test.assertSame
29 import kotlin.test.assertTrue
30 
31 class PsiPropertyItemTest : BaseModelTest() {
32     @Test
primary constructor properties have constructor parametersnull33     fun `primary constructor properties have constructor parameters`() {
34         runCodebaseTest(kotlin("class Foo(val myVal: Int)")) {
35             val myVal = codebase.assertClass("Foo").properties().single()
36 
37             assertNotNull(myVal.constructorParameter)
38             assertSame(myVal, myVal.constructorParameter?.property)
39         }
40     }
41 
42     @Test
properties have gettersnull43     fun `properties have getters`() {
44         runCodebaseTest(
45             kotlin(
46                 """
47                     class Foo {
48                         val myVal: Int = 0
49                         var myVar: Int = 0
50                     }
51                 """
52             )
53         ) {
54             val properties = codebase.assertClass("Foo").properties()
55             val myVal = properties.single { it.name() == "myVal" }
56             val myVar = properties.single { it.name() == "myVar" }
57 
58             assertNotNull(myVal.getter)
59             assertNotNull(myVar.getter)
60 
61             assertEquals("getMyVal", myVal.getter?.name())
62             assertEquals("getMyVar", myVar.getter?.name())
63 
64             assertSame(myVal, myVal.getter?.property)
65             assertSame(myVar, myVar.getter?.property)
66         }
67     }
68 
69     @Test
var properties have settersnull70     fun `var properties have setters`() {
71         runCodebaseTest(kotlin("class Foo { var myVar: Int = 0 }")) {
72             val myVar = codebase.assertClass("Foo").properties().single()
73 
74             assertNotNull(myVar.setter)
75             assertEquals("setMyVar", myVar.setter?.name())
76             assertSame(myVar, myVar.setter?.property)
77         }
78     }
79 
80     @Test
setter visibilitynull81     fun `setter visibility`() {
82         runCodebaseTest(
83             kotlin(
84                 """
85                     class Foo {
86                         var internalSet: Int = 0
87                             internal set
88 
89                         var privateSet: Int = 0
90                             private set
91 
92                         var privateCustomSet: Int = 0
93                             private set(value) { field = value + 1 }
94                     }
95                 """
96             )
97         ) {
98             val properties = codebase.assertClass("Foo").properties()
99             val internalSet = properties.single { it.name() == "internalSet" }
100             val privateSet = properties.single { it.name() == "privateSet" }
101             val privateCustomSet = properties.single { it.name() == "privateCustomSet" }
102 
103             assertTrue(internalSet.isPublic)
104             assertTrue(internalSet.getter!!.isPublic)
105             assertTrue(internalSet.setter!!.isInternal)
106 
107             assertTrue(privateSet.isPublic)
108             assertTrue(privateSet.getter!!.isPublic)
109             assertNull(privateSet.setter) // Private setter is replaced with direct field access
110 
111             assertTrue(privateCustomSet.isPublic)
112             assertTrue(privateCustomSet.getter!!.isPublic)
113             assertTrue(privateCustomSet.setter!!.isPrivate)
114         }
115     }
116 
117     @Test
properties have backing fieldsnull118     fun `properties have backing fields`() {
119         runCodebaseTest(
120             kotlin(
121                 """
122                     class Foo(val withField: Int) {
123                         val withoutField: Int
124                             get() = 0
125                     }
126                 """
127             )
128         ) {
129             val properties = codebase.assertClass("Foo").properties()
130             val withField = properties.single { it.name() == "withField" }
131             val withoutField = properties.single { it.name() == "withoutField" }
132 
133             assertNull(withoutField.backingField)
134 
135             assertNotNull(withField.backingField)
136             assertEquals("withField", withField.backingField?.name())
137             assertSame(withField, withField.backingField?.property)
138         }
139     }
140 
141     @Test
annotation on propertiesnull142     fun `annotation on properties`() {
143         fun List<AnnotationItem>.exceptNullness() = filterNot { it.isNullnessAnnotation() }
144 
145         runCodebaseTest(
146             kotlin(
147                 """
148                     annotation class ExperimentalFooApi
149                     annotation class ExperimentalBarApi(vararg val values: String)
150 
151                     class Foo {
152                         @ExperimentalFooApi
153                         var withField: String = "42"
154                             get() { return field }
155                             set(value) { field = "v=" + value }
156 
157                         @ExperimentalFooApi
158                         val withoutField: String
159                             get() = ""
160 
161                         @get:ExperimentalFooApi
162                         val withoutFieldOnGetter: String
163                             get() = ""
164 
165                         @ExperimentalFooApi
166                         @get:ExperimentalFooApi
167                         val withoutFieldOnGetterAndNoUseSite: String
168                             get() = ""
169 
170                         @ExperimentalBarApi("42")
171                         @get:ExperimentalBarApi("42")
172                         val withoutFieldOnGetterAndNoUseSiteSameArg: String
173                             get() = ""
174 
175                         @ExperimentalBarApi("42")
176                         @get:ExperimentalBarApi("24")
177                         val withoutFieldOnGetterAndNoUseSiteDiffArg: String
178                             get() = ""
179 
180                         @property:ExperimentalFooApi
181                         val withoutFieldOnProperty: String
182                             get() = ""
183 
184                         @ExperimentalFooApi
185                         @property:ExperimentalFooApi
186                         val withoutFieldOnPropertyAndNoUseSite: String
187                             get() = ""
188 
189                         @ExperimentalBarApi("40", "2")
190                         @property:ExperimentalBarApi(values = ["40", "2"])
191                         val withoutFieldOnPropertyAndNoUseSiteSameArg: String
192                             get() = ""
193 
194                         @ExperimentalBarApi("42")
195                         @property:ExperimentalBarApi("42", "24")
196                         val withoutFieldOnPropertyAndNoUseSiteDiffArg: String
197                             get() = ""
198 
199                         // Make sure that when `values` is an array and the arrays are not equal but
200                         // one is a subset of the other, then they are not treated as equal. This
201                         // checks when the first is a subset of the second. The following tests the
202                         // opposite.
203                         @ExperimentalBarApi("42", "24")
204                         @property:ExperimentalBarApi("42", "24", "11")
205                         val withoutFieldOnPropertyAndNoUseSiteDiffArgLists1: String
206                             get() = ""
207 
208                         // Make sure that when `values` is an array and the arrays are not equal but
209                         // one is a subset of the other, then they are not treated as equal. This
210                         // checks when the second is a subset of the first. The previous tests the
211                         // opposite.
212                         @ExperimentalBarApi("42", "24", "11")
213                         @property:ExperimentalBarApi("42", "24")
214                         val withoutFieldOnPropertyAndNoUseSiteDiffArgLists2: String
215                             get() = ""
216                     }
217                 """
218             )
219         ) {
220             val properties = codebase.assertClass("Foo").properties()
221             val withField = properties.single { it.name() == "withField" }
222             val withoutField = properties.single { it.name() == "withoutField" }
223             val withoutFieldOnGetter = properties.single { it.name() == "withoutFieldOnGetter" }
224             val withoutFieldOnGetterAndNoUseSite =
225                 properties.single { it.name() == "withoutFieldOnGetterAndNoUseSite" }
226             val withoutFieldOnGetterAndNoUseSiteSameArg =
227                 properties.single { it.name() == "withoutFieldOnGetterAndNoUseSiteSameArg" }
228             val withoutFieldOnGetterAndNoUseSiteDiffArg =
229                 properties.single { it.name() == "withoutFieldOnGetterAndNoUseSiteDiffArg" }
230             val withoutFieldOnProperty = properties.single { it.name() == "withoutFieldOnProperty" }
231             val withoutFieldOnPropertyAndNoUseSite =
232                 properties.single { it.name() == "withoutFieldOnPropertyAndNoUseSite" }
233             val withoutFieldOnPropertyAndNoUseSiteSameArg =
234                 properties.single { it.name() == "withoutFieldOnPropertyAndNoUseSiteSameArg" }
235             val withoutFieldOnPropertyAndNoUseSiteDiffArg =
236                 properties.single { it.name() == "withoutFieldOnPropertyAndNoUseSiteDiffArg" }
237             val withoutFieldOnPropertyAndNoUseSiteDiffArgLists1 =
238                 properties.single { it.name() == "withoutFieldOnPropertyAndNoUseSiteDiffArgLists1" }
239             val withoutFieldOnPropertyAndNoUseSiteDiffArgLists2 =
240                 properties.single { it.name() == "withoutFieldOnPropertyAndNoUseSiteDiffArgLists2" }
241 
242             val withFieldBackingField = withField.backingField
243             assertNotNull(withFieldBackingField)
244             assertNull(withoutField.backingField)
245             assertNull(withoutFieldOnGetter.backingField)
246             assertNull(withoutFieldOnGetterAndNoUseSite.backingField)
247             assertNull(withoutFieldOnGetterAndNoUseSiteSameArg.backingField)
248             assertNull(withoutFieldOnGetterAndNoUseSiteDiffArg.backingField)
249             assertNull(withoutFieldOnProperty.backingField)
250             assertNull(withoutFieldOnPropertyAndNoUseSite.backingField)
251             assertNull(withoutFieldOnPropertyAndNoUseSiteSameArg.backingField)
252             assertNull(withoutFieldOnPropertyAndNoUseSiteDiffArg.backingField)
253             assertNull(withoutFieldOnPropertyAndNoUseSiteDiffArgLists1.backingField)
254             assertNull(withoutFieldOnPropertyAndNoUseSiteDiffArgLists2.backingField)
255 
256             val fooApi = "ExperimentalFooApi"
257             val barApi = "ExperimentalBarApi"
258 
259             val annotationsOnWithFieldBackingField =
260                 withFieldBackingField.modifiers.annotations().exceptNullness()
261             assertEquals(1, annotationsOnWithFieldBackingField.size)
262             assertEquals(fooApi, annotationsOnWithFieldBackingField.single().qualifiedName)
263 
264             fun checkSingleAnnotation(
265                 propertyItem: PropertyItem,
266                 expectedAnnotationName: String = fooApi,
267             ) {
268                 val annotations = propertyItem.modifiers.annotations().exceptNullness()
269                 assertEquals(1, annotations.size)
270                 assertEquals(expectedAnnotationName, annotations.single().qualifiedName)
271             }
272 
273             checkSingleAnnotation(withoutField)
274             checkSingleAnnotation(withoutFieldOnGetter)
275             checkSingleAnnotation(withoutFieldOnGetterAndNoUseSite)
276             checkSingleAnnotation(withoutFieldOnGetterAndNoUseSiteSameArg, barApi)
277             checkSingleAnnotation(withoutFieldOnProperty)
278             checkSingleAnnotation(withoutFieldOnPropertyAndNoUseSite)
279             checkSingleAnnotation(withoutFieldOnPropertyAndNoUseSiteSameArg, barApi)
280 
281             fun checkAnnotations(
282                 propertyItem: PropertyItem,
283                 expectedAnnotationCounts: Int,
284                 expectedAnnotationName: String = barApi,
285             ) {
286                 val annotations = propertyItem.modifiers.annotations().exceptNullness()
287                 assertEquals(expectedAnnotationCounts, annotations.size)
288                 annotations.forEach { assertEquals(expectedAnnotationName, it.qualifiedName) }
289             }
290 
291             checkAnnotations(withoutFieldOnGetterAndNoUseSiteDiffArg, 2)
292             checkAnnotations(withoutFieldOnPropertyAndNoUseSiteDiffArg, 2)
293             checkAnnotations(withoutFieldOnPropertyAndNoUseSiteDiffArgLists1, 2)
294             checkAnnotations(withoutFieldOnPropertyAndNoUseSiteDiffArgLists2, 2)
295         }
296     }
297 
298     @Test
properties have documentationnull299     fun `properties have documentation`() {
300         runCodebaseTest(
301             kotlin(
302                 """
303                     class Foo(/** parameter doc */ val parameter: Int) {
304                         /** body doc */
305                         var body: Int = 0
306 
307                         /** accessors property doc */
308                         var accessors: Int
309                             /** getter doc */
310                             get() = field + 1
311                             /** setter doc */
312                             set(value) = { field = value - 1 }
313                     }
314                 """
315             )
316         ) {
317             val properties = codebase.assertClass("Foo").properties()
318             val parameter = properties.single { it.name() == "parameter" }
319             val body = properties.single { it.name() == "body" }
320             val accessors = properties.single { it.name() == "accessors" }
321 
322             assertContains(parameter.documentation, "parameter doc")
323             assertContains(body.documentation, "body doc")
324             assertContains(accessors.documentation, "accessors property doc")
325             assertContains(accessors.getter?.documentation.orEmpty(), "getter doc")
326             assertContains(accessors.setter?.documentation.orEmpty(), "setter doc")
327         }
328     }
329 }
330