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