Implement light classes for Kotlin scripts

This commit is contained in:
Vyacheslav Gerasimov
2017-09-04 15:30:47 +03:00
parent 26cbcfa1ac
commit 89257e6397
25 changed files with 495 additions and 139 deletions
@@ -27,10 +27,7 @@ import org.jetbrains.kotlin.asJava.LightClassBuilder
import org.jetbrains.kotlin.asJava.LightClassGenerationSupport
import org.jetbrains.kotlin.asJava.builder.ClsWrapperStubPsiFactory
import org.jetbrains.kotlin.asJava.builder.LightClassDataHolder
import org.jetbrains.kotlin.asJava.classes.FakeLightClassForFileOfPackage
import org.jetbrains.kotlin.asJava.classes.KtLightClass
import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade
import org.jetbrains.kotlin.asJava.classes.KtLightClassForSourceDeclaration
import org.jetbrains.kotlin.asJava.classes.*
import org.jetbrains.kotlin.asJava.finder.JavaElementFinder
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
@@ -84,6 +81,14 @@ class IDELightClassGenerationSupport(private val project: Project) : LightClassG
)
}
override fun createDataHolderForScript(script: KtScript, builder: LightClassBuilder): LightClassDataHolder.ForScript {
return LazyLightClassDataHolder.ForScript(
builder,
exactContextProvider = { IDELightClassContexts.contextForScript(script) },
dummyContextProvider = { IDELightClassContexts.lightContextForScript(script) }
)
}
override fun findClassOrObjectDeclarations(fqName: FqName, searchScope: GlobalSearchScope): Collection<KtClassOrObject> {
return runReadAction {
KotlinFullClassNameIndex.getInstance().get(fqName.asString(), project, sourceAndClassFiles(searchScope, project))
@@ -131,6 +136,8 @@ class IDELightClassGenerationSupport(private val project: Project) : LightClassG
return null
}
override fun getLightClassForScript(script: KtScript): KtLightClassForScript? = KtLightClassForScript.create(script)
private fun withFakeLightClasses(
lightClassForFacade: KtLightClassForFacade?,
facadeFiles: List<KtFile>
@@ -155,6 +162,12 @@ class IDELightClassGenerationSupport(private val project: Project) : LightClassG
}
}
override fun getScriptClasses(scriptFqName: FqName, scope: GlobalSearchScope): Collection<PsiClass> {
return KotlinScriptFqnIndex.instance.get(scriptFqName.asString(), project, scope).mapNotNull {
getLightClassForScript(it)
}
}
override fun getKotlinInternalClasses(fqName: FqName, scope: GlobalSearchScope): Collection<PsiClass> {
if (fqName.isRoot) return emptyList()
@@ -129,6 +129,21 @@ object IDELightClassContexts {
return IDELightClassConstructionContext(resolveSession.bindingContext, resolveSession.moduleDescriptor, EXACT)
}
fun contextForScript(script: KtScript): LightClassConstructionContext {
val resolutionFacade = script.getResolutionFacade()
val bindingContext = resolutionFacade.analyze(script)
val descriptor = bindingContext[BindingContext.SCRIPT, script]
if (descriptor == null) {
LOG.warn("No script descriptor in context for script: " + script.getElementTextWithContext())
return IDELightClassConstructionContext(bindingContext, resolutionFacade.moduleDescriptor, EXACT)
}
ForceResolveUtil.forceResolveAllContents(descriptor)
return IDELightClassConstructionContext(bindingContext, resolutionFacade.moduleDescriptor, EXACT)
}
fun lightContextForClassOrObject(classOrObject: KtClassOrObject): LightClassConstructionContext? {
if (!isDummyResolveApplicable(classOrObject)) return null
@@ -139,6 +154,26 @@ object IDELightClassContexts {
return IDELightClassConstructionContext(resolveSession.bindingContext, resolveSession.moduleDescriptor, LIGHT)
}
fun lightContextForFacade(files: List<KtFile>): LightClassConstructionContext {
val representativeFile = files.first()
val resolveSession = setupAdHocResolve(representativeFile.project, representativeFile.getResolutionFacade().moduleDescriptor, files)
forceResolvePackageDeclarations(files, resolveSession)
return IDELightClassConstructionContext(resolveSession.bindingContext, resolveSession.moduleDescriptor, LIGHT)
}
fun lightContextForScript(script: KtScript): LightClassConstructionContext {
val resolveSession = setupAdHocResolve(
script.project,
script.getResolutionFacade().moduleDescriptor,
listOf(script.containingKtFile))
ForceResolveUtil.forceResolveAllContents(resolveSession.resolveToDescriptor(script))
return IDELightClassConstructionContext(resolveSession.bindingContext, resolveSession.moduleDescriptor, LIGHT)
}
private fun isDummyResolveApplicable(classOrObject: KtClassOrObject): Boolean {
if (classOrObject.hasLightClassMatchingErrors) return false
@@ -193,15 +228,6 @@ object IDELightClassContexts {
return result
}
fun lightContextForFacade(files: List<KtFile>): LightClassConstructionContext {
val representativeFile = files.first()
val resolveSession = setupAdHocResolve(representativeFile.project, representativeFile.getResolutionFacade().moduleDescriptor, files)
forceResolvePackageDeclarations(files, resolveSession)
return IDELightClassConstructionContext(resolveSession.bindingContext, resolveSession.moduleDescriptor, LIGHT)
}
fun forceResolvePackageDeclarations(files: Collection<KtFile>, session: ResolveSession) {
for (file in files) {
if (file.isScript()) continue
@@ -74,6 +74,10 @@ sealed class LazyLightClassDataHolder(
builder: LightClassBuilder, exactContextProvider: ExactLightClassContextProvider, dummyContextProvider: DummyLightClassContextProvider
) : LazyLightClassDataHolder(builder, exactContextProvider, dummyContextProvider), LightClassDataHolder.ForFacade
class ForScript(
builder: LightClassBuilder, exactContextProvider: ExactLightClassContextProvider, dummyContextProvider: DummyLightClassContextProvider
) : LazyLightClassDataHolder(builder, exactContextProvider, dummyContextProvider), LightClassDataHolder.ForScript
private inner class LazyLightClassData(
findDelegate: (LightClassBuilderResult) -> PsiClass
) : LightClassData {
@@ -32,6 +32,7 @@ import org.jetbrains.kotlin.asJava.classes.KtLightClass
import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade
import org.jetbrains.kotlin.asJava.classes.KtLightClassForSourceDeclaration
import org.jetbrains.kotlin.asJava.elements.*
import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.idea.KotlinDaemonAnalyzerTestCase
import org.jetbrains.kotlin.idea.caches.resolve.LightClassLazinessChecker.Tracker.Level.*
import org.jetbrains.kotlin.idea.caches.resolve.lightClasses.IDELightClassConstructionContext
@@ -53,9 +54,13 @@ import kotlin.test.assertTrue
abstract class AbstractIdeLightClassTest : KotlinLightCodeInsightFixtureTestCase() {
fun doTest(testDataPath: String) {
val extraFilePath = testDataPath.replace(".kt", ".extra.kt")
val testFiles = if (File(extraFilePath).isFile) listOf(testDataPath, extraFilePath) else listOf(testDataPath)
val extraFilePath = when {
testDataPath.endsWith(".kt") -> testDataPath.replace(".kt", ".extra.kt")
testDataPath.endsWith(".kts") -> testDataPath.replace(".kts", ".extra.kts")
else -> error("Invalid test data extension")
}
val testFiles = if (File(extraFilePath).isFile) listOf(testDataPath, extraFilePath) else listOf(testDataPath)
val lazinessMode = lazinessModeByFileText(testDataPath)
myFixture.configureByFiles(*testFiles.toTypedArray())
@@ -91,11 +96,13 @@ abstract class AbstractIdeCompiledLightClassTest : KotlinDaemonAnalyzerTestCase(
val testName = getTestName(false)
if (KotlinTestUtils.isAllFilesPresentTest(testName)) return
val filePath = "${KotlinTestUtils.getTestsRoot(this::class.java)}/${getTestName(false)}.kt"
val filePathWithoutExtension = "${KotlinTestUtils.getTestsRoot(this::class.java)}/${getTestName(false)}"
val testFile = File("$filePathWithoutExtension.kt").takeIf { it.exists() } ?:
File("$filePathWithoutExtension.kts").takeIf { it.exists() }
Assert.assertTrue("File doesn't exist $filePath", File(filePath).exists())
Assert.assertNotNull("Test file not found!", testFile)
val libraryJar = MockLibraryUtil.compileJvmLibraryToJar(filePath, libName())
val libraryJar = MockLibraryUtil.compileJvmLibraryToJar(testFile!!.canonicalPath, libName())
val jarUrl = "jar://" + FileUtilRt.toSystemIndependentName(libraryJar.absolutePath) + "!/"
ModuleRootModificationUtil.addModuleLibrary(module, jarUrl)
}
@@ -128,6 +135,7 @@ private fun testLightClass(expected: File, testData: File, normalize: (String) -
.replace("java.lang.String s1", "java.lang.String p1")
.replace("java.lang.String s2", "java.lang.String p2")
.replace("java.lang.Object o)", "java.lang.Object p)")
.replace("java.lang.String[] strings", "java.lang.String[] p")
.removeLinesStartingWith("@" + JvmAnnotationNames.METADATA_FQ_NAME.asString())
.run(normalize)
}
@@ -135,6 +143,10 @@ private fun testLightClass(expected: File, testData: File, normalize: (String) -
}
private fun findClass(fqName: String, ktFile: KtFile?, project: Project): PsiClass? {
ktFile?.script?.let {
return it.toLightClass()
}
return JavaPsiFacade.getInstance(project).findClass(fqName, GlobalSearchScope.allScope(project)) ?:
PsiTreeUtil.findChildrenOfType(ktFile, KtClassOrObject::class.java)
.find { fqName.endsWith(it.nameAsName!!.asString()) }
@@ -33,7 +33,7 @@ import java.util.regex.Pattern;
@RunWith(JUnit3RunnerWithInners.class)
public class IdeCompiledLightClassTestGenerated extends AbstractIdeCompiledLightClassTest {
public void testAllFilesPresentInLightClasses() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/asJava/lightClasses"), Pattern.compile("^([^.]+)\\.kt$"), TargetBackend.ANY, true, "local", "compilationErrors", "ideRegression");
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/asJava/lightClasses"), Pattern.compile("^([^.]+)\\.(kt|kts)$"), TargetBackend.ANY, true, "local", "compilationErrors", "ideRegression");
}
@TestMetadata("AnnotationClass.kt")
@@ -155,7 +155,7 @@ public class IdeCompiledLightClassTestGenerated extends AbstractIdeCompiledLight
@RunWith(JUnit3RunnerWithInners.class)
public static class Delegation extends AbstractIdeCompiledLightClassTest {
public void testAllFilesPresentInDelegation() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/asJava/lightClasses/delegation"), Pattern.compile("^([^.]+)\\.kt$"), TargetBackend.ANY, true);
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/asJava/lightClasses/delegation"), Pattern.compile("^([^.]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("Function.kt")
@@ -176,7 +176,7 @@ public class IdeCompiledLightClassTestGenerated extends AbstractIdeCompiledLight
@RunWith(JUnit3RunnerWithInners.class)
public static class Facades extends AbstractIdeCompiledLightClassTest {
public void testAllFilesPresentInFacades() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/asJava/lightClasses/facades"), Pattern.compile("^([^.]+)\\.kt$"), TargetBackend.ANY, true);
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/asJava/lightClasses/facades"), Pattern.compile("^([^.]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("MultiFile.kt")
@@ -203,7 +203,7 @@ public class IdeCompiledLightClassTestGenerated extends AbstractIdeCompiledLight
@RunWith(JUnit3RunnerWithInners.class)
public static class NullabilityAnnotations extends AbstractIdeCompiledLightClassTest {
public void testAllFilesPresentInNullabilityAnnotations() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/asJava/lightClasses/nullabilityAnnotations"), Pattern.compile("^([^.]+)\\.kt$"), TargetBackend.ANY, true);
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/asJava/lightClasses/nullabilityAnnotations"), Pattern.compile("^([^.]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("Class.kt")
@@ -320,7 +320,7 @@ public class IdeCompiledLightClassTestGenerated extends AbstractIdeCompiledLight
@RunWith(JUnit3RunnerWithInners.class)
public static class Object extends AbstractIdeCompiledLightClassTest {
public void testAllFilesPresentInObject() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/asJava/lightClasses/object"), Pattern.compile("^([^.]+)\\.kt$"), TargetBackend.ANY, true);
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/asJava/lightClasses/object"), Pattern.compile("^([^.]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("SimpleObject.kt")
@@ -335,7 +335,7 @@ public class IdeCompiledLightClassTestGenerated extends AbstractIdeCompiledLight
@RunWith(JUnit3RunnerWithInners.class)
public static class PublicField extends AbstractIdeCompiledLightClassTest {
public void testAllFilesPresentInPublicField() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/asJava/lightClasses/publicField"), Pattern.compile("^([^.]+)\\.kt$"), TargetBackend.ANY, true);
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/asJava/lightClasses/publicField"), Pattern.compile("^([^.]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("CompanionObject.kt")
@@ -350,4 +350,25 @@ public class IdeCompiledLightClassTestGenerated extends AbstractIdeCompiledLight
doTest(fileName);
}
}
@TestMetadata("compiler/testData/asJava/lightClasses/script")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Script extends AbstractIdeCompiledLightClassTest {
public void testAllFilesPresentInScript() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/asJava/lightClasses/script"), Pattern.compile("^([^.]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("HelloWorld.kts")
public void testHelloWorld() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/asJava/lightClasses/script/HelloWorld.kts");
doTest(fileName);
}
@TestMetadata("InnerClasses.kts")
public void testInnerClasses() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/asJava/lightClasses/script/InnerClasses.kts");
doTest(fileName);
}
}
}
@@ -33,7 +33,7 @@ import java.util.regex.Pattern;
@RunWith(JUnit3RunnerWithInners.class)
public class IdeLightClassTestGenerated extends AbstractIdeLightClassTest {
public void testAllFilesPresentInLightClasses() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/asJava/lightClasses"), Pattern.compile("^([^.]+)\\.kt$"), TargetBackend.ANY, true, "delegation");
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/asJava/lightClasses"), Pattern.compile("^([^.]+)\\.(kt|kts)$"), TargetBackend.ANY, true, "delegation");
}
@TestMetadata("AnnotationClass.kt")
@@ -155,7 +155,7 @@ public class IdeLightClassTestGenerated extends AbstractIdeLightClassTest {
@RunWith(JUnit3RunnerWithInners.class)
public static class CompilationErrors extends AbstractIdeLightClassTest {
public void testAllFilesPresentInCompilationErrors() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/asJava/lightClasses/compilationErrors"), Pattern.compile("^([^.]+)\\.kt$"), TargetBackend.ANY, true);
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/asJava/lightClasses/compilationErrors"), Pattern.compile("^([^.]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("AnnotationModifiers.kt")
@@ -206,7 +206,7 @@ public class IdeLightClassTestGenerated extends AbstractIdeLightClassTest {
@RunWith(JUnit3RunnerWithInners.class)
public static class Facades extends AbstractIdeLightClassTest {
public void testAllFilesPresentInFacades() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/asJava/lightClasses/facades"), Pattern.compile("^([^.]+)\\.kt$"), TargetBackend.ANY, true);
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/asJava/lightClasses/facades"), Pattern.compile("^([^.]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("MultiFile.kt")
@@ -233,7 +233,7 @@ public class IdeLightClassTestGenerated extends AbstractIdeLightClassTest {
@RunWith(JUnit3RunnerWithInners.class)
public static class IdeRegression extends AbstractIdeLightClassTest {
public void testAllFilesPresentInIdeRegression() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/asJava/lightClasses/ideRegression"), Pattern.compile("^([^.]+)\\.kt$"), TargetBackend.ANY, true);
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/asJava/lightClasses/ideRegression"), Pattern.compile("^([^.]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("AllOpenAnnotatedClasses.kt")
@@ -290,7 +290,7 @@ public class IdeLightClassTestGenerated extends AbstractIdeLightClassTest {
@RunWith(JUnit3RunnerWithInners.class)
public static class Local extends AbstractIdeLightClassTest {
public void testAllFilesPresentInLocal() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/asJava/lightClasses/local"), Pattern.compile("^([^.]+)\\.kt$"), TargetBackend.ANY, true);
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/asJava/lightClasses/local"), Pattern.compile("^([^.]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("DollarsInNameLocal.kt")
@@ -305,7 +305,7 @@ public class IdeLightClassTestGenerated extends AbstractIdeLightClassTest {
@RunWith(JUnit3RunnerWithInners.class)
public static class NullabilityAnnotations extends AbstractIdeLightClassTest {
public void testAllFilesPresentInNullabilityAnnotations() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/asJava/lightClasses/nullabilityAnnotations"), Pattern.compile("^([^.]+)\\.kt$"), TargetBackend.ANY, true);
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/asJava/lightClasses/nullabilityAnnotations"), Pattern.compile("^([^.]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("Class.kt")
@@ -422,7 +422,7 @@ public class IdeLightClassTestGenerated extends AbstractIdeLightClassTest {
@RunWith(JUnit3RunnerWithInners.class)
public static class Object extends AbstractIdeLightClassTest {
public void testAllFilesPresentInObject() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/asJava/lightClasses/object"), Pattern.compile("^([^.]+)\\.kt$"), TargetBackend.ANY, true);
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/asJava/lightClasses/object"), Pattern.compile("^([^.]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("SimpleObject.kt")
@@ -437,7 +437,7 @@ public class IdeLightClassTestGenerated extends AbstractIdeLightClassTest {
@RunWith(JUnit3RunnerWithInners.class)
public static class PublicField extends AbstractIdeLightClassTest {
public void testAllFilesPresentInPublicField() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/asJava/lightClasses/publicField"), Pattern.compile("^([^.]+)\\.kt$"), TargetBackend.ANY, true);
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/asJava/lightClasses/publicField"), Pattern.compile("^([^.]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("CompanionObject.kt")
@@ -452,4 +452,25 @@ public class IdeLightClassTestGenerated extends AbstractIdeLightClassTest {
doTest(fileName);
}
}
@TestMetadata("compiler/testData/asJava/lightClasses/script")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Script extends AbstractIdeLightClassTest {
public void testAllFilesPresentInScript() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/asJava/lightClasses/script"), Pattern.compile("^([^.]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("HelloWorld.kts")
public void testHelloWorld() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/asJava/lightClasses/script/HelloWorld.kts");
doTest(fileName);
}
@TestMetadata("InnerClasses.kts")
public void testInnerClasses() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/asJava/lightClasses/script/InnerClasses.kts");
doTest(fileName);
}
}
}