Analyze Data Flow: Support dereference processing
#KT-11994 In Progress
This commit is contained in:
+4
@@ -28,6 +28,10 @@ import org.jetbrains.kotlin.psi.*
|
|||||||
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
|
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
|
||||||
|
|
||||||
class KotlinReadWriteAccessDetector : ReadWriteAccessDetector() {
|
class KotlinReadWriteAccessDetector : ReadWriteAccessDetector() {
|
||||||
|
companion object {
|
||||||
|
val INSTANCE = KotlinReadWriteAccessDetector()
|
||||||
|
}
|
||||||
|
|
||||||
override fun isReadWriteAccessible(element: PsiElement) = element is KtVariableDeclaration || element is KtParameter
|
override fun isReadWriteAccessible(element: PsiElement) = element is KtVariableDeclaration || element is KtParameter
|
||||||
|
|
||||||
override fun isDeclarationWriteAccess(element: PsiElement) = isReadWriteAccessible(element)
|
override fun isDeclarationWriteAccess(element: PsiElement) = isReadWriteAccessible(element)
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* 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.slicer
|
||||||
|
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.slicer.SliceUsage
|
||||||
|
import com.intellij.usages.UsagePresentation
|
||||||
|
import com.intellij.util.Processor
|
||||||
|
|
||||||
|
class KotlinSliceDereferenceUsage(
|
||||||
|
element: PsiElement,
|
||||||
|
parent: KotlinSliceUsage
|
||||||
|
) : KotlinSliceUsage(element, parent) {
|
||||||
|
override fun processChildren(processor: Processor<SliceUsage>) {
|
||||||
|
// no children
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPresentation() = object : UsagePresentation by super.getPresentation() {
|
||||||
|
override fun getTooltipText() = "Variable dereferenced"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.slicer
|
|||||||
|
|
||||||
import com.intellij.slicer.SliceUsage
|
import com.intellij.slicer.SliceUsage
|
||||||
import com.intellij.slicer.SliceUsageCellRendererBase
|
import com.intellij.slicer.SliceUsageCellRendererBase
|
||||||
|
import com.intellij.ui.JBColor
|
||||||
import com.intellij.ui.SimpleTextAttributes
|
import com.intellij.ui.SimpleTextAttributes
|
||||||
import com.intellij.util.FontUtil
|
import com.intellij.util.FontUtil
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||||
@@ -38,9 +39,15 @@ object KotlinSliceUsageCellRenderer : SliceUsageCellRendererBase() {
|
|||||||
|
|
||||||
override fun customizeCellRendererFor(sliceUsage: SliceUsage) {
|
override fun customizeCellRendererFor(sliceUsage: SliceUsage) {
|
||||||
if (sliceUsage !is KotlinSliceUsage) return
|
if (sliceUsage !is KotlinSliceUsage) return
|
||||||
|
val isDereference = sliceUsage is KotlinSliceDereferenceUsage
|
||||||
|
|
||||||
for ((i, textChunk) in sliceUsage.getText().withIndex()) {
|
for ((i, textChunk) in sliceUsage.getText().withIndex()) {
|
||||||
append(textChunk.text, textChunk.simpleAttributesIgnoreBackground)
|
var attributes = textChunk.simpleAttributesIgnoreBackground
|
||||||
|
if (isDereference) {
|
||||||
|
attributes = attributes.derive(attributes.style, JBColor.LIGHT_GRAY, attributes.bgColor, attributes.waveColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
append(textChunk.text, attributes)
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
append(FontUtil.spaceAndThinSpace())
|
append(FontUtil.spaceAndThinSpace())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,11 +16,14 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.slicer
|
package org.jetbrains.kotlin.idea.slicer
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.highlighting.ReadWriteAccessDetector
|
||||||
|
import com.intellij.codeInsight.highlighting.ReadWriteAccessDetector.Access
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
import com.intellij.psi.search.SearchScope
|
import com.intellij.psi.search.SearchScope
|
||||||
import com.intellij.slicer.SliceUsage
|
import com.intellij.slicer.SliceUsage
|
||||||
import com.intellij.usageView.UsageInfo
|
import com.intellij.usageView.UsageInfo
|
||||||
import com.intellij.util.Processor
|
import com.intellij.util.Processor
|
||||||
|
import org.jetbrains.kotlin.cfg.pseudocode.PseudoValue
|
||||||
import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode
|
import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode
|
||||||
import org.jetbrains.kotlin.cfg.pseudocode.containingDeclarationForPseudocode
|
import org.jetbrains.kotlin.cfg.pseudocode.containingDeclarationForPseudocode
|
||||||
import org.jetbrains.kotlin.cfg.pseudocode.getContainingPseudocode
|
import org.jetbrains.kotlin.cfg.pseudocode.getContainingPseudocode
|
||||||
@@ -37,6 +40,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
|||||||
import org.jetbrains.kotlin.idea.findUsages.KotlinFunctionFindUsagesOptions
|
import org.jetbrains.kotlin.idea.findUsages.KotlinFunctionFindUsagesOptions
|
||||||
import org.jetbrains.kotlin.idea.findUsages.KotlinPropertyFindUsagesOptions
|
import org.jetbrains.kotlin.idea.findUsages.KotlinPropertyFindUsagesOptions
|
||||||
import org.jetbrains.kotlin.idea.findUsages.processAllUsages
|
import org.jetbrains.kotlin.idea.findUsages.processAllUsages
|
||||||
|
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReadWriteAccessDetector
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.*
|
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||||
@@ -58,13 +62,17 @@ private fun KtFunction.processCalls(scope: SearchScope, processor: (UsageInfo) -
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun KtDeclaration.processVariableAccesses(scope: SearchScope, isRead: Boolean, processor: (UsageInfo) -> Unit) {
|
private fun KtDeclaration.processVariableAccesses(
|
||||||
|
scope: SearchScope,
|
||||||
|
kind: Access,
|
||||||
|
processor: (UsageInfo) -> Unit
|
||||||
|
) {
|
||||||
processAllUsages(
|
processAllUsages(
|
||||||
{
|
{
|
||||||
KotlinPropertyFindUsagesOptions(project).apply {
|
KotlinPropertyFindUsagesOptions(project).apply {
|
||||||
isReadAccess = isRead
|
isReadAccess = kind == Access.Read || kind == Access.ReadWrite
|
||||||
isWriteAccess = !isRead
|
isWriteAccess = kind == Access.Write || kind == Access.ReadWrite
|
||||||
isReadWriteAccess = false
|
isReadWriteAccess = kind == Access.ReadWrite
|
||||||
isSearchForTextOccurrences = false
|
isSearchForTextOccurrences = false
|
||||||
isSkipImportStatements = true
|
isSkipImportStatements = true
|
||||||
searchScope = scope.intersectWith(useScope)
|
searchScope = scope.intersectWith(useScope)
|
||||||
@@ -88,7 +96,7 @@ private fun KtParameter.canProcess(): Boolean {
|
|||||||
abstract class Slicer(
|
abstract class Slicer(
|
||||||
protected val element: KtExpression,
|
protected val element: KtExpression,
|
||||||
protected val processor: Processor<SliceUsage>,
|
protected val processor: Processor<SliceUsage>,
|
||||||
protected val parentUsage: SliceUsage
|
protected val parentUsage: KotlinSliceUsage
|
||||||
) {
|
) {
|
||||||
protected class PseudocodeCache {
|
protected class PseudocodeCache {
|
||||||
private val computedPseudocodes = HashMap<KtElement, Pseudocode>()
|
private val computedPseudocodes = HashMap<KtElement, Pseudocode>()
|
||||||
@@ -113,13 +121,13 @@ abstract class Slicer(
|
|||||||
class InflowSlicer(
|
class InflowSlicer(
|
||||||
element: KtExpression,
|
element: KtExpression,
|
||||||
processor: Processor<SliceUsage>,
|
processor: Processor<SliceUsage>,
|
||||||
parentUsage: SliceUsage
|
parentUsage: KotlinSliceUsage
|
||||||
) : Slicer(element, processor, parentUsage) {
|
) : Slicer(element, processor, parentUsage) {
|
||||||
private fun KtProperty.processProperty() {
|
private fun KtProperty.processProperty() {
|
||||||
if (!canProcess()) return
|
if (!canProcess()) return
|
||||||
|
|
||||||
initializer?.passToProcessor()
|
initializer?.passToProcessor()
|
||||||
processVariableAccesses(parentUsage.scope.toSearchScope(), false) body@ {
|
processVariableAccesses(parentUsage.scope.toSearchScope(), Access.Write) body@ {
|
||||||
val refExpression = it.element as? KtExpression ?: return@body
|
val refExpression = it.element as? KtExpression ?: return@body
|
||||||
val rhs = KtPsiUtil.safeDeparenthesize(refExpression).getAssignmentByLHS()?.right ?: return@body
|
val rhs = KtPsiUtil.safeDeparenthesize(refExpression).getAssignmentByLHS()?.right ?: return@body
|
||||||
rhs.passToProcessor()
|
rhs.passToProcessor()
|
||||||
@@ -192,7 +200,7 @@ class InflowSlicer(
|
|||||||
class OutflowSlicer(
|
class OutflowSlicer(
|
||||||
element: KtExpression,
|
element: KtExpression,
|
||||||
processor: Processor<SliceUsage>,
|
processor: Processor<SliceUsage>,
|
||||||
parentUsage: SliceUsage
|
parentUsage: KotlinSliceUsage
|
||||||
) : Slicer(element, processor, parentUsage) {
|
) : Slicer(element, processor, parentUsage) {
|
||||||
private fun KtDeclaration.processVariable() {
|
private fun KtDeclaration.processVariable() {
|
||||||
when (this) {
|
when (this) {
|
||||||
@@ -200,9 +208,18 @@ class OutflowSlicer(
|
|||||||
is KtParameter -> if (!canProcess()) return
|
is KtParameter -> if (!canProcess()) return
|
||||||
else -> return
|
else -> return
|
||||||
}
|
}
|
||||||
processVariableAccesses(parentUsage.scope.toSearchScope(),true) body@ {
|
val withDereferences = parentUsage.params.showInstanceDereferences
|
||||||
|
processVariableAccesses(
|
||||||
|
parentUsage.scope.toSearchScope(),
|
||||||
|
if (withDereferences) Access.ReadWrite else Access.Read
|
||||||
|
) body@ {
|
||||||
val refExpression = (it.element as? KtExpression)?.let { KtPsiUtil.safeDeparenthesize(it) } ?: return@body
|
val refExpression = (it.element as? KtExpression)?.let { KtPsiUtil.safeDeparenthesize(it) } ?: return@body
|
||||||
refExpression.processExpression()
|
if (withDereferences) {
|
||||||
|
refExpression.processDereferences()
|
||||||
|
}
|
||||||
|
if (!withDereferences || KotlinReadWriteAccessDetector.INSTANCE.getExpressionAccess(refExpression) == Access.Read) {
|
||||||
|
refExpression.processExpression()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,17 +241,49 @@ class OutflowSlicer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun KtExpression.processExpression() {
|
private fun processDereferenceIsNeeded(
|
||||||
|
expression: KtExpression,
|
||||||
|
pseudoValue: PseudoValue,
|
||||||
|
instr: InstructionWithReceivers
|
||||||
|
) {
|
||||||
|
if (!parentUsage.params.showInstanceDereferences) return
|
||||||
|
|
||||||
|
val receiver = instr.receiverValues[pseudoValue]
|
||||||
|
val resolvedCall = when (instr) {
|
||||||
|
is CallInstruction -> instr.resolvedCall
|
||||||
|
is ReadValueInstruction -> (instr.target as? AccessTarget.Call)?.resolvedCall
|
||||||
|
else -> null
|
||||||
|
} ?: return
|
||||||
|
|
||||||
|
if (receiver != null && resolvedCall.dispatchReceiver == receiver) {
|
||||||
|
processor.process(KotlinSliceDereferenceUsage(expression, parentUsage))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KtExpression.processPseudocodeUsages(processor: (PseudoValue, Instruction) -> Unit) {
|
||||||
val pseudocode = pseudocodeCache[this] ?: return
|
val pseudocode = pseudocodeCache[this] ?: return
|
||||||
val pseudoValue = pseudocode.getElementValue(this) ?: return
|
val pseudoValue = pseudocode.getElementValue(this) ?: return
|
||||||
pseudocode.getUsages(pseudoValue).forEach { instr ->
|
pseudocode.getUsages(pseudoValue).forEach { processor(pseudoValue, it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KtExpression.processDereferences() {
|
||||||
|
processPseudocodeUsages { pseudoValue, instr ->
|
||||||
|
when (instr) {
|
||||||
|
is ReadValueInstruction -> processDereferenceIsNeeded(this, pseudoValue, instr)
|
||||||
|
is CallInstruction -> processDereferenceIsNeeded(this, pseudoValue, instr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KtExpression.processExpression() {
|
||||||
|
processPseudocodeUsages { pseudoValue, instr ->
|
||||||
when (instr) {
|
when (instr) {
|
||||||
is WriteValueInstruction -> (instr.target.accessedDescriptor?.source?.getPsi() as? KtDeclaration)?.passToProcessor()
|
is WriteValueInstruction -> (instr.target.accessedDescriptor?.source?.getPsi() as? KtDeclaration)?.passToProcessor()
|
||||||
is CallInstruction -> instr.arguments[pseudoValue]?.source?.getPsi()?.passToProcessor()
|
is CallInstruction -> instr.arguments[pseudoValue]?.source?.getPsi()?.passToProcessor()
|
||||||
is ReturnValueInstruction -> (instr.owner.correspondingElement as? KtFunction)?.passToProcessor()
|
is ReturnValueInstruction -> (instr.owner.correspondingElement as? KtFunction)?.passToProcessor()
|
||||||
is MagicInstruction -> when (instr.kind) {
|
is MagicInstruction -> when (instr.kind) {
|
||||||
MagicKind.NOT_NULL_ASSERTION, MagicKind.CAST -> instr.outputValue.element?.passToProcessor()
|
MagicKind.NOT_NULL_ASSERTION, MagicKind.CAST -> instr.outputValue.element?.passToProcessor()
|
||||||
else -> return
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// FLOW: OUT
|
||||||
|
// WITH_DEREFERENCES
|
||||||
|
|
||||||
|
class A
|
||||||
|
|
||||||
|
operator fun A.get(i: Int) = this
|
||||||
|
operator fun A.set(i: Int, a: A) = this
|
||||||
|
operator fun A.plusAssign(a: A) = this
|
||||||
|
operator fun A.times(a: A) = this
|
||||||
|
operator fun A.inc() = this
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
val <caret>x = A()
|
||||||
|
val y = A()
|
||||||
|
|
||||||
|
x[1]
|
||||||
|
x[1] = y
|
||||||
|
x[1] += y
|
||||||
|
x[1] *= y
|
||||||
|
x[1]++
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
13 val <bold>x = A()</bold>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// FLOW: OUT
|
||||||
|
// WITH_DEREFERENCES
|
||||||
|
|
||||||
|
class A {
|
||||||
|
operator fun get(i: Int) = this
|
||||||
|
operator fun set(i: Int, a: A) = this
|
||||||
|
operator fun plusAssign(a: A) = this
|
||||||
|
operator fun times(a: A) = this
|
||||||
|
operator fun inc() = this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
val <caret>x = A()
|
||||||
|
val y = A()
|
||||||
|
|
||||||
|
x[1]
|
||||||
|
x[1] = y
|
||||||
|
x[1] += y
|
||||||
|
x[1] *= y
|
||||||
|
x[1]++
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
13 val <bold>x = A()</bold>
|
||||||
|
16 DEREFERENCE: <bold>x</bold>[1]
|
||||||
|
17 DEREFERENCE: <bold>x</bold>[1] = y
|
||||||
|
18 DEREFERENCE: <bold>x</bold>[1] += y
|
||||||
|
19 DEREFERENCE: <bold>x</bold>[1] *= y
|
||||||
|
20 DEREFERENCE: <bold>x</bold>[1]++
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// FLOW: OUT
|
||||||
|
// WITH_DEREFERENCES
|
||||||
|
|
||||||
|
class A {
|
||||||
|
operator fun plus(n: Int) = this
|
||||||
|
operator fun unaryPlus() = this
|
||||||
|
operator fun inc() = this
|
||||||
|
operator fun timesAssign(n: Int) = this
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun A.minus(n: Int) = this
|
||||||
|
operator fun A.unaryMinus() = this
|
||||||
|
operator fun A.dec() = this
|
||||||
|
operator fun A.divAssign(n: Int) = this
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
var <caret>x = A()
|
||||||
|
|
||||||
|
+x
|
||||||
|
x + 1
|
||||||
|
x++
|
||||||
|
x += 1
|
||||||
|
x *= 1
|
||||||
|
|
||||||
|
-x
|
||||||
|
x - 1
|
||||||
|
x--
|
||||||
|
x -= 1
|
||||||
|
x /= 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
17 var <bold>x = A()</bold>
|
||||||
|
19 DEREFERENCE: +<bold>x</bold>
|
||||||
|
20 DEREFERENCE: <bold>x</bold> + 1
|
||||||
|
21 DEREFERENCE: <bold>x</bold>++
|
||||||
|
22 DEREFERENCE: <bold>x</bold> += 1
|
||||||
|
23 DEREFERENCE: <bold>x</bold> *= 1
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// FLOW: OUT
|
||||||
|
// WITH_DEREFERENCES
|
||||||
|
|
||||||
|
class A {
|
||||||
|
fun foo() = 1
|
||||||
|
val bar = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
fun A.fooExt() = 1
|
||||||
|
val A.barExt: Int get() = 2
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
val <caret>x = A()
|
||||||
|
|
||||||
|
x.foo()
|
||||||
|
x.bar
|
||||||
|
x.fooExt()
|
||||||
|
x.barExt
|
||||||
|
|
||||||
|
val y: A? = x
|
||||||
|
|
||||||
|
y?.foo()
|
||||||
|
y?.bar
|
||||||
|
y?.fooExt()
|
||||||
|
y?.barExt
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
13 val <bold>x = A()</bold>
|
||||||
|
15 DEREFERENCE: <bold>x</bold>.foo()
|
||||||
|
16 DEREFERENCE: <bold>x</bold>.bar
|
||||||
|
20 val <bold>y: A? = x</bold>
|
||||||
|
22 DEREFERENCE: <bold>y</bold>?.foo()
|
||||||
|
23 DEREFERENCE: <bold>y</bold>?.bar
|
||||||
@@ -40,6 +40,8 @@ abstract class AbstractSlicerTest : KotlinLightCodeInsightFixtureTestCase() {
|
|||||||
|
|
||||||
// Based on SliceUsage.processChildren
|
// Based on SliceUsage.processChildren
|
||||||
private fun KotlinSliceUsage.processChildrenWithoutProgress(processor: (KotlinSliceUsage) -> Unit) {
|
private fun KotlinSliceUsage.processChildrenWithoutProgress(processor: (KotlinSliceUsage) -> Unit) {
|
||||||
|
if (this is KotlinSliceDereferenceUsage) return
|
||||||
|
|
||||||
val element = runReadAction { element }
|
val element = runReadAction { element }
|
||||||
|
|
||||||
val uniqueProcessor = CommonProcessors.UniqueProcessor(
|
val uniqueProcessor = CommonProcessors.UniqueProcessor(
|
||||||
@@ -78,6 +80,9 @@ abstract class AbstractSlicerTest : KotlinLightCodeInsightFixtureTestCase() {
|
|||||||
val chunks = usage.text
|
val chunks = usage.text
|
||||||
append(chunks.first().render() + " ")
|
append(chunks.first().render() + " ")
|
||||||
append("\t".repeat(indent))
|
append("\t".repeat(indent))
|
||||||
|
if (usage is KotlinSliceDereferenceUsage) {
|
||||||
|
append("DEREFERENCE: ")
|
||||||
|
}
|
||||||
chunks.slice(1..chunks.size - 1).joinTo(
|
chunks.slice(1..chunks.size - 1).joinTo(
|
||||||
this,
|
this,
|
||||||
separator = "",
|
separator = "",
|
||||||
@@ -85,7 +90,9 @@ abstract class AbstractSlicerTest : KotlinLightCodeInsightFixtureTestCase() {
|
|||||||
postfix = "\n"
|
postfix = "\n"
|
||||||
) { it.render() }
|
) { it.render() }
|
||||||
if (!isDuplicated) {
|
if (!isDuplicated) {
|
||||||
usage.processChildrenWithoutProgress { append(process(it, indent + 1)) }
|
usage.processChildrenWithoutProgress {
|
||||||
|
append(process(it, indent + 1))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}.replace(Regex("</bold><bold>"), "")
|
}.replace(Regex("</bold><bold>"), "")
|
||||||
}
|
}
|
||||||
@@ -100,12 +107,14 @@ abstract class AbstractSlicerTest : KotlinLightCodeInsightFixtureTestCase() {
|
|||||||
|
|
||||||
val fileText = FileUtil.loadFile(mainFile, true)
|
val fileText = FileUtil.loadFile(mainFile, true)
|
||||||
val flowKind = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// FLOW: ")
|
val flowKind = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// FLOW: ")
|
||||||
|
val withDereferences = InTextDirectivesUtils.isDirectiveDefined(fileText, "// WITH_DEREFERENCES")
|
||||||
val analysisParams = SliceAnalysisParams().apply {
|
val analysisParams = SliceAnalysisParams().apply {
|
||||||
dataFlowToThis = when (flowKind) {
|
dataFlowToThis = when (flowKind) {
|
||||||
"IN" -> true
|
"IN" -> true
|
||||||
"OUT" -> false
|
"OUT" -> false
|
||||||
else -> throw AssertionError("Invalid flow kind: $flowKind")
|
else -> throw AssertionError("Invalid flow kind: $flowKind")
|
||||||
}
|
}
|
||||||
|
showInstanceDereferences = withDereferences
|
||||||
scope = AnalysisScope(project)
|
scope = AnalysisScope(project)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -174,6 +174,12 @@ public class SlicerTestGenerated extends AbstractSlicerTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("outflow/extensionIndexingDereferences.kt")
|
||||||
|
public void testOutflow_ExtensionIndexingDereferences() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/extensionIndexingDereferences.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("outflow/funBodyExpression.kt")
|
@TestMetadata("outflow/funBodyExpression.kt")
|
||||||
public void testOutflow_FunBodyExpression() throws Exception {
|
public void testOutflow_FunBodyExpression() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/funBodyExpression.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/funBodyExpression.kt");
|
||||||
@@ -210,6 +216,12 @@ public class SlicerTestGenerated extends AbstractSlicerTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("outflow/indexingDereferences.kt")
|
||||||
|
public void testOutflow_IndexingDereferences() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/indexingDereferences.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("outflow/localVariableUsages.kt")
|
@TestMetadata("outflow/localVariableUsages.kt")
|
||||||
public void testOutflow_LocalVariableUsages() throws Exception {
|
public void testOutflow_LocalVariableUsages() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/localVariableUsages.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/localVariableUsages.kt");
|
||||||
@@ -234,6 +246,12 @@ public class SlicerTestGenerated extends AbstractSlicerTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("outflow/operatorCallDereferences.kt")
|
||||||
|
public void testOutflow_OperatorCallDereferences() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/operatorCallDereferences.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("outflow/primaryConstructorCalls.kt")
|
@TestMetadata("outflow/primaryConstructorCalls.kt")
|
||||||
public void testOutflow_PrimaryConstructorCalls() throws Exception {
|
public void testOutflow_PrimaryConstructorCalls() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/primaryConstructorCalls.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/primaryConstructorCalls.kt");
|
||||||
@@ -252,6 +270,12 @@ public class SlicerTestGenerated extends AbstractSlicerTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("outflow/simpleCallDereferences.kt")
|
||||||
|
public void testOutflow_SimpleCallDereferences() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/simpleCallDereferences.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("outflow/topLevelPropertyUsages.kt")
|
@TestMetadata("outflow/topLevelPropertyUsages.kt")
|
||||||
public void testOutflow_TopLevelPropertyUsages() throws Exception {
|
public void testOutflow_TopLevelPropertyUsages() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/topLevelPropertyUsages.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/topLevelPropertyUsages.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user