blob: 40f2d8ca85dff7857e6a5fd377fc4c070adeee60 [file]
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.navigation.lint
import com.intellij.psi.PsiClass
import com.intellij.psi.impl.source.PsiClassReferenceType
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.analysis.api.symbols.KaClassKind
import org.jetbrains.kotlin.analysis.api.symbols.KaClassSymbol
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtReferenceExpression
import org.jetbrains.uast.UClassLiteralExpression
import org.jetbrains.uast.UExpression
import org.jetbrains.uast.UQualifiedReferenceExpression
import org.jetbrains.uast.USimpleNameReferenceExpression
/** Catches simple class/interface name reference */
fun UExpression.isClassReference(
checkClass: Boolean = true,
checkInterface: Boolean = true,
checkCompanion: Boolean = true,
): Pair<Boolean, String?> {
/**
* True if:
* 1. reference to object (i.e. val myStart = TestStart(), startDest = myStart)
* 2. object declaration (i.e. object MyStart, startDest = MyStart)
* 3. class reference (i.e. class MyStart, startDest = MyStart)
*
* We only want to catch case 3., so we need more filters to eliminate case 1 & 2.
*/
val isSimpleRefExpression = this is USimpleNameReferenceExpression
/** True if nested class i.e. OuterClass.InnerClass */
val isQualifiedRefExpression = this is UQualifiedReferenceExpression
if (!(isSimpleRefExpression || isQualifiedRefExpression)) return false to null
val sourcePsi = sourcePsi as? KtExpression ?: return false to null
return analyze(sourcePsi) {
val symbol =
when (sourcePsi) {
is KtDotQualifiedExpression -> {
val lastChild = sourcePsi.lastChild
if (lastChild is KtReferenceExpression) {
lastChild.mainReference.resolveToSymbol()
} else {
null
}
}
is KtReferenceExpression -> sourcePsi.mainReference.resolveToSymbol()
else -> null
}
as? KaClassSymbol ?: return false to null
((checkClass && symbol.classKind.isClass) ||
(checkInterface && symbol.classKind == KaClassKind.INTERFACE) ||
(checkCompanion && symbol.classKind == KaClassKind.COMPANION_OBJECT)) to
symbol.name?.asString()
}
}
fun UExpression.getKClassType(): PsiClass? {
// filter for KClass<*>
if (this !is UClassLiteralExpression) return null
val expressionType = getExpressionType() ?: return null
if (expressionType !is PsiClassReferenceType) return null
@Suppress("UnstableApiUsage")
val typeArg = expressionType.typeArguments().firstOrNull() ?: return null
return (typeArg as? PsiClassReferenceType)?.reference?.resolve() as? PsiClass
}
const val MIN_ANALYSIS_API = 14