Skip to content

Commit 30f8713

Browse files
authored
Replace internal nj2k.descendantsOfType with stable PsiTreeUtil API (#109)
1 parent 2b049be commit 30f8713

File tree

2 files changed

+36
-49
lines changed

2 files changed

+36
-49
lines changed

compose-stability-analyzer-idea/src/main/kotlin/com/skydoves/compose/stability/idea/StabilityAnalyzer.kt

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import com.intellij.openapi.project.Project
2121
import com.intellij.openapi.roots.ProjectRootManager
2222
import com.intellij.openapi.vfs.VfsUtilCore
2323
import com.intellij.psi.PsiManager
24+
import com.intellij.psi.util.PsiTreeUtil
2425
import com.skydoves.compose.stability.idea.k2.StabilityAnalyzerK2
2526
import com.skydoves.compose.stability.idea.settings.StabilityProjectSettingsState
2627
import com.skydoves.compose.stability.idea.settings.StabilitySettingsState
@@ -35,7 +36,6 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor
3536
import org.jetbrains.kotlin.idea.caches.resolve.analyze
3637
import org.jetbrains.kotlin.idea.caches.resolve.resolveMainReference
3738
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
38-
import org.jetbrains.kotlin.nj2k.descendantsOfType
3939
import org.jetbrains.kotlin.psi.KtClass
4040
import org.jetbrains.kotlin.psi.KtFile
4141
import org.jetbrains.kotlin.psi.KtNamedFunction
@@ -47,9 +47,10 @@ import org.jetbrains.kotlin.psi.KtUserType
4747
import org.jetbrains.kotlin.resolve.BindingContext
4848
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
4949
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
50+
import org.jetbrains.kotlin.types.AbbreviatedType
5051
import org.jetbrains.kotlin.types.KotlinType
5152
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
52-
import java.lang.reflect.InvocationTargetException
53+
import java.util.concurrent.ConcurrentHashMap
5354

5455
/**
5556
* Helper class to hold stability result with reason.
@@ -81,6 +82,14 @@ internal object StabilityAnalyzer {
8182
private val ignoredPatterns: List<Regex>
8283
get() = settings.getIgnoredPatternsAsRegex()
8384

85+
/**
86+
* Cache for project-wide typealias lookups to avoid repeated filesystem scans.
87+
* Key: "aliasName|fqNameHint", Value: Pair(result, timestampMs).
88+
* Entries older than [TYPE_ALIAS_CACHE_TTL_MS] are considered stale.
89+
*/
90+
private val typeAliasCache = ConcurrentHashMap<String, Pair<KtTypeAlias?, Long>>()
91+
private const val TYPE_ALIAS_CACHE_TTL_MS = 30_000L // 30 seconds
92+
8493
@get:TestOnly
8594
@set:TestOnly
8695
internal var resolveMainReferenceOverride: ((KtReferenceExpression) -> Any?)? = null
@@ -692,8 +701,7 @@ internal object StabilityAnalyzer {
692701
val ktFile = typeRef.containingKtFile
693702

694703
// 1) File-local scan (fast, no IO)
695-
ktFile.declarations
696-
.descendantsOfType<KtTypeAlias>()
704+
PsiTreeUtil.findChildrenOfType(ktFile, KtTypeAlias::class.java)
697705
.firstOrNull { it.name == aliasName }
698706
?.let { return it }
699707

@@ -714,6 +722,26 @@ internal object StabilityAnalyzer {
714722
project: Project,
715723
aliasName: String,
716724
fqNameHint: String?,
725+
): KtTypeAlias? {
726+
val cacheKey = "$aliasName|$fqNameHint"
727+
val now = System.currentTimeMillis()
728+
729+
// Check cache first
730+
typeAliasCache[cacheKey]?.let { (cached, timestamp) ->
731+
if (now - timestamp < TYPE_ALIAS_CACHE_TTL_MS && cached?.isValid != false) {
732+
return cached
733+
}
734+
}
735+
736+
val result = findTypeAliasInProjectUncached(project, aliasName, fqNameHint)
737+
typeAliasCache[cacheKey] = result to now
738+
return result
739+
}
740+
741+
private fun findTypeAliasInProjectUncached(
742+
project: Project,
743+
aliasName: String,
744+
fqNameHint: String?,
717745
): KtTypeAlias? {
718746
val psiManager = PsiManager.getInstance(project)
719747
val packageHint = fqNameHint?.substringBeforeLast('.', "")
@@ -735,8 +763,7 @@ internal object StabilityAnalyzer {
735763
return@iterateChildrenRecursively true
736764
}
737765

738-
val alias = psi.declarations
739-
.descendantsOfType<KtTypeAlias>()
766+
val alias = PsiTreeUtil.findChildrenOfType(psi, KtTypeAlias::class.java)
740767
.firstOrNull { it.name == aliasName }
741768
?: return@iterateChildrenRecursively true
742769

@@ -1217,33 +1244,7 @@ internal fun KtNamedFunction.hasAnnotation(shortName: String): Boolean {
12171244
*/
12181245

12191246
private fun KotlinType.expandTypeAliasIfNeeded(): KotlinType {
1220-
val abbreviatedTypeClass = try {
1221-
Class.forName("org.jetbrains.kotlin.types.AbbreviatedType")
1222-
} catch (_: ClassNotFoundException) {
1223-
return this
1224-
} catch (_: NoClassDefFoundError) {
1225-
return this
1226-
} catch (_: LinkageError) {
1227-
return this
1228-
}
1229-
1230-
if (!abbreviatedTypeClass.isInstance(this)) return this
1231-
1232-
val getExpanded = abbreviatedTypeClass.methods.firstOrNull {
1233-
it.name == "getExpandedType" && it.parameterCount == 0
1234-
} ?: return this
1235-
1236-
return try {
1237-
(getExpanded.invoke(this) as? KotlinType) ?: this
1238-
} catch (_: IllegalAccessException) {
1239-
this
1240-
} catch (_: IllegalArgumentException) {
1241-
this
1242-
} catch (_: InvocationTargetException) {
1243-
this
1244-
} catch (_: ClassCastException) {
1245-
this
1246-
}
1247+
return if (this is AbbreviatedType) expandedType else this
12471248
}
12481249

12491250
/**

compose-stability-analyzer-idea/src/main/kotlin/com/skydoves/compose/stability/idea/k2/KtStabilityInferencer.kt

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -111,15 +111,6 @@ internal class KtStabilityInferencer(
111111
// Use fullyExpandedType to get the actual underlying type
112112
val expandedType = type.fullyExpandedType
113113

114-
val expandedTypeString = try {
115-
expandedType.render(position = org.jetbrains.kotlin.types.Variance.INVARIANT)
116-
} catch (_: StackOverflowError) {
117-
return KtStability.Runtime(
118-
className = "Unknown",
119-
reason = "Unable to render type due to complexity",
120-
)
121-
}
122-
123114
// 1. Nullable types - MUST be checked first to strip nullability
124115
// Use KaTypeNullability enum for compatibility with Android Studio AI-243
125116
val nonNullableType = if (expandedType.isMarkedNullable) {
@@ -138,7 +129,6 @@ internal class KtStabilityInferencer(
138129
// Function types are ALWAYS stable (captured values are checked separately in Compose compiler)
139130
val isFunctionType = nonNullableType.isFunctionType ||
140131
nonNullableType.isSuspendFunctionType ||
141-
expandedTypeString.containsTopLevelArrow() ||
142132
originalTypeString.containsTopLevelArrow()
143133

144134
if (isFunctionType) {
@@ -149,11 +139,9 @@ internal class KtStabilityInferencer(
149139
} || nonNullableType.annotations.any { annotation ->
150140
annotation.classId?.asSingleFqName()?.asString() ==
151141
"androidx.compose.runtime.Composable"
152-
} || expandedTypeString.contains("@Composable") ||
153-
originalTypeString.contains("@Composable")
142+
} || originalTypeString.contains("@Composable")
154143

155144
val isSuspend = nonNullableType.isSuspendFunctionType ||
156-
expandedTypeString.contains("suspend") ||
157145
originalTypeString.contains("suspend")
158146

159147
return KtStability.Certain(
@@ -191,11 +179,9 @@ internal class KtStabilityInferencer(
191179
} || nonNullableType.annotations.any { annotation ->
192180
annotation.classId?.asSingleFqName()?.asString() ==
193181
"androidx.compose.runtime.Composable"
194-
} || expandedTypeString.contains("@Composable") ||
195-
originalTypeString.contains("@Composable")
182+
} || originalTypeString.contains("@Composable")
196183

197184
val isSuspend = nonNullableType.isSuspendFunctionType ||
198-
expandedTypeString.contains("suspend") ||
199185
originalTypeString.contains("suspend")
200186

201187
return KtStability.Certain(

0 commit comments

Comments
 (0)