Data flow to/from here: supported expected/actual declarations and extension receivers
This commit is contained in:
@@ -124,6 +124,7 @@ import org.jetbrains.kotlin.idea.script.AbstractScriptConfigurationHighlightingT
|
||||
import org.jetbrains.kotlin.idea.script.AbstractScriptConfigurationNavigationTest
|
||||
import org.jetbrains.kotlin.idea.script.AbstractScriptDefinitionsOrderTest
|
||||
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerLeafGroupingTest
|
||||
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerMultiplatformTest
|
||||
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerNullnessGroupingTest
|
||||
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerTreeTest
|
||||
import org.jetbrains.kotlin.idea.structureView.AbstractKotlinFileStructureTest
|
||||
@@ -872,7 +873,7 @@ fun main(args: Array<String>) {
|
||||
}
|
||||
|
||||
testClass<AbstractSlicerTreeTest> {
|
||||
model("slicer", singleClass = true)
|
||||
model("slicer", excludeDirs = listOf("mpp"))
|
||||
}
|
||||
|
||||
testClass<AbstractSlicerLeafGroupingTest> {
|
||||
@@ -882,6 +883,10 @@ fun main(args: Array<String>) {
|
||||
testClass<AbstractSlicerNullnessGroupingTest> {
|
||||
model("slicer/inflow", singleClass = true)
|
||||
}
|
||||
|
||||
testClass<AbstractSlicerMultiplatformTest> {
|
||||
model("slicer/mpp", recursive = false, extension = null)
|
||||
}
|
||||
}
|
||||
|
||||
testGroup("idea/idea-fir/tests", "idea/testData") {
|
||||
|
||||
@@ -126,6 +126,7 @@ import org.jetbrains.kotlin.idea.script.AbstractScriptDefinitionsOrderTest
|
||||
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerLeafGroupingTest
|
||||
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerNullnessGroupingTest
|
||||
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerTreeTest
|
||||
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerMultiplatformTest
|
||||
import org.jetbrains.kotlin.idea.structureView.AbstractKotlinFileStructureTest
|
||||
import org.jetbrains.kotlin.idea.stubs.AbstractMultiFileHighlightingTest
|
||||
import org.jetbrains.kotlin.idea.stubs.AbstractResolveByStubTest
|
||||
@@ -821,7 +822,7 @@ fun main(args: Array<String>) {
|
||||
}
|
||||
|
||||
testClass<AbstractSlicerTreeTest> {
|
||||
model("slicer", singleClass = true)
|
||||
model("slicer", excludeDirs = listOf("mpp"))
|
||||
}
|
||||
|
||||
testClass<AbstractSlicerLeafGroupingTest> {
|
||||
@@ -831,6 +832,10 @@ fun main(args: Array<String>) {
|
||||
testClass<AbstractSlicerNullnessGroupingTest> {
|
||||
model("slicer/inflow", singleClass = true)
|
||||
}
|
||||
|
||||
testClass<AbstractSlicerMultiplatformTest> {
|
||||
model("slicer/mpp", recursive = false, extension = null)
|
||||
}
|
||||
}
|
||||
|
||||
testGroup("idea/scripting-support/test", "idea/scripting-support/testData") {
|
||||
|
||||
@@ -126,6 +126,7 @@ import org.jetbrains.kotlin.idea.script.AbstractScriptDefinitionsOrderTest
|
||||
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerLeafGroupingTest
|
||||
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerNullnessGroupingTest
|
||||
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerTreeTest
|
||||
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerMultiplatformTest
|
||||
import org.jetbrains.kotlin.idea.structureView.AbstractKotlinFileStructureTest
|
||||
import org.jetbrains.kotlin.idea.stubs.AbstractMultiFileHighlightingTest
|
||||
import org.jetbrains.kotlin.idea.stubs.AbstractResolveByStubTest
|
||||
@@ -821,7 +822,7 @@ fun main(args: Array<String>) {
|
||||
}
|
||||
|
||||
testClass<AbstractSlicerTreeTest> {
|
||||
model("slicer", singleClass = true)
|
||||
model("slicer", excludeDirs = listOf("mpp"))
|
||||
}
|
||||
|
||||
testClass<AbstractSlicerLeafGroupingTest> {
|
||||
@@ -831,6 +832,10 @@ fun main(args: Array<String>) {
|
||||
testClass<AbstractSlicerNullnessGroupingTest> {
|
||||
model("slicer/inflow", singleClass = true)
|
||||
}
|
||||
|
||||
testClass<AbstractSlicerMultiplatformTest> {
|
||||
model("slicer/mpp", recursive = false, extension = null)
|
||||
}
|
||||
}
|
||||
|
||||
testGroup("idea/scripting-support/test", "idea/scripting-support/testData") {
|
||||
|
||||
@@ -126,6 +126,7 @@ import org.jetbrains.kotlin.idea.script.AbstractScriptDefinitionsOrderTest
|
||||
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerLeafGroupingTest
|
||||
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerNullnessGroupingTest
|
||||
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerTreeTest
|
||||
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerMultiplatformTest
|
||||
import org.jetbrains.kotlin.idea.structureView.AbstractKotlinFileStructureTest
|
||||
import org.jetbrains.kotlin.idea.stubs.AbstractMultiFileHighlightingTest
|
||||
import org.jetbrains.kotlin.idea.stubs.AbstractResolveByStubTest
|
||||
@@ -821,7 +822,7 @@ fun main(args: Array<String>) {
|
||||
}
|
||||
|
||||
testClass<AbstractSlicerTreeTest> {
|
||||
model("slicer", singleClass = true)
|
||||
model("slicer", excludeDirs = listOf("mpp"))
|
||||
}
|
||||
|
||||
testClass<AbstractSlicerLeafGroupingTest> {
|
||||
@@ -831,6 +832,10 @@ fun main(args: Array<String>) {
|
||||
testClass<AbstractSlicerNullnessGroupingTest> {
|
||||
model("slicer/inflow", singleClass = true)
|
||||
}
|
||||
|
||||
testClass<AbstractSlicerMultiplatformTest> {
|
||||
model("slicer/mpp", recursive = false, extension = null)
|
||||
}
|
||||
}
|
||||
|
||||
testGroup("idea/scripting-support/test", "idea/scripting-support/testData") {
|
||||
|
||||
@@ -18,7 +18,9 @@ import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.*
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.ReturnValueInstruction
|
||||
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraversalOrder
|
||||
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.traverse
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource
|
||||
import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.VariableDescriptorWithAccessors
|
||||
import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.*
|
||||
@@ -30,6 +32,8 @@ import org.jetbrains.kotlin.idea.references.ReferenceAccess
|
||||
import org.jetbrains.kotlin.idea.references.readWriteAccessWithFullExpression
|
||||
import org.jetbrains.kotlin.idea.search.declarationsSearch.HierarchySearchRequest
|
||||
import org.jetbrains.kotlin.idea.search.declarationsSearch.searchOverriders
|
||||
import org.jetbrains.kotlin.idea.util.actualsForExpected
|
||||
import org.jetbrains.kotlin.idea.util.isExpectDeclaration
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
|
||||
@@ -45,23 +49,34 @@ import org.jetbrains.kotlin.resolve.source.getPsi
|
||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||
|
||||
class InflowSlicer(
|
||||
element: KtExpression,
|
||||
element: KtElement,
|
||||
processor: SliceUsageProcessor,
|
||||
parentUsage: KotlinSliceUsage
|
||||
) : Slicer(element, processor, parentUsage) {
|
||||
|
||||
override fun processChildren() {
|
||||
if (parentUsage.forcedExpressionMode) {
|
||||
processExpression(element)
|
||||
(element as? KtExpression)?.let { processExpression(it) }
|
||||
return
|
||||
}
|
||||
|
||||
when (element) {
|
||||
is KtProperty -> processProperty(element)
|
||||
// for parameter, we include overriders only when the feature is invoked on parameter itself
|
||||
|
||||
// include overriders only when invoked on the parameter declaration
|
||||
is KtParameter -> processParameter(parameter = element, includeOverriders = parentUsage.parent == null)
|
||||
|
||||
is KtDeclarationWithBody -> element.processBody()
|
||||
else -> processExpression(element)
|
||||
|
||||
is KtTypeReference -> {
|
||||
val parent = element.parent
|
||||
require(parent is KtCallableDeclaration)
|
||||
require(element == parent.receiverTypeReference)
|
||||
// include overriders only when invoked on receiver type in the declaration
|
||||
processExtensionReceiver(parent, includeOverriders = parentUsage.parent == null)
|
||||
}
|
||||
|
||||
is KtExpression -> processExpression(element)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,8 +150,9 @@ class InflowSlicer(
|
||||
}
|
||||
|
||||
fun processCall(usageInfo: UsageInfo) {
|
||||
val refElement = usageInfo.element ?: return
|
||||
extractArgumentExpression(refElement)?.passToProcessorAsValue()
|
||||
usageInfo.element?.let {
|
||||
extractArgumentExpression(it)?.passToProcessorAsValue()
|
||||
}
|
||||
}
|
||||
|
||||
processCalls(function, analysisScope, includeOverriders, ::processCall)
|
||||
@@ -147,6 +163,31 @@ class InflowSlicer(
|
||||
}
|
||||
}
|
||||
|
||||
private fun processExtensionReceiver(declaration: KtCallableDeclaration, includeOverriders: Boolean) {
|
||||
fun extractReceiverExpression(refElement: PsiElement): PsiElement? {
|
||||
return when (refElement) {
|
||||
is KtExpression -> {
|
||||
val resolvedCall = refElement.resolveToCall() ?: return null
|
||||
//TODO: implicit receiver
|
||||
val expressionReceiver = resolvedCall.extensionReceiver as? ExpressionReceiver ?: return null
|
||||
return expressionReceiver.expression
|
||||
}
|
||||
|
||||
else -> {
|
||||
(refElement.parent as? PsiCall)?.argumentList?.expressions?.getOrNull(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun processCall(usageInfo: UsageInfo) {
|
||||
usageInfo.element?.let {
|
||||
extractReceiverExpression(it)?.passToProcessorAsValue()
|
||||
}
|
||||
}
|
||||
|
||||
processCalls(declaration, analysisScope, includeOverriders, ::processCall)
|
||||
}
|
||||
|
||||
private fun processExpression(expression: KtExpression) {
|
||||
val lambda = when (expression) {
|
||||
is KtLambdaExpression -> expression.functionLiteral
|
||||
@@ -171,18 +212,30 @@ class InflowSlicer(
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
val accessedDescriptor = createdAt.target.accessedDescriptor ?: return
|
||||
val accessedDeclaration = accessedDescriptor.originalSource.getPsi() ?: return
|
||||
if (accessedDescriptor is SyntheticFieldDescriptor) {
|
||||
val property = accessedDeclaration as? KtProperty ?: return
|
||||
if (accessedDescriptor.propertyDescriptor.setter?.isDefault != false) {
|
||||
property.processPropertyAssignments()
|
||||
} else {
|
||||
property.setter?.processBackingFieldAssignments()
|
||||
val accessedDeclaration = accessedDescriptor.originalSource.getPsi()
|
||||
when (accessedDescriptor) {
|
||||
is SyntheticFieldDescriptor -> {
|
||||
val property = accessedDeclaration as? KtProperty ?: return
|
||||
if (accessedDescriptor.propertyDescriptor.setter?.isDefault != false) {
|
||||
property.processPropertyAssignments()
|
||||
} else {
|
||||
property.setter?.processBackingFieldAssignments()
|
||||
}
|
||||
}
|
||||
|
||||
is ReceiverParameterDescriptor -> {
|
||||
val callable = accessedDescriptor.containingDeclaration as? CallableDescriptor ?: return
|
||||
val callableDeclaration = callable.originalSource.getPsi() as? KtCallableDeclaration ?: return
|
||||
//TODO: what about non-extensions?
|
||||
callableDeclaration.receiverTypeReference?.passToProcessor()
|
||||
}
|
||||
|
||||
else -> {
|
||||
accessedDeclaration?.passDeclarationToProcessorWithOverriders()
|
||||
}
|
||||
return
|
||||
}
|
||||
accessedDeclaration.passDeclarationToProcessorWithOverriders()
|
||||
}
|
||||
|
||||
is MergeInstruction -> createdAt.passInputsToProcessor()
|
||||
@@ -287,8 +340,17 @@ class InflowSlicer(
|
||||
|
||||
private fun PsiElement.passDeclarationToProcessorWithOverriders() {
|
||||
passToProcessor()
|
||||
|
||||
HierarchySearchRequest(this, analysisScope)
|
||||
.searchOverriders()
|
||||
.forEach { it.namedUnwrappedElement?.passToProcessor() }
|
||||
|
||||
if (this is KtCallableDeclaration && isExpectDeclaration()) {
|
||||
resolveToDescriptorIfAny(BodyResolveMode.FULL)
|
||||
?.actualsForExpected()
|
||||
?.forEach {
|
||||
(it as? DeclarationDescriptorWithSource)?.originalSource?.getPsi()?.passToProcessor()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import com.intellij.codeInsight.Nullability
|
||||
import com.intellij.ide.util.treeView.AbstractTreeStructure
|
||||
import com.intellij.openapi.actionSystem.DefaultActionGroup
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.util.parentOfType
|
||||
import com.intellij.slicer.*
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
@@ -30,6 +31,7 @@ import org.jetbrains.kotlin.idea.references.mainReference
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isPlainWithEscapes
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
|
||||
import org.jetbrains.kotlin.psi2ir.deparenthesize
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
import org.jetbrains.kotlin.types.isError
|
||||
@@ -73,26 +75,34 @@ class KotlinSliceProvider : SliceLanguageSupportProvider, SliceUsageTransformer
|
||||
return listOf(KotlinSliceUsage(usage.element, usage.parent, 0, false))
|
||||
}
|
||||
|
||||
override fun getExpressionAtCaret(atCaret: PsiElement, dataFlowToThis: Boolean): KtExpression? {
|
||||
val element =
|
||||
atCaret.parentsWithSelf
|
||||
.firstOrNull {
|
||||
it is KtProperty ||
|
||||
it is KtParameter ||
|
||||
it is KtDeclarationWithBody ||
|
||||
(it is KtClass && !it.hasExplicitPrimaryConstructor()) ||
|
||||
(it is KtExpression && it !is KtDeclaration)
|
||||
}
|
||||
?.let { KtPsiUtil.safeDeparenthesize(it as KtExpression) } ?: return null
|
||||
override fun getExpressionAtCaret(atCaret: PsiElement, dataFlowToThis: Boolean): KtElement? {
|
||||
val element = atCaret.parentsWithSelf
|
||||
.filterIsInstance<KtElement>()
|
||||
.firstOrNull(::isSliceElement)
|
||||
?.deparenthesize() ?: return null
|
||||
|
||||
if (dataFlowToThis) {
|
||||
if (element is KtConstantExpression) return null
|
||||
if (element is KtStringTemplateExpression && element.isPlainWithEscapes()) return null
|
||||
if (element is KtClassLiteralExpression) return null
|
||||
if (element is KtCallableReferenceExpression) return null
|
||||
}
|
||||
|
||||
return element
|
||||
}
|
||||
|
||||
private fun isSliceElement(element: KtElement): Boolean {
|
||||
return when {
|
||||
element is KtProperty -> true
|
||||
element is KtParameter -> true
|
||||
element is KtDeclarationWithBody -> true
|
||||
element is KtClass && !element.hasExplicitPrimaryConstructor() -> true
|
||||
element is KtExpression && element !is KtDeclaration && element.parentOfType<KtTypeReference>() == null -> true
|
||||
element is KtTypeReference && element == (element.parent as? KtCallableDeclaration)?.receiverTypeReference -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun getElementForDescription(element: PsiElement): PsiElement {
|
||||
return (element as? KtSimpleNameExpression)?.mainReference?.resolve() ?: element
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.intellij.slicer.SliceAnalysisParams
|
||||
import com.intellij.slicer.SliceUsage
|
||||
import com.intellij.usageView.UsageInfo
|
||||
import org.jetbrains.kotlin.idea.findUsages.handlers.SliceUsageProcessor
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
|
||||
open class KotlinSliceUsage : SliceUsage {
|
||||
@@ -58,10 +59,10 @@ open class KotlinSliceUsage : SliceUsage {
|
||||
override fun canBeLeaf() = element != null && lambdaLevel == 0
|
||||
|
||||
public override fun processUsagesFlownDownTo(element: PsiElement, uniqueProcessor: SliceUsageProcessor) {
|
||||
InflowSlicer(element as? KtExpression ?: return, uniqueProcessor, this).processChildren()
|
||||
InflowSlicer(element as? KtElement ?: return, uniqueProcessor, this).processChildren()
|
||||
}
|
||||
|
||||
public override fun processUsagesFlownFromThe(element: PsiElement, uniqueProcessor: SliceUsageProcessor) {
|
||||
OutflowSlicer(element as? KtExpression ?: return, uniqueProcessor, this).processChildren()
|
||||
OutflowSlicer(element as? KtElement ?: return, uniqueProcessor, this).processChildren()
|
||||
}
|
||||
}
|
||||
@@ -12,33 +12,77 @@ import com.intellij.psi.impl.light.LightMemberReference
|
||||
import com.intellij.usageView.UsageInfo
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.PseudoValue
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.KtElementInstruction
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.*
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.ReturnValueInstruction
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||
import org.jetbrains.kotlin.idea.findUsages.handlers.SliceUsageProcessor
|
||||
import org.jetbrains.kotlin.idea.search.declarationsSearch.forEachOverridingElement
|
||||
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReadWriteAccessDetector
|
||||
import org.jetbrains.kotlin.idea.util.actualsForExpected
|
||||
import org.jetbrains.kotlin.idea.util.isExpectDeclaration
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parameterIndex
|
||||
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||
|
||||
class OutflowSlicer(
|
||||
element: KtExpression,
|
||||
element: KtElement,
|
||||
processor: SliceUsageProcessor,
|
||||
parentUsage: KotlinSliceUsage
|
||||
) : Slicer(element, processor, parentUsage) {
|
||||
|
||||
override fun processChildren() {
|
||||
if (parentUsage.forcedExpressionMode) return processExpression(element)
|
||||
if (parentUsage.forcedExpressionMode) {
|
||||
(element as? KtExpression)?.let { processExpression(it) }
|
||||
return
|
||||
}
|
||||
|
||||
when (element) {
|
||||
is KtProperty -> processVariable(element)
|
||||
|
||||
is KtParameter -> processVariable(element)
|
||||
|
||||
is KtFunction -> processFunction(element)
|
||||
is KtPropertyAccessor -> if (element.isGetter) processVariable(element.property)
|
||||
else -> processExpression(element)
|
||||
|
||||
is KtPropertyAccessor -> {
|
||||
if (element.isGetter) {
|
||||
processVariable(element.property)
|
||||
}
|
||||
}
|
||||
|
||||
is KtTypeReference -> {
|
||||
val declaration = element.parent
|
||||
require(declaration is KtCallableDeclaration)
|
||||
require(element == declaration.receiverTypeReference)
|
||||
|
||||
if (declaration.isExpectDeclaration()) {
|
||||
declaration.resolveToDescriptorIfAny(BodyResolveMode.FULL)
|
||||
?.actualsForExpected()
|
||||
?.forEach {
|
||||
val actualDeclaration = (it as? DeclarationDescriptorWithSource)?.originalSource?.getPsi()
|
||||
(actualDeclaration as? KtCallableDeclaration)?.receiverTypeReference?.passToProcessor()
|
||||
}
|
||||
}
|
||||
|
||||
when (declaration) {
|
||||
is KtFunction -> {
|
||||
processExtensionReceiver(declaration, declaration)
|
||||
}
|
||||
|
||||
is KtProperty -> {
|
||||
//TODO: process only one of them or both depending on the usage type
|
||||
declaration.getter?.let { processExtensionReceiver(declaration, it) }
|
||||
declaration.setter?.let { processExtensionReceiver(declaration, it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is KtExpression -> processExpression(element)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,28 +109,37 @@ class OutflowSlicer(
|
||||
if (variable is KtParameter) {
|
||||
if (!canProcessParameter(variable)) return //TODO
|
||||
|
||||
//TODO: expect/actual
|
||||
|
||||
val callable = variable.ownerFunction as? KtCallableDeclaration
|
||||
val parameterIndex = variable.parameterIndex()
|
||||
callable?.forEachOverridingElement(scope = analysisScope) { _, overridingMember ->
|
||||
when (overridingMember) {
|
||||
is KtCallableDeclaration -> {
|
||||
val parameters = overridingMember.valueParameters
|
||||
check(parameters.size == callable.valueParameters.size)
|
||||
parameters[parameterIndex].passToProcessor()
|
||||
}
|
||||
|
||||
is PsiMethod -> {
|
||||
val parameters = overridingMember.parameterList.parameters
|
||||
val shift = if (callable.receiverTypeReference != null) 1 else 0
|
||||
check(parameters.size == callable.valueParameters.size + shift)
|
||||
parameters[parameterIndex + shift].passToProcessor()
|
||||
}
|
||||
|
||||
else -> {} // not supported
|
||||
if (callable != null) {
|
||||
if (callable.isExpectDeclaration()) {
|
||||
variable.resolveToDescriptorIfAny(BodyResolveMode.FULL)
|
||||
?.actualsForExpected()
|
||||
?.forEach {
|
||||
(it as? DeclarationDescriptorWithSource)?.originalSource?.getPsi()?.passToProcessor()
|
||||
}
|
||||
}
|
||||
|
||||
val parameterIndex = variable.parameterIndex()
|
||||
callable.forEachOverridingElement(scope = analysisScope) { _, overridingMember ->
|
||||
when (overridingMember) {
|
||||
is KtCallableDeclaration -> {
|
||||
val parameters = overridingMember.valueParameters
|
||||
check(parameters.size == callable.valueParameters.size)
|
||||
parameters[parameterIndex].passToProcessor()
|
||||
}
|
||||
|
||||
is PsiMethod -> {
|
||||
val parameters = overridingMember.parameterList.parameters
|
||||
val shift = if (callable.receiverTypeReference != null) 1 else 0
|
||||
check(parameters.size == callable.valueParameters.size + shift)
|
||||
parameters[parameterIndex + shift].passToProcessor()
|
||||
}
|
||||
|
||||
else -> {} // not supported
|
||||
}
|
||||
true
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,31 +166,81 @@ class OutflowSlicer(
|
||||
is KtNamedFunction -> function
|
||||
else -> null
|
||||
} ?: return
|
||||
funExpression as PsiElement
|
||||
funExpression.passToProcessor(parentUsage.lambdaLevel + 1, true)
|
||||
}
|
||||
|
||||
private fun processExtensionReceiver(declaration: KtCallableDeclaration, declarationWithBody: KtDeclarationWithBody) {
|
||||
//TODO: overriders
|
||||
//TODO: implicit receivers
|
||||
val resolutionFacade = declaration.getResolutionFacade()
|
||||
val callableDescriptor = declaration.resolveToDescriptorIfAny(resolutionFacade) as? CallableDescriptor ?: return
|
||||
val extensionReceiver = callableDescriptor.extensionReceiverParameter ?: return
|
||||
declarationWithBody.bodyExpression?.forEachDescendantOfType<KtThisExpression> { thisExpression ->
|
||||
val receiverDescriptor = thisExpression.resolveToCall(resolutionFacade)?.resultingDescriptor
|
||||
if (receiverDescriptor == extensionReceiver) {
|
||||
thisExpression.passToProcessor()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun processExpression(expression: KtExpression) {
|
||||
expression.processPseudocodeUsages { pseudoValue, instr ->
|
||||
when (instr) {
|
||||
is WriteValueInstruction -> instr.target.accessedDescriptor?.originalSource?.getPsi()?.passToProcessor()
|
||||
is CallInstruction -> {
|
||||
if (parentUsage.lambdaLevel > 0 && instr.receiverValues[pseudoValue] != null) {
|
||||
instr.element.passToProcessor(parentUsage.lambdaLevel - 1)
|
||||
} else {
|
||||
instr.arguments[pseudoValue]?.originalSource?.getPsi()?.passToProcessor()
|
||||
expression.processPseudocodeUsages { pseudoValue, instruction ->
|
||||
when (instruction) {
|
||||
is WriteValueInstruction -> {
|
||||
if (!processIfReceiverValue(instruction, pseudoValue)) {
|
||||
instruction.target.accessedDescriptor?.originalSource?.getPsi()?.passToProcessor()
|
||||
}
|
||||
}
|
||||
is ReturnValueInstruction -> instr.subroutine.passToProcessor()
|
||||
is MagicInstruction -> when (instr.kind) {
|
||||
MagicKind.NOT_NULL_ASSERTION, MagicKind.CAST -> instr.outputValue.element?.passToProcessor()
|
||||
else -> {
|
||||
|
||||
is ReadValueInstruction -> {
|
||||
processIfReceiverValue(instruction, pseudoValue)
|
||||
}
|
||||
|
||||
is CallInstruction -> {
|
||||
if (!processIfReceiverValue(instruction, pseudoValue)) {
|
||||
instruction.arguments[pseudoValue]?.originalSource?.getPsi()?.passToProcessor()
|
||||
}
|
||||
}
|
||||
|
||||
is ReturnValueInstruction -> {
|
||||
instruction.subroutine.passToProcessor()
|
||||
}
|
||||
|
||||
is MagicInstruction -> {
|
||||
when (instruction.kind) {
|
||||
MagicKind.NOT_NULL_ASSERTION, MagicKind.CAST -> instruction.outputValue.element?.passToProcessor()
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun processIfReceiverValue(instruction: KtElementInstruction, pseudoValue: PseudoValue): Boolean {
|
||||
val receiverValue = (instruction as? InstructionWithReceivers)?.receiverValues?.get(pseudoValue) ?: return false
|
||||
val resolvedCall = instruction.element.resolveToCall() ?: return true
|
||||
when (resolvedCall.call.callType) {
|
||||
Call.CallType.DEFAULT -> {
|
||||
if (receiverValue == resolvedCall.extensionReceiver) {
|
||||
val targetDeclaration = resolvedCall.resultingDescriptor.originalSource.getPsi()
|
||||
(targetDeclaration as? KtCallableDeclaration)?.receiverTypeReference?.passToProcessor()
|
||||
}
|
||||
}
|
||||
|
||||
Call.CallType.INVOKE -> {
|
||||
if (parentUsage.lambdaLevel > 0 && receiverValue == resolvedCall.dispatchReceiver) {
|
||||
instruction.element.passToProcessor(parentUsage.lambdaLevel - 1)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
//TODO
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun PsiElement.getCallElementForExactCallee(): PsiElement? {
|
||||
if (this is KtArrayAccessExpression) return this
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.intellij.psi.PsiMethod
|
||||
import com.intellij.psi.search.SearchScope
|
||||
import com.intellij.slicer.JavaSliceUsage
|
||||
import com.intellij.usageView.UsageInfo
|
||||
import com.intellij.util.containers.addIfNotNull
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.containingDeclarationForPseudocode
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.getContainingPseudocode
|
||||
@@ -17,21 +18,22 @@ import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource
|
||||
import org.jetbrains.kotlin.descriptors.SourceElement
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithContent
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||
import org.jetbrains.kotlin.idea.core.getDeepestSuperDeclarations
|
||||
import org.jetbrains.kotlin.idea.findUsages.KotlinFunctionFindUsagesOptions
|
||||
import org.jetbrains.kotlin.idea.findUsages.KotlinPropertyFindUsagesOptions
|
||||
import org.jetbrains.kotlin.idea.findUsages.handlers.SliceUsageProcessor
|
||||
import org.jetbrains.kotlin.idea.findUsages.processAllExactUsages
|
||||
import org.jetbrains.kotlin.idea.findUsages.processAllUsages
|
||||
import org.jetbrains.kotlin.idea.util.expectedDescriptor
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.contains
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||
import java.util.*
|
||||
|
||||
abstract class Slicer(
|
||||
protected val element: KtExpression,
|
||||
protected val element: KtElement,
|
||||
protected val processor: SliceUsageProcessor,
|
||||
protected val parentUsage: KotlinSliceUsage
|
||||
) {
|
||||
@@ -62,22 +64,43 @@ abstract class Slicer(
|
||||
}
|
||||
|
||||
protected fun processCalls(
|
||||
function: KtFunction,
|
||||
callable: KtCallableDeclaration,
|
||||
scope: SearchScope,
|
||||
includeOverriders: Boolean,
|
||||
usageProcessor: (UsageInfo) -> Unit
|
||||
) {
|
||||
val options = KotlinFunctionFindUsagesOptions(project).apply {
|
||||
isSearchForTextOccurrences = false
|
||||
isSkipImportStatements = true
|
||||
searchScope = scope
|
||||
val options = when (callable) {
|
||||
is KtFunction -> {
|
||||
KotlinFunctionFindUsagesOptions(project).apply {
|
||||
isSearchForTextOccurrences = false
|
||||
isSkipImportStatements = true
|
||||
searchScope = scope
|
||||
}
|
||||
}
|
||||
|
||||
is KtProperty -> {
|
||||
KotlinPropertyFindUsagesOptions(project).apply {
|
||||
isSearchForTextOccurrences = false
|
||||
isSkipImportStatements = true
|
||||
searchScope = scope
|
||||
}
|
||||
}
|
||||
|
||||
else -> return
|
||||
}
|
||||
|
||||
val descriptor = function.unsafeResolveToDescriptor() as? CallableMemberDescriptor ?: return
|
||||
val superDescriptors = if (includeOverriders)
|
||||
val descriptor = callable.resolveToDescriptorIfAny(BodyResolveMode.FULL) as? CallableMemberDescriptor ?: return
|
||||
val superDescriptors = if (includeOverriders) {
|
||||
descriptor.getDeepestSuperDeclarations()
|
||||
else
|
||||
listOf(descriptor) + DescriptorUtils.getAllOverriddenDeclarations(descriptor)
|
||||
} else {
|
||||
mutableListOf<CallableMemberDescriptor>().apply {
|
||||
add(descriptor)
|
||||
addAll(DescriptorUtils.getAllOverriddenDeclarations(descriptor))
|
||||
if (descriptor.isActual) {
|
||||
addIfNotNull(descriptor.expectedDescriptor() as CallableMemberDescriptor?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (superDescriptor in superDescriptors) {
|
||||
val declaration = superDescriptor.originalSource.getPsi() ?: continue
|
||||
@@ -123,11 +146,17 @@ abstract class Slicer(
|
||||
}
|
||||
|
||||
val allDeclarations = mutableListOf(declaration)
|
||||
val descriptor = declaration.unsafeResolveToDescriptor()
|
||||
val descriptor = declaration.resolveToDescriptorIfAny()
|
||||
if (descriptor is CallableMemberDescriptor) {
|
||||
DescriptorUtils.getAllOverriddenDeclarations(descriptor).mapNotNullTo(allDeclarations) {
|
||||
val additionalDescriptors = if (descriptor.isActual) {
|
||||
listOfNotNull(descriptor.expectedDescriptor() as? CallableMemberDescriptor)
|
||||
} else {
|
||||
DescriptorUtils.getAllOverriddenDeclarations(descriptor)
|
||||
}
|
||||
additionalDescriptors.mapNotNullTo(allDeclarations) {
|
||||
it.originalSource.getPsi() as? KtCallableDeclaration
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
allDeclarations.forEach { it.processAllExactUsages(options, usageProcessor) }
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
class JavaClass {
|
||||
void f() {
|
||||
KotlinUtil.extensionFun("x");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// FLOW: IN
|
||||
|
||||
@file: JvmName("KotlinUtil")
|
||||
|
||||
import kotlin.jvm.JvmName
|
||||
|
||||
fun <caret>Any.extensionFun() {
|
||||
}
|
||||
|
||||
fun foo() {
|
||||
"".extensionFun()
|
||||
1.extensionFun()
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
11 <bold>""</bold>.extensionFun()
|
||||
7 fun <bold>Any</bold>.extensionFun() {
|
||||
11 <bold>""</bold>.extensionFun()
|
||||
|
||||
3 KotlinUtil.extensionFun(<bold>"x"</bold>);
|
||||
7 fun <bold>Any</bold>.extensionFun() {
|
||||
3 KotlinUtil.extensionFun(<bold>"x"</bold>);
|
||||
|
||||
12 <bold>1</bold>.extensionFun()
|
||||
7 fun <bold>Any</bold>.extensionFun() {
|
||||
12 <bold>1</bold>.extensionFun()
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
[NotNull Values]
|
||||
11 <bold>""</bold>.extensionFun()
|
||||
7 fun <bold>Any</bold>.extensionFun() {
|
||||
11 <bold>""</bold>.extensionFun()
|
||||
12 <bold>1</bold>.extensionFun()
|
||||
7 fun <bold>Any</bold>.extensionFun() {
|
||||
12 <bold>1</bold>.extensionFun()
|
||||
|
||||
[Other Values]
|
||||
3 KotlinUtil.extensionFun(<bold>"x"</bold>);
|
||||
7 fun <bold>Any</bold>.extensionFun() {
|
||||
3 KotlinUtil.extensionFun(<bold>"x"</bold>);
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
7 fun <bold>Any</bold>.extensionFun() {
|
||||
3 KotlinUtil.extensionFun(<bold>"x"</bold>);
|
||||
11 <bold>""</bold>.extensionFun()
|
||||
12 <bold>1</bold>.extensionFun()
|
||||
@@ -0,0 +1,10 @@
|
||||
// FLOW: IN
|
||||
|
||||
var <caret>Any.property: Int
|
||||
get() = 1
|
||||
set(value) { }
|
||||
|
||||
fun foo() {
|
||||
println("a".property)
|
||||
"b".property = 2
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
9 <bold>"b"</bold>.property = 2
|
||||
3 var <bold>Any</bold>.property: Int
|
||||
9 <bold>"b"</bold>.property = 2
|
||||
|
||||
8 println(<bold>"a"</bold>.property)
|
||||
3 var <bold>Any</bold>.property: Int
|
||||
8 println(<bold>"a"</bold>.property)
|
||||
@@ -0,0 +1,7 @@
|
||||
[NotNull Values]
|
||||
8 println(<bold>"a"</bold>.property)
|
||||
3 var <bold>Any</bold>.property: Int
|
||||
8 println(<bold>"a"</bold>.property)
|
||||
9 <bold>"b"</bold>.property = 2
|
||||
3 var <bold>Any</bold>.property: Int
|
||||
9 <bold>"b"</bold>.property = 2
|
||||
@@ -0,0 +1,3 @@
|
||||
3 var <bold>Any</bold>.property: Int
|
||||
8 println(<bold>"a"</bold>.property)
|
||||
9 <bold>"b"</bold>.property = 2
|
||||
@@ -0,0 +1,20 @@
|
||||
// FLOW: IN
|
||||
|
||||
open class Base {
|
||||
open fun Int.extensionFun() {
|
||||
println(<caret>this)
|
||||
}
|
||||
|
||||
fun baseF() {
|
||||
1.extensionFun()
|
||||
}
|
||||
}
|
||||
|
||||
class Derived : Base() {
|
||||
override fun Int.extensionFun() {
|
||||
}
|
||||
|
||||
fun derivedF() {
|
||||
2.extensionFun()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
9 <bold>1</bold>.extensionFun()
|
||||
5 println(<bold>this</bold>)
|
||||
4 open fun <bold>Int</bold>.extensionFun() {
|
||||
9 <bold>1</bold>.extensionFun()
|
||||
@@ -0,0 +1,3 @@
|
||||
[NotNull Values]
|
||||
5 println(<bold>this</bold>)
|
||||
5 println(<bold>this</bold>)
|
||||
@@ -0,0 +1,3 @@
|
||||
5 println(<bold>this</bold>)
|
||||
4 open fun <bold>Int</bold>.extensionFun() {
|
||||
9 <bold>1</bold>.extensionFun()
|
||||
@@ -0,0 +1,20 @@
|
||||
// FLOW: IN
|
||||
|
||||
open class Base {
|
||||
open fun <caret>Int.extensionFun() {
|
||||
println(this)
|
||||
}
|
||||
|
||||
fun baseF() {
|
||||
1.extensionFun()
|
||||
}
|
||||
}
|
||||
|
||||
class Derived : Base() {
|
||||
override fun Int.extensionFun() {
|
||||
}
|
||||
|
||||
fun derivedF() {
|
||||
2.extensionFun()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
9 <bold>1</bold>.extensionFun()
|
||||
4 open fun <bold>Int</bold>.extensionFun() {
|
||||
9 <bold>1</bold>.extensionFun()
|
||||
|
||||
18 <bold>2</bold>.extensionFun()
|
||||
4 open fun <bold>Int</bold>.extensionFun() {
|
||||
18 <bold>2</bold>.extensionFun()
|
||||
@@ -0,0 +1,7 @@
|
||||
[NotNull Values]
|
||||
9 <bold>1</bold>.extensionFun()
|
||||
4 open fun <bold>Int</bold>.extensionFun() {
|
||||
9 <bold>1</bold>.extensionFun()
|
||||
18 <bold>2</bold>.extensionFun()
|
||||
4 open fun <bold>Int</bold>.extensionFun() {
|
||||
18 <bold>2</bold>.extensionFun()
|
||||
@@ -0,0 +1,3 @@
|
||||
4 open fun <bold>Int</bold>.extensionFun() {
|
||||
9 <bold>1</bold>.extensionFun()
|
||||
18 <bold>2</bold>.extensionFun()
|
||||
@@ -0,0 +1,9 @@
|
||||
// FLOW: IN
|
||||
|
||||
fun Any.extensionFun(): Any {
|
||||
return this
|
||||
}
|
||||
|
||||
fun foo() {
|
||||
val <caret>x = "".extensionFun()
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
8 val x = <bold>""</bold>.extensionFun()
|
||||
8 val <bold>x = "".extensionFun()</bold>
|
||||
8 val x = <bold>"".extensionFun()</bold>
|
||||
3 fun Any.<bold>extensionFun(): Any {</bold>
|
||||
4 return <bold>this</bold>
|
||||
3 fun <bold>Any</bold>.extensionFun(): Any {
|
||||
8 val x = <bold>""</bold>.extensionFun()
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
[NotNull Values]
|
||||
8 val <bold>x = "".extensionFun()</bold>
|
||||
8 val <bold>x = "".extensionFun()</bold>
|
||||
@@ -0,0 +1,6 @@
|
||||
8 val <bold>x = "".extensionFun()</bold>
|
||||
8 val x = <bold>"".extensionFun()</bold>
|
||||
3 fun Any.<bold>extensionFun(): Any {</bold>
|
||||
4 return <bold>this</bold>
|
||||
3 fun <bold>Any</bold>.extensionFun(): Any {
|
||||
8 val x = <bold>""</bold>.extensionFun()
|
||||
@@ -0,0 +1,8 @@
|
||||
// FLOW: IN
|
||||
|
||||
val Any.extensionProp: Any
|
||||
get() = this
|
||||
|
||||
fun foo() {
|
||||
val <caret>x = "".extensionProp
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
7 val x = <bold>""</bold>.extensionProp
|
||||
7 val <bold>x = "".extensionProp</bold>
|
||||
7 val x = <bold>"".extensionProp</bold>
|
||||
3 val Any.<bold>extensionProp: Any</bold>
|
||||
4 get() = <bold>this</bold>
|
||||
3 val <bold>Any</bold>.extensionProp: Any
|
||||
7 val x = <bold>""</bold>.extensionProp
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
[NotNull Values]
|
||||
7 val <bold>x = "".extensionProp</bold>
|
||||
7 val <bold>x = "".extensionProp</bold>
|
||||
@@ -0,0 +1,6 @@
|
||||
7 val <bold>x = "".extensionProp</bold>
|
||||
7 val x = <bold>"".extensionProp</bold>
|
||||
3 val Any.<bold>extensionProp: Any</bold>
|
||||
4 get() = <bold>this</bold>
|
||||
3 val <bold>Any</bold>.extensionProp: Any
|
||||
7 val x = <bold>""</bold>.extensionProp
|
||||
@@ -0,0 +1,9 @@
|
||||
package sample
|
||||
|
||||
expect class ExpectClass() {
|
||||
fun foo(p: Any)
|
||||
}
|
||||
|
||||
fun f(c: ExpectClass) {
|
||||
c.foo(1)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// FLOW: IN
|
||||
|
||||
package sample
|
||||
|
||||
actual class ExpectClass {
|
||||
actual fun foo(p: Any) {
|
||||
println(<caret>p)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
7 println(<bold>p</bold>)
|
||||
6 actual fun foo(<bold>p: Any</bold>) {
|
||||
8 c.foo(<bold>1</bold>)
|
||||
@@ -0,0 +1,5 @@
|
||||
expect fun foo(p: Any)
|
||||
|
||||
fun bar() {
|
||||
foo(1)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// FLOW: IN
|
||||
|
||||
actual fun foo(p: Any) {
|
||||
println(<caret>p)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
4 println(<bold>p</bold>)
|
||||
3 actual fun foo(<bold>p: Any</bold>) {
|
||||
4 foo(<bold>1</bold>)
|
||||
@@ -0,0 +1,11 @@
|
||||
// FLOW: OUT
|
||||
|
||||
package sample
|
||||
|
||||
expect class ExpectClass() {
|
||||
fun foo(p: Any)
|
||||
}
|
||||
|
||||
fun f(c: ExpectClass) {
|
||||
c.foo(<caret>1)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package sample
|
||||
|
||||
actual class ExpectClass {
|
||||
actual fun foo(p: Any) {
|
||||
println(p)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
10 c.foo(<bold>1</bold>)
|
||||
6 fun foo(<bold>p: Any</bold>)
|
||||
4 actual fun foo(<bold>p: Any</bold>) {
|
||||
5 println(<bold>p</bold>)
|
||||
@@ -0,0 +1,8 @@
|
||||
// FLOW: OUT
|
||||
|
||||
expect fun Any.foo(): Any
|
||||
|
||||
fun bar() {
|
||||
val result = <caret>1.foo()
|
||||
println(result)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
actual fun Any.foo(): Any {
|
||||
return this
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
6 val result = <bold>1</bold>.foo()
|
||||
3 expect fun <bold>Any</bold>.foo(): Any
|
||||
1 actual fun <bold>Any</bold>.foo(): Any {
|
||||
2 return <bold>this</bold>
|
||||
1 actual fun Any.<bold>foo(): Any {</bold>
|
||||
6 val result = 1.<bold>foo()</bold>
|
||||
6 val <bold>result = 1.foo()</bold>
|
||||
7 println(<bold>result</bold>)
|
||||
@@ -0,0 +1,7 @@
|
||||
// FLOW: OUT
|
||||
|
||||
expect fun foo(p: Any)
|
||||
|
||||
fun bar() {
|
||||
foo(<caret>1)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
actual fun foo(p: Any) {
|
||||
println(p)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
6 foo(<bold>1</bold>)
|
||||
3 expect fun foo(<bold>p: Any</bold>)
|
||||
1 actual fun foo(<bold>p: Any</bold>) {
|
||||
2 println(<bold>p</bold>)
|
||||
@@ -0,0 +1,8 @@
|
||||
// FLOW: IN
|
||||
|
||||
expect fun foo(p: Any): Any
|
||||
|
||||
fun bar() {
|
||||
val result = foo(1)
|
||||
println(<caret>result)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
actual fun foo(p: Any): Any {
|
||||
return p
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
7 println(<bold>result</bold>)
|
||||
6 val <bold>result = foo(1)</bold>
|
||||
6 val result = <bold>foo(1)</bold>
|
||||
1 actual fun <bold>foo(p: Any): Any {</bold>
|
||||
2 return <bold>p</bold>
|
||||
1 actual fun foo(<bold>p: Any</bold>): Any {
|
||||
6 val result = foo(<bold>1</bold>)
|
||||
3 expect fun <bold>foo(p: Any): Any</bold>
|
||||
@@ -0,0 +1,8 @@
|
||||
// FLOW: OUT
|
||||
|
||||
expect fun foo(p: Any): Any
|
||||
|
||||
fun bar() {
|
||||
val result = foo(<caret>1)
|
||||
println(result)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
actual fun foo(p: Any): Any {
|
||||
return p
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
6 val result = foo(<bold>1</bold>)
|
||||
3 expect fun foo(<bold>p: Any</bold>): Any
|
||||
1 actual fun foo(<bold>p: Any</bold>): Any {
|
||||
2 return <bold>p</bold>
|
||||
1 actual fun <bold>foo(p: Any): Any {</bold>
|
||||
6 val result = <bold>foo(1)</bold>
|
||||
6 val <bold>result = foo(1)</bold>
|
||||
7 println(<bold>result</bold>)
|
||||
@@ -0,0 +1,8 @@
|
||||
// FLOW: IN
|
||||
|
||||
expect val Int.property: Any
|
||||
|
||||
fun bar() {
|
||||
val result = 1.property
|
||||
println(<caret>result)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
actual val Int.property: Any
|
||||
get() = this
|
||||
@@ -0,0 +1,8 @@
|
||||
7 println(<bold>result</bold>)
|
||||
6 val <bold>result = 1.property</bold>
|
||||
6 val result = <bold>1.property</bold>
|
||||
1 actual val Int.<bold>property: Any</bold>
|
||||
2 get() = <bold>this</bold>
|
||||
1 actual val <bold>Int</bold>.property: Any
|
||||
6 val result = <bold>1</bold>.property
|
||||
3 expect val Int.<bold>property: Any</bold>
|
||||
@@ -0,0 +1,8 @@
|
||||
// FLOW: OUT
|
||||
|
||||
expect val Int.property: Any
|
||||
|
||||
fun bar() {
|
||||
val result = <caret>1.property
|
||||
println(result)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
actual val Int.property: Any
|
||||
get() = this
|
||||
@@ -0,0 +1,8 @@
|
||||
6 val result = <bold>1</bold>.property
|
||||
3 expect val <bold>Int</bold>.property: Any
|
||||
1 actual val <bold>Int</bold>.property: Any
|
||||
2 get() = <bold>this</bold>
|
||||
2 <bold>get() = this</bold>
|
||||
6 val result = 1.<bold>property</bold>
|
||||
6 val <bold>result = 1.property</bold>
|
||||
7 println(<bold>result</bold>)
|
||||
@@ -5,5 +5,18 @@
|
||||
22 DEREFERENCE: <bold>x</bold> += 1
|
||||
23 DEREFERENCE: <bold>x</bold> *= 1
|
||||
25 -<bold>x</bold>
|
||||
12 operator fun <bold>A</bold>.unaryMinus() = this
|
||||
12 operator fun A.unaryMinus() = <bold>this</bold>
|
||||
12 operator fun A.<bold>unaryMinus() = this</bold>
|
||||
25 <bold>-x</bold>
|
||||
26 <bold>x</bold> - 1
|
||||
11 operator fun <bold>A</bold>.minus(n: Int) = this
|
||||
11 operator fun A.minus(n: Int) = <bold>this</bold>
|
||||
11 operator fun A.<bold>minus(n: Int) = this</bold>
|
||||
26 <bold>x - 1</bold>
|
||||
28 <bold>x -= 1</bold>
|
||||
17 DUPLICATE: var <bold>x = A()</bold>
|
||||
29 <bold>x</bold> /= 1
|
||||
14 operator fun <bold>A</bold>.divAssign(n: Int) = this
|
||||
14 operator fun A.divAssign(n: Int) = <bold>this</bold>
|
||||
14 operator fun A.<bold>divAssign(n: Int) = this</bold>
|
||||
|
||||
@@ -2,10 +2,14 @@
|
||||
15 DEREFERENCE: <bold>x</bold>.foo()
|
||||
16 DEREFERENCE: <bold>x</bold>.bar
|
||||
17 <bold>x</bold>.fooExt()
|
||||
9 fun <bold>A</bold>.fooExt() = 1
|
||||
18 <bold>x</bold>.barExt
|
||||
10 val <bold>A</bold>.barExt: Int get() = 2
|
||||
20 val y: A? = <bold>x</bold>
|
||||
20 val <bold>y: A? = x</bold>
|
||||
22 DEREFERENCE: <bold>y</bold>?.foo()
|
||||
23 DEREFERENCE: <bold>y</bold>?.bar
|
||||
24 <bold>y</bold>?.fooExt()
|
||||
9 DUPLICATE: fun <bold>A</bold>.fooExt() = 1
|
||||
25 <bold>y</bold>?.barExt
|
||||
10 DUPLICATE: val <bold>A</bold>.barExt: Int get() = 2
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
// FLOW: OUT
|
||||
|
||||
fun String.extensionFun(): Any = this
|
||||
|
||||
fun foo() {
|
||||
val x = <caret>"".extensionFun()
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
6 val x = <bold>""</bold>.extensionFun()
|
||||
3 fun <bold>String</bold>.extensionFun(): Any = this
|
||||
3 fun String.extensionFun(): Any = <bold>this</bold>
|
||||
3 fun String.<bold>extensionFun(): Any = this</bold>
|
||||
6 val x = "".<bold>extensionFun()</bold>
|
||||
6 val <bold>x = "".extensionFun()</bold>
|
||||
@@ -0,0 +1,8 @@
|
||||
// FLOW: OUT
|
||||
|
||||
val String.extensionProp: Any
|
||||
get() = this
|
||||
|
||||
fun foo() {
|
||||
val x = <caret>"".extensionProp
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
7 val x = <bold>""</bold>.extensionProp
|
||||
3 val <bold>String</bold>.extensionProp: Any
|
||||
4 get() = <bold>this</bold>
|
||||
4 <bold>get() = this</bold>
|
||||
7 val x = "".<bold>extensionProp</bold>
|
||||
7 val <bold>x = "".extensionProp</bold>
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// FLOW: OUT
|
||||
|
||||
class C {
|
||||
fun String.extensionFun(): Any {
|
||||
with("A") {
|
||||
println(this.length)
|
||||
println(this@extensionFun.length)
|
||||
}
|
||||
return this@C
|
||||
}
|
||||
|
||||
fun foo() {
|
||||
val x = <caret>"".extensionFun()
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T, R> with(receiver: T, block: T.() -> R): R {
|
||||
return receiver.block()
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
13 val x = <bold>""</bold>.extensionFun()
|
||||
4 fun <bold>String</bold>.extensionFun(): Any {
|
||||
7 println(<bold>this@extensionFun</bold>.length)
|
||||
@@ -11,7 +11,7 @@ import java.io.File
|
||||
|
||||
abstract class AbstractSlicerLeafGroupingTest : AbstractSlicerTest() {
|
||||
override fun doTest(path: String, sliceProvider: KotlinSliceProvider, rootNode: SliceRootNode) {
|
||||
val treeStructure = SliceTreeStructure(rootNode)
|
||||
val treeStructure = TestSliceTreeStructure(rootNode)
|
||||
val analyzer = sliceProvider.leafAnalyzer
|
||||
val possibleElementsByNode = analyzer.createMap()
|
||||
val leafExpressions = analyzer.calcLeafExpressions(rootNode, treeStructure, possibleElementsByNode)
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2010-2020 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.slicer
|
||||
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import org.jetbrains.annotations.NotNull
|
||||
import org.jetbrains.kotlin.idea.multiplatform.setupMppProjectFromDirStructure
|
||||
import org.jetbrains.kotlin.idea.stubs.AbstractMultiModuleTest
|
||||
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
|
||||
import org.jetbrains.kotlin.idea.test.extractMarkerOffset
|
||||
import org.jetbrains.kotlin.idea.test.findFileWithCaret
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import java.io.File
|
||||
|
||||
abstract class AbstractSlicerMultiplatformTest : AbstractMultiModuleTest() {
|
||||
override fun getTestDataPath() =
|
||||
File(PluginTestCaseBase.getTestDataPathBase(), "/slicer/mpp").path + File.separator
|
||||
|
||||
protected fun doTest(filePath: String) {
|
||||
val testRoot = File(filePath)
|
||||
setupMppProjectFromDirStructure(testRoot)
|
||||
|
||||
val file = project.findFileWithCaret() as KtFile
|
||||
val document = PsiDocumentManager.getInstance(myProject).getDocument(file)!!
|
||||
val offset = document.extractMarkerOffset(project, "<caret>")
|
||||
|
||||
testSliceFromOffset(file, offset) { _, rootNode ->
|
||||
KotlinTestUtils.assertEqualsToFile(testRoot.resolve("results.txt"), buildTreeRepresentation(rootNode))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import java.io.File
|
||||
|
||||
abstract class AbstractSlicerNullnessGroupingTest : AbstractSlicerTest() {
|
||||
override fun doTest(path: String, sliceProvider: KotlinSliceProvider, rootNode: SliceRootNode) {
|
||||
val treeStructure = SliceTreeStructure(rootNode)
|
||||
val treeStructure = TestSliceTreeStructure(rootNode)
|
||||
val analyzer = sliceProvider.nullnessAnalyzer
|
||||
val nullnessByNode = SliceNullnessAnalyzerBase.createMap()
|
||||
val nullness = analyzer.calcNullableLeaves(rootNode, treeStructure, nullnessByNode)
|
||||
|
||||
@@ -5,96 +5,15 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.slicer
|
||||
|
||||
import com.intellij.analysis.AnalysisScope
|
||||
import com.intellij.ide.projectView.TreeStructureProvider
|
||||
import com.intellij.ide.util.treeView.AbstractTreeStructureBase
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.slicer.DuplicateMap
|
||||
import com.intellij.slicer.SliceAnalysisParams
|
||||
import com.intellij.slicer.SliceNode
|
||||
import com.intellij.slicer.SliceRootNode
|
||||
import com.intellij.usages.TextChunk
|
||||
import com.intellij.util.PathUtil
|
||||
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import java.awt.Font
|
||||
import java.io.File
|
||||
|
||||
abstract class AbstractSlicerTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
protected class SliceTreeStructure(private val rootNode: SliceNode) : AbstractTreeStructureBase(rootNode.project) {
|
||||
override fun getProviders(): List<TreeStructureProvider>? = emptyList()
|
||||
|
||||
override fun getRootElement() = rootNode
|
||||
|
||||
override fun commit() {
|
||||
|
||||
}
|
||||
|
||||
override fun hasSomethingToCommit() = false
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val SliceNode.sortedChildren: List<SliceNode>
|
||||
get() = children.sortedBy { it.value.element?.startOffset ?: -1 }
|
||||
|
||||
@JvmStatic
|
||||
protected fun buildTreeRepresentation(rootNode: SliceNode): String {
|
||||
fun TextChunk.render(): String {
|
||||
var text = text
|
||||
if (attributes.fontType == Font.BOLD) {
|
||||
text = "<bold>$text</bold>"
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
fun SliceNode.isSliceLeafValueClassNode() = javaClass.name == "com.intellij.slicer.SliceLeafValueClassNode"
|
||||
|
||||
fun process(node: SliceNode, indent: Int): String {
|
||||
val usage = node.element!!.value
|
||||
|
||||
node.calculateDupNode()
|
||||
val isDuplicated = !node.isSliceLeafValueClassNode() && node.duplicate != null
|
||||
|
||||
return buildString {
|
||||
when {
|
||||
node is SliceRootNode && usage.element is KtFile -> {
|
||||
node.sortedChildren.forEach { append(process(it, indent)) }
|
||||
return@buildString
|
||||
}
|
||||
// SliceLeafValueClassNode is package-private
|
||||
node.isSliceLeafValueClassNode() -> append("[${node.nodeText}]\n")
|
||||
else -> {
|
||||
val chunks = usage.text
|
||||
append(chunks.first().render() + " ")
|
||||
append("\t".repeat(indent))
|
||||
if (usage is KotlinSliceDereferenceUsage) {
|
||||
append("DEREFERENCE: ")
|
||||
}
|
||||
if (usage is KotlinSliceUsage) {
|
||||
append("[LAMBDA] ".repeat(usage.lambdaLevel))
|
||||
}
|
||||
chunks.slice(1 until chunks.size).joinTo(
|
||||
this,
|
||||
separator = "",
|
||||
prefix = if (isDuplicated) "DUPLICATE: " else "",
|
||||
postfix = "\n"
|
||||
) { it.render() }
|
||||
}
|
||||
}
|
||||
|
||||
if (!isDuplicated) {
|
||||
node.sortedChildren.forEach { append(process(it, indent + 1)) }
|
||||
}
|
||||
}.replace(Regex("</bold><bold>"), "")
|
||||
}
|
||||
|
||||
return process(rootNode, 0)
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract fun doTest(path: String, sliceProvider: KotlinSliceProvider, rootNode: SliceRootNode)
|
||||
|
||||
protected fun doTest(path: String) {
|
||||
@@ -104,30 +23,15 @@ abstract class AbstractSlicerTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
val namePrefix = FileUtil.getNameWithoutExtension(mainFile)
|
||||
val extraFiles = rootDir.listFiles { _, name ->
|
||||
name.startsWith("$namePrefix.") && PathUtil.getFileExtension(name).let { it == "kt" || it == "java" }
|
||||
}
|
||||
}!!
|
||||
|
||||
myFixture.testDataPath = "${KotlinTestUtils.getHomeDirectory()}/${rootDir.path}"
|
||||
|
||||
val fileText = FileUtil.loadFile(mainFile, true)
|
||||
val flowKind = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// FLOW: ")
|
||||
val withDereferences = InTextDirectivesUtils.isDirectiveDefined(fileText, "// WITH_DEREFERENCES")
|
||||
val analysisParams = SliceAnalysisParams().apply {
|
||||
dataFlowToThis = when (flowKind) {
|
||||
"IN" -> true
|
||||
"OUT" -> false
|
||||
else -> throw AssertionError("Invalid flow kind: $flowKind")
|
||||
}
|
||||
showInstanceDereferences = withDereferences
|
||||
scope = AnalysisScope(project)
|
||||
}
|
||||
|
||||
|
||||
extraFiles.forEach { myFixture.configureByFile(it.name) }
|
||||
val file = myFixture.configureByFile(mainFile.name) as KtFile
|
||||
val elementAtCaret = file.findElementAt(editor.caretModel.offset)!!
|
||||
val sliceProvider = KotlinSliceProvider()
|
||||
val expression = sliceProvider.getExpressionAtCaret(elementAtCaret, analysisParams.dataFlowToThis)!!
|
||||
val rootUsage = sliceProvider.createRootUsage(expression, analysisParams)
|
||||
val rootNode = SliceRootNode(project, DuplicateMap(), rootUsage)
|
||||
doTest(path, sliceProvider, rootNode)
|
||||
|
||||
testSliceFromOffset(file, editor.caretModel.offset) { sliceProvider, rootNode ->
|
||||
doTest(path, sliceProvider, rootNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
@@ -228,6 +228,16 @@ public class SlicerLeafGroupingTestGenerated extends AbstractSlicerLeafGroupingT
|
||||
runTest("idea/testData/slicer/inflow/nullsAndNotNulls.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("onFunctionReceiverType.kt")
|
||||
public void testOnFunctionReceiverType() throws Exception {
|
||||
runTest("idea/testData/slicer/inflow/onFunctionReceiverType.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("onPropertyReceiverType.kt")
|
||||
public void testOnPropertyReceiverType() throws Exception {
|
||||
runTest("idea/testData/slicer/inflow/onPropertyReceiverType.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("openFun.kt")
|
||||
public void testOpenFun() throws Exception {
|
||||
runTest("idea/testData/slicer/inflow/openFun.kt");
|
||||
@@ -238,6 +248,16 @@ public class SlicerLeafGroupingTestGenerated extends AbstractSlicerLeafGroupingT
|
||||
runTest("idea/testData/slicer/inflow/openFunInvokeOnParameter.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("overrideExtension1.kt")
|
||||
public void testOverrideExtension1() throws Exception {
|
||||
runTest("idea/testData/slicer/inflow/overrideExtension1.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("overrideExtension2.kt")
|
||||
public void testOverrideExtension2() throws Exception {
|
||||
runTest("idea/testData/slicer/inflow/overrideExtension2.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("overrideFun.kt")
|
||||
public void testOverrideFun() throws Exception {
|
||||
runTest("idea/testData/slicer/inflow/overrideFun.kt");
|
||||
@@ -323,6 +343,16 @@ public class SlicerLeafGroupingTestGenerated extends AbstractSlicerLeafGroupingT
|
||||
runTest("idea/testData/slicer/inflow/settersViaJavaDelegate.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("thisInExtensionFunction.kt")
|
||||
public void testThisInExtensionFunction() throws Exception {
|
||||
runTest("idea/testData/slicer/inflow/thisInExtensionFunction.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("thisInExtensionProperty.kt")
|
||||
public void testThisInExtensionProperty() throws Exception {
|
||||
runTest("idea/testData/slicer/inflow/thisInExtensionProperty.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("topLevelVal.kt")
|
||||
public void testTopLevelVal() throws Exception {
|
||||
runTest("idea/testData/slicer/inflow/topLevelVal.kt");
|
||||
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2010-2020 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.slicer;
|
||||
|
||||
import com.intellij.testFramework.TestDataPath;
|
||||
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils;
|
||||
import org.jetbrains.kotlin.test.TestMetadata;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
|
||||
@SuppressWarnings("all")
|
||||
@TestMetadata("idea/testData/slicer/mpp")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public class SlicerMultiplatformTestGenerated extends AbstractSlicerMultiplatformTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||
}
|
||||
|
||||
@TestMetadata("actualClassFunctionParameter")
|
||||
public void testActualClassFunctionParameter() throws Exception {
|
||||
runTest("idea/testData/slicer/mpp/actualClassFunctionParameter/");
|
||||
}
|
||||
|
||||
@TestMetadata("actualFunctionParameter")
|
||||
public void testActualFunctionParameter() throws Exception {
|
||||
runTest("idea/testData/slicer/mpp/actualFunctionParameter/");
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInMpp() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/slicer/mpp"), Pattern.compile("^([^\\.]+)$"), null, false);
|
||||
}
|
||||
|
||||
@TestMetadata("expectClassFunctionParameter")
|
||||
public void testExpectClassFunctionParameter() throws Exception {
|
||||
runTest("idea/testData/slicer/mpp/expectClassFunctionParameter/");
|
||||
}
|
||||
|
||||
@TestMetadata("expectExtensionFunctionResultOut")
|
||||
public void testExpectExtensionFunctionResultOut() throws Exception {
|
||||
runTest("idea/testData/slicer/mpp/expectExtensionFunctionResultOut/");
|
||||
}
|
||||
|
||||
@TestMetadata("expectFunctionParameter")
|
||||
public void testExpectFunctionParameter() throws Exception {
|
||||
runTest("idea/testData/slicer/mpp/expectFunctionParameter/");
|
||||
}
|
||||
|
||||
@TestMetadata("expectFunctionResultIn")
|
||||
public void testExpectFunctionResultIn() throws Exception {
|
||||
runTest("idea/testData/slicer/mpp/expectFunctionResultIn/");
|
||||
}
|
||||
|
||||
@TestMetadata("expectFunctionResultOut")
|
||||
public void testExpectFunctionResultOut() throws Exception {
|
||||
runTest("idea/testData/slicer/mpp/expectFunctionResultOut/");
|
||||
}
|
||||
|
||||
@TestMetadata("expectPropertyResultIn")
|
||||
public void testExpectPropertyResultIn() throws Exception {
|
||||
runTest("idea/testData/slicer/mpp/expectPropertyResultIn/");
|
||||
}
|
||||
|
||||
@TestMetadata("expectPropertyResultOut")
|
||||
public void testExpectPropertyResultOut() throws Exception {
|
||||
runTest("idea/testData/slicer/mpp/expectPropertyResultOut/");
|
||||
}
|
||||
}
|
||||
+30
@@ -228,6 +228,16 @@ public class SlicerNullnessGroupingTestGenerated extends AbstractSlicerNullnessG
|
||||
runTest("idea/testData/slicer/inflow/nullsAndNotNulls.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("onFunctionReceiverType.kt")
|
||||
public void testOnFunctionReceiverType() throws Exception {
|
||||
runTest("idea/testData/slicer/inflow/onFunctionReceiverType.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("onPropertyReceiverType.kt")
|
||||
public void testOnPropertyReceiverType() throws Exception {
|
||||
runTest("idea/testData/slicer/inflow/onPropertyReceiverType.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("openFun.kt")
|
||||
public void testOpenFun() throws Exception {
|
||||
runTest("idea/testData/slicer/inflow/openFun.kt");
|
||||
@@ -238,6 +248,16 @@ public class SlicerNullnessGroupingTestGenerated extends AbstractSlicerNullnessG
|
||||
runTest("idea/testData/slicer/inflow/openFunInvokeOnParameter.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("overrideExtension1.kt")
|
||||
public void testOverrideExtension1() throws Exception {
|
||||
runTest("idea/testData/slicer/inflow/overrideExtension1.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("overrideExtension2.kt")
|
||||
public void testOverrideExtension2() throws Exception {
|
||||
runTest("idea/testData/slicer/inflow/overrideExtension2.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("overrideFun.kt")
|
||||
public void testOverrideFun() throws Exception {
|
||||
runTest("idea/testData/slicer/inflow/overrideFun.kt");
|
||||
@@ -323,6 +343,16 @@ public class SlicerNullnessGroupingTestGenerated extends AbstractSlicerNullnessG
|
||||
runTest("idea/testData/slicer/inflow/settersViaJavaDelegate.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("thisInExtensionFunction.kt")
|
||||
public void testThisInExtensionFunction() throws Exception {
|
||||
runTest("idea/testData/slicer/inflow/thisInExtensionFunction.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("thisInExtensionProperty.kt")
|
||||
public void testThisInExtensionProperty() throws Exception {
|
||||
runTest("idea/testData/slicer/inflow/thisInExtensionProperty.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("topLevelVal.kt")
|
||||
public void testTopLevelVal() throws Exception {
|
||||
runTest("idea/testData/slicer/inflow/topLevelVal.kt");
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2010-2020 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.slicer
|
||||
|
||||
import com.intellij.analysis.AnalysisScope
|
||||
import com.intellij.ide.projectView.TreeStructureProvider
|
||||
import com.intellij.ide.util.treeView.AbstractTreeStructureBase
|
||||
import com.intellij.slicer.DuplicateMap
|
||||
import com.intellij.slicer.SliceAnalysisParams
|
||||
import com.intellij.slicer.SliceNode
|
||||
import com.intellij.slicer.SliceRootNode
|
||||
import com.intellij.usages.TextChunk
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||
import java.awt.Font
|
||||
|
||||
internal class TestSliceTreeStructure(private val rootNode: SliceNode) : AbstractTreeStructureBase(rootNode.project) {
|
||||
override fun getProviders() = emptyList<TreeStructureProvider>()
|
||||
|
||||
override fun getRootElement() = rootNode
|
||||
|
||||
override fun commit() {
|
||||
}
|
||||
|
||||
override fun hasSomethingToCommit() = false
|
||||
}
|
||||
|
||||
internal fun buildTreeRepresentation(rootNode: SliceNode): String {
|
||||
fun TextChunk.render(): String {
|
||||
var text = text
|
||||
if (attributes.fontType == Font.BOLD) {
|
||||
text = "<bold>$text</bold>"
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
fun SliceNode.isSliceLeafValueClassNode() = javaClass.name == "com.intellij.slicer.SliceLeafValueClassNode"
|
||||
|
||||
fun process(node: SliceNode, indent: Int): String {
|
||||
val usage = node.element!!.value
|
||||
|
||||
node.calculateDupNode()
|
||||
val isDuplicated = !node.isSliceLeafValueClassNode() && node.duplicate != null
|
||||
|
||||
return buildString {
|
||||
when {
|
||||
node is SliceRootNode && usage.element is KtFile -> {
|
||||
node.sortedChildren.forEach { append(process(it, indent)) }
|
||||
return@buildString
|
||||
}
|
||||
// SliceLeafValueClassNode is package-private
|
||||
node.isSliceLeafValueClassNode() -> append("[${node.nodeText}]\n")
|
||||
else -> {
|
||||
val chunks = usage.text
|
||||
append(chunks.first().render() + " ")
|
||||
append("\t".repeat(indent))
|
||||
if (usage is KotlinSliceDereferenceUsage) {
|
||||
append("DEREFERENCE: ")
|
||||
}
|
||||
if (usage is KotlinSliceUsage) {
|
||||
append("[LAMBDA] ".repeat(usage.lambdaLevel))
|
||||
}
|
||||
chunks.slice(1 until chunks.size).joinTo(
|
||||
this,
|
||||
separator = "",
|
||||
prefix = if (isDuplicated) "DUPLICATE: " else "",
|
||||
postfix = "\n"
|
||||
) { it.render() }
|
||||
}
|
||||
}
|
||||
|
||||
if (!isDuplicated) {
|
||||
node.sortedChildren.forEach { append(process(it, indent + 1)) }
|
||||
}
|
||||
}.replace(Regex("</bold><bold>"), "")
|
||||
}
|
||||
|
||||
return process(rootNode, 0)
|
||||
}
|
||||
|
||||
private val SliceNode.sortedChildren: List<SliceNode>
|
||||
get() = children.sortedBy { it.value.element?.startOffset ?: -1 }
|
||||
|
||||
internal fun testSliceFromOffset(
|
||||
file: KtFile,
|
||||
offset: Int,
|
||||
doTest: (sliceProvider: KotlinSliceProvider, rootNode: SliceRootNode) -> Unit
|
||||
) {
|
||||
val fileText = file.text
|
||||
val flowKind = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// FLOW: ")
|
||||
val withDereferences = InTextDirectivesUtils.isDirectiveDefined(fileText, "// WITH_DEREFERENCES")
|
||||
val analysisParams = SliceAnalysisParams().apply {
|
||||
dataFlowToThis = when (flowKind) {
|
||||
"IN" -> true
|
||||
"OUT" -> false
|
||||
else -> throw AssertionError("Invalid flow kind: $flowKind")
|
||||
}
|
||||
showInstanceDereferences = withDereferences
|
||||
scope = AnalysisScope(file.project)
|
||||
}
|
||||
|
||||
val elementAtCaret = file.findElementAt(offset)!!
|
||||
val sliceProvider = KotlinSliceProvider()
|
||||
val expression = sliceProvider.getExpressionAtCaret(elementAtCaret, analysisParams.dataFlowToThis)!!
|
||||
val rootUsage = sliceProvider.createRootUsage(expression, analysisParams)
|
||||
val rootNode = SliceRootNode(file.project, DuplicateMap(), rootUsage)
|
||||
doTest(sliceProvider, rootNode)
|
||||
}
|
||||
+682
-611
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user