202: Fix compatibility with 202 IDEA branch (code)

This commit is contained in:
Yan Zhulanow
2020-04-09 18:34:53 +09:00
parent f489ac1d03
commit 46ac241e2f
18 changed files with 6456 additions and 0 deletions
@@ -0,0 +1,182 @@
/*
* 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.codegen
import com.intellij.util.ArrayUtil
import org.jetbrains.kotlin.backend.common.bridges.findImplementationFromInterface
import org.jetbrains.kotlin.backend.common.bridges.firstSuperMethodFromKotlin
import org.jetbrains.kotlin.codegen.context.ClassContext
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.load.java.descriptors.JavaMethodDescriptor
import org.jetbrains.kotlin.psi.KtPureClassOrObject
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOriginKind
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.org.objectweb.asm.MethodVisitor
import org.jetbrains.org.objectweb.asm.Opcodes.*
class InterfaceImplBodyCodegen(
aClass: KtPureClassOrObject,
context: ClassContext,
v: ClassBuilder,
state: GenerationState,
parentCodegen: MemberCodegen<*>?
) : ClassBodyCodegen(aClass, context, InterfaceImplBodyCodegen.InterfaceImplClassBuilder(v), state, parentCodegen) {
private var isAnythingGenerated: Boolean = false
get() = (v as InterfaceImplClassBuilder).isAnythingGenerated
private val defaultImplType = typeMapper.mapDefaultImpls(descriptor)
override fun generateDeclaration() {
val codegenFlags = ACC_PUBLIC or ACC_FINAL or ACC_SUPER
val flags = if (state.classBuilderMode == ClassBuilderMode.LIGHT_CLASSES) codegenFlags or ACC_STATIC else codegenFlags
v.defineClass(
myClass.psiOrParent, state.classFileVersion, flags,
defaultImplType.internalName,
null, "java/lang/Object", ArrayUtil.EMPTY_STRING_ARRAY
)
v.visitSource(myClass.containingKtFile.name, null)
}
override fun classForInnerClassRecord(): ClassDescriptor? {
if (!isAnythingGenerated) return null
return InnerClassConsumer.classForInnerClassRecord(descriptor, true)
}
override fun generateSyntheticPartsAfterBody() {
for (memberDescriptor in descriptor.defaultType.memberScope.getContributedDescriptors()) {
if (memberDescriptor !is CallableMemberDescriptor) continue
if (memberDescriptor.kind.isReal) continue
if (memberDescriptor.visibility == Visibilities.INVISIBLE_FAKE) continue
if (memberDescriptor.modality == Modality.ABSTRACT) continue
val implementation = findImplementationFromInterface(memberDescriptor) ?: continue
// If implementation is a default interface method (JVM 8 only)
if (implementation.isDefinitelyNotDefaultImplsMethod()) continue
if (memberDescriptor is FunctionDescriptor) {
generateDelegationToSuperDefaultImpls(memberDescriptor, implementation as FunctionDescriptor)
}
else if (memberDescriptor is PropertyDescriptor) {
implementation as PropertyDescriptor
val getter = memberDescriptor.getter
val implGetter = implementation.getter
if (getter != null && implGetter != null) {
generateDelegationToSuperDefaultImpls(getter, implGetter)
}
val setter = memberDescriptor.setter
val implSetter = implementation.setter
if (setter != null && implSetter != null) {
generateDelegationToSuperDefaultImpls(setter, implSetter)
}
}
}
generateSyntheticAccessors()
}
private fun generateDelegationToSuperDefaultImpls(descriptor: FunctionDescriptor, implementation: FunctionDescriptor) {
val delegateTo = firstSuperMethodFromKotlin(descriptor, implementation) as FunctionDescriptor? ?: return
// We can't call super methods from Java 1.8 interfaces because that requires INVOKESPECIAL which is forbidden from TImpl class
if (delegateTo is JavaMethodDescriptor) return
functionCodegen.generateMethod(
JvmDeclarationOrigin(
JvmDeclarationOriginKind.DEFAULT_IMPL_DELEGATION_TO_SUPERINTERFACE_DEFAULT_IMPL,
DescriptorToSourceUtils.descriptorToDeclaration(descriptor), descriptor
),
descriptor,
object : FunctionGenerationStrategy.CodegenBased(state) {
override fun doGenerateBody(codegen: ExpressionCodegen, signature: JvmMethodSignature) {
val iv = codegen.v
val method = typeMapper.mapToCallableMethod(delegateTo, true)
val myParameters = signature.valueParameters
val calleeParameters = method.getValueParameters()
if (myParameters.size != calleeParameters.size) {
throw AssertionError(
"Method from super interface has a different signature.\n" +
"This method:\n%s\n%s\n%s\nSuper method:\n%s\n%s\n%s".format(
descriptor, signature, myParameters, delegateTo, method, calleeParameters
)
)
}
var k = 0
val it = calleeParameters.iterator()
for (parameter in myParameters) {
val type = parameter.asmType
StackValue.local(k, type).put(it.next().asmType, iv)
k += type.size
}
method.genInvokeInstruction(iv)
StackValue.coerce(method.returnType, signature.returnType, iv)
iv.areturn(signature.returnType)
}
})
}
override fun generateKotlinMetadataAnnotation() {
(v as InterfaceImplClassBuilder).stopCounting()
writeSyntheticClassMetadata(v, state)
}
override fun done() {
super.done()
if (!isAnythingGenerated) {
state.factory.removeClasses(setOf(defaultImplType.internalName))
}
}
private class InterfaceImplClassBuilder(private val v: ClassBuilder) : DelegatingClassBuilder() {
private var shouldCount: Boolean = true
var isAnythingGenerated: Boolean = false
private set
fun stopCounting() {
shouldCount = false
}
override fun getDelegate() = v
override fun newMethod(
origin: JvmDeclarationOrigin,
access: Int,
name: String,
desc: String,
signature: String?,
exceptions: Array<out String?>?
): MethodVisitor {
if (shouldCount) {
isAnythingGenerated = true
}
return super.newMethod(origin, access, name, desc, signature, exceptions)
}
}
override fun generateSyntheticPartsBeforeBody() {
generatePropertyMetadataArrayFieldIfNeeded(defaultImplType)
}
}
@@ -0,0 +1,68 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.codegen
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
import org.jetbrains.org.objectweb.asm.*
import org.jetbrains.org.objectweb.asm.tree.*
class OriginCollectingClassBuilderFactory(private val builderMode: ClassBuilderMode) : ClassBuilderFactory {
val compiledClasses = mutableListOf<ClassNode>()
val origins = mutableMapOf<Any, JvmDeclarationOrigin>()
override fun getClassBuilderMode(): ClassBuilderMode = builderMode
override fun newClassBuilder(origin: JvmDeclarationOrigin): AbstractClassBuilder.Concrete {
val classNode = ClassNode()
compiledClasses += classNode
origins[classNode] = origin
return OriginCollectingClassBuilder(classNode)
}
private inner class OriginCollectingClassBuilder(val classNode: ClassNode) : AbstractClassBuilder.Concrete(classNode) {
override fun newField(
origin: JvmDeclarationOrigin,
access: Int,
name: String,
desc: String,
signature: String?,
value: Any?
): FieldVisitor {
val fieldNode = super.newField(origin, access, name, desc, signature, value) as FieldNode
origins[fieldNode] = origin
return fieldNode
}
override fun newMethod(
origin: JvmDeclarationOrigin,
access: Int,
name: String,
desc: String,
signature: String?,
exceptions: Array<out String?>?
): MethodVisitor {
val methodNode = super.newMethod(origin, access, name, desc, signature, exceptions) as MethodNode
origins[methodNode] = origin
// ASM doesn't read information about local variables for the `abstract` methods so we need to get it manually
if ((access and Opcodes.ACC_ABSTRACT) != 0 && methodNode.localVariables == null) {
methodNode.localVariables = mutableListOf<LocalVariableNode>()
}
return methodNode
}
}
override fun asBytes(builder: ClassBuilder): ByteArray {
val classWriter = ClassWriter(0)
(builder as OriginCollectingClassBuilder).classNode.accept(classWriter)
return classWriter.toByteArray()
}
override fun asText(builder: ClassBuilder) = throw UnsupportedOperationException()
override fun close() {}
}
@@ -0,0 +1,89 @@
/*
* 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.codegen
import com.intellij.psi.PsiElement
import com.intellij.util.containers.LinkedMultiMap
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.resolve.jvm.diagnostics.ConflictingJvmDeclarationsData
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
import org.jetbrains.kotlin.resolve.jvm.diagnostics.MemberKind
import org.jetbrains.kotlin.resolve.jvm.diagnostics.RawSignature
import org.jetbrains.org.objectweb.asm.FieldVisitor
import org.jetbrains.org.objectweb.asm.MethodVisitor
abstract class SignatureCollectingClassBuilderFactory(
delegate: ClassBuilderFactory, val shouldGenerate: (JvmDeclarationOrigin) -> Boolean
) : DelegatingClassBuilderFactory(delegate) {
protected abstract fun handleClashingSignatures(data: ConflictingJvmDeclarationsData)
protected abstract fun onClassDone(classOrigin: JvmDeclarationOrigin,
classInternalName: String,
signatures: MultiMap<RawSignature, JvmDeclarationOrigin>)
override fun newClassBuilder(origin: JvmDeclarationOrigin): DelegatingClassBuilder {
return SignatureCollectingClassBuilder(origin, delegate.newClassBuilder(origin))
}
private inner class SignatureCollectingClassBuilder(
private val classCreatedFor: JvmDeclarationOrigin,
internal val _delegate: ClassBuilder
) : DelegatingClassBuilder() {
override fun getDelegate() = _delegate
private lateinit var classInternalName: String
private val signatures = LinkedMultiMap<RawSignature, JvmDeclarationOrigin>()
override fun defineClass(origin: PsiElement?, version: Int, access: Int, name: String, signature: String?, superName: String, interfaces: Array<out String>) {
classInternalName = name
super.defineClass(origin, version, access, name, signature, superName, interfaces)
}
override fun newField(origin: JvmDeclarationOrigin, access: Int, name: String, desc: String, signature: String?, value: Any?): FieldVisitor {
signatures.putValue(RawSignature(name, desc, MemberKind.FIELD), origin)
if (!shouldGenerate(origin)) {
return AbstractClassBuilder.EMPTY_FIELD_VISITOR
}
return super.newField(origin, access, name, desc, signature, value)
}
override fun newMethod(origin: JvmDeclarationOrigin, access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String?>?): MethodVisitor {
signatures.putValue(RawSignature(name, desc, MemberKind.METHOD), origin)
if (!shouldGenerate(origin)) {
return AbstractClassBuilder.EMPTY_METHOD_VISITOR
}
return super.newMethod(origin, access, name, desc, signature, exceptions)
}
override fun done() {
for ((signature, elementsAndDescriptors) in signatures.entrySet()) {
if (elementsAndDescriptors.size == 1) continue // no clash
handleClashingSignatures(ConflictingJvmDeclarationsData(
classInternalName,
classCreatedFor,
signature,
elementsAndDescriptors
))
}
onClassDone(classCreatedFor, classInternalName, signatures)
super.done()
}
}
}
@@ -0,0 +1,101 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.codegen.optimization.common
import org.jetbrains.kotlin.codegen.coroutines.SUSPEND_FUNCTION_COMPLETION_PARAMETER_NAME
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
import org.jetbrains.org.objectweb.asm.tree.IincInsnNode
import org.jetbrains.org.objectweb.asm.tree.MethodNode
import org.jetbrains.org.objectweb.asm.tree.VarInsnNode
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue
import org.jetbrains.org.objectweb.asm.tree.analysis.Frame
import java.util.*
class VariableLivenessFrame(val maxLocals: Int) : VarFrame<VariableLivenessFrame> {
private val bitSet = BitSet(maxLocals)
override fun mergeFrom(other: VariableLivenessFrame) {
bitSet.or(other.bitSet)
}
fun markAlive(varIndex: Int) {
bitSet.set(varIndex, true)
}
fun markAllAlive(bitSet: BitSet) {
this.bitSet.or(bitSet)
}
fun markDead(varIndex: Int) {
bitSet.set(varIndex, false)
}
fun isAlive(varIndex: Int): Boolean = bitSet.get(varIndex)
override fun equals(other: Any?): Boolean {
if (other !is VariableLivenessFrame) return false
return bitSet == other.bitSet
}
override fun hashCode() = bitSet.hashCode()
}
fun analyzeLiveness(node: MethodNode): List<VariableLivenessFrame> {
val typeAnnotatedFrames = MethodTransformer.analyze("fake", node, OptimizationBasicInterpreter())
val visibleByDebuggerVariables = analyzeVisibleByDebuggerVariables(node, typeAnnotatedFrames)
return analyze(node, object : BackwardAnalysisInterpreter<VariableLivenessFrame> {
override fun newFrame(maxLocals: Int) = VariableLivenessFrame(maxLocals)
override fun def(frame: VariableLivenessFrame, insn: AbstractInsnNode) = defVar(frame, insn)
override fun use(frame: VariableLivenessFrame, insn: AbstractInsnNode) =
useVar(frame, insn, node, visibleByDebuggerVariables[node.instructions.indexOf(insn)])
})
}
private fun analyzeVisibleByDebuggerVariables(
node: MethodNode,
typeAnnotatedFrames: Array<Frame<BasicValue>>
): Array<BitSet> {
val res = Array(node.instructions.size()) { BitSet(node.maxLocals) }
for (local in node.localVariables) {
if (local.name.isInvisibleDebuggerVariable()) continue
for (index in node.instructions.indexOf(local.start) until node.instructions.indexOf(local.end)) {
if (Type.getType(local.desc).sort == typeAnnotatedFrames[index]?.getLocal(local.index)?.type?.sort) {
res[index].set(local.index)
}
}
}
return res
}
private fun defVar(frame: VariableLivenessFrame, insn: AbstractInsnNode) {
if (insn is VarInsnNode && insn.isStoreOperation()) {
frame.markDead(insn.`var`)
}
}
private fun useVar(
frame: VariableLivenessFrame,
insn: AbstractInsnNode,
node: MethodNode,
visibleByDebuggerVariables: BitSet
) {
frame.markAllAlive(visibleByDebuggerVariables)
if (insn is VarInsnNode && insn.isLoadOperation()) {
frame.markAlive(insn.`var`)
} else if (insn is IincInsnNode) {
frame.markAlive(insn.`var`)
}
}
private fun String.isInvisibleDebuggerVariable(): Boolean =
startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT) ||
startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION) ||
this == SUSPEND_FUNCTION_COMPLETION_PARAMETER_NAME
@@ -0,0 +1,151 @@
/*
* 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.codegen.state
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.codegen.ClassBuilder
import org.jetbrains.kotlin.codegen.ClassBuilderFactory
import org.jetbrains.kotlin.codegen.DelegatingClassBuilder
import org.jetbrains.kotlin.codegen.DelegatingClassBuilderFactory
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithVisibility
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.renderer.DescriptorRendererModifier
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
import org.jetbrains.kotlin.resolve.jvm.diagnostics.MemberKind
import org.jetbrains.kotlin.resolve.jvm.diagnostics.RawSignature
import org.jetbrains.org.objectweb.asm.FieldVisitor
import org.jetbrains.org.objectweb.asm.MethodVisitor
import org.jetbrains.kotlin.codegen.coroutines.unwrapInitialDescriptorForSuspendFunction
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import java.io.BufferedWriter
import java.io.File
class SignatureDumpingBuilderFactory(
builderFactory: ClassBuilderFactory,
val destination: File
) : DelegatingClassBuilderFactory(builderFactory) {
companion object {
val MEMBER_RENDERER = DescriptorRenderer.withOptions {
withDefinedIn = false
modifiers -= DescriptorRendererModifier.VISIBILITY
}
val TYPE_RENDERER = DescriptorRenderer.withOptions {
withSourceFileForTopLevel = false
modifiers -= DescriptorRendererModifier.VISIBILITY
}
}
private val outputStream: BufferedWriter by lazy {
// TODO: Replace with LOG.info and make log output go to MessageCollector
println("[INFO] Dumping signatures to $destination")
destination.parentFile?.mkdirs()
destination.bufferedWriter().apply { append("[\n") }
}
private var firstClassWritten: Boolean = false
override fun close() {
outputStream.append("\n]\n")
outputStream.close()
super.close()
}
override fun newClassBuilder(origin: JvmDeclarationOrigin): DelegatingClassBuilder {
return SignatureDumpingClassBuilder(origin, delegate.newClassBuilder(origin))
}
private inner class SignatureDumpingClassBuilder(val origin: JvmDeclarationOrigin, val _delegate: ClassBuilder) : DelegatingClassBuilder() {
override fun getDelegate() = _delegate
private val signatures = mutableListOf<Pair<RawSignature, DeclarationDescriptor?>>()
private lateinit var javaClassName: String
override fun defineClass(origin: PsiElement?, version: Int, access: Int, name: String, signature: String?, superName: String, interfaces: Array<out String>) {
javaClassName = name
super.defineClass(origin, version, access, name, signature, superName, interfaces)
}
override fun newMethod(origin: JvmDeclarationOrigin, access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String?>?): MethodVisitor {
signatures += RawSignature(name, desc, MemberKind.METHOD) to origin.descriptor?.let {
if (it is CallableDescriptor) it.unwrapInitialDescriptorForSuspendFunction() else it
}
return super.newMethod(origin, access, name, desc, signature, exceptions)
}
override fun newField(origin: JvmDeclarationOrigin, access: Int, name: String, desc: String, signature: String?, value: Any?): FieldVisitor {
signatures += RawSignature(name, desc, MemberKind.FIELD) to origin.descriptor
return super.newField(origin, access, name, desc, signature, value)
}
override fun done() {
if (firstClassWritten) outputStream.append(",\n") else firstClassWritten = true
outputStream.append("\t{\n")
origin.descriptor?.let {
outputStream.append("\t\t").appendNameValue("declaration", TYPE_RENDERER.render(it)).append(",\n")
(it as? DeclarationDescriptorWithVisibility)?.visibility?.let {
outputStream.append("\t\t").appendNameValue("visibility", it.internalDisplayName).append(",\n")
}
}
outputStream.append("\t\t").appendNameValue("class", javaClassName).append(",\n")
outputStream.append("\t\t").appendQuoted("members").append(": [\n")
signatures.joinTo(outputStream, ",\n") { buildString {
val (signature, descriptor) = it
append("\t\t\t{")
descriptor?.let {
(it as? DeclarationDescriptorWithVisibility)?.visibility?.let {
appendNameValue("visibility", it.internalDisplayName).append(",\t")
}
appendNameValue("declaration", MEMBER_RENDERER.render(it)).append(", ")
}
appendNameValue("name", signature.name).append(", ")
appendNameValue("desc", signature.desc).append("}")
}}
outputStream.append("\n\t\t]\n\t}")
super.done()
}
}
}
private fun Appendable.appendQuoted(value: String?): Appendable = value?.let { append('"').append(jsonEscape(it)).append('"') } ?: append("null")
private fun Appendable.appendNameValue(name: String, value: String?): Appendable = appendQuoted(name).append(": ").appendQuoted(value)
private fun jsonEscape(value: String): String = buildString {
for (index in 0..value.length - 1) {
val ch = value[index]
when (ch) {
'\b' -> append("\\b")
'\t' -> append("\\t")
'\n' -> append("\\n")
'\r' -> append("\\r")
'\"' -> append("\\\"")
'\\' -> append("\\\\")
else -> if (ch.toInt() < 32) {
append("\\u" + Integer.toHexString(ch.toInt()).padStart(4, '0'))
}
else {
append(ch)
}
}
}
}
@@ -0,0 +1,345 @@
/*
* Copyright 2000-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.test.testFramework;
import com.intellij.core.CoreASTFactory;
import com.intellij.ide.util.AppPropertiesComponentImpl;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.lang.*;
import com.intellij.lang.impl.PsiBuilderFactoryImpl;
import com.intellij.mock.*;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.impl.LoadTextUtil;
import com.intellij.openapi.fileTypes.FileTypeFactory;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.fileTypes.FileTypeRegistry;
import com.intellij.openapi.options.SchemeManagerFactory;
import com.intellij.openapi.progress.EmptyProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.impl.CoreProgressManager;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.pom.PomModel;
import com.intellij.pom.core.impl.PomModelImpl;
import com.intellij.pom.tree.TreeAspect;
import com.intellij.psi.*;
import com.intellij.psi.impl.*;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.testFramework.LightVirtualFile;
import com.intellij.testFramework.MockSchemeManagerFactory;
import com.intellij.testFramework.TestDataFile;
import com.intellij.util.CachedValuesManagerImpl;
import com.intellij.util.Function;
import junit.framework.TestCase;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.idea.KotlinFileType;
import org.picocontainer.ComponentAdapter;
import org.picocontainer.MutablePicoContainer;
import java.io.File;
import java.io.IOException;
import java.util.Set;
@SuppressWarnings("ALL")
public abstract class KtParsingTestCase extends KtPlatformLiteFixture {
public static final Key<Document> HARD_REF_TO_DOCUMENT_KEY = Key.create("HARD_REF_TO_DOCUMENT_KEY");
protected String myFilePrefix = "";
protected String myFileExt;
protected final String myFullDataPath;
protected PsiFile myFile;
private MockPsiManager myPsiManager;
private PsiFileFactoryImpl myFileFactory;
protected Language myLanguage;
private final ParserDefinition[] myDefinitions;
private final boolean myLowercaseFirstLetter;
protected KtParsingTestCase(@NonNls @NotNull String dataPath, @NotNull String fileExt, @NotNull ParserDefinition... definitions) {
this(dataPath, fileExt, false, definitions);
}
protected KtParsingTestCase(@NonNls @NotNull String dataPath, @NotNull String fileExt, boolean lowercaseFirstLetter, @NotNull ParserDefinition... definitions) {
myDefinitions = definitions;
myFullDataPath = getTestDataPath() + "/" + dataPath;
myFileExt = fileExt;
myLowercaseFirstLetter = lowercaseFirstLetter;
}
@Override
protected void setUp() throws Exception {
super.setUp();
initApplication();
ComponentAdapter component = getApplication().getPicoContainer().getComponentAdapter(ProgressManager.class.getName());
Extensions.registerAreaClass("IDEA_PROJECT", null);
myProject = new MockProjectEx(getTestRootDisposable());
myPsiManager = new MockPsiManager(myProject);
myFileFactory = new PsiFileFactoryImpl(myPsiManager);
MutablePicoContainer appContainer = getApplication().getPicoContainer();
final MockEditorFactory editorFactory = new MockEditorFactory();
MockFileTypeManager mockFileTypeManager = new MockFileTypeManager(KotlinFileType.INSTANCE);
MockFileDocumentManagerImpl mockFileDocumentManager = new MockFileDocumentManagerImpl(new Function<CharSequence, Document>() {
@Override
public Document fun(CharSequence charSequence) {
return editorFactory.createDocument(charSequence);
}
}, HARD_REF_TO_DOCUMENT_KEY);
registerApplicationService(PropertiesComponent.class, new AppPropertiesComponentImpl());
registerApplicationService(PsiBuilderFactory.class, new PsiBuilderFactoryImpl());
registerApplicationService(DefaultASTFactory.class, new CoreASTFactory());
registerApplicationService(SchemeManagerFactory.class, new MockSchemeManagerFactory());
registerApplicationService(FileTypeManager.class, mockFileTypeManager);
registerApplicationService(FileDocumentManager.class, mockFileDocumentManager);
registerApplicationService(ProgressManager.class, new CoreProgressManager());
registerComponentInstance(appContainer, FileTypeRegistry.class, mockFileTypeManager);
registerComponentInstance(appContainer, FileTypeManager.class, mockFileTypeManager);
registerComponentInstance(appContainer, EditorFactory.class, editorFactory);
registerComponentInstance(appContainer, FileDocumentManager.class, mockFileDocumentManager);
registerComponentInstance(appContainer, PsiDocumentManager.class, new MockPsiDocumentManager());
myProject.registerService(CachedValuesManager.class, new CachedValuesManagerImpl(myProject, new PsiCachedValuesFactory(myPsiManager)));
myProject.registerService(PsiManager.class, myPsiManager);
this.registerExtensionPoint(FileTypeFactory.FILE_TYPE_FACTORY_EP, FileTypeFactory.class);
registerExtensionPoint(MetaLanguage.EP_NAME, MetaLanguage.class);
for (ParserDefinition definition : myDefinitions) {
addExplicitExtension(LanguageParserDefinitions.INSTANCE, definition.getFileNodeType().getLanguage(), definition);
}
if (myDefinitions.length > 0) {
configureFromParserDefinition(myDefinitions[0], myFileExt);
}
// That's for reparse routines
final PomModelImpl pomModel = new PomModelImpl(myProject);
myProject.registerService(PomModel.class, pomModel);
}
public void configureFromParserDefinition(ParserDefinition definition, String extension) {
myLanguage = definition.getFileNodeType().getLanguage();
myFileExt = extension;
addExplicitExtension(LanguageParserDefinitions.INSTANCE, this.myLanguage, definition);
registerComponentInstance(
getApplication().getPicoContainer(), FileTypeManager.class,
new MockFileTypeManager(new MockLanguageFileType(myLanguage, myFileExt)));
}
protected <T> void addExplicitExtension(final LanguageExtension<T> instance, final Language language, final T object) {
instance.addExplicitExtension(language, object);
Disposer.register(myProject, new Disposable() {
@Override
public void dispose() {
instance.removeExplicitExtension(language, object);
}
});
}
@Override
protected <T> void registerExtensionPoint(final ExtensionPointName<T> extensionPointName, Class<T> aClass) {
super.registerExtensionPoint(extensionPointName, aClass);
Disposer.register(myProject, new Disposable() {
@Override
public void dispose() {
Extensions.getRootArea().unregisterExtensionPoint(extensionPointName.getName());
}
});
}
protected <T> void registerApplicationService(final Class<T> aClass, T object) {
getApplication().registerService(aClass, object);
Disposer.register(myProject, new Disposable() {
@Override
public void dispose() {
getApplication().getPicoContainer().unregisterComponent(aClass.getName());
}
});
}
public MockProjectEx getProject() {
return myProject;
}
public MockPsiManager getPsiManager() {
return myPsiManager;
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
myFile = null;
myProject = null;
myPsiManager = null;
}
protected String getTestDataPath() {
return PathManager.getHomePath();
}
@NotNull
public final String getTestName() {
return getTestName(myLowercaseFirstLetter);
}
protected boolean includeRanges() {
return false;
}
protected boolean skipSpaces() {
return false;
}
protected boolean checkAllPsiRoots() {
return true;
}
protected void doTest(boolean checkResult) {
String name = getTestName();
try {
String text = loadFile(name + "." + myFileExt);
myFile = createPsiFile(name, text);
ensureParsed(myFile);
assertEquals("light virtual file text mismatch", text, ((LightVirtualFile)myFile.getVirtualFile()).getContent().toString());
assertEquals("virtual file text mismatch", text, LoadTextUtil.loadText(myFile.getVirtualFile()));
assertEquals("doc text mismatch", text, myFile.getViewProvider().getDocument().getText());
assertEquals("psi text mismatch", text, myFile.getText());
ensureCorrectReparse(myFile);
if (checkResult){
checkResult(name, myFile);
}
else{
toParseTreeText(myFile, skipSpaces(), includeRanges());
}
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
protected void doTest(String suffix) throws IOException {
String name = getTestName();
String text = loadFile(name + "." + myFileExt);
myFile = createPsiFile(name, text);
ensureParsed(myFile);
assertEquals(text, myFile.getText());
checkResult(name + suffix, myFile);
}
protected void doCodeTest(String code) throws IOException {
String name = getTestName();
myFile = createPsiFile("a", code);
ensureParsed(myFile);
assertEquals(code, myFile.getText());
checkResult(myFilePrefix + name, myFile);
}
protected PsiFile createPsiFile(String name, String text) {
return createFile(name + "." + myFileExt, text);
}
protected PsiFile createFile(@NonNls String name, String text) {
LightVirtualFile virtualFile = new LightVirtualFile(name, myLanguage, text);
virtualFile.setCharset(CharsetToolkit.UTF8_CHARSET);
return createFile(virtualFile);
}
protected PsiFile createFile(LightVirtualFile virtualFile) {
return myFileFactory.trySetupPsiForFile(virtualFile, myLanguage, true, false);
}
protected void checkResult(@NonNls @TestDataFile String targetDataName, final PsiFile file) throws IOException {
doCheckResult(myFullDataPath, file, checkAllPsiRoots(), targetDataName, skipSpaces(), includeRanges());
}
public static void doCheckResult(String testDataDir,
PsiFile file,
boolean checkAllPsiRoots,
String targetDataName,
boolean skipSpaces,
boolean printRanges) throws IOException {
FileViewProvider provider = file.getViewProvider();
Set<Language> languages = provider.getLanguages();
if (!checkAllPsiRoots || languages.size() == 1) {
doCheckResult(testDataDir, targetDataName + ".txt", toParseTreeText(file, skipSpaces, printRanges).trim());
return;
}
for (Language language : languages) {
PsiFile root = provider.getPsi(language);
String expectedName = targetDataName + "." + language.getID() + ".txt";
doCheckResult(testDataDir, expectedName, toParseTreeText(root, skipSpaces, printRanges).trim());
}
}
protected void checkResult(String actual) throws IOException {
String name = getTestName();
doCheckResult(myFullDataPath, myFilePrefix + name + ".txt", actual);
}
protected void checkResult(@TestDataFile @NonNls String targetDataName, String actual) throws IOException {
doCheckResult(myFullDataPath, targetDataName, actual);
}
public static void doCheckResult(String fullPath, String targetDataName, String actual) throws IOException {
String expectedFileName = fullPath + File.separatorChar + targetDataName;
KtUsefulTestCase.assertSameLinesWithFile(expectedFileName, actual);
}
protected static String toParseTreeText(PsiElement file, boolean skipSpaces, boolean printRanges) {
return DebugUtil.psiToString(file, skipSpaces, printRanges);
}
protected String loadFile(@NonNls @TestDataFile String name) throws IOException {
return loadFileDefault(myFullDataPath, name);
}
public static String loadFileDefault(String dir, String name) throws IOException {
return FileUtil.loadFile(new File(dir, name), CharsetToolkit.UTF8, true).trim();
}
public static void ensureParsed(PsiFile file) {
file.accept(new PsiElementVisitor() {
@Override
public void visitElement(PsiElement element) {
element.acceptChildren(this);
}
});
}
public static void ensureCorrectReparse(@NotNull PsiFile file) {
String psiToStringDefault = DebugUtil.psiToString(file, false, false);
String fileText = file.getText();
DiffLog diffLog = (new BlockSupportImpl()).reparseRange(
file, file.getNode(), TextRange.allOf(fileText), fileText, new EmptyProgressIndicator(), fileText);
diffLog.performActualPsiChange(file);
TestCase.assertEquals(psiToStringDefault, DebugUtil.psiToString(file, false, false));
}
}