1 /*
2  * Copyright (C) 2017 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.ANDROIDX_VISIBLE_FOR_TESTING
20 import com.android.tools.metalava.ANDROID_SUPPORT_VISIBLE_FOR_TESTING
21 import com.android.tools.metalava.ATTR_OTHERWISE
22 import com.android.tools.metalava.model.AnnotationItem
23 import com.android.tools.metalava.model.Codebase
24 import com.android.tools.metalava.model.DefaultModifierList
25 import com.android.tools.metalava.model.ModifierList
26 import com.android.tools.metalava.model.MutableModifierList
27 import com.intellij.psi.PsiDocCommentOwner
28 import com.intellij.psi.PsiModifier
29 import com.intellij.psi.PsiModifierList
30 import com.intellij.psi.PsiModifierListOwner
31 import com.intellij.psi.PsiPrimitiveType
32 import com.intellij.psi.PsiReferenceExpression
33 import com.intellij.psi.impl.light.LightModifierList
34 import org.jetbrains.kotlin.asJava.elements.KtLightModifierList
35 import org.jetbrains.kotlin.asJava.elements.KtLightNullabilityAnnotation
36 import org.jetbrains.kotlin.lexer.KtTokens
37 import org.jetbrains.kotlin.psi.KtModifierList
38 import org.jetbrains.kotlin.psi.KtNamedFunction
39 import org.jetbrains.kotlin.psi.KtProperty
40 import org.jetbrains.uast.UAnnotated
41 import org.jetbrains.uast.UMethod
42 import org.jetbrains.uast.UVariable
43 import org.jetbrains.uast.kotlin.KotlinNullabilityUAnnotation
44 import org.jetbrains.uast.kotlin.declarations.KotlinUMethod
45 
46 class PsiModifierItem(
47     codebase: Codebase,
48     flags: Int = PACKAGE_PRIVATE,
49     annotations: MutableList<AnnotationItem>? = null
50 ) : DefaultModifierList(codebase, flags, annotations), ModifierList, MutableModifierList {
51     companion object {
createnull52         fun create(codebase: PsiBasedCodebase, element: PsiModifierListOwner, documentation: String?): PsiModifierItem {
53             val modifiers =
54                 if (element is UAnnotated) {
55                     create(codebase, element, element)
56                 } else {
57                     create(codebase, element)
58                 }
59             if (documentation?.contains("@deprecated") == true ||
60                 // Check for @Deprecated annotation
61                 ((element as? PsiDocCommentOwner)?.isDeprecated == true)
62             ) {
63                 modifiers.setDeprecated(true)
64             }
65 
66             return modifiers
67         }
68 
computeFlagnull69         private fun computeFlag(element: PsiModifierListOwner, modifierList: PsiModifierList): Int {
70             var visibilityFlags = when {
71                 modifierList.hasModifierProperty(PsiModifier.PUBLIC) -> PUBLIC
72                 modifierList.hasModifierProperty(PsiModifier.PROTECTED) -> PROTECTED
73                 modifierList.hasModifierProperty(PsiModifier.PRIVATE) -> PRIVATE
74                 else -> PACKAGE_PRIVATE
75             }
76             var flags = 0
77             if (modifierList.hasModifierProperty(PsiModifier.STATIC)) {
78                 flags = flags or STATIC
79             }
80             if (modifierList.hasModifierProperty(PsiModifier.ABSTRACT)) {
81                 flags = flags or ABSTRACT
82             }
83             if (modifierList.hasModifierProperty(PsiModifier.FINAL)) {
84                 flags = flags or FINAL
85             }
86             if (modifierList.hasModifierProperty(PsiModifier.NATIVE)) {
87                 flags = flags or NATIVE
88             }
89             if (modifierList.hasModifierProperty(PsiModifier.SYNCHRONIZED)) {
90                 flags = flags or SYNCHRONIZED
91             }
92             if (modifierList.hasModifierProperty(PsiModifier.STRICTFP)) {
93                 flags = flags or STRICT_FP
94             }
95             if (modifierList.hasModifierProperty(PsiModifier.TRANSIENT)) {
96                 flags = flags or TRANSIENT
97             }
98             if (modifierList.hasModifierProperty(PsiModifier.VOLATILE)) {
99                 flags = flags or VOLATILE
100             }
101             if (modifierList.hasModifierProperty(PsiModifier.DEFAULT)) {
102                 flags = flags or DEFAULT
103             }
104 
105             // Look for special Kotlin keywords
106             var ktModifierList: KtModifierList? = null
107             if (modifierList is KtLightModifierList<*>) {
108                 ktModifierList = modifierList.kotlinOrigin
109             } else if (modifierList is LightModifierList && element is KotlinUMethod) {
110                 ktModifierList = element.sourcePsi?.modifierList
111             }
112             if (ktModifierList != null) {
113                 if (ktModifierList.hasModifier(KtTokens.VARARG_KEYWORD)) {
114                     flags = flags or VARARG
115                 }
116                 if (ktModifierList.hasModifier(KtTokens.SEALED_KEYWORD)) {
117                     flags = flags or SEALED
118                 }
119                 if (ktModifierList.hasModifier(KtTokens.INTERNAL_KEYWORD)) {
120                     // Also remove public flag which at the UAST levels it promotes these
121                     // methods to, e.g. "internal myVar" gets turned into
122                     //    public final boolean getMyHiddenVar$lintWithKotlin()
123                     visibilityFlags = INTERNAL
124                 }
125                 if (ktModifierList.hasModifier(KtTokens.INFIX_KEYWORD)) {
126                     flags = flags or INFIX
127                 }
128                 if (ktModifierList.hasModifier(KtTokens.CONST_KEYWORD)) {
129                     flags = flags or CONST
130                 }
131                 if (ktModifierList.hasModifier(KtTokens.OPERATOR_KEYWORD)) {
132                     flags = flags or OPERATOR
133                 }
134                 if (ktModifierList.hasModifier(KtTokens.INLINE_KEYWORD)) {
135                     flags = flags or INLINE
136 
137                     // Workaround for b/117565118:
138                     val func = (element as? UMethod)?.sourcePsi as? KtNamedFunction
139                     if (func != null &&
140                         (func.typeParameterList?.text ?: "").contains("reified") &&
141                         !ktModifierList.hasModifier(KtTokens.PRIVATE_KEYWORD) &&
142                         !ktModifierList.hasModifier(KtTokens.INTERNAL_KEYWORD)
143                     ) {
144                         // Switch back from private to public
145                         visibilityFlags = PUBLIC
146                     }
147                 }
148                 if (ktModifierList.hasModifier(KtTokens.SUSPEND_KEYWORD)) {
149                     flags = flags or SUSPEND
150                 }
151                 if (ktModifierList.hasModifier(KtTokens.COMPANION_KEYWORD)) {
152                     flags = flags or COMPANION
153                 }
154             } else {
155                 // UAST returns a null modifierList.kotlinOrigin for get/set methods for
156                 // properties
157                 if (element is UMethod && element.sourceElement is KtProperty) {
158                     // If the name contains the marker of an internal method, mark it internal
159                     if (element.name.endsWith("\$lintWithKotlin")) {
160                         visibilityFlags = INTERNAL
161                     }
162                 }
163             }
164 
165             // Merge in the visibility flags.
166             flags = flags or visibilityFlags
167 
168             return flags
169         }
170 
createnull171         private fun create(codebase: PsiBasedCodebase, element: PsiModifierListOwner): PsiModifierItem {
172             val modifierList = element.modifierList ?: return PsiModifierItem(codebase)
173 
174             var flags = computeFlag(element, modifierList)
175 
176             val psiAnnotations = modifierList.annotations
177             return if (psiAnnotations.isEmpty()) {
178                 PsiModifierItem(codebase, flags)
179             } else {
180                 val annotations: MutableList<AnnotationItem> =
181                     // psi sometimes returns duplicate annotations, using distint() to counter that.
182                     psiAnnotations.distinct().map {
183                         val qualifiedName = it.qualifiedName
184                         // Consider also supporting com.android.internal.annotations.VisibleForTesting?
185                         if (qualifiedName == ANDROIDX_VISIBLE_FOR_TESTING ||
186                             qualifiedName == ANDROID_SUPPORT_VISIBLE_FOR_TESTING) {
187                             val otherwise = it.findAttributeValue(ATTR_OTHERWISE)
188                             val ref = when {
189                                 otherwise is PsiReferenceExpression -> otherwise.referenceName ?: ""
190                                 otherwise != null -> otherwise.text
191                                 else -> ""
192                             }
193                             flags = getVisibilityFlag(ref, flags)
194                         }
195 
196                         PsiAnnotationItem.create(codebase, it, qualifiedName)
197                     }.toMutableList()
198                 PsiModifierItem(codebase, flags, annotations)
199             }
200         }
201 
createnull202         private fun create(
203             codebase: PsiBasedCodebase,
204             element: PsiModifierListOwner,
205             annotated: UAnnotated
206         ): PsiModifierItem {
207             val modifierList = element.modifierList ?: return PsiModifierItem(codebase)
208             var flags = computeFlag(element, modifierList)
209             val uAnnotations = annotated.uAnnotations
210 
211             return if (uAnnotations.isEmpty()) {
212                 val psiAnnotations = modifierList.annotations
213                 if (psiAnnotations.isNotEmpty()) {
214                     val annotations: MutableList<AnnotationItem> =
215                         psiAnnotations.map { PsiAnnotationItem.create(codebase, it) }.toMutableList()
216                     PsiModifierItem(codebase, flags, annotations)
217                 } else {
218                     PsiModifierItem(codebase, flags)
219                 }
220             } else {
221                 val isPrimitiveVariable = element is UVariable && element.type is PsiPrimitiveType
222 
223                 val annotations: MutableList<AnnotationItem> = uAnnotations
224                     // Uast sometimes puts nullability annotations on primitives!?
225                     .filter { !isPrimitiveVariable || it !is KotlinNullabilityUAnnotation }
226                     .map {
227 
228                         val qualifiedName = it.qualifiedName
229                         if (qualifiedName == ANDROIDX_VISIBLE_FOR_TESTING ||
230                             qualifiedName == ANDROID_SUPPORT_VISIBLE_FOR_TESTING) {
231                             val otherwise = it.findAttributeValue(ATTR_OTHERWISE)
232                             val ref = when {
233                                 otherwise is PsiReferenceExpression -> otherwise.referenceName ?: ""
234                                 otherwise != null -> otherwise.asSourceString()
235                                 else -> ""
236                             }
237                             flags = getVisibilityFlag(ref, flags)
238                         }
239 
240                         UAnnotationItem.create(codebase, it, qualifiedName)
241                     }.toMutableList()
242 
243                 if (!isPrimitiveVariable) {
244                     val psiAnnotations = modifierList.annotations
245                     if (psiAnnotations.isNotEmpty() && annotations.none { it.isNullnessAnnotation() }) {
246                         val ktNullAnnotation = psiAnnotations.firstOrNull { it is KtLightNullabilityAnnotation<*> }
247                         ktNullAnnotation?.let {
248                             annotations.add(PsiAnnotationItem.create(codebase, it))
249                         }
250                     }
251                 }
252 
253                 PsiModifierItem(codebase, flags, annotations)
254             }
255         }
256 
257         /** Modifies the modifier flags based on the VisibleForTesting otherwise constants */
getVisibilityFlagnull258         private fun getVisibilityFlag(ref: String, flags: Int): Int {
259             val visibilityFlags = if (ref.endsWith("PROTECTED")) {
260                 PROTECTED
261             } else if (ref.endsWith("PACKAGE_PRIVATE")) {
262                 PACKAGE_PRIVATE
263             } else if (ref.endsWith("PRIVATE") || ref.endsWith("NONE")) {
264                 PRIVATE
265             } else {
266                 flags and VISIBILITY_MASK
267             }
268 
269             return (flags and VISIBILITY_MASK.inv()) or visibilityFlags
270         }
271 
createnull272         fun create(codebase: PsiBasedCodebase, original: PsiModifierItem): PsiModifierItem {
273             val originalAnnotations = original.annotations ?: return PsiModifierItem(codebase, original.flags)
274             val copy: MutableList<AnnotationItem> = ArrayList(originalAnnotations.size)
275             originalAnnotations.mapTo(copy) { PsiAnnotationItem.create(codebase, it as PsiAnnotationItem) }
276             return PsiModifierItem(codebase, original.flags, copy)
277         }
278     }
279 }
280