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.openapi.vfs.VirtualFile
import com.intellij.psi.NonClasspathClassFinder import com.intellij.psi.NonClasspathClassFinder
import com.intellij.psi.PsiClass import com.intellij.psi.PsiClass
import com.intellij.psi.PsiClassOwner
import com.intellij.psi.search.EverythingGlobalScope import com.intellij.psi.search.EverythingGlobalScope
import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScope
import com.intellij.util.containers.ConcurrentFactoryMap import com.intellij.util.containers.ConcurrentFactoryMap
import com.intellij.util.containers.ContainerUtil
import org.jetbrains.kotlin.idea.caches.resolve.ScriptModuleSearchScope import org.jetbrains.kotlin.idea.caches.resolve.ScriptModuleSearchScope
import org.jetbrains.kotlin.load.java.AbstractJavaClassFinder import org.jetbrains.kotlin.load.java.AbstractJavaClassFinder
import org.jetbrains.kotlin.resolve.jvm.KotlinSafeClassFinder import org.jetbrains.kotlin.resolve.jvm.KotlinSafeClassFinder
@@ -56,21 +58,69 @@ class KotlinScriptDependenciesClassFinder(project: Project,
myCaches.clear() myCaches.clear()
} }
override fun findClass(qualifiedName: String, scope: GlobalSearchScope): PsiClass? = override fun findClass(qualifiedName: String, scope: GlobalSearchScope): PsiClass? {
super.findClass(qualifiedName, scope)?.let { aClass -> val psiClass = findClassInCache(qualifiedName, scope) ?: return null
when { return when {
scope is ScriptModuleSearchScope || scope is ScriptModuleSearchScope ||
(scope as? AbstractJavaClassFinder.FilterOutKotlinSourceFilesScope)?.base is ScriptModuleSearchScope || (scope as? AbstractJavaClassFinder.FilterOutKotlinSourceFilesScope)?.base is ScriptModuleSearchScope ||
scope is EverythingGlobalScope || scope is EverythingGlobalScope ||
aClass.containingFile?.virtualFile.let { file -> psiClass.containingFile?.virtualFile.let { file ->
file != null && file != null &&
with (ProjectFileIndex.SERVICE.getInstance(myProject)) { with(ProjectFileIndex.SERVICE.getInstance(myProject)) {
!isInContent(file) && !isInContent(file) &&
!isInLibraryClasses(file) && !isInLibraryClasses(file) &&
!isInLibrarySource(file) !isInLibrarySource(file)
} }
} -> aClass } -> psiClass
else -> null 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('.'))
}
} }
@@ -0,0 +1,13 @@
package lib;
public interface JavaInterface {
public interface F {
public interface I {
}
}
public class G {
}
}
@@ -0,0 +1,9 @@
import lib.*
fun f(
<warning>g</warning>: JavaInterface.G,
<warning>f</warning>: JavaInterface.F,
<warning>i</warning>: JavaInterface.F.I
) {
}
@@ -0,0 +1,28 @@
package custom.scriptDefinition
import kotlin.script.dependencies.*
import kotlin.script.templates.*
import java.io.File
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Future
class TestDependenciesResolver : ScriptDependenciesResolver {
override fun resolve(
script: ScriptContents,
environment: Map<String, Any?>?,
report: (ScriptDependenciesResolver.ReportSeverity, String, ScriptContents.Position?) -> Unit, previousDependencies: KotlinScriptExternalDependencies?
): Future<KotlinScriptExternalDependencies?> {
return CompletableFuture.completedFuture(
object : KotlinScriptExternalDependencies {
override val classpath: Iterable<File> = listOf(
environment?.get("lib-classes") as File
)
override val imports: Iterable<String> = listOf("custom.library.*")
})
}
}
@ScriptTemplateDefinition(TestDependenciesResolver::class, scriptFilePattern = "script.kts")
class Template
@@ -84,6 +84,12 @@ public class ScriptConfigurationHighlightingTestGenerated extends AbstractScript
doTest(fileName); doTest(fileName);
} }
@TestMetadata("javaNestedClass")
public void testJavaNestedClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/script/definition/highlighting/javaNestedClass/");
doTest(fileName);
}
@TestMetadata("noResolver") @TestMetadata("noResolver")
public void testNoResolver() throws Exception { public void testNoResolver() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/script/definition/highlighting/noResolver/"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/script/definition/highlighting/noResolver/");