bf1971d80740b4d47461acbc130ed2363a3382b2
[idea/community.git] / java / java-analysis-impl / src / com / intellij / codeInspection / apiUsage / ApiUsageUastVisitor.kt
1 // Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
2 package com.intellij.codeInspection.apiUsage
3
4 import com.intellij.lang.java.JavaLanguage
5 import com.intellij.psi.*
6 import com.intellij.psi.util.PsiTreeUtil
7 import com.intellij.psi.util.PsiUtil
8 import com.intellij.uast.UastVisitorAdapter
9 import com.intellij.util.castSafelyTo
10 import org.jetbrains.annotations.ApiStatus
11 import org.jetbrains.uast.*
12 import org.jetbrains.uast.visitor.AbstractUastNonRecursiveVisitor
13
14 /**
15  * Non-recursive UAST visitor that detects usages of APIs in source code of UAST-supporting languages
16  * and reports them via [ApiUsageProcessor] interface.
17  */
18
19 @ApiStatus.Experimental
20 open class ApiUsageUastVisitor(private val apiUsageProcessor: ApiUsageProcessor) : AbstractUastNonRecursiveVisitor() {
21
22   companion object {
23     @JvmStatic
24     fun createPsiElementVisitor(apiUsageProcessor: ApiUsageProcessor): PsiElementVisitor =
25       UastVisitorAdapter(ApiUsageUastVisitor(apiUsageProcessor), true)
26   }
27
28   override fun visitSimpleNameReferenceExpression(node: USimpleNameReferenceExpression): Boolean {
29     if (maybeProcessReferenceInsideImportStatement(node)) {
30       return true
31     }
32     if (maybeProcessJavaModuleReference(node)) {
33       return true
34     }
35     if (isMethodReferenceOfCallExpression(node)
36         || isNewArrayClassReference(node)
37         || isMethodReferenceOfCallableReferenceExpression(node)
38         || isSelectorOfQualifiedReference(node)
39     ) {
40       return true
41     }
42     if (isSuperOrThisCall(node)) {
43       return true
44     }
45     val resolved = node.resolve()
46     if (resolved is PsiMethod) {
47       if (isClassReferenceInConstructorInvocation(node)) {
48         /*
49           Suppose a code:
50           ```
51              object : SomeClass(42) { }
52
53              or
54
55              new SomeClass(42)
56           ```
57           with USimpleNameReferenceExpression pointing to `SomeClass`.
58
59           We want ApiUsageProcessor to notice two events: 1) reference to `SomeClass` and 2) reference to `SomeClass(int)` constructor.
60
61           But Kotlin UAST resolves this simple reference to the PSI constructor of the class SomeClass.
62           So we resolve it manually to the class because the constructor will be handled separately
63           in "visitObjectLiteralExpression" or "visitCallExpression".
64         */
65         val resolvedClass = resolved.containingClass
66         if (resolvedClass != null) {
67           apiUsageProcessor.processReference(node, resolvedClass, null)
68         }
69         return true
70       }
71     }
72     if (resolved is PsiModifierListOwner) {
73       apiUsageProcessor.processReference(node, resolved, null)
74       return true
75     }
76     return true
77   }
78
79   override fun visitQualifiedReferenceExpression(node: UQualifiedReferenceExpression): Boolean {
80     if (maybeProcessReferenceInsideImportStatement(node)) {
81       return true
82     }
83     if (node.sourcePsi is PsiMethodCallExpression || node.selector is UCallExpression) {
84       //UAST for Java produces UQualifiedReferenceExpression for both PsiMethodCallExpression and PsiReferenceExpression inside it
85       //UAST for Kotlin produces UQualifiedReferenceExpression with UCallExpression as selector
86       return true
87     }
88     var resolved = node.resolve()
89     if (resolved == null) {
90       resolved = node.selector.tryResolve()
91     }
92     if (resolved is PsiModifierListOwner) {
93       apiUsageProcessor.processReference(node.selector, resolved, node.receiver)
94     }
95     return true
96   }
97
98   private fun isKotlin(node: UElement): Boolean {
99     val sourcePsi = node.sourcePsi ?: return false
100     return sourcePsi.language.id.contains("kotlin", true)
101   }
102
103   override fun visitCallableReferenceExpression(node: UCallableReferenceExpression): Boolean {
104     /*
105      * KT-31181: Kotlin UAST: UCallableReferenceExpression.referenceNameElement is always null.
106      */
107     fun workaroundKotlinGetReferenceNameElement(node: UCallableReferenceExpression): UElement? {
108       if (isKotlin(node)) {
109         val sourcePsi = node.sourcePsi
110         if (sourcePsi != null) {
111           val children = sourcePsi.children
112           if (children.size == 2) {
113             return children[1].toUElement()
114           }
115         }
116       }
117       return null
118     }
119
120     val resolve = node.resolve()
121     if (resolve is PsiModifierListOwner) {
122       val sourceNode = node.referenceNameElement ?: workaroundKotlinGetReferenceNameElement(node) ?: node
123       apiUsageProcessor.processReference(sourceNode, resolve, node.qualifierExpression)
124
125       //todo support this for other JVM languages
126       val javaMethodReference = node.sourcePsi as? PsiMethodReferenceExpression
127       if (javaMethodReference != null) {
128         //a reference to the functional interface will be added by compiler
129         val resolved = PsiUtil.resolveGenericsClassInType(javaMethodReference.functionalInterfaceType).element
130         if (resolved != null) {
131           apiUsageProcessor.processReference(node, resolved, null)
132         }
133       }
134     }
135     return true
136   }
137
138   override fun visitCallExpression(node: UCallExpression): Boolean {
139     if (node.sourcePsi is PsiExpressionStatement) {
140       //UAST for Java generates UCallExpression for PsiExpressionStatement and PsiMethodCallExpression inside it.
141       return true
142     }
143
144     val psiMethod = node.resolve()
145     val sourceNode = node.methodIdentifier ?: node.classReference?.referenceNameElement ?: node.classReference ?: node
146     if (psiMethod != null) {
147       val containingClass = psiMethod.containingClass
148       if (psiMethod.isConstructor) {
149         if (containingClass != null) {
150           apiUsageProcessor.processConstructorInvocation(sourceNode, containingClass, psiMethod, null)
151         }
152       }
153       else {
154         apiUsageProcessor.processReference(sourceNode, psiMethod, node.receiver)
155       }
156       return true
157     }
158
159     if (node.kind == UastCallKind.CONSTRUCTOR_CALL) {
160       //Java does not resolve constructor for subclass constructor's "super()" statement
161       // if the superclass has the default constructor, which is not declared in source code and lacks PsiMethod.
162       val superClass = node.getContainingUClass()?.javaPsi?.superClass ?: return true
163       apiUsageProcessor.processConstructorInvocation(sourceNode, superClass, null, null)
164       return true
165     }
166
167     val classReference = node.classReference
168     if (classReference != null) {
169       val resolvedClass = classReference.resolve() as? PsiClass
170       if (resolvedClass != null) {
171         if (node.kind == UastCallKind.CONSTRUCTOR_CALL) {
172           val emptyConstructor = resolvedClass.constructors.find { it.parameterList.isEmpty }
173           apiUsageProcessor.processConstructorInvocation(sourceNode, resolvedClass, emptyConstructor, null)
174         }
175         else {
176           apiUsageProcessor.processReference(sourceNode, resolvedClass, node.receiver)
177         }
178       }
179       return true
180     }
181     return true
182   }
183
184   override fun visitObjectLiteralExpression(node: UObjectLiteralExpression): Boolean {
185     val psiMethod = node.resolve()
186     val sourceNode = node.methodIdentifier
187                      ?: node.classReference?.referenceNameElement
188                      ?: node.classReference
189                      ?: node.declaration.uastSuperTypes.firstOrNull()
190                      ?: node
191     if (psiMethod != null) {
192       val containingClass = psiMethod.containingClass
193       if (psiMethod.isConstructor) {
194         if (containingClass != null) {
195           apiUsageProcessor.processConstructorInvocation(sourceNode, containingClass, psiMethod, node.declaration)
196         }
197       }
198     }
199     else {
200       maybeProcessImplicitConstructorInvocationAtSubclassDeclaration(sourceNode, node.declaration)
201     }
202     return true
203   }
204
205   override fun visitElement(node: UElement): Boolean {
206     if (node is UNamedExpression) {
207       //IDEA-209279: UAstVisitor lacks a hook for UNamedExpression
208       //KT-30522: Kotlin does not generate UNamedExpression for annotation's parameters.
209       processNamedExpression(node)
210       return true
211     }
212     return super.visitElement(node)
213   }
214
215   override fun visitClass(node: UClass): Boolean {
216     val uastAnchor = node.uastAnchor
217     if (uastAnchor == null || node is UAnonymousClass || node.javaPsi is PsiTypeParameter) {
218       return true
219     }
220     maybeProcessImplicitConstructorInvocationAtSubclassDeclaration(uastAnchor, node)
221     return true
222   }
223
224   override fun visitMethod(node: UMethod): Boolean {
225     if (node.isConstructor) {
226       checkImplicitCallOfSuperEmptyConstructor(node)
227     }
228     else {
229       checkMethodOverriding(node)
230     }
231     return true
232   }
233
234   override fun visitLambdaExpression(node: ULambdaExpression): Boolean {
235     val explicitClassReference = (node.uastParent as? UCallExpression)?.classReference
236     if (explicitClassReference == null) {
237       //a reference to the functional interface will be added by compiler
238       val resolved = PsiUtil.resolveGenericsClassInType(node.functionalInterfaceType).element
239       if (resolved != null) {
240         apiUsageProcessor.processReference(node, resolved, null)
241       }
242     }
243     return true
244   }
245
246   private fun maybeProcessJavaModuleReference(node: UElement): Boolean {
247     val sourcePsi = node.sourcePsi
248     if (sourcePsi is PsiJavaModuleReferenceElement) {
249       val reference = sourcePsi.reference
250       val target = reference?.resolve()
251       if (target != null) {
252         apiUsageProcessor.processJavaModuleReference(reference, target)
253       }
254       return true
255     }
256     return false
257   }
258
259   private fun maybeProcessReferenceInsideImportStatement(node: UReferenceExpression): Boolean {
260     if (isInsideImportStatement(node)) {
261       val parentingQualifier = node.castSafelyTo<USimpleNameReferenceExpression>()?.uastParent.castSafelyTo<UQualifiedReferenceExpression>()
262       if (node != parentingQualifier?.selector) {
263         val resolved = node.resolve() as? PsiModifierListOwner
264         if (resolved != null) {
265           apiUsageProcessor.processImportReference(node.referenceNameElement ?: node, resolved)
266         }
267       }
268       return true
269     }
270     return false
271   }
272
273   private fun isInsideImportStatement(node: UElement): Boolean {
274     val sourcePsi = node.sourcePsi
275     if (sourcePsi != null && sourcePsi.language == JavaLanguage.INSTANCE) {
276       return PsiTreeUtil.getParentOfType(sourcePsi, PsiImportStatementBase::class.java) != null
277     }
278     return sourcePsi.findContaining(UImportStatement::class.java) != null
279   }
280
281   private fun maybeProcessImplicitConstructorInvocationAtSubclassDeclaration(sourceNode: UElement, subclassDeclaration: UClass) {
282     val instantiatedClass = subclassDeclaration.javaPsi.superClass ?: return
283     val subclassHasExplicitConstructor = subclassDeclaration.methods.any { it.isConstructor }
284     val emptyConstructor = instantiatedClass.constructors.find { it.parameterList.isEmpty }
285     if (subclassDeclaration is UAnonymousClass || !subclassHasExplicitConstructor) {
286       apiUsageProcessor.processConstructorInvocation(sourceNode, instantiatedClass, emptyConstructor, subclassDeclaration)
287     }
288   }
289
290   private fun processNamedExpression(node: UNamedExpression) {
291     val sourcePsi = node.sourcePsi
292     val annotationMethod = sourcePsi?.reference?.resolve() as? PsiAnnotationMethod
293     if (annotationMethod != null) {
294       val sourceNode = (sourcePsi as? PsiNameValuePair)?.nameIdentifier?.toUElement() ?: node
295       apiUsageProcessor.processReference(sourceNode, annotationMethod, null)
296     }
297   }
298
299   protected fun checkImplicitCallOfSuperEmptyConstructor(constructor: UMethod) {
300     val containingUClass = constructor.getContainingUClass() ?: return
301     val superClass = containingUClass.javaPsi.superClass ?: return
302     val uastBody = constructor.uastBody
303     val uastAnchor = constructor.uastAnchor
304     if (uastAnchor != null && isImplicitCallOfSuperEmptyConstructorFromSubclassConstructorBody(uastBody)) {
305       val emptyConstructor = superClass.constructors.find { it.parameterList.isEmpty }
306       apiUsageProcessor.processConstructorInvocation(uastAnchor, superClass, emptyConstructor, null)
307     }
308   }
309
310   private fun isImplicitCallOfSuperEmptyConstructorFromSubclassConstructorBody(constructorBody: UExpression?): Boolean {
311     if (constructorBody == null || constructorBody is UBlockExpression && constructorBody.expressions.isEmpty()) {
312       //Empty constructor body => implicit super() call.
313       return true
314     }
315     val firstExpression = (constructorBody as? UBlockExpression)?.expressions?.firstOrNull() ?: constructorBody
316     if (firstExpression !is UCallExpression) {
317       //First expression is not super() => the super() is implicit.
318       return true
319     }
320     if (firstExpression.valueArgumentCount > 0) {
321       //Invocation of non-empty super(args) constructor.
322       return false
323     }
324     val methodName = firstExpression.methodIdentifier?.name ?: firstExpression.methodName
325     return methodName != "super" && methodName != "this"
326   }
327
328   private fun checkMethodOverriding(node: UMethod) {
329     val method = node.javaPsi
330     val superMethods = method.findSuperMethods(true)
331     for (superMethod in superMethods) {
332       apiUsageProcessor.processMethodOverriding(node, superMethod)
333     }
334   }
335
336   private fun isSelectorOfQualifiedReference(expression: USimpleNameReferenceExpression): Boolean {
337     val qualifiedReference = expression.uastParent as? UQualifiedReferenceExpression ?: return false
338     return haveSameSourceElement(expression, qualifiedReference.selector)
339   }
340
341   private fun isNewArrayClassReference(simpleReference: USimpleNameReferenceExpression): Boolean {
342     val callExpression = simpleReference.uastParent as? UCallExpression ?: return false
343     return callExpression.kind == UastCallKind.NEW_ARRAY_WITH_DIMENSIONS
344   }
345
346   private fun isSuperOrThisCall(simpleReference: UReferenceExpression): Boolean {
347     val callExpression = simpleReference.uastParent as? UCallExpression ?: return false
348     return callExpression.kind == UastCallKind.CONSTRUCTOR_CALL &&
349            (callExpression.methodIdentifier?.name == "super" || callExpression.methodIdentifier?.name == "this")
350   }
351
352   private fun isClassReferenceInConstructorInvocation(reference: UReferenceExpression): Boolean {
353     if (isSuperOrThisCall(reference)) {
354       return false
355     }
356     val callExpression = reference.uastParent as? UCallExpression ?: return false
357     if (callExpression.kind != UastCallKind.CONSTRUCTOR_CALL) {
358       return false
359     }
360     val classReferenceNameElement = callExpression.classReference?.referenceNameElement
361     if (classReferenceNameElement != null) {
362       return haveSameSourceElement(classReferenceNameElement, reference.referenceNameElement)
363     }
364     return callExpression.resolve()?.name == reference.resolvedName
365   }
366
367   private fun isMethodReferenceOfCallExpression(expression: USimpleNameReferenceExpression): Boolean {
368     val callExpression = expression.uastParent as? UCallExpression ?: return false
369     if (callExpression.kind != UastCallKind.METHOD_CALL) {
370       return false
371     }
372     val expressionNameElement = expression.referenceNameElement
373     val methodIdentifier = callExpression.methodIdentifier
374     return methodIdentifier != null && haveSameSourceElement(expressionNameElement, methodIdentifier)
375   }
376
377   private fun isMethodReferenceOfCallableReferenceExpression(expression: USimpleNameReferenceExpression): Boolean {
378     val callableReferenceExpression = expression.uastParent as? UCallableReferenceExpression ?: return false
379     if (haveSameSourceElement(callableReferenceExpression.referenceNameElement, expression)) {
380       return true
381     }
382     return expression.identifier == callableReferenceExpression.callableName
383   }
384
385   private fun haveSameSourceElement(element1: UElement?, element2: UElement?): Boolean {
386     if (element1 == null || element2 == null) return false
387     val sourcePsi1 = element1.sourcePsi
388     return sourcePsi1 != null && sourcePsi1 == element2.sourcePsi
389   }
390 }