1 /* 2 * Copyright() 2023 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.compose.runtime.lint 18 19 import com.android.tools.lint.detector.api.AnnotationInfo 20 import com.android.tools.lint.detector.api.AnnotationUsageInfo 21 import com.android.tools.lint.detector.api.AnnotationUsageType 22 import com.android.tools.lint.detector.api.Category 23 import com.android.tools.lint.detector.api.Detector 24 import com.android.tools.lint.detector.api.Implementation 25 import com.android.tools.lint.detector.api.Issue 26 import com.android.tools.lint.detector.api.JavaContext 27 import com.android.tools.lint.detector.api.LintFix 28 import com.android.tools.lint.detector.api.Scope 29 import com.android.tools.lint.detector.api.Severity 30 import com.android.tools.lint.detector.api.SourceCodeScanner 31 import com.android.tools.lint.detector.api.UastLintUtils 32 import java.util.EnumSet 33 import org.jetbrains.uast.UAnnotation 34 import org.jetbrains.uast.UElement 35 import org.jetbrains.uast.USimpleNameReferenceExpression 36 37 class AutoboxingStateValuePropertyDetector : Detector(), SourceCodeScanner { 38 39 private val UAnnotation.preferredPropertyName: String? 40 get() = UastLintUtils.getAnnotationStringValue(this, "preferredPropertyName") 41 42 private val UElement.identifier: String? 43 get() = (this as? USimpleNameReferenceExpression)?.identifier 44 45 private val UElement.resolvedName: String? 46 get() = (this as? USimpleNameReferenceExpression)?.resolvedName 47 applicableAnnotationsnull48 override fun applicableAnnotations(): List<String> { 49 return listOf("androidx.compose.runtime.snapshots.AutoboxingStateValueProperty") 50 } 51 isApplicableAnnotationUsagenull52 override fun isApplicableAnnotationUsage(type: AnnotationUsageType): Boolean { 53 return type == AnnotationUsageType.FIELD_REFERENCE 54 } 55 visitAnnotationUsagenull56 override fun visitAnnotationUsage( 57 context: JavaContext, 58 element: UElement, 59 annotationInfo: AnnotationInfo, 60 usageInfo: AnnotationUsageInfo 61 ) { 62 val resolvedPropertyName = element.identifier ?: "<unknown identifier>" 63 val preferredPropertyName = 64 annotationInfo.annotation.preferredPropertyName ?: "<unknown replacement>" 65 66 val accessKind = 67 when (element.resolvedName?.takeWhile { it.isLowerCase() }) { 68 "get" -> "Reading" 69 "set" -> "Assigning" 70 else -> "Accessing" 71 } 72 73 context.report( 74 AutoboxingStateValueProperty, 75 element, 76 context.getLocation(element), 77 "$accessKind `$resolvedPropertyName` will cause an autoboxing operation. " + 78 "Use `$preferredPropertyName` to avoid unnecessary allocations.", 79 createPropertyReplacementQuickFix( 80 resolvedPropertyName = resolvedPropertyName, 81 preferredPropertyName = preferredPropertyName 82 ) 83 ) 84 } 85 createPropertyReplacementQuickFixnull86 private fun createPropertyReplacementQuickFix( 87 resolvedPropertyName: String, 88 preferredPropertyName: String 89 ): LintFix { 90 return fix() 91 .name("Replace with `$preferredPropertyName`") 92 .replace() 93 .text(resolvedPropertyName) 94 .with(preferredPropertyName) 95 .build() 96 } 97 98 companion object { 99 100 val AutoboxingStateValueProperty = 101 Issue.create( 102 "AutoboxingStateValueProperty", 103 "State access causes value to be autoboxed", 104 "Avoid using the generic `value` property when using a specialized State type. " + 105 "Reading or writing to the state's generic `value` property will result in an " + 106 "unnecessary autoboxing operation. Prefer the specialized value property " + 107 "(e.g. `intValue` for `MutableIntState`), or use property delegation to " + 108 "avoid unnecessary allocations.", 109 Category.PERFORMANCE, 110 3, 111 Severity.WARNING, 112 Implementation( 113 AutoboxingStateValuePropertyDetector::class.java, 114 EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES) 115 ) 116 ) 117 } 118 } 119