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
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
15 * Non-recursive UAST visitor that detects usages of APIs in source code of UAST-supporting languages
16 * and reports them via [ApiUsageProcessor] interface.
19 @ApiStatus.Experimental
20 open class ApiUsageUastVisitor(private val apiUsageProcessor: ApiUsageProcessor) : AbstractUastNonRecursiveVisitor() {
24 fun createPsiElementVisitor(apiUsageProcessor: ApiUsageProcessor): PsiElementVisitor =
25 UastVisitorAdapter(ApiUsageUastVisitor(apiUsageProcessor), true)
28 override fun visitSimpleNameReferenceExpression(node: USimpleNameReferenceExpression): Boolean {
29 if (maybeProcessReferenceInsideImportStatement(node)) {
32 if (maybeProcessJavaModuleReference(node)) {
35 if (isMethodReferenceOfCallExpression(node)
36 || isNewArrayClassReference(node)
37 || isMethodReferenceOfCallableReferenceExpression(node)
38 || isSelectorOfQualifiedReference(node)
42 if (isSuperOrThisCall(node)) {
45 val resolved = node.resolve()
46 if (resolved is PsiMethod) {
47 if (isClassReferenceInConstructorInvocation(node)) {
51 object : SomeClass(42) { }
57 with USimpleNameReferenceExpression pointing to `SomeClass`.
59 We want ApiUsageProcessor to notice two events: 1) reference to `SomeClass` and 2) reference to `SomeClass(int)` constructor.
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".
65 val resolvedClass = resolved.containingClass
66 if (resolvedClass != null) {
67 apiUsageProcessor.processReference(node, resolvedClass, null)
72 if (resolved is PsiModifierListOwner) {
73 apiUsageProcessor.processReference(node, resolved, null)
79 override fun visitQualifiedReferenceExpression(node: UQualifiedReferenceExpression): Boolean {
80 if (maybeProcessReferenceInsideImportStatement(node)) {
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
88 var resolved = node.resolve()
89 if (resolved == null) {
90 resolved = node.selector.tryResolve()
92 if (resolved is PsiModifierListOwner) {
93 apiUsageProcessor.processReference(node.selector, resolved, node.receiver)
98 private fun isKotlin(node: UElement): Boolean {
99 val sourcePsi = node.sourcePsi ?: return false
100 return sourcePsi.language.id.contains("kotlin", true)
103 override fun visitCallableReferenceExpression(node: UCallableReferenceExpression): Boolean {
105 * KT-31181: Kotlin UAST: UCallableReferenceExpression.referenceNameElement is always null.
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()
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)
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)
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.
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)
154 apiUsageProcessor.processReference(sourceNode, psiMethod, node.receiver)
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)
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)
176 apiUsageProcessor.processReference(sourceNode, resolvedClass, node.receiver)
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()
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)
200 maybeProcessImplicitConstructorInvocationAtSubclassDeclaration(sourceNode, node.declaration)
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)
212 return super.visitElement(node)
215 override fun visitClass(node: UClass): Boolean {
216 val uastAnchor = node.uastAnchor
217 if (uastAnchor == null || node is UAnonymousClass || node.javaPsi is PsiTypeParameter) {
220 maybeProcessImplicitConstructorInvocationAtSubclassDeclaration(uastAnchor, node)
224 override fun visitMethod(node: UMethod): Boolean {
225 if (node.isConstructor) {
226 checkImplicitCallOfSuperEmptyConstructor(node)
229 checkMethodOverriding(node)
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)
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)
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)
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
278 return sourcePsi.findContaining(UImportStatement::class.java) != null
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)
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)
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)
310 private fun isImplicitCallOfSuperEmptyConstructorFromSubclassConstructorBody(constructorBody: UExpression?): Boolean {
311 if (constructorBody == null || constructorBody is UBlockExpression && constructorBody.expressions.isEmpty()) {
312 //Empty constructor body => implicit super() call.
315 val firstExpression = (constructorBody as? UBlockExpression)?.expressions?.firstOrNull() ?: constructorBody
316 if (firstExpression !is UCallExpression) {
317 //First expression is not super() => the super() is implicit.
320 if (firstExpression.valueArgumentCount > 0) {
321 //Invocation of non-empty super(args) constructor.
324 val methodName = firstExpression.methodIdentifier?.name ?: firstExpression.methodName
325 return methodName != "super" && methodName != "this"
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)
336 private fun isSelectorOfQualifiedReference(expression: USimpleNameReferenceExpression): Boolean {
337 val qualifiedReference = expression.uastParent as? UQualifiedReferenceExpression ?: return false
338 return haveSameSourceElement(expression, qualifiedReference.selector)
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
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")
352 private fun isClassReferenceInConstructorInvocation(reference: UReferenceExpression): Boolean {
353 if (isSuperOrThisCall(reference)) {
356 val callExpression = reference.uastParent as? UCallExpression ?: return false
357 if (callExpression.kind != UastCallKind.CONSTRUCTOR_CALL) {
360 val classReferenceNameElement = callExpression.classReference?.referenceNameElement
361 if (classReferenceNameElement != null) {
362 return haveSameSourceElement(classReferenceNameElement, reference.referenceNameElement)
364 return callExpression.resolve()?.name == reference.resolvedName
367 private fun isMethodReferenceOfCallExpression(expression: USimpleNameReferenceExpression): Boolean {
368 val callExpression = expression.uastParent as? UCallExpression ?: return false
369 if (callExpression.kind != UastCallKind.METHOD_CALL) {
372 val expressionNameElement = expression.referenceNameElement
373 val methodIdentifier = callExpression.methodIdentifier
374 return methodIdentifier != null && haveSameSourceElement(expressionNameElement, methodIdentifier)
377 private fun isMethodReferenceOfCallableReferenceExpression(expression: USimpleNameReferenceExpression): Boolean {
378 val callableReferenceExpression = expression.uastParent as? UCallableReferenceExpression ?: return false
379 if (haveSameSourceElement(callableReferenceExpression.referenceNameElement, expression)) {
382 return expression.identifier == callableReferenceExpression.callableName
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