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));
}
}
@@ -0,0 +1,174 @@
/*
* 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.idea.highlighter
import com.intellij.codeInsight.highlighting.HighlightUsagesHandlerBase
import com.intellij.codeInsight.highlighting.HighlightUsagesHandlerFactoryBase
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.impl.source.tree.LeafPsiElement
import com.intellij.psi.tree.TokenSet
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.Consumer
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsResultOfLambda
import org.jetbrains.kotlin.resolve.inline.InlineUtil
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
class KotlinHighlightExitPointsHandlerFactory : HighlightUsagesHandlerFactoryBase() {
companion object {
private val RETURN_AND_THROW = TokenSet.create(KtTokens.RETURN_KEYWORD, KtTokens.THROW_KEYWORD)
private fun getOnReturnOrThrowUsageHandler(editor: Editor, file: PsiFile, target: PsiElement): HighlightUsagesHandlerBase<*>? {
if (target !is LeafPsiElement || target.elementType !in RETURN_AND_THROW) {
return null
}
val returnOrThrow = PsiTreeUtil.getParentOfType<KtExpression>(
target,
KtReturnExpression::class.java,
KtThrowExpression::class.java
) ?: return null
return OnExitUsagesHandler(editor, file, returnOrThrow)
}
private fun getOnLambdaCallUsageHandler(editor: Editor, file: PsiFile, target: PsiElement): HighlightUsagesHandlerBase<*>? {
if (target !is LeafPsiElement || target.elementType != KtTokens.IDENTIFIER) {
return null
}
val refExpr = target.parent as? KtNameReferenceExpression ?: return null
val call = refExpr.parent as? KtCallExpression ?: return null
if (call.calleeExpression != refExpr) return null
val lambda = call.lambdaArguments.singleOrNull() ?: return null
val literal = lambda.getLambdaExpression()?.functionLiteral ?: return null
return OnExitUsagesHandler(editor, file, literal, highlightReferences = true)
}
}
override fun createHighlightUsagesHandler(editor: Editor, file: PsiFile, target: PsiElement): HighlightUsagesHandlerBase<*>? {
return getOnReturnOrThrowUsageHandler(editor, file, target)
?: getOnLambdaCallUsageHandler(editor, file, target)
}
private class OnExitUsagesHandler(editor: Editor, file: PsiFile, val target: KtExpression, val highlightReferences: Boolean = false) :
HighlightUsagesHandlerBase<PsiElement>(editor, file) {
override fun getTargets() = listOf(target)
override fun selectTargets(targets: MutableList<out PsiElement>, selectionConsumer: Consumer<in MutableList<out PsiElement>>) {
selectionConsumer.consume(targets)
}
override fun computeUsages(targets: MutableList<out PsiElement>) {
val relevantFunction: KtDeclarationWithBody? =
if (target is KtFunctionLiteral) {
target
} else {
target.getRelevantDeclaration()
}
relevantFunction?.accept(object : KtVisitorVoid() {
override fun visitKtElement(element: KtElement) {
element.acceptChildren(this)
}
override fun visitExpression(expression: KtExpression) {
if (relevantFunction is KtFunctionLiteral) {
if (occurrenceForFunctionLiteralReturnExpression(expression)) {
return
}
}
super.visitExpression(expression)
}
private fun occurrenceForFunctionLiteralReturnExpression(expression: KtExpression): Boolean {
if (!KtPsiUtil.isStatement(expression)) return false
if (expression is KtIfExpression || expression is KtWhenExpression || expression is KtBlockExpression) {
return false
}
val bindingContext = expression.analyze(BodyResolveMode.FULL)
if (!expression.isUsedAsResultOfLambda(bindingContext)) {
return false
}
if (expression.getRelevantDeclaration() != relevantFunction) {
return false
}
addOccurrence(expression)
return true
}
private fun visitReturnOrThrow(expression: KtExpression) {
if (expression.getRelevantDeclaration() == relevantFunction) {
addOccurrence(expression)
}
}
override fun visitReturnExpression(expression: KtReturnExpression) {
visitReturnOrThrow(expression)
}
override fun visitThrowExpression(expression: KtThrowExpression) {
visitReturnOrThrow(expression)
}
})
}
override fun highlightReferences() = highlightReferences
}
}
private fun KtExpression.getRelevantDeclaration(): KtDeclarationWithBody? {
if (this is KtReturnExpression) {
(this.getTargetLabel()?.mainReference?.resolve() as? KtFunction)?.let {
return it
}
}
if (this is KtThrowExpression || this is KtReturnExpression) {
for (parent in parents) {
if (parent is KtDeclarationWithBody) {
if (parent is KtPropertyAccessor) {
return parent
}
if (InlineUtil.canBeInlineArgument(parent) &&
!InlineUtil.isInlinedArgument(parent as KtFunction, parent.analyze(BodyResolveMode.FULL), false)
) {
return parent
}
}
}
return null
}
return parents.filterIsInstance<KtDeclarationWithBody>().firstOrNull()
}
@@ -0,0 +1,58 @@
/*
* 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.idea.highlighter
import com.intellij.codeInsight.highlighting.HighlightUsagesHandlerBase
import com.intellij.codeInsight.highlighting.HighlightUsagesHandlerFactoryBase
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.impl.source.tree.LeafPsiElement
import com.intellij.util.Consumer
import org.jetbrains.kotlin.idea.intentions.getLambdaByImplicitItReference
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.psi.KtTreeVisitorVoid
class KotlinHighlightImplicitItHandlerFactory : HighlightUsagesHandlerFactoryBase() {
override fun createHighlightUsagesHandler(editor: Editor, file: PsiFile, target: PsiElement): HighlightUsagesHandlerBase<*>? {
if (!(target is LeafPsiElement && target.elementType == KtTokens.IDENTIFIER)) return null
val refExpr = target.parent as? KtNameReferenceExpression ?: return null
val lambda = getLambdaByImplicitItReference(refExpr) ?: return null
return object : HighlightUsagesHandlerBase<KtNameReferenceExpression>(editor, file) {
override fun getTargets() = listOf(refExpr)
override fun selectTargets(
targets: MutableList<out KtNameReferenceExpression>,
selectionConsumer: Consumer<in MutableList<out KtNameReferenceExpression>>
) = selectionConsumer.consume(targets)
override fun computeUsages(targets: MutableList<out KtNameReferenceExpression>) {
lambda.accept(
object : KtTreeVisitorVoid() {
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
if (expression is KtNameReferenceExpression && getLambdaByImplicitItReference(expression) == lambda) {
addOccurrence(expression)
}
}
}
)
}
}
}
}
@@ -0,0 +1,188 @@
/*
* 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.idea.highlighter
import com.intellij.codeHighlighting.Pass
import com.intellij.codeInsight.daemon.LineMarkerInfo
import com.intellij.codeInsight.daemon.LineMarkerProvider
import com.intellij.icons.AllIcons
import com.intellij.openapi.editor.markup.GutterIconRenderer
import com.intellij.openapi.progress.ProgressManager
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.inspections.RecursivePropertyAccessorInspection
import org.jetbrains.kotlin.idea.util.getReceiverTargetDescriptor
import org.jetbrains.kotlin.lexer.KtToken
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.inline.InlineUtil
import org.jetbrains.kotlin.resolve.scopes.receivers.Receiver
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
import org.jetbrains.kotlin.types.expressions.OperatorConventions
import org.jetbrains.kotlin.util.OperatorNameConventions
import java.util.*
class KotlinRecursiveCallLineMarkerProvider : LineMarkerProvider {
override fun getLineMarkerInfo(element: PsiElement) = null
override fun collectSlowLineMarkers(elements: MutableList<out PsiElement>, result: MutableCollection<in LineMarkerInfo<*>>) {
val markedLineNumbers = HashSet<Int>()
for (element in elements) {
ProgressManager.checkCanceled()
if (element is KtElement) {
val lineNumber = element.getLineNumber()
if (lineNumber !in markedLineNumbers && isRecursiveCall(element)) {
markedLineNumbers.add(lineNumber)
result.add(RecursiveMethodCallMarkerInfo(getElementForLineMark(element)))
}
}
}
}
private fun getEnclosingFunction(element: KtElement, stopOnNonInlinedLambdas: Boolean): KtNamedFunction? {
for (parent in element.parents) {
when (parent) {
is KtFunctionLiteral -> if (stopOnNonInlinedLambdas && !InlineUtil.isInlinedArgument(
parent,
parent.analyze(),
false
)
) return null
is KtNamedFunction -> {
when (parent.parent) {
is KtBlockExpression, is KtClassBody, is KtFile, is KtScript -> return parent
else -> if (stopOnNonInlinedLambdas && !InlineUtil.isInlinedArgument(parent, parent.analyze(), false)) return null
}
}
is KtClassOrObject -> return null
}
}
return null
}
private fun isRecursiveCall(element: KtElement): Boolean {
if (RecursivePropertyAccessorInspection.isRecursivePropertyAccess(element)) return true
if (RecursivePropertyAccessorInspection.isRecursiveSyntheticPropertyAccess(element)) return true
// Fast check for names without resolve
val resolveName = getCallNameFromPsi(element) ?: return false
val enclosingFunction = getEnclosingFunction(element, false) ?: return false
val enclosingFunctionName = enclosingFunction.name
if (enclosingFunctionName != OperatorNameConventions.INVOKE.asString()
&& enclosingFunctionName != resolveName.asString()
) return false
// Check that there were no not-inlined lambdas on the way to enclosing function
if (enclosingFunction != getEnclosingFunction(element, true)) return false
val bindingContext = element.analyze()
val enclosingFunctionDescriptor = bindingContext[BindingContext.FUNCTION, enclosingFunction] ?: return false
val call = bindingContext[BindingContext.CALL, element] ?: return false
val resolvedCall = bindingContext[BindingContext.RESOLVED_CALL, call] ?: return false
if (resolvedCall.candidateDescriptor.original != enclosingFunctionDescriptor) return false
fun isDifferentReceiver(receiver: Receiver?): Boolean {
if (receiver !is ReceiverValue) return false
val receiverOwner = receiver.getReceiverTargetDescriptor(bindingContext) ?: return true
return when (receiverOwner) {
is SimpleFunctionDescriptor -> receiverOwner != enclosingFunctionDescriptor
is ClassDescriptor -> receiverOwner != enclosingFunctionDescriptor.containingDeclaration
else -> return true
}
}
if (isDifferentReceiver(resolvedCall.dispatchReceiver)) return false
return true
}
private class RecursiveMethodCallMarkerInfo(callElement: PsiElement) : LineMarkerInfo<PsiElement>(
callElement,
callElement.textRange,
AllIcons.Gutter.RecursiveMethod,
Pass.LINE_MARKERS,
{ KotlinBundle.message("highlighter.tool.tip.text.recursive.call") },
null,
GutterIconRenderer.Alignment.RIGHT
) {
override fun createGutterRenderer(): GutterIconRenderer? {
return object : LineMarkerInfo.LineMarkerGutterIconRenderer<PsiElement>(this) {
override fun getClickAction() = null // to place breakpoint on mouse click
}
}
}
}
internal fun getElementForLineMark(callElement: PsiElement): PsiElement =
when (callElement) {
is KtSimpleNameExpression -> callElement.getReferencedNameElement()
else ->
// a fallback,
//but who knows what to reference in KtArrayAccessExpression ?
generateSequence(callElement, { it.firstChild }).last()
}
private fun PsiElement.getLineNumber(): Int {
return PsiDocumentManager.getInstance(project).getDocument(containingFile)!!.getLineNumber(textOffset)
}
private fun getCallNameFromPsi(element: KtElement): Name? {
when (element) {
is KtSimpleNameExpression -> {
val elementParent = element.getParent()
when (elementParent) {
is KtCallExpression -> return Name.identifier(element.getText())
is KtOperationExpression -> {
val operationReference = elementParent.operationReference
if (element == operationReference) {
val node = operationReference.getReferencedNameElementType()
return if (node is KtToken) {
val conventionName = if (elementParent is KtPrefixExpression)
OperatorConventions.getNameForOperationSymbol(node, true, false)
else
OperatorConventions.getNameForOperationSymbol(node)
conventionName ?: Name.identifier(element.getText())
} else {
Name.identifier(element.getText())
}
}
}
}
}
is KtArrayAccessExpression ->
return OperatorNameConventions.GET
is KtThisExpression ->
if (element.getParent() is KtCallExpression) {
return OperatorNameConventions.INVOKE
}
}
return null
}
@@ -0,0 +1,125 @@
/*
* 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.idea.highlighter
import com.intellij.codeHighlighting.Pass
import com.intellij.codeInsight.daemon.LineMarkerInfo
import com.intellij.codeInsight.daemon.LineMarkerProvider
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.editor.markup.GutterIconRenderer
import com.intellij.openapi.progress.ProgressManager
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptorWithAccessors
import org.jetbrains.kotlin.descriptors.accessors
import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.KotlinIcons
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.refactoring.getLineNumber
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingContext.*
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
class KotlinSuspendCallLineMarkerProvider : LineMarkerProvider {
private class SuspendCallMarkerInfo(callElement: PsiElement, message: String) : LineMarkerInfo<PsiElement>(
callElement,
callElement.textRange,
KotlinIcons.SUSPEND_CALL,
Pass.LINE_MARKERS,
{ message },
null,
GutterIconRenderer.Alignment.RIGHT
) {
override fun createGutterRenderer(): GutterIconRenderer? {
return object : LineMarkerInfo.LineMarkerGutterIconRenderer<PsiElement>(this) {
override fun getClickAction(): AnAction? = null
}
}
}
override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? = null
override fun collectSlowLineMarkers(
elements: MutableList<out PsiElement>,
result: MutableCollection<in LineMarkerInfo<*>>
) {
val markedLineNumbers = HashSet<Int>()
for (element in elements) {
ProgressManager.checkCanceled()
if (element !is KtExpression) continue
val containingFile = element.containingFile
if (containingFile !is KtFile || containingFile is KtCodeFragment) {
continue
}
val lineNumber = element.getLineNumber()
if (lineNumber in markedLineNumbers) continue
if (!element.hasSuspendCalls()) continue
markedLineNumbers += lineNumber
result += if (element is KtForExpression) {
SuspendCallMarkerInfo(
getElementForLineMark(element.loopRange!!),
KotlinBundle.message("highlighter.message.suspending.iteration")
)
} else {
SuspendCallMarkerInfo(getElementForLineMark(element), KotlinBundle.message("highlighter.message.suspend.function.call"))
}
}
}
}
private fun KtExpression.isValidCandidateExpression(): Boolean {
if (this is KtParenthesizedExpression) return false
if (this is KtOperationReferenceExpression || this is KtForExpression || this is KtProperty || this is KtNameReferenceExpression) return true
val parent = parent
if (parent is KtCallExpression && parent.calleeExpression == this) return true
if (this is KtCallExpression && (calleeExpression is KtCallExpression || calleeExpression is KtParenthesizedExpression)) return true
return false
}
fun KtExpression.hasSuspendCalls(bindingContext: BindingContext = analyze(BodyResolveMode.PARTIAL)): Boolean {
if (!isValidCandidateExpression()) return false
return when (this) {
is KtForExpression -> {
val iteratorResolvedCall = bindingContext[LOOP_RANGE_ITERATOR_RESOLVED_CALL, loopRange]
val loopRangeHasNextResolvedCall = bindingContext[LOOP_RANGE_HAS_NEXT_RESOLVED_CALL, loopRange]
val loopRangeNextResolvedCall = bindingContext[LOOP_RANGE_NEXT_RESOLVED_CALL, loopRange]
listOf(iteratorResolvedCall, loopRangeHasNextResolvedCall, loopRangeNextResolvedCall).any {
it?.resultingDescriptor?.isSuspend == true
}
}
is KtProperty -> {
if (hasDelegateExpression()) {
val variableDescriptor = bindingContext[DECLARATION_TO_DESCRIPTOR, this] as? VariableDescriptorWithAccessors
val accessors = variableDescriptor?.accessors ?: emptyList()
accessors.any { accessor ->
val delegatedFunctionDescriptor = bindingContext[DELEGATED_PROPERTY_RESOLVED_CALL, accessor]?.resultingDescriptor
delegatedFunctionDescriptor?.isSuspend == true
}
} else {
false
}
}
else -> {
val resolvedCall = getResolvedCall(bindingContext)
if ((resolvedCall?.resultingDescriptor as? FunctionDescriptor)?.isSuspend == true) true
else {
val propertyDescriptor = resolvedCall?.resultingDescriptor as? PropertyDescriptor
val s = propertyDescriptor?.fqNameSafe?.asString()
s?.startsWith("kotlin.coroutines.") == true && s.endsWith(".coroutineContext")
}
}
}
}
@@ -0,0 +1,65 @@
/*
* Copyright 2000-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.idea.highlighter.markers
import com.intellij.application.options.colors.ColorAndFontOptions
import com.intellij.codeHighlighting.Pass
import com.intellij.codeInsight.daemon.GutterIconNavigationHandler
import com.intellij.codeInsight.daemon.LineMarkerInfo
import com.intellij.ide.DataManager
import com.intellij.openapi.editor.markup.GutterIconRenderer
import com.intellij.psi.PsiElement
import com.intellij.util.Function
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.core.toDescriptor
import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.highlighter.dsl.DslHighlighterExtension
import org.jetbrains.kotlin.idea.highlighter.dsl.isDslHighlightingMarker
import org.jetbrains.kotlin.psi.KtClass
import javax.swing.JComponent
private val navHandler = GutterIconNavigationHandler<PsiElement> { event, element ->
val dataContext = (event.component as? JComponent)?.let { DataManager.getInstance().getDataContext(it) }
?: return@GutterIconNavigationHandler
val ktClass = element?.parent as? KtClass ?: return@GutterIconNavigationHandler
val styleId = ktClass.styleIdForMarkerAnnotation() ?: return@GutterIconNavigationHandler
ColorAndFontOptions.selectOrEditColor(dataContext, DslHighlighterExtension.styleOptionDisplayName(styleId), KotlinLanguage.NAME)
}
private val toolTipHandler = Function<PsiElement, String> {
KotlinBundle.message("highlighter.tool.tip.marker.annotation.for.dsl")
}
fun collectHighlightingColorsMarkers(
ktClass: KtClass,
result: MutableCollection<in LineMarkerInfo<*>>
) {
if (!KotlinLineMarkerOptions.dslOption.isEnabled) return
val styleId = ktClass.styleIdForMarkerAnnotation() ?: return
val anchor = ktClass.nameIdentifier ?: return
result.add(
LineMarkerInfo<PsiElement>(
anchor,
anchor.textRange,
createDslStyleIcon(styleId),
Pass.LINE_MARKERS,
toolTipHandler, navHandler,
GutterIconRenderer.Alignment.RIGHT
)
)
}
private fun KtClass.styleIdForMarkerAnnotation(): Int? {
val classDescriptor = toDescriptor() as? ClassDescriptor ?: return null
if (classDescriptor.kind != ClassKind.ANNOTATION_CLASS) return null
if (!classDescriptor.isDslHighlightingMarker()) return null
return DslHighlighterExtension.styleIdByMarkerAnnotation(classDescriptor)
}
@@ -0,0 +1,561 @@
/*
* Copyright 2000-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.idea.highlighter.markers
import com.intellij.codeHighlighting.Pass
import com.intellij.codeInsight.daemon.*
import com.intellij.codeInsight.daemon.impl.LineMarkerNavigator
import com.intellij.codeInsight.daemon.impl.MarkerType
import com.intellij.codeInsight.daemon.impl.PsiElementListNavigator
import com.intellij.codeInsight.navigation.ListBackgroundUpdaterTask
import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.colors.CodeInsightColors
import com.intellij.openapi.editor.colors.EditorColorsManager
import com.intellij.openapi.editor.markup.GutterIconRenderer
import com.intellij.openapi.editor.markup.SeparatorPlacement
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.*
import com.intellij.psi.search.searches.ClassInheritorsSearch
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.asJava.LightClassUtil
import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.MemberDescriptor
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.idea.caches.lightClasses.KtFakeLightClass
import org.jetbrains.kotlin.idea.caches.lightClasses.KtFakeLightMethod
import org.jetbrains.kotlin.idea.caches.project.implementedDescriptors
import org.jetbrains.kotlin.idea.caches.project.implementingDescriptors
import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor
import org.jetbrains.kotlin.idea.core.isInheritable
import org.jetbrains.kotlin.idea.core.isOverridable
import org.jetbrains.kotlin.idea.core.toDescriptor
import org.jetbrains.kotlin.idea.editor.fixers.startLine
import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.presentation.DeclarationByModuleRenderer
import org.jetbrains.kotlin.idea.search.declarationsSearch.toPossiblyFakeLightMethods
import org.jetbrains.kotlin.idea.util.*
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.getPrevSiblingIgnoringWhitespaceAndComments
import java.awt.event.MouseEvent
import java.util.*
import javax.swing.ListCellRenderer
class KotlinLineMarkerProvider : LineMarkerProviderDescriptor() {
override fun getName() = KotlinBundle.message("highlighter.name.kotlin.line.markers")
override fun getOptions(): Array<Option> = KotlinLineMarkerOptions.options
override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<PsiElement>? {
if (DaemonCodeAnalyzerSettings.getInstance().SHOW_METHOD_SEPARATORS) {
if (element.canHaveSeparator()) {
val prevSibling = element.getPrevSiblingIgnoringWhitespaceAndComments()
if (prevSibling.canHaveSeparator() &&
(element.wantsSeparator() || prevSibling?.wantsSeparator() == true)
) {
return createLineSeparatorByElement(element)
}
}
}
return null
}
private fun PsiElement?.canHaveSeparator() =
this is KtFunction || this is KtClassInitializer || (this is KtProperty && !isLocal)
private fun PsiElement.wantsSeparator() = this is KtFunction || StringUtil.getLineBreakCount(text) > 0
private fun createLineSeparatorByElement(element: PsiElement): LineMarkerInfo<PsiElement> {
val anchor = PsiTreeUtil.getDeepestFirst(element)
val info = LineMarkerInfo(anchor, anchor.textRange, null, Pass.LINE_MARKERS, null, null, GutterIconRenderer.Alignment.RIGHT)
info.separatorColor = EditorColorsManager.getInstance().globalScheme.getColor(CodeInsightColors.METHOD_SEPARATORS_COLOR)
info.separatorPlacement = SeparatorPlacement.TOP
return info
}
override fun collectSlowLineMarkers(elements: List<PsiElement>, result: MutableCollection<in LineMarkerInfo<*>>) {
if (elements.isEmpty()) return
if (KotlinLineMarkerOptions.options.none { option -> option.isEnabled }) return
val first = elements.first()
if (DumbService.getInstance(first.project).isDumb || !ProjectRootsUtil.isInProjectOrLibSource(first)) return
val functions = hashSetOf<KtNamedFunction>()
val properties = hashSetOf<KtNamedDeclaration>()
val declarations = hashSetOf<KtNamedDeclaration>()
for (leaf in elements) {
ProgressManager.checkCanceled()
if (leaf !is PsiIdentifier && leaf.firstChild != null) continue
val element = leaf.parent as? KtNamedDeclaration ?: continue
if (!declarations.add(element)) continue
when (element) {
is KtClass -> {
collectInheritedClassMarker(element, result)
collectHighlightingColorsMarkers(element, result)
}
is KtNamedFunction -> {
functions.add(element)
collectSuperDeclarationMarkers(element, result)
}
is KtProperty -> {
properties.add(element)
collectSuperDeclarationMarkers(element, result)
}
is KtParameter -> {
if (element.hasValOrVar()) {
properties.add(element)
collectSuperDeclarationMarkers(element, result)
}
}
}
collectMultiplatformMarkers(element, result)
}
collectOverriddenFunctions(functions, result)
collectOverriddenPropertyAccessors(properties, result)
}
}
data class NavigationPopupDescriptor(
val targets: Collection<NavigatablePsiElement>,
val title: String,
val findUsagesTitle: String,
val renderer: ListCellRenderer<*>,
val updater: ListBackgroundUpdaterTask? = null
) {
fun showPopup(e: MouseEvent?) {
PsiElementListNavigator.openTargets(e, targets.toTypedArray(), title, findUsagesTitle, renderer, updater)
}
}
interface TestableLineMarkerNavigator {
fun getTargetsPopupDescriptor(element: PsiElement?): NavigationPopupDescriptor?
}
private val SUBCLASSED_CLASS = MarkerType(
"SUBCLASSED_CLASS",
{ getPsiClass(it)?.let(::getSubclassedClassTooltip) },
object : LineMarkerNavigator() {
override fun browse(e: MouseEvent?, element: PsiElement?) {
getPsiClass(element)?.let {
MarkerType.navigateToSubclassedClass(e, it, DeclarationByModuleRenderer())
}
}
})
private val OVERRIDDEN_FUNCTION = object : MarkerType(
"OVERRIDDEN_FUNCTION",
{ getPsiMethod(it)?.let(::getOverriddenMethodTooltip) },
object : LineMarkerNavigator() {
override fun browse(e: MouseEvent?, element: PsiElement?) {
buildNavigateToOverriddenMethodPopup(e, element)?.showPopup(e)
}
}) {
override fun getNavigationHandler(): GutterIconNavigationHandler<PsiElement> {
val superHandler = super.getNavigationHandler()
return object : GutterIconNavigationHandler<PsiElement>, TestableLineMarkerNavigator {
override fun navigate(e: MouseEvent?, elt: PsiElement?) {
superHandler.navigate(e, elt)
}
override fun getTargetsPopupDescriptor(element: PsiElement?) = buildNavigateToOverriddenMethodPopup(null, element)
}
}
}
private val OVERRIDDEN_PROPERTY = object : MarkerType(
"OVERRIDDEN_PROPERTY",
{ it?.let { getOverriddenPropertyTooltip(it.parent as KtNamedDeclaration) } },
object : LineMarkerNavigator() {
override fun browse(e: MouseEvent?, element: PsiElement?) {
buildNavigateToPropertyOverriddenDeclarationsPopup(e, element)?.showPopup(e)
}
}) {
override fun getNavigationHandler(): GutterIconNavigationHandler<PsiElement> {
val superHandler = super.getNavigationHandler()
return object : GutterIconNavigationHandler<PsiElement>, TestableLineMarkerNavigator {
override fun navigate(e: MouseEvent?, elt: PsiElement?) {
superHandler.navigate(e, elt)
}
override fun getTargetsPopupDescriptor(element: PsiElement?) = buildNavigateToPropertyOverriddenDeclarationsPopup(null, element)
}
}
}
val PsiElement.markerDeclaration
get() = (this as? KtDeclaration) ?: (parent as? KtDeclaration)
private val PLATFORM_ACTUAL = object : MarkerType(
"PLATFORM_ACTUAL",
{ element -> element?.markerDeclaration?.let { getPlatformActualTooltip(it) } },
object : LineMarkerNavigator() {
override fun browse(e: MouseEvent?, element: PsiElement?) {
buildNavigateToActualDeclarationsPopup(element)?.showPopup(e)
}
}) {
override fun getNavigationHandler(): GutterIconNavigationHandler<PsiElement> {
val superHandler = super.getNavigationHandler()
return object : GutterIconNavigationHandler<PsiElement>, TestableLineMarkerNavigator {
override fun navigate(e: MouseEvent?, elt: PsiElement?) {
superHandler.navigate(e, elt)
}
override fun getTargetsPopupDescriptor(element: PsiElement?): NavigationPopupDescriptor? {
return buildNavigateToActualDeclarationsPopup(element)
}
}
}
}
private val EXPECTED_DECLARATION = object : MarkerType(
"EXPECTED_DECLARATION",
{ element -> element?.markerDeclaration?.let { getExpectedDeclarationTooltip(it) } },
object : LineMarkerNavigator() {
override fun browse(e: MouseEvent?, element: PsiElement?) {
buildNavigateToExpectedDeclarationsPopup(element)?.showPopup(e)
}
}) {
override fun getNavigationHandler(): GutterIconNavigationHandler<PsiElement> {
val superHandler = super.getNavigationHandler()
return object : GutterIconNavigationHandler<PsiElement>, TestableLineMarkerNavigator {
override fun navigate(e: MouseEvent?, elt: PsiElement?) {
superHandler.navigate(e, elt)
}
override fun getTargetsPopupDescriptor(element: PsiElement?): NavigationPopupDescriptor? {
return buildNavigateToExpectedDeclarationsPopup(element)
}
}
}
}
private fun isImplementsAndNotOverrides(
descriptor: CallableMemberDescriptor,
overriddenMembers: Collection<CallableMemberDescriptor>
): Boolean {
return descriptor.modality != Modality.ABSTRACT && overriddenMembers.all { it.modality == Modality.ABSTRACT }
}
private fun collectSuperDeclarationMarkers(declaration: KtDeclaration, result: MutableCollection<in LineMarkerInfo<*>>) {
if (!(KotlinLineMarkerOptions.implementingOption.isEnabled || KotlinLineMarkerOptions.overridingOption.isEnabled)) return
assert(declaration is KtNamedFunction || declaration is KtProperty || declaration is KtParameter)
if (!declaration.hasModifier(KtTokens.OVERRIDE_KEYWORD)) return
val resolveWithParents = resolveDeclarationWithParents(declaration)
if (resolveWithParents.overriddenDescriptors.isEmpty()) return
val implements = isImplementsAndNotOverrides(resolveWithParents.descriptor!!, resolveWithParents.overriddenDescriptors)
val anchor = (declaration as? KtNamedDeclaration)?.nameIdentifier ?: declaration
// NOTE: Don't store descriptors in line markers because line markers are not deleted while editing other files and this can prevent
// clearing the whole BindingTrace.
val lineMarkerInfo = LineMarkerInfo(
anchor,
anchor.textRange,
if (implements) KotlinLineMarkerOptions.implementingOption.icon else KotlinLineMarkerOptions.overridingOption.icon,
Pass.LINE_MARKERS,
SuperDeclarationMarkerTooltip,
SuperDeclarationMarkerNavigationHandler(),
GutterIconRenderer.Alignment.RIGHT
)
NavigateAction.setNavigateAction(
lineMarkerInfo,
if (declaration is KtNamedFunction) KotlinBundle.message("highlighter.action.text.go.to.super.method") else KotlinBundle.message(
"highlighter.action.text.go.to.super.property"
),
IdeActions.ACTION_GOTO_SUPER
)
result.add(lineMarkerInfo)
}
private fun collectInheritedClassMarker(element: KtClass, result: MutableCollection<in LineMarkerInfo<*>>) {
if (!(KotlinLineMarkerOptions.implementedOption.isEnabled || KotlinLineMarkerOptions.overriddenOption.isEnabled)) return
if (!element.isInheritable()) {
return
}
val lightClass = element.toLightClass() ?: KtFakeLightClass(element)
if (ClassInheritorsSearch.search(lightClass, false).findFirst() == null) return
val anchor = element.nameIdentifier ?: element
val lineMarkerInfo = LineMarkerInfo(
anchor,
anchor.textRange,
if (element.isInterface()) KotlinLineMarkerOptions.implementedOption.icon else KotlinLineMarkerOptions.overriddenOption.icon,
Pass.LINE_MARKERS,
SUBCLASSED_CLASS.tooltip,
SUBCLASSED_CLASS.navigationHandler,
GutterIconRenderer.Alignment.RIGHT
)
NavigateAction.setNavigateAction(
lineMarkerInfo,
if (element.isInterface()) KotlinBundle.message("highlighter.action.text.go.to.implementations") else KotlinBundle.message(
"highlighter.action.text.go.to.subclasses"
),
IdeActions.ACTION_GOTO_IMPLEMENTATION
)
result.add(lineMarkerInfo)
}
private fun collectOverriddenPropertyAccessors(
properties: Collection<KtNamedDeclaration>,
result: MutableCollection<in LineMarkerInfo<*>>
) {
if (!(KotlinLineMarkerOptions.implementedOption.isEnabled || KotlinLineMarkerOptions.overriddenOption.isEnabled)) return
val mappingToJava = HashMap<PsiElement, KtNamedDeclaration>()
for (property in properties) {
if (property.isOverridable()) {
property.toPossiblyFakeLightMethods().forEach { mappingToJava[it] = property }
mappingToJava[property] = property
}
}
val classes = collectContainingClasses(mappingToJava.keys.filterIsInstance<PsiMethod>())
for (property in getOverriddenDeclarations(mappingToJava, classes)) {
ProgressManager.checkCanceled()
val anchor = (property as? PsiNameIdentifierOwner)?.nameIdentifier ?: property
val lineMarkerInfo = LineMarkerInfo(
anchor,
anchor.textRange,
if (isImplemented(property)) KotlinLineMarkerOptions.implementedOption.icon else KotlinLineMarkerOptions.overriddenOption.icon,
Pass.LINE_MARKERS,
OVERRIDDEN_PROPERTY.tooltip,
OVERRIDDEN_PROPERTY.navigationHandler,
GutterIconRenderer.Alignment.RIGHT
)
NavigateAction.setNavigateAction(
lineMarkerInfo,
KotlinBundle.message("highlighter.action.text.go.to.overridden.properties"),
IdeActions.ACTION_GOTO_IMPLEMENTATION
)
result.add(lineMarkerInfo)
}
}
private val KtNamedDeclaration.expectOrActualAnchor
get() =
nameIdentifier ?: when (this) {
is KtConstructor<*> -> getConstructorKeyword() ?: getValueParameterList()?.leftParenthesis
is KtObjectDeclaration -> getObjectKeyword()
else -> null
} ?: this
private fun collectMultiplatformMarkers(
declaration: KtNamedDeclaration,
result: MutableCollection<in LineMarkerInfo<*>>
) {
if (KotlinLineMarkerOptions.actualOption.isEnabled) {
if (declaration.isExpectDeclaration()) {
collectActualMarkers(declaration, result)
return
}
}
if (KotlinLineMarkerOptions.expectOption.isEnabled) {
if (!declaration.isExpectDeclaration() && declaration.isEffectivelyActual()) {
collectExpectedMarkers(declaration, result)
return
}
}
}
private fun Document.areAnchorsOnOneLine(
first: KtNamedDeclaration,
second: KtNamedDeclaration?
): Boolean {
if (second == null) return false
val firstAnchor = first.expectOrActualAnchor
val secondAnchor = second.expectOrActualAnchor
return firstAnchor.startLine(this) == secondAnchor.startLine(this)
}
private fun KtNamedDeclaration.requiresNoMarkers(
document: Document? = PsiDocumentManager.getInstance(project).getDocument(containingFile)
): Boolean {
when (this) {
is KtPrimaryConstructor -> {
return true
}
is KtParameter,
is KtEnumEntry -> {
if (document?.areAnchorsOnOneLine(this, containingClassOrObject) == true) {
return true
}
if (this is KtEnumEntry) {
val enumEntries = containingClassOrObject?.body?.enumEntries.orEmpty()
val previousEnumEntry = enumEntries.getOrNull(enumEntries.indexOf(this) - 1)
if (document?.areAnchorsOnOneLine(this, previousEnumEntry) == true) {
return true
}
}
if (this is KtParameter && hasValOrVar()) {
val parameters = containingClassOrObject?.primaryConstructorParameters.orEmpty()
val previousParameter = parameters.getOrNull(parameters.indexOf(this) - 1)
if (document?.areAnchorsOnOneLine(this, previousParameter) == true) {
return true
}
}
}
}
return false
}
internal fun KtDeclaration.findMarkerBoundDeclarations(): Sequence<KtNamedDeclaration> {
if (this !is KtClass && this !is KtParameter) return emptySequence()
val document = PsiDocumentManager.getInstance(project).getDocument(containingFile)
fun <T : KtNamedDeclaration> Sequence<T>.takeBound(bound: KtNamedDeclaration) = takeWhile {
document?.areAnchorsOnOneLine(bound, it) == true
}
return when (this) {
is KtParameter -> {
val propertyParameters = takeIf { hasValOrVar() }?.containingClassOrObject?.primaryConstructorParameters
?: return emptySequence()
propertyParameters.asSequence().dropWhile {
it !== this
}.drop(1).takeBound(this).filter { it.hasValOrVar() }
}
is KtEnumEntry -> {
val enumEntries = containingClassOrObject?.body?.enumEntries ?: return emptySequence()
enumEntries.asSequence().dropWhile {
it !== this
}.drop(1).takeBound(this)
}
is KtClass -> {
val boundParameters =
primaryConstructor?.valueParameters?.asSequence()?.takeBound(this)?.filter { it.hasValOrVar() }.orEmpty()
val boundEnumEntries = this.takeIf { isEnum() }?.body?.enumEntries?.asSequence()?.takeBound(this).orEmpty()
boundParameters + boundEnumEntries
}
else -> emptySequence()
}
}
private fun collectActualMarkers(
declaration: KtNamedDeclaration,
result: MutableCollection<in LineMarkerInfo<*>>
) {
if (!KotlinLineMarkerOptions.actualOption.isEnabled) return
if (declaration.requiresNoMarkers()) return
val descriptor = declaration.toDescriptor() as? MemberDescriptor ?: return
val commonModuleDescriptor = declaration.containingKtFile.findModuleDescriptor()
if (commonModuleDescriptor.implementingDescriptors.none { it.hasActualsFor(descriptor) }) return
val anchor = declaration.expectOrActualAnchor
val lineMarkerInfo = LineMarkerInfo(
anchor,
anchor.textRange,
KotlinLineMarkerOptions.actualOption.icon,
Pass.LINE_MARKERS,
PLATFORM_ACTUAL.tooltip,
PLATFORM_ACTUAL.navigationHandler,
GutterIconRenderer.Alignment.RIGHT
)
NavigateAction.setNavigateAction(
lineMarkerInfo,
KotlinBundle.message("highlighter.action.text.go.to.actual.declarations"),
IdeActions.ACTION_GOTO_IMPLEMENTATION
)
result.add(lineMarkerInfo)
}
private fun collectExpectedMarkers(
declaration: KtNamedDeclaration,
result: MutableCollection<in LineMarkerInfo<*>>
) {
if (!KotlinLineMarkerOptions.expectOption.isEnabled) return
if (declaration.requiresNoMarkers()) return
val descriptor = declaration.toDescriptor() as? MemberDescriptor ?: return
val platformModuleDescriptor = declaration.containingKtFile.findModuleDescriptor()
if (!platformModuleDescriptor.implementedDescriptors.any { it.hasDeclarationOf(descriptor) }) return
val anchor = declaration.expectOrActualAnchor
val lineMarkerInfo = LineMarkerInfo(
anchor,
anchor.textRange,
KotlinLineMarkerOptions.expectOption.icon,
Pass.LINE_MARKERS,
EXPECTED_DECLARATION.tooltip,
EXPECTED_DECLARATION.navigationHandler,
GutterIconRenderer.Alignment.RIGHT
)
NavigateAction.setNavigateAction(
lineMarkerInfo,
KotlinBundle.message("highlighter.action.text.go.to.expected.declaration"),
null
)
result.add(lineMarkerInfo)
}
private fun collectOverriddenFunctions(functions: Collection<KtNamedFunction>, result: MutableCollection<in LineMarkerInfo<*>>) {
if (!(KotlinLineMarkerOptions.implementedOption.isEnabled || KotlinLineMarkerOptions.overriddenOption.isEnabled)) {
return
}
val mappingToJava = HashMap<PsiElement, KtNamedFunction>()
for (function in functions) {
if (function.isOverridable()) {
val method = LightClassUtil.getLightClassMethod(function) ?: KtFakeLightMethod.get(function)
if (method != null) {
mappingToJava[method] = function
}
mappingToJava[function] = function
}
}
val classes = collectContainingClasses(mappingToJava.keys.filterIsInstance<PsiMethod>())
for (function in getOverriddenDeclarations(mappingToJava, classes)) {
ProgressManager.checkCanceled()
val anchor = function.nameIdentifier ?: function
val lineMarkerInfo = LineMarkerInfo(
anchor,
anchor.textRange,
if (isImplemented(function)) KotlinLineMarkerOptions.implementedOption.icon else KotlinLineMarkerOptions.overriddenOption.icon,
Pass.LINE_MARKERS, OVERRIDDEN_FUNCTION.tooltip,
OVERRIDDEN_FUNCTION.navigationHandler,
GutterIconRenderer.Alignment.RIGHT
)
NavigateAction.setNavigateAction(
lineMarkerInfo,
KotlinBundle.message("highlighter.action.text.go.to.overridden.methods"),
IdeActions.ACTION_GOTO_IMPLEMENTATION
)
result.add(lineMarkerInfo)
}
}
@@ -0,0 +1,77 @@
/*
* Copyright 2010-2019 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.idea.navigation
import com.intellij.ide.util.gotoByName.FilteringGotoByModel
import com.intellij.ide.util.gotoByName.LanguageRef
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiElement
import com.intellij.testFramework.UsefulTestCase
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.util.renderAsGotoImplementation
import org.junit.Assert
import kotlin.test.assertEquals
object GotoCheck {
@JvmStatic
@JvmOverloads
fun checkGotoDirectives(
model: FilteringGotoByModel<LanguageRef>,
editor: Editor,
nonProjectSymbols: Boolean = false,
checkNavigation: Boolean = false
) {
val documentText = editor.document.text
val searchTextList = InTextDirectivesUtils.findListWithPrefixes(documentText, "// SEARCH_TEXT:")
Assert.assertFalse(
"There's no search text in test data file given. Use '// SEARCH_TEXT:' directive",
searchTextList.isEmpty()
)
val expectedReferences =
InTextDirectivesUtils.findLinesWithPrefixesRemoved(documentText, "// REF:").map { input -> input.trim { it <= ' ' } }
val includeNonProjectSymbols = nonProjectSymbols || InTextDirectivesUtils.isDirectiveDefined(documentText, "// CHECK_BOX")
val searchText = searchTextList.first()
val foundSymbols = model.getNames(includeNonProjectSymbols).filter { it?.startsWith(searchText) ?: false }.flatMap {
model.getElementsByName(it, includeNonProjectSymbols, "$it*").toList()
}
val inexactMatching = InTextDirectivesUtils.isDirectiveDefined(documentText, "// ALLOW_MORE_RESULTS")
val renderedSymbols = foundSymbols.map { (it as PsiElement).renderAsGotoImplementation() }
if (checkNavigation && (expectedReferences.size != 1 || inexactMatching)) {
error("Cannot check navigation targets when multiple references are expected")
}
if (inexactMatching) {
UsefulTestCase.assertContainsElements(renderedSymbols, expectedReferences)
} else {
UsefulTestCase.assertOrderedEquals(renderedSymbols.sorted(), expectedReferences)
}
if (!checkNavigation) return
assertNavigationElementMatches(foundSymbols.single() as PsiElement, documentText)
}
@JvmStatic
fun assertNavigationElementMatches(resolved: PsiElement, textWithDirectives: String) {
val expectedBinaryFile = InTextDirectivesUtils.findStringWithPrefixes(textWithDirectives, "// BINARY:")
val expectedSourceFile = InTextDirectivesUtils.findStringWithPrefixes(textWithDirectives, "// SRC:")
assertEquals(expectedBinaryFile, getFileWithDir(resolved))
val srcElement = resolved.navigationElement
Assert.assertNotEquals(srcElement, resolved)
assertEquals(expectedSourceFile, getFileWithDir(srcElement))
}
// TODO: move somewhere
fun getFileWithDir(resolved: PsiElement): String {
val targetFile = resolved.containingFile
val targetDir = targetFile.parent
return targetDir!!.name + "/" + targetFile.name
}
}
@@ -0,0 +1,56 @@
/*
* Copyright 2010-2019 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.idea.resolve
import com.intellij.mock.MockProject
import com.intellij.pom.PomModel
import com.intellij.pom.core.impl.PomModelImpl
import com.intellij.pom.tree.TreeAspect
import org.jetbrains.kotlin.container.ComponentProvider
import org.jetbrains.kotlin.container.get
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener
import org.jetbrains.kotlin.idea.project.IdeaEnvironment
import org.jetbrains.kotlin.idea.project.ResolveElementCache
import org.jetbrains.kotlin.psi.KtAnonymousInitializer
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtPsiUtil
import org.jetbrains.kotlin.renderer.AbstractDescriptorRendererTest
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.TargetEnvironment
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.lazy.ResolveSession
import org.picocontainer.MutablePicoContainer
abstract class AbstractAdditionalResolveDescriptorRendererTest : AbstractDescriptorRendererTest() {
override fun setUp() {
super.setUp()
val pomModelImpl = PomModelImpl(project)
val treeAspect = TreeAspect.getInstance(project)
val mockProject = project as MockProject
createAndRegisterKotlinCodeBlockModificationListener(mockProject, pomModelImpl, treeAspect)
}
override fun tearDown() {
unregisterKotlinCodeBlockModificationListener(project as MockProject)
super.tearDown()
}
override fun getDescriptor(declaration: KtDeclaration, container: ComponentProvider): DeclarationDescriptor {
if (declaration is KtAnonymousInitializer || KtPsiUtil.isLocal(declaration)) {
return container.get<ResolveElementCache>()
.resolveToElements(listOf(declaration), BodyResolveMode.FULL)
.get(BindingContext.DECLARATION_TO_DESCRIPTOR, declaration)!!
}
return container.get<ResolveSession>().resolveToDescriptor(declaration)
}
override val targetEnvironment: TargetEnvironment
get() = IdeaEnvironment
}
@@ -0,0 +1,660 @@
/*
* 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.jps.build
import com.intellij.openapi.Disposable
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.io.FileUtilRt
import com.intellij.testFramework.TestLoggerFactory
import com.intellij.testFramework.UsefulTestCase
import junit.framework.TestCase
import org.apache.log4j.ConsoleAppender
import org.apache.log4j.Level
import org.apache.log4j.Logger
import org.apache.log4j.PatternLayout
import org.jetbrains.jps.ModuleChunk
import org.jetbrains.jps.api.CanceledStatus
import org.jetbrains.jps.builders.BuildResult
import org.jetbrains.jps.builders.CompileScopeTestBuilder
import org.jetbrains.jps.builders.impl.BuildDataPathsImpl
import org.jetbrains.jps.builders.impl.logging.ProjectBuilderLoggerBase
import org.jetbrains.jps.builders.java.dependencyView.Callbacks
import org.jetbrains.jps.builders.logging.BuildLoggingManager
import org.jetbrains.jps.cmdline.ProjectDescriptor
import org.jetbrains.jps.incremental.*
import org.jetbrains.jps.incremental.messages.BuildMessage
import org.jetbrains.jps.model.JpsDummyElement
import org.jetbrains.jps.model.JpsModuleRootModificationUtil
import org.jetbrains.jps.model.java.JpsJavaExtensionService
import org.jetbrains.jps.model.library.sdk.JpsSdk
import org.jetbrains.jps.util.JpsPathUtil
import org.jetbrains.kotlin.cli.common.KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
import org.jetbrains.kotlin.cli.common.arguments.K2MetadataCompilerArguments
import org.jetbrains.kotlin.incremental.LookupSymbol
import org.jetbrains.kotlin.incremental.testingUtils.*
import org.jetbrains.kotlin.jps.build.dependeciestxt.ModulesTxt
import org.jetbrains.kotlin.jps.build.dependeciestxt.ModulesTxtBuilder
import org.jetbrains.kotlin.jps.build.fixtures.EnableICFixture
import org.jetbrains.kotlin.jps.incremental.*
import org.jetbrains.kotlin.jps.model.JpsKotlinFacetModuleExtension
import org.jetbrains.kotlin.jps.model.kotlinFacet
import org.jetbrains.kotlin.jps.targets.KotlinModuleBuildTarget
import org.jetbrains.kotlin.platform.idePlatformKind
import org.jetbrains.kotlin.platform.impl.isJavaScript
import org.jetbrains.kotlin.platform.impl.isJvm
import org.jetbrains.kotlin.platform.orDefault
import org.jetbrains.kotlin.utils.Printer
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.PrintStream
import java.util.*
import kotlin.reflect.jvm.javaField
abstract class AbstractIncrementalJpsTest(
private val allowNoFilesWithSuffixInTestData: Boolean = false,
private val checkDumpsCaseInsensitively: Boolean = false
) : BaseKotlinJpsBuildTestCase() {
companion object {
private val COMPILATION_FAILED = "COMPILATION FAILED"
// change to "/tmp" or anything when default is too long (for easier debugging)
private val TEMP_DIRECTORY_TO_USE = File(FileUtilRt.getTempDirectory())
private val DEBUG_LOGGING_ENABLED = System.getProperty("debug.logging.enabled") == "true"
}
protected lateinit var testDataDir: File
protected lateinit var workDir: File
protected lateinit var projectDescriptor: ProjectDescriptor
// is used to compare lookup dumps in a human readable way (lookup symbols are hashed in an actual lookup storage)
protected lateinit var lookupsDuringTest: MutableSet<LookupSymbol>
private var isJvmICEnabledBackup: Boolean = false
private var isJsICEnabledBackup: Boolean = false
protected var mapWorkingToOriginalFile: MutableMap<File, File> = hashMapOf()
lateinit var kotlinCompileContext: KotlinCompileContext
protected open val buildLogFinder: BuildLogFinder
get() = BuildLogFinder()
private fun enableDebugLogging() {
com.intellij.openapi.diagnostic.Logger.setFactory(TestLoggerFactory::class.java)
TestLoggerFactory.dumpLogToStdout("")
TestLoggerFactory.enableDebugLogging(testRootDisposable, "#org")
val console = ConsoleAppender()
console.layout = PatternLayout("%d [%p|%c|%C{1}] %m%n")
console.threshold = Level.ALL
console.activateOptions()
Logger.getRootLogger().addAppender(console)
}
private var systemPropertiesBackup = run {
val props = System.getProperties()
val output = ByteArrayOutputStream()
props.store(output, "System properties backup")
output.toByteArray()
}
private fun restoreSystemProperties() {
val input = ByteArrayInputStream(systemPropertiesBackup)
val props = Properties()
props.load(input)
System.setProperties(props)
}
private val enableICFixture = EnableICFixture()
override fun setUp() {
super.setUp()
enableICFixture.setUp()
lookupsDuringTest = hashSetOf()
System.setProperty(KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY, "true")
if (DEBUG_LOGGING_ENABLED) {
enableDebugLogging()
}
}
override fun tearDown() {
restoreSystemProperties()
(AbstractIncrementalJpsTest::myProject).javaField!![this] = null
(AbstractIncrementalJpsTest::projectDescriptor).javaField!![this] = null
(AbstractIncrementalJpsTest::systemPropertiesBackup).javaField!![this] = null
lookupsDuringTest.clear()
enableICFixture.tearDown()
super.tearDown()
}
// JPS forces rebuild of all files when JVM constant has been changed and Callbacks.ConstantAffectionResolver
// is not provided, so ConstantAffectionResolver is mocked with empty implementation
// Usages in Kotlin files are expected to be found by KotlinLookupConstantSearch
protected open val mockConstantSearch: Callbacks.ConstantAffectionResolver?
get() = MockJavaConstantSearch(workDir)
private fun build(
name: String?,
scope: CompileScopeTestBuilder = CompileScopeTestBuilder.make().allModules()
): MakeResult {
val workDirPath = FileUtil.toSystemIndependentName(workDir.absolutePath)
val logger = MyLogger(workDirPath)
projectDescriptor = createProjectDescriptor(BuildLoggingManager(logger))
val lookupTracker = TestLookupTracker()
val testingContext = TestingContext(lookupTracker, logger)
projectDescriptor.project.setTestingContext(testingContext)
try {
val builder = IncProjectBuilder(
projectDescriptor,
BuilderRegistry.getInstance(),
myBuildParams,
CanceledStatus.NULL,
true
)
val buildResult = BuildResult()
builder.addMessageHandler(buildResult)
val finalScope = scope.build()
builder.build(finalScope, false)
// testingContext.kotlinCompileContext is initialized in KotlinBuilder.initializeKotlinContext
kotlinCompileContext = testingContext.kotlinCompileContext!!
lookupTracker.lookups.mapTo(lookupsDuringTest) { LookupSymbol(it.name, it.scopeFqName) }
if (!buildResult.isSuccessful) {
val errorMessages =
buildResult
.getMessages(BuildMessage.Kind.ERROR)
.map { it.messageText }
.map { it.replace("^.+:\\d+:\\s+".toRegex(), "").trim() }
.joinToString("\n")
return MakeResult(
log = logger.log + "$COMPILATION_FAILED\n" + errorMessages + "\n",
makeFailed = true,
mappingsDump = null,
name = name
)
} else {
return MakeResult(
log = logger.log,
makeFailed = false,
mappingsDump = createMappingsDump(projectDescriptor, kotlinCompileContext, lookupsDuringTest),
name = name
)
}
} finally {
projectDescriptor.dataManager.flush(false)
projectDescriptor.release()
}
}
private fun initialMake(): MakeResult {
val makeResult = build(null)
val initBuildLogFile = File(testDataDir, "init-build.log")
if (initBuildLogFile.exists()) {
UsefulTestCase.assertSameLinesWithFile(initBuildLogFile.absolutePath, makeResult.log)
} else {
assertFalse("Initial make failed:\n$makeResult", makeResult.makeFailed)
}
return makeResult
}
private fun make(name: String?): MakeResult {
return build(name)
}
private fun rebuild(): MakeResult {
return build(null, CompileScopeTestBuilder.rebuild().allModules())
}
private fun rebuildAndCheckOutput(makeOverallResult: MakeResult) {
val outDir = File(getAbsolutePath("out"))
val outAfterMake = File(getAbsolutePath("out-after-make"))
if (outDir.exists()) {
FileUtil.copyDir(outDir, outAfterMake)
}
val rebuildResult = rebuild()
assertEquals(
"Rebuild failed: ${rebuildResult.makeFailed}, last make failed: ${makeOverallResult.makeFailed}. Rebuild result: $rebuildResult",
rebuildResult.makeFailed, makeOverallResult.makeFailed
)
if (!outAfterMake.exists()) {
assertFalse(outDir.exists())
} else {
assertEqualDirectories(outDir, outAfterMake, makeOverallResult.makeFailed)
}
if (!makeOverallResult.makeFailed) {
if (checkDumpsCaseInsensitively && rebuildResult.mappingsDump?.toLowerCase() == makeOverallResult.mappingsDump?.toLowerCase()) {
// do nothing
} else {
TestCase.assertEquals(rebuildResult.mappingsDump, makeOverallResult.mappingsDump)
}
}
FileUtil.delete(outAfterMake)
}
private fun clearCachesRebuildAndCheckOutput(makeOverallResult: MakeResult) {
FileUtil.delete(BuildDataPathsImpl(myDataStorageRoot).dataStorageRoot!!)
rebuildAndCheckOutput(makeOverallResult)
}
open val modulesTxtFile
get() = File(testDataDir, "dependencies.txt")
private fun readModulesTxt(): ModulesTxt? {
var actualModulesTxtFile = modulesTxtFile
if (!actualModulesTxtFile.exists()) {
// also try `"_${fileName}.txt"`. Useful for sorting files in IDE.
actualModulesTxtFile = modulesTxtFile.parentFile.resolve("_" + modulesTxtFile.name)
if (!actualModulesTxtFile.exists()) return null
}
return ModulesTxtBuilder().readFile(actualModulesTxtFile)
}
protected open fun createBuildLog(incrementalMakeResults: List<AbstractIncrementalJpsTest.MakeResult>): String =
buildString {
incrementalMakeResults.forEachIndexed { i, makeResult ->
if (i > 0) append("\n")
if (makeResult.name != null) {
append("================ Step #${i + 1} ${makeResult.name} =================\n\n")
} else {
append("================ Step #${i + 1} =================\n\n")
}
append(makeResult.log)
}
}
protected open fun doTest(testDataPath: String) {
testDataDir = File(testDataPath)
workDir = FileUtilRt.createTempDirectory(TEMP_DIRECTORY_TO_USE, "jps-build", null)
val buildLogFile = buildLogFinder.findBuildLog(testDataDir)
Disposer.register(testRootDisposable, Disposable { FileUtilRt.delete(workDir) })
val modulesTxt = configureModules()
if (modulesTxt?.muted == true) return
initialMake()
val otherMakeResults = performModificationsAndMake(
modulesTxt?.modules?.map { it.name },
hasBuildLog = buildLogFile != null
)
buildLogFile?.let {
val logs = createBuildLog(otherMakeResults)
UsefulTestCase.assertSameLinesWithFile(buildLogFile.absolutePath, logs)
val lastMakeResult = otherMakeResults.last()
clearCachesRebuildAndCheckOutput(lastMakeResult)
}
}
protected data class MakeResult(
val log: String,
val makeFailed: Boolean,
val mappingsDump: String?,
val name: String? = null
)
open val testDataSrc: File
get() = testDataDir
private fun performModificationsAndMake(
moduleNames: Collection<String>?,
hasBuildLog: Boolean
): List<MakeResult> {
val results = arrayListOf<MakeResult>()
val modifications = getModificationsToPerform(
testDataSrc,
moduleNames,
allowNoFilesWithSuffixInTestData = allowNoFilesWithSuffixInTestData || !hasBuildLog,
touchPolicy = TouchPolicy.TIMESTAMP
)
if (!hasBuildLog) {
check(modifications.size == 1 && modifications.single().isEmpty()) {
"Bad test data: build steps are provided, but there is no `build.log` file"
}
return results
}
val stepsTxt = File(testDataSrc, "_steps.txt")
val modificationNames = if (stepsTxt.exists()) stepsTxt.readLines() else null
modifications.forEachIndexed { index, step ->
step.forEach { it.perform(workDir, mapWorkingToOriginalFile) }
performAdditionalModifications(step)
if (moduleNames == null) {
preProcessSources(File(workDir, "src"))
} else {
moduleNames.forEach { preProcessSources(File(workDir, "$it/src")) }
}
val name = modificationNames?.getOrNull(index)
val makeResult = make(name)
results.add(makeResult)
}
return results
}
protected open fun performAdditionalModifications(modifications: List<Modification>) {
}
protected open fun generateModuleSources(modulesTxt: ModulesTxt) = Unit
// null means one module
private fun configureModules(): ModulesTxt? {
JpsJavaExtensionService.getInstance().getOrCreateProjectExtension(myProject).outputUrl =
JpsPathUtil.pathToUrl(getAbsolutePath("out"))
val jdk = addJdk("my jdk")
val modulesTxt = readModulesTxt()
mapWorkingToOriginalFile = hashMapOf()
if (modulesTxt == null) configureSingleModuleProject(jdk)
else configureMultiModuleProject(modulesTxt, jdk)
overrideModuleSettings()
configureRequiredLibraries()
return modulesTxt
}
open fun overrideModuleSettings() {
}
private fun configureSingleModuleProject(jdk: JpsSdk<JpsDummyElement>?) {
addModule("module", arrayOf(getAbsolutePath("src")), null, null, jdk)
val sourceDestinationDir = File(workDir, "src")
val sourcesMapping = copyTestSources(testDataDir, File(workDir, "src"), "")
mapWorkingToOriginalFile.putAll(sourcesMapping)
preProcessSources(sourceDestinationDir)
}
protected open val ModulesTxt.Module.sourceFilePrefix: String
get() = "${name}_"
private fun configureMultiModuleProject(
modulesTxt: ModulesTxt,
jdk: JpsSdk<JpsDummyElement>?
) {
modulesTxt.modules.forEach { module ->
module.jpsModule = addModule(
module.name,
arrayOf(getAbsolutePath("${module.name}/src")),
null,
null,
jdk
)!!
val kotlinFacetSettings = module.kotlinFacetSettings
if (kotlinFacetSettings != null) {
val compilerArguments = kotlinFacetSettings.compilerArguments
if (compilerArguments is K2MetadataCompilerArguments) {
val out = getAbsolutePath("${module.name}/out")
File(out).mkdirs()
compilerArguments.destination = out
} else if (compilerArguments is K2JVMCompilerArguments) {
compilerArguments.disableDefaultScriptingPlugin = true
}
module.jpsModule.container.setChild(
JpsKotlinFacetModuleExtension.KIND,
JpsKotlinFacetModuleExtension(kotlinFacetSettings)
)
}
}
modulesTxt.dependencies.forEach {
JpsModuleRootModificationUtil.addDependency(
it.from.jpsModule, it.to.jpsModule,
it.scope, it.exported
)
}
// configure module contents
generateModuleSources(modulesTxt)
modulesTxt.modules.forEach { module ->
val sourceDirName = "${module.name}/src"
val sourceDestinationDir = File(workDir, sourceDirName)
val sourcesMapping = copyTestSources(testDataSrc, sourceDestinationDir, module.sourceFilePrefix)
mapWorkingToOriginalFile.putAll(sourcesMapping)
preProcessSources(sourceDestinationDir)
}
}
private fun configureRequiredLibraries() {
myProject.modules.forEach { module ->
val platformKind = module.kotlinFacet?.settings?.targetPlatform?.idePlatformKind.orDefault()
when {
platformKind.isJvm -> {
JpsModuleRootModificationUtil.addDependency(module, requireLibrary(KotlinJpsLibrary.JvmStdLib))
JpsModuleRootModificationUtil.addDependency(module, requireLibrary(KotlinJpsLibrary.JvmTest))
}
platformKind.isJavaScript -> {
JpsModuleRootModificationUtil.addDependency(module, requireLibrary(KotlinJpsLibrary.JsStdLib))
JpsModuleRootModificationUtil.addDependency(module, requireLibrary(KotlinJpsLibrary.JsTest))
}
}
}
}
protected open fun preProcessSources(srcDir: File) {
}
override fun doGetProjectDir(): File? = workDir
internal class MyLogger(val rootPath: String) : ProjectBuilderLoggerBase(), TestingBuildLogger {
private val markedDirtyBeforeRound = ArrayList<File>()
private val markedDirtyAfterRound = ArrayList<File>()
private val customMessages = mutableListOf<String>()
override fun invalidOrUnusedCache(
chunk: KotlinChunk?,
target: KotlinModuleBuildTarget<*>?,
attributesDiff: CacheAttributesDiff<*>
) {
val cacheManager = attributesDiff.manager
val cacheTitle = when (cacheManager) {
is CacheVersionManager -> "Local cache for ${chunk ?: target}"
is CompositeLookupsCacheAttributesManager -> "Lookups cache"
else -> error("Unknown cache manager $cacheManager")
}
logLine("$cacheTitle are ${attributesDiff.status}")
}
override fun markedAsDirtyBeforeRound(files: Iterable<File>) {
markedDirtyBeforeRound.addAll(files)
}
override fun markedAsDirtyAfterRound(files: Iterable<File>) {
markedDirtyAfterRound.addAll(files)
}
override fun chunkBuildStarted(context: CompileContext, chunk: ModuleChunk) {
logDirtyFiles(markedDirtyBeforeRound) // files can be marked as dirty during build start (KotlinCompileContext initialization)
if (!chunk.isDummy(context) && context.projectDescriptor.project.modules.size > 1) {
logLine("Building ${chunk.modules.sortedBy { it.name }.joinToString { it.name }}")
}
}
override fun afterChunkBuildStarted(context: CompileContext, chunk: ModuleChunk) {
logDirtyFiles(markedDirtyBeforeRound)
}
override fun addCustomMessage(message: String) {
customMessages.add(message)
}
override fun buildFinished(exitCode: ModuleLevelBuilder.ExitCode) {
customMessages.forEach {
logLine(it)
}
customMessages.clear()
logDirtyFiles(markedDirtyAfterRound)
logLine("Exit code: $exitCode")
logLine("------------------------------------------")
}
private fun logDirtyFiles(files: MutableList<File>) {
if (files.isEmpty()) return
logLine("Marked as dirty by Kotlin:")
files.apply {
map { FileUtil.toSystemIndependentName(it.path) }
.sorted()
.forEach { logLine(it) }
clear()
}
}
private val logBuf = StringBuilder()
val log: String
get() = logBuf.toString()
val compiledFiles = hashSetOf<File>()
override fun isEnabled(): Boolean = true
override fun logCompiledFiles(files: MutableCollection<File>?, builderName: String?, description: String?) {
super.logCompiledFiles(files, builderName, description)
if (builderName == KotlinBuilder.KOTLIN_BUILDER_NAME) {
compiledFiles.addAll(files!!)
}
}
override fun logLine(message: String?) {
logBuf.append(message!!.replace("^$rootPath/".toRegex(), " ")).append('\n')
}
}
}
private fun createMappingsDump(
project: ProjectDescriptor,
kotlinContext: KotlinCompileContext,
lookupsDuringTest: Set<LookupSymbol>
) = createKotlinCachesDump(project, kotlinContext, lookupsDuringTest) + "\n\n\n" +
createCommonMappingsDump(project) + "\n\n\n" +
createJavaMappingsDump(project)
internal fun createKotlinCachesDump(
project: ProjectDescriptor,
kotlinContext: KotlinCompileContext,
lookupsDuringTest: Set<LookupSymbol>
) = createKotlinIncrementalCacheDump(project, kotlinContext) + "\n\n\n" +
createLookupCacheDump(kotlinContext, lookupsDuringTest)
private fun createKotlinIncrementalCacheDump(
project: ProjectDescriptor,
kotlinContext: KotlinCompileContext
): String {
return buildString {
for (target in project.allModuleTargets.sortedBy { it.presentableName }) {
val kotlinCache = project.dataManager.getKotlinCache(kotlinContext.targetsBinding[target])
if (kotlinCache != null) {
append("<target $target>\n")
append(kotlinCache.dump())
append("</target $target>\n\n\n")
}
}
}
}
private fun createLookupCacheDump(kotlinContext: KotlinCompileContext, lookupsDuringTest: Set<LookupSymbol>): String {
val sb = StringBuilder()
val p = Printer(sb)
p.println("Begin of Lookup Maps")
p.println()
kotlinContext.lookupStorageManager.withLookupStorage { lookupStorage ->
lookupStorage.forceGC()
p.print(lookupStorage.dump(lookupsDuringTest))
}
p.println()
p.println("End of Lookup Maps")
return sb.toString()
}
private fun createCommonMappingsDump(project: ProjectDescriptor): String {
val resultBuf = StringBuilder()
val result = Printer(resultBuf)
result.println("Begin of SourceToOutputMap")
result.pushIndent()
for (target in project.allModuleTargets) {
result.println(target)
result.pushIndent()
val mapping = project.dataManager.getSourceToOutputMap(target)
mapping.sources.sorted().forEach {
val outputs = mapping.getOutputs(it)!!.sorted()
if (outputs.isNotEmpty()) {
result.println("source $it -> $outputs")
}
}
result.popIndent()
}
result.popIndent()
result.println("End of SourceToOutputMap")
return resultBuf.toString()
}
private fun createJavaMappingsDump(project: ProjectDescriptor): String {
val byteArrayOutputStream = ByteArrayOutputStream()
PrintStream(byteArrayOutputStream).use {
project.dataManager.mappings.toStream(it)
}
return byteArrayOutputStream.toString()
}
internal val ProjectDescriptor.allModuleTargets: Collection<ModuleBuildTarget>
get() = buildTargetIndex.allTargets.filterIsInstance<ModuleBuildTarget>()
private val EXPORTED_SUFFIX = "[exported]"