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