diff --git a/compiler/light-classes/build.gradle.kts.181 b/compiler/light-classes/build.gradle.kts.181 new file mode 100644 index 00000000000..b083039080a --- /dev/null +++ b/compiler/light-classes/build.gradle.kts.181 @@ -0,0 +1,22 @@ + +plugins { + kotlin("jvm") + id("jps-compatible") +} + +jvmTarget = "1.6" + +dependencies { + compile(project(":compiler:util")) + compile(project(":compiler:backend")) + compile(project(":compiler:frontend")) + compile(project(":compiler:frontend.java")) + compileOnly(intellijCoreDep()) { includeJars("intellij-core") } + compileOnly(intellijDep()) { includeJars("annotations", "platform-impl", "asm-all", "trove4j", "guava", rootProject = rootProject) } +} + +sourceSets { + "main" { projectDefault() } + "test" {} +} + diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/builder/LightClassBuilder.kt.181 b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/builder/LightClassBuilder.kt.181 new file mode 100644 index 00000000000..0346594da4c --- /dev/null +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/builder/LightClassBuilder.kt.181 @@ -0,0 +1,129 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * 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 org.jetbrains.kotlin.asJava.builder + +import com.intellij.openapi.components.ServiceManager +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.progress.ProcessCanceledException +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.SystemInfo +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiElement +import com.intellij.psi.impl.compiled.ClsFileImpl +import com.intellij.psi.impl.java.stubs.PsiJavaFileStub +import com.intellij.psi.impl.java.stubs.impl.PsiJavaFileStubImpl +import org.jetbrains.kotlin.asJava.elements.getFileViewProviderForLightClass +import com.intellij.psi.impl.source.tree.TreeElement +import org.jetbrains.kotlin.codegen.state.GenerationState +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics +import java.lang.StringBuilder + +data class LightClassBuilderResult(val stub: PsiJavaFileStub, val bindingContext: BindingContext, val diagnostics: Diagnostics) + +fun buildLightClass( + packageFqName: FqName, + files: Collection, + generateClassFilter: GenerationState.GenerateClassFilter, + context: LightClassConstructionContext, + generate: (state: GenerationState, files: Collection) -> Unit +): LightClassBuilderResult { + val project = files.first().project + + try { + val classBuilderFactory = KotlinLightClassBuilderFactory(createJavaFileStub(project, packageFqName, files)) + val state = GenerationState.Builder( + project, + classBuilderFactory, + context.module, + context.bindingContext, + files.toList(), + CompilerConfiguration.EMPTY + ).generateDeclaredClassFilter(generateClassFilter).wantsDiagnostics(false).build() + state.beforeCompile() + + generate(state, files) + + val javaFileStub = classBuilderFactory.result() + + ServiceManager.getService(project, StubComputationTracker::class.java)?.onStubComputed(javaFileStub, context) + return LightClassBuilderResult(javaFileStub, context.bindingContext, state.collectedExtraJvmDiagnostics) + } + catch (e: ProcessCanceledException) { + throw e + } + catch (e: RuntimeException) { + logErrorWithOSInfo(e, packageFqName, null) + throw e + } +} + +private fun createJavaFileStub(project: Project, packageFqName: FqName, files: Collection): PsiJavaFileStub { + val javaFileStub = PsiJavaFileStubImpl(packageFqName.asString(), /*compiled = */true) + javaFileStub.psiFactory = ClsWrapperStubPsiFactory.INSTANCE + + val fakeFile = object : ClsFileImpl(getFileViewProviderForLightClass(files.first())) { + override fun getStub() = javaFileStub + + override fun getPackageName() = packageFqName.asString() + + override fun isPhysical() = false + + override fun appendMirrorText(indentLevel: Int, buffer: StringBuilder) { + if (files.size == 1) { + LOG.error("Mirror text should never be calculated for light classes generated from a single file") + } + super.appendMirrorText(indentLevel, buffer) + } + + + override fun setMirror(element: TreeElement) { + if (files.size == 1) { + LOG.error("Mirror element should never be set for light classes generated from a single file") + } + super.setMirror(element) + } + + override fun getMirror(): PsiElement { + if (files.size == 1) { + LOG.error("Mirror element should never be calculated for light classes generated from a single file") + } + return super.getMirror() + } + + override fun getText(): String { + return files.singleOrNull()?.text ?: super.getText() + } + } + + javaFileStub.psi = fakeFile + return javaFileStub +} + +private fun logErrorWithOSInfo(cause: Throwable?, fqName: FqName, virtualFile: VirtualFile?) { + val path = if (virtualFile == null) "" else virtualFile.path + LOG.error( + "Could not generate LightClass for $fqName declared in $path\n" + + "System: ${SystemInfo.OS_NAME} ${SystemInfo.OS_VERSION} Java Runtime: ${SystemInfo.JAVA_RUNTIME_VERSION}", + cause + ) +} + +private val LOG = Logger.getInstance(LightClassBuilderResult::class.java) \ No newline at end of file diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/FakeFileForLightClass.kt.181 b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/FakeFileForLightClass.kt.181 new file mode 100644 index 00000000000..0357157e0b7 --- /dev/null +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/FakeFileForLightClass.kt.181 @@ -0,0 +1,94 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * 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 org.jetbrains.kotlin.asJava.elements + +import com.intellij.pom.java.LanguageLevel +import com.intellij.psi.ClassFileViewProvider +import com.intellij.psi.FileViewProvider +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiElementVisitor +import com.intellij.psi.impl.compiled.ClsFileImpl +import com.intellij.psi.impl.source.tree.injected.InjectedFileViewProvider +import com.intellij.psi.stubs.PsiClassHolderFileStub +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.name.FqName +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.utils.addToStdlib.safeAs + +open class FakeFileForLightClass( + val ktFile: KtFile, + private val lightClass: () -> KtLightClass, + private val stub: () -> PsiClassHolderFileStub<*>, + private val packageFqName: FqName = ktFile.packageFqName +) : ClsFileImpl(getFileViewProviderForLightClass(ktFile)) { + override fun getPackageName() = packageFqName.asString() + + override fun getStub() = stub() + + override fun getClasses() = arrayOf(lightClass()) + + override fun getNavigationElement() = ktFile + + override fun accept(visitor: PsiElementVisitor) { + // Prevent access to compiled PSI + // TODO: More complex traversal logic may be implemented when necessary + } + + // this should be equal to current compiler target language level + override fun getLanguageLevel() = LanguageLevel.JDK_1_6 + + override fun hashCode(): Int { + val thisClass = lightClass() + if (thisClass is KtLightClassForSourceDeclaration) return ktFile.hashCode() + return thisClass.hashCode() + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is FakeFileForLightClass) return false + val thisClass = lightClass() + val anotherClass = other.lightClass() + + if (thisClass is KtLightClassForSourceDeclaration) { + return anotherClass is KtLightClassForSourceDeclaration && ktFile == other.ktFile + } + + return thisClass == anotherClass + } + + override fun isEquivalentTo(another: PsiElement?) = this == another + + override fun setPackageName(packageName: String) { + if (lightClass() is KtLightClassForFacade) { + ktFile.packageDirective?.fqName = FqName(packageName) + } + else { + super.setPackageName(packageName) + } + } + + override fun isPhysical() = false +} + +fun getFileViewProviderForLightClass(ktFile: KtFile): FileViewProvider { + ktFile.viewProvider.safeAs()?.let { return it } + return ClassFileViewProvider( + ktFile.manager, ktFile.virtualFile ?: ktFile.originalFile.virtualFile ?: ktFile.viewProvider.virtualFile + ) +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/asJava/KtFileLightClassTest.kt.181 b/idea/tests/org/jetbrains/kotlin/asJava/KtFileLightClassTest.kt.181 new file mode 100644 index 00000000000..7fb17f689c1 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/asJava/KtFileLightClassTest.kt.181 @@ -0,0 +1,95 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * 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 org.jetbrains.kotlin.asJava + +import com.intellij.injected.editor.EditorWindow +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.testFramework.LightProjectDescriptor +import org.intellij.lang.annotations.Language +import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase +import org.jetbrains.kotlin.idea.test.KotlinLightProjectDescriptor +import org.jetbrains.kotlin.idea.test.PluginTestCaseBase +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtFile + +class KtFileLightClassTest : KotlinLightCodeInsightFixtureTestCase() { + override fun getProjectDescriptor(): LightProjectDescriptor = KotlinLightProjectDescriptor.INSTANCE + + fun testSimple() { + val file = myFixture.configureByText("A.kt", "class C {}\nobject O {}") as KtFile + val classes = file.classes + assertEquals(2, classes.size) + assertEquals("C", classes[0].qualifiedName) + assertEquals("O", classes[1].qualifiedName) + } + + fun testFileClass() { + val file = myFixture.configureByText("A.kt", "fun f() {}") as KtFile + val classes = file.classes + assertEquals(1, classes.size) + assertEquals("AKt", classes[0].qualifiedName) + } + + fun testMultifileClass() { + val file = myFixture.configureByFiles("multifile1.kt", "multifile2.kt")[0] as KtFile + val aClass = file.classes.single() + assertEquals(1, aClass.findMethodsByName("foo", false).size) + assertEquals(1, aClass.findMethodsByName("bar", false).size) + } + + fun testAliasesOnly() { + val file = myFixture.configureByFile("aliasesOnly.kt") as KtFile + val aClass = file.classes.single() + assertEquals(0, aClass.getMethods().size) + } + + fun testNoFacadeForScript() { + val file = myFixture.configureByText("foo.kts", "package foo") as KtFile + assertEquals(0, file.classes.size) + val facadeFiles = KotlinAsJavaSupport.getInstance(project).findFilesForFacade(FqName("foo.FooKt"), GlobalSearchScope.allScope(project)) + assertEquals(0, facadeFiles.size) + } + + fun testNoFacadeForHeaderClass() { + val file = myFixture.configureByText("foo.kt", "header fun foo(): Int") as KtFile + assertEquals(0, file.classes.size) + val facadeFiles = KotlinAsJavaSupport.getInstance(project).findFilesForFacade(FqName("foo.FooKt"), GlobalSearchScope.allScope(project)) + assertEquals(0, facadeFiles.size) + } + + override fun getTestDataPath(): String { + return PluginTestCaseBase.getTestDataPathBase() + "/asJava/fileLightClass/" + } + + fun testInjectedCode() { + myFixture.configureByText("foo.kt", """ + import org.intellij.lang.annotations.Language + + fun foo(@Language("kotlin") a: String){a.toString()} + + fun bar(){ foo("class A") } + """) + + + myFixture.testHighlighting("foo.kt") + + val injectedFile = (editor as? EditorWindow)?.injectedFile + assertEquals("Wrong injection language", "kotlin", injectedFile?.language?.id) + assertEquals("Injected class should be `A`", "A", (injectedFile as KtFile).classes.single().name) + } + +}