202: Fix compatibility with 202 IDEA branch (code)
This commit is contained in:
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
+68
@@ -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() {}
|
||||||
|
}
|
||||||
+89
@@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
+1236
File diff suppressed because it is too large
Load Diff
+101
@@ -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
|
||||||
+151
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+345
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
+1189
File diff suppressed because it is too large
Load Diff
+174
@@ -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()
|
||||||
|
}
|
||||||
+58
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+188
@@ -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
|
||||||
|
}
|
||||||
+125
@@ -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)
|
||||||
|
}
|
||||||
+561
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
+56
@@ -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
|
||||||
|
}
|
||||||
+660
@@ -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]"
|
||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user