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.animation.core.lint 18 19 import androidx.compose.lint.test.bytecodeStub 20 import com.android.tools.lint.checks.infrastructure.LintDetectorTest 21 import com.android.tools.lint.detector.api.Detector 22 import com.android.tools.lint.detector.api.Issue 23 import org.junit.Test 24 import org.junit.runner.RunWith 25 import org.junit.runners.JUnit4 26 27 /** 28 * Detector to discourage the use of arc-based animations on types other than the specified (known 29 * 2-dimensional types such as Offset, IntOffset, DpOffset). 30 * 31 * TODO(b/299477780): Support detecting usages on keyframes. Note that it would only apply to usages 32 * of `KeyframeEntity<T>.using(arcMode: ArcMode)` where arc mode is ArcAbove/ArcBelow. 33 */ 34 @RunWith(JUnit4::class) 35 class ArcAnimationSpecTypeDetectorTest : LintDetectorTest() { getDetectornull36 override fun getDetector(): Detector = ArcAnimationSpecTypeDetector() 37 38 override fun getIssues(): MutableList<Issue> = 39 mutableListOf(ArcAnimationSpecTypeDetector.ArcAnimationSpecTypeIssue) 40 41 // Simplified version of Arc animation classes in AnimationSpec.kt 42 private val ArcAnimationSpecStub = 43 bytecodeStub( 44 filename = "AnimationSpec.kt", 45 filepath = "androidx/compose/animation/core", 46 checksum = 0x9d0cdf8f, 47 source = 48 """ 49 package androidx.compose.animation.core 50 51 class ArcAnimationSpec<T>(val mode: ArcMode, val durationMillis: Int = 400) 52 53 sealed class ArcMode 54 55 object ArcAbove : ArcMode() 56 """, 57 """ 58 META-INF/main.kotlin_module: 59 H4sIAAAAAAAA/2NgYGBmYGBgBGJOBijg4uViTsvPF2ILSS0u8S5RYtBiAACf 60 q36HJwAAAA== 61 """, 62 """ 63 androidx/compose/animation/core/ArcAbove.class: 64 H4sIAAAAAAAA/41SS2/TQBD+1knzhqbllVDe5ZH0gJuKWyukEECylORAqkio 65 p42zwDb2brXeRD3mxA/hH1QcKoGEIrjxoxCzJoUDl9jyzM4333yzM/LPX1++ 66 AXiGxwwNrsZGy/GpH+r4RCfC50rG3EqtCDHCb5uwPdIzkQdjeLICu6fHRM4w 67 5A6kkvY5Q6bRHDK0Gt2JtpFU/vEs9qWywige+S/FOz6NbEerxJppaLXpcTMR 68 Zr85rGAN+RKyKDBk7QeZMOx0V73vPkPhIIzSKzihXAkeLhEY9AeH7X7nVQXr 69 KBcJrDJsd7V57x8LOzJcqoQ0lbapaOL3te1Po4j0Ni4G6AnLx9xywrx4lqFd 70 MmeKzoCBTQg/lS7apdO4RQ0W81LJq3npt5gXfnz0aov5nrfLXuQL3vdPOa/q 71 OeoeQ3OVEd2SqXu1fZEZnIjw6cQybL2ZKitjEaiZTOQoEu1/s9AaO1TIsN6V 72 SvSn8UiYQ04chs2uDnk05Ea6eAmWBnpqQvFauqC+FB7+J4sWbTGbjl53SyV/ 73 j6Ic+U3yHr1raXTfbYQi5rI77BzFszT/YMkGCtgmW/nDQIm0HFb5W32D2O4p 74 f4X39hyXP2PjLAU8PEztXTxKf22GK9T06hEyAa4FuB5QaY2OqAe4ia0jsAS3 75 cJvyCcoJ7iTI/QY7flqdFwMAAA== 76 """, 77 """ 78 androidx/compose/animation/core/ArcAnimationSpec.class: 79 H4sIAAAAAAAA/5VSS28TVxT+7vg1HgyMXUKCCRQID8cujONCH5gipSCkkeyA 80 cJRNurkZ35obj2eiudcRqyo/odtuWbMACUTVRRV12R9V9Vx7EvLowt3c87jf 81 Oec7j7//+eNPAPfRZmjyqJ/Esv/aC+LRTqyExyM54lrGEXkS4a0mweqBp7cj 82 ggIYQ+3R+sPONt/lXsijgfd8a1sEuv34tIvBPekrIMuQfyQjqR8z3K11ZmDQ 83 jfui7S9vMCx14mTgbQu9lXAZKcJGsZ6AlbcW67VxGFLR7IgCbBQZrg5jHcrI 84 294deTLSIol46PmRTihYBqqAMwxzwSsRDNPoFzzhI0FAhju10w0d8fRMkkF7 85 eaOEszjnoITzDJmasfMoO8ihwrA8c3slFHGhCAtzDOf642QC6cowlIqB+SXM 86 Y8F8X6L29CvjbM2S+9jyaDQ//Y+B+53/mt5T8TMfh/oJjVwn40DHSZcnQ5G0 87 p50XHCJ5laEwENqkYWjUZp8CQ5ninp5on6bq088Bna7QvM81J7Q12s3QLTPz 88 FM0DGtaQ/K+lsZqk9VcY1P5e1bEWLGd/z7Fceow8MG3HsnML+3v1rL2/57KW 89 1bR+nK/k3UzVamYrtm25OdLyf73JW27hZfnQsim8mrVtt0jOCfiz03HPmNIt 90 YrPODCn32CLuDTXD5ZfjSMuR8KNdqeRWKFY/HzMt+clkeOc7MhJr49GWSNY5 91 YRgqnTjg4QZPpLFT582TuQ7v+FjSsz3Ng2GX76RhxZ4cRFyPE9KdXjxOAvFM 92 mo9Lab6NU8ywQvvN0WwtVMzJUm8tsvIkbZIVc6cks2TTMRDqa7J6JM0+5hoV 93 53e49U/4ot74iIv1xY+ovp8ku58myVPoA9KvTQNwGYtmraRNixnNlLDwjdm5 94 ldaFa0KvkGXqtSjYcCxfyf3yGwpl9usP9cbiJ3w5rfUtvRkw57Co4VumktdT 95 vp45I5K5+gdcfHeMH1J+pSkg5Xd0BGXcwFJK5Gii6tsZEmXw3QSVwfcTuYKH 96 JJ8T5iZhbm0i4+O2jzs+algmFXUfDXy1CaZwF/c2UVJYVPAUmgpFhQsK8xO9 97 oHBDYUnhmsL1fwEMDsmpAwYAAA== 98 """, 99 """ 100 androidx/compose/animation/core/ArcMode.class: 101 H4sIAAAAAAAA/5VRXWsTQRQ9s9lu0jW229aP1O+KYFOx2xbRh4oQK0IgUbCS 102 lzzIZDPqJLszZXY29DH4U/wHfRJ8kNBHf5R4Z5Pia4Xlfpwz596Zs7///PwF 103 4BkeMjzmami0HJ7Gic5OdC5irmTGrdSKECPilkm6eiiqYAzRiE94nHL1JX4/ 104 GInEVlFhCF5KJe0rhsp2s1fHEoIQPqoMvv0qc4Zm55I7Dhn2tztjbVOp4tEk 105 i6Wywiiexm/EZ16k9kir3Joisdp0uRkLc9jshfDcro1HyT/yU1ayDLv/N41h 106 7ULQFZYPueWEedmkQnYxF5ZdAAMbE34qXbdH1XCf4clsuhJ6DS/0otk0pI/q 107 2vPGbHrg7bHX1Zp//j3wIu/8G2MVJzlgbtDOZcxpDfTEuRO1LqjjE5Hsji15 108 fETGMax2pBLvimwgzEc+SAlZ7+iEpz1upOsXYHisC5OIt9I1mx8KZWUmejKX 109 xLaU0rYcnvtbZKrvXkrZc3+UbnqXutg9nfLSzg/Uzkr6HsWgBAPcp1ifH8Ay 110 QiBiVF1ZiJ9S9hbi+llpoxPcmINzQVldxUp59EG54A62KL8gZJW4qI9KG2tt 111 rLexgWtU4nqbZtzsg+VoYLMPP0eY41aOIMftv/zlm/jsAgAA 112 """ 113 ) 114 115 // Simplified version of Offset.kt in geometry package 116 private val GeometryStub = 117 bytecodeStub( 118 filename = "Offset.kt", 119 filepath = "androidx/compose/ui/geometry", 120 checksum = 0x471b639e, 121 source = 122 """ 123 package androidx.compose.ui.geometry 124 125 class Offset 126 """, 127 """ 128 META-INF/main.kotlin_module: 129 H4sIAAAAAAAA/2NgYGBmYGBgBGIOBijgMuKST8xLKcrPTKnQS87PLcgvTtVL 130 zMvMTSzJzM8DihSlCvE7wvjBBanJ3iVcvFzMafn5QmwhqcUl3iVKDFoMAHnM 131 zO9bAAAA 132 """, 133 """ 134 androidx/compose/ui/geometry/Offset.class: 135 H4sIAAAAAAAA/41RzS5DQRg937S9uIr6r9+NSLBwETsiQSJpUiRIN1bT3sFo 136 74zcmQq7Pos3sJJYSGPpocR3Lw9gc3J+vpk5M/P1/f4BYBdLhBVp4tTq+Clq 137 2eTBOhV1dXSrbKJ8+hyd39w45QdAhMq9fJRRR5rb6Lx5r1rsFgjBvjbaHxAK 138 a+uNMkoIQhQxQCj6O+0Iq/V/7L9HGK+3re9oE50qL2PpJXsieSxwTcpgMAMQ 139 qM3+k87UFrN4m7Dc74WhqIpQVJj1e9V+b0ds0VHp8yUQFZFN7VC2duj3tM22 140 53rHNlaEsbo26qybNFV6JZsddibqtiU7DZnqTP+Z4aXtpi11ojMxd9E1Xieq 141 oZ3m9NAY66XX1jhsQ/Dt/5pmj8FYZRXlGihtvGHwlYnAHGOQm0XMM5Z/BzCE 142 MM8XcpzFYv5RhGHOytco1DBSw2gNY6gwxXgNE5i8BjlMYZpzh9BhxiH4AWXo 143 H/7lAQAA 144 """ 145 ) 146 147 // Simplified classes of ui/unit package 148 private val UnitStub = 149 bytecodeStub( 150 filename = "Units.kt", 151 filepath = "androidx/compose/ui/unit", 152 checksum = 0x137591fb, 153 source = 154 """ 155 package androidx.compose.ui.unit 156 157 class IntOffset 158 159 class DpOffset 160 """, 161 """ 162 META-INF/main.kotlin_module: 163 H4sIAAAAAAAA/2NgYGBmYGBgBGIOBijgMuKST8xLKcrPTKnQS87PLcgvTtVL 164 zMvMTSzJzM8DihSlCvE7wvjBBanJ3iVcvFzMafn5QmwhqcUl3iVKDFoMAHnM 165 zO9bAAAA 166 """, 167 """ 168 androidx/compose/ui/unit/DpOffset.class: 169 H4sIAAAAAAAA/4VRy0oDMRQ9N7VjHavWd32CuFEXjoo7RfCBUKgKPrpxlXZS 170 jW0TaTList/iH7gSXEhx6UeJd0b3bg7ncZOcJF/f7x8AdrFEWJEm7lodP0cN 171 23m0TkWJjhKjfXTyeNFsOuUHQYTSg3ySUVuau+ii/qAa7OYIwb7myQNCbm29 172 VkQeQYgBDBIG/L12hNXqv7vvEcarLevb2kRnystYesme6DzluCKlUEgBBGqx 173 /6xTtcUs3iYs93thKMoiFCVm/V6539sRW3SU/3wJREmkUzuUri3c8KFus+W5 174 27GNFWGsqo06Tzp11b2W9TY7E1XbkO2a7OpU/5nhlU26DXWqUzF3mRivO6qm 175 neb00BjrpdfWOGxD8NX/iqYvwVhmFWUayG+8ofDKRGCOMcjMAPOMxd8BDCHM 176 8oUMZ7GY/RFhmLPiLXIVjFQwWsEYSkwxXsEEJm9BDlOY5twhdJhxCH4AObkh 177 xeABAAA= 178 """, 179 """ 180 androidx/compose/ui/unit/IntOffset.class: 181 H4sIAAAAAAAA/4VRTS9rQRh+3ml71FHUd3FZiAUWDmJHJEhucpIiwe3Gatoz 182 ZbSdkc4csexv8Q+sJBbS3KUfJd5z2Ns8eT7emXlm5uPz7R3APlYIa9IkfauT 183 p6hlew/WqSjVUWq0j2LjL9ptp/wIiFC9l48y6kpzG10071WL3QIhONQ8ekQo 184 bGw2KighCFHECKHo77QjrNd/3/6AMFXvWN/VJjpTXibSS/ZE77HAJSmDcgYg 185 UIf9J52pHWbJLmF1OAhDUROhqDIbDmrDwZ7YoZPS/+dAVEU2tUfZ2vI/PtVt 186 dzyXO7WJIkzWtVHnaa+p+tey2WVnum5bstuQfZ3pHzO8smm/pf7qTCxepsbr 187 nmpopzk9NsZ66bU1DrsQfPefotlTMNZYRbkGSluvKL8wEVhkDHKziCXGyvcA 188 RhHm+XKOC/iT/xJhjLPKDQoxxmNMxJhElSmmYkxj5gbkMIs5zh1Ch3mH4Au3 189 DmZN4gEAAA== 190 """ 191 ) 192 193 @Test 194 fun testPreferredTypeIssue() { 195 lint() 196 .files( 197 kotlin( 198 """ 199 package foo 200 201 import androidx.compose.animation.core.ArcAnimationSpec 202 import androidx.compose.animation.core.ArcAbove 203 import androidx.compose.ui.geometry.Offset 204 import androidx.compose.ui.unit.DpOffset 205 import androidx.compose.ui.unit.IntOffset 206 207 fun test() { 208 ArcAnimationSpec<Offset>(ArcAbove) 209 ArcAnimationSpec<IntOffset>(ArcAbove) 210 ArcAnimationSpec<DpOffset>(ArcAbove) 211 ArcAnimationSpec<Float>(ArcAbove) 212 ArcAnimationSpec<String>(ArcAbove) 213 } 214 """ 215 ), 216 ArcAnimationSpecStub, 217 GeometryStub, 218 UnitStub 219 ) 220 .run() 221 .expect( 222 """src/foo/test.kt:14: Hint: Arc animation is intended for 2D values such as Offset, IntOffset or DpOffset. 223 Otherwise, the animation might not be what you expect. [ArcAnimationSpecTypeIssue] 224 ArcAnimationSpec<Float>(ArcAbove) 225 ~~~~~~~~~~~~~~~~ 226 src/foo/test.kt:15: Hint: Arc animation is intended for 2D values such as Offset, IntOffset or DpOffset. 227 Otherwise, the animation might not be what you expect. [ArcAnimationSpecTypeIssue] 228 ArcAnimationSpec<String>(ArcAbove) 229 ~~~~~~~~~~~~~~~~ 230 0 errors, 0 warnings, 2 hints""" 231 ) 232 } 233 } 234