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