Workaround NonClasspathClassFinder not supporting inner classes

Explicitly split qualified names, can be removed once platform is fixed

 #KT-19310 Fixed
This commit is contained in:
Pavel V. Talanov
2017-08-18 18:57:42 +03:00
parent a6c2135851
commit a120130069
5 changed files with 122 additions and 16 deletions
@@ -22,9 +22,11 @@ import com.intellij.openapi.roots.impl.PackageDirectoryCache
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.NonClasspathClassFinder
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiClassOwner
import com.intellij.psi.search.EverythingGlobalScope
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.util.containers.ConcurrentFactoryMap
import com.intellij.util.containers.ContainerUtil
import org.jetbrains.kotlin.idea.caches.resolve.ScriptModuleSearchScope
import org.jetbrains.kotlin.load.java.AbstractJavaClassFinder
import org.jetbrains.kotlin.resolve.jvm.KotlinSafeClassFinder
@@ -56,21 +58,69 @@ class KotlinScriptDependenciesClassFinder(project: Project,
myCaches.clear()
}
override fun findClass(qualifiedName: String, scope: GlobalSearchScope): PsiClass? =
super.findClass(qualifiedName, scope)?.let { aClass ->
when {
scope is ScriptModuleSearchScope ||
(scope as? AbstractJavaClassFinder.FilterOutKotlinSourceFilesScope)?.base is ScriptModuleSearchScope ||
scope is EverythingGlobalScope ||
aClass.containingFile?.virtualFile.let { file ->
file != null &&
with (ProjectFileIndex.SERVICE.getInstance(myProject)) {
!isInContent(file) &&
!isInLibraryClasses(file) &&
!isInLibrarySource(file)
}
} -> aClass
else -> null
}
override fun findClass(qualifiedName: String, scope: GlobalSearchScope): PsiClass? {
val psiClass = findClassInCache(qualifiedName, scope) ?: return null
return when {
scope is ScriptModuleSearchScope ||
(scope as? AbstractJavaClassFinder.FilterOutKotlinSourceFilesScope)?.base is ScriptModuleSearchScope ||
scope is EverythingGlobalScope ||
psiClass.containingFile?.virtualFile.let { file ->
file != null &&
with(ProjectFileIndex.SERVICE.getInstance(myProject)) {
!isInContent(file) &&
!isInLibraryClasses(file) &&
!isInLibrarySource(file)
}
} -> psiClass
else -> null
}
}
private fun findClassInCache(qualifiedName: String, scope: GlobalSearchScope): PsiClass? {
if (qualifiedName.isEmpty()) return null
return splitDotQualifiedName(qualifiedName).map { (packageName, classNames) ->
findClassInPackage(packageName, classNames, scope)
}.find { it != null }
}
private fun findClassInPackage(packageName: String, classNames: List<String>, scope: GlobalSearchScope): PsiClass? {
var result: PsiClass? = null
ContainerUtil.process(getCache(scope).getDirectoriesByPackageName(packageName)) { dir ->
if (dir !in scope) return@process true
findClassInDir(dir, classNames)?.let {
result = it
return@process false
}
return@process true
}
return result
}
private fun findClassInDir(dir: VirtualFile, classNames: List<String>): PsiClass? {
val firstClassName = classNames.first()
val virtualFile = dir.findChild("$firstClassName.class") ?: return null
val psiFile = psiManager.findFile(virtualFile) as? PsiClassOwner ?: return null
val topLevelClass = psiFile.classes.singleOrNull() ?: return null
return classNames.subList(1, classNames.size).fold<String, PsiClass?>(topLevelClass) { currentPsiClass, className ->
currentPsiClass?.findInnerClassByName(className, false)
}
}
private fun splitDotQualifiedName(qualifiedName: String): Sequence<Pair<String, List<String>>> {
val (packageName, className) = qualifiedName.splitByLastDot()
return generateSequence(Pair(packageName, listOf(className))) {
(prevPackageName, prevClassNames) ->
if (prevPackageName == "") return@generateSequence null
val (newPackageName, newTopLevelClassName) = prevPackageName.splitByLastDot()
Pair(newPackageName, listOf(newTopLevelClassName) + prevClassNames)
}
}
private fun String.splitByLastDot(): Pair<String, String> {
return Pair(substringBeforeLast('.', missingDelimiterValue = ""), substringAfterLast('.'))
}
}