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