181: using InjectedFileViewProvider for light-files in language-injections
#KT-22929
This commit is contained in:
committed by
Nikolay Krasko
parent
5951fbb390
commit
55753d79e8
@@ -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" {}
|
||||
}
|
||||
|
||||
+129
@@ -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<KtFile>,
|
||||
generateClassFilter: GenerationState.GenerateClassFilter,
|
||||
context: LightClassConstructionContext,
|
||||
generate: (state: GenerationState, files: Collection<KtFile>) -> 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<KtFile>): 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) "<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)
|
||||
+94
@@ -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<InjectedFileViewProvider>()?.let { return it }
|
||||
return ClassFileViewProvider(
|
||||
ktFile.manager, ktFile.virtualFile ?: ktFile.originalFile.virtualFile ?: ktFile.viewProvider.virtualFile
|
||||
)
|
||||
}
|
||||
@@ -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<caret> 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)
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user