Analyze Data Flow: Support properties with custom accessors
#KT-11994 In Progress
This commit is contained in:
@@ -183,7 +183,7 @@ public class KtParameter extends KtNamedDeclarationStub<KotlinParameterStub> imp
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public KtFunction getOwnerFunction() {
|
||||
public KtDeclarationWithBody getOwnerFunction() {
|
||||
PsiElement parent = getParentByStub();
|
||||
if (!(parent instanceof KtParameterList)) return null;
|
||||
return ((KtParameterList) parent).getOwnerFunction();
|
||||
|
||||
@@ -74,10 +74,10 @@ public class KtParameterList extends KtElementImplStub<KotlinPlaceHolderStub<KtP
|
||||
removeParameter(getParameters().get(index));
|
||||
}
|
||||
|
||||
public KtFunction getOwnerFunction() {
|
||||
public KtDeclarationWithBody getOwnerFunction() {
|
||||
PsiElement parent = getParentByStub();
|
||||
if (!(parent instanceof KtFunction)) return null;
|
||||
return (KtFunction) parent;
|
||||
if (!(parent instanceof KtDeclarationWithBody)) return null;
|
||||
return (KtDeclarationWithBody) parent;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
+1
-1
@@ -155,7 +155,7 @@ class KotlinReferencesSearcher : QueryExecutorBase<PsiReference, ReferencesSearc
|
||||
|
||||
private fun searchNamedArguments(parameter: KtParameter) {
|
||||
val parameterName = parameter.name ?: return
|
||||
val function = parameter.ownerFunction ?: return
|
||||
val function = parameter.ownerFunction as? KtFunction ?: return
|
||||
if (function.nameAsName?.isSpecial ?: true) return
|
||||
val project = function.project
|
||||
var namedArgsScope = function.useScope.intersectWith(queryParameters.scopeDeterminedByUser)
|
||||
|
||||
+1
-1
@@ -327,7 +327,7 @@ class ConvertFunctionTypeParameterToReceiverIntention : SelfTargetingRangeIntent
|
||||
if (functionType.receiverTypeReference != null) return null
|
||||
val lambdaType = functionType.getAbbreviatedTypeOrType(functionType.analyze(BodyResolveMode.PARTIAL)) ?: return null
|
||||
val containingParameter = (functionType.parent as? KtTypeReference)?.parent as? KtParameter ?: return null
|
||||
val ownerFunction = containingParameter.ownerFunction ?: return null
|
||||
val ownerFunction = containingParameter.ownerFunction as? KtFunction ?: return null
|
||||
val typeParameterIndex = functionType.parameters.indexOf(parameter)
|
||||
val functionParameterIndex = ownerFunction.valueParameters.indexOf(containingParameter)
|
||||
return ConversionData(typeParameterIndex, functionParameterIndex, lambdaType, ownerFunction)
|
||||
|
||||
+1
-1
@@ -195,7 +195,7 @@ class ConvertFunctionTypeReceiverToParameterIntention : SelfTargetingRangeIntent
|
||||
?.getReceiverTypeFromFunctionType()
|
||||
?: return null
|
||||
val containingParameter = (functionType.parent as? KtTypeReference)?.parent as? KtParameter ?: return null
|
||||
val ownerFunction = containingParameter.ownerFunction ?: return null
|
||||
val ownerFunction = containingParameter.ownerFunction as? KtFunction ?: return null
|
||||
val functionParameterIndex = ownerFunction.valueParameters.indexOf(containingParameter)
|
||||
return ConversionData(functionParameterIndex, lambdaReceiverType, ownerFunction)
|
||||
}
|
||||
|
||||
@@ -99,7 +99,8 @@ class KtParameterPattern : PsiElementPattern<KtParameter, KtParameterPattern>(Kt
|
||||
override fun processValues(ktParameter: KtParameter,
|
||||
context: ProcessingContext,
|
||||
processor: PairProcessor<KtFunction, ProcessingContext>): Boolean {
|
||||
return processor.process(ktParameter.ownerFunction, context)
|
||||
val function = ktParameter.ownerFunction as? KtFunction ?: return true
|
||||
return processor.process(function, context)
|
||||
}
|
||||
|
||||
override fun accepts(ktParameter: KtParameter, context: ProcessingContext): Boolean {
|
||||
|
||||
@@ -36,7 +36,7 @@ class KotlinSliceProvider : SliceLanguageSupportProvider {
|
||||
?.firstOrNull {
|
||||
it is KtProperty ||
|
||||
it is KtParameter ||
|
||||
it is KtFunction ||
|
||||
it is KtDeclarationWithBody ||
|
||||
(it is KtExpression && it !is KtDeclaration)
|
||||
}
|
||||
?.let { KtPsiUtil.safeDeparenthesize(it as KtExpression) } ?: return null
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.slicer
|
||||
|
||||
import com.intellij.codeInsight.highlighting.ReadWriteAccessDetector.Access
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.search.LocalSearchScope
|
||||
import com.intellij.psi.search.SearchScope
|
||||
import com.intellij.slicer.SliceUsage
|
||||
import com.intellij.usageView.UsageInfo
|
||||
@@ -33,14 +34,16 @@ import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.ReturnValueInstruc
|
||||
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraversalOrder
|
||||
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.traverse
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource
|
||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.VariableDescriptorWithAccessors
|
||||
import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFully
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.findUsages.KotlinFunctionFindUsagesOptions
|
||||
import org.jetbrains.kotlin.idea.findUsages.KotlinPropertyFindUsagesOptions
|
||||
import org.jetbrains.kotlin.idea.findUsages.processAllUsages
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReadWriteAccessDetector
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||
@@ -85,13 +88,6 @@ private fun KtDeclaration.processVariableAccesses(
|
||||
)
|
||||
}
|
||||
|
||||
private fun KtProperty.canProcess(): Boolean {
|
||||
if (hasDelegate()) return false
|
||||
if (isLocal) return true
|
||||
val descriptor = resolveToDescriptor() as? PropertyDescriptor ?: return false
|
||||
return descriptor.accessors.all { it.isDefault }
|
||||
}
|
||||
|
||||
private fun KtParameter.canProcess(): Boolean {
|
||||
return !(isLoopParameter || isVarArg || hasValOrVar())
|
||||
}
|
||||
@@ -131,26 +127,69 @@ class InflowSlicer(
|
||||
) : Slicer(element, processor, parentUsage) {
|
||||
private fun PsiElement.passToProcessorAsValue(lambdaLevel: Int = parentUsage.lambdaLevel) = passToProcessor(lambdaLevel, true)
|
||||
|
||||
private fun KtProperty.processProperty() {
|
||||
if (!canProcess()) return
|
||||
|
||||
initializer?.passToProcessorAsValue()
|
||||
processVariableAccesses(parentUsage.scope.toSearchScope(), Access.Write) body@ {
|
||||
private fun KtProperty.processAssignments() {
|
||||
val analysisScope = parentUsage.scope.toSearchScope()
|
||||
val accessSearchScope = if (isVar) analysisScope else {
|
||||
val containerScope = getStrictParentOfType<KtDeclaration>()?.let { LocalSearchScope(it) } ?: return
|
||||
analysisScope.intersectWith(containerScope)
|
||||
}
|
||||
processVariableAccesses(accessSearchScope, Access.Write) body@ {
|
||||
val refExpression = it.element as? KtExpression ?: return@body
|
||||
val rhs = KtPsiUtil.safeDeparenthesize(refExpression).getAssignmentByLHS()?.right ?: return@body
|
||||
rhs.passToProcessorAsValue()
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtPropertyAccessor.processBackingFieldAssignments() {
|
||||
forEachDescendantOfType<KtBinaryExpression> body@ {
|
||||
if (it.operationToken != KtTokens.EQ) return@body
|
||||
val lhs = it.left?.let { KtPsiUtil.safeDeparenthesize(it) } ?: return@body
|
||||
val rhs = it.right ?: return@body
|
||||
if (!lhs.isBackingFieldReference()) return@body
|
||||
rhs.passToProcessor()
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtProperty.processProperty() {
|
||||
val bindingContext by lazy { analyzeFully() }
|
||||
|
||||
if (hasDelegateExpression()) {
|
||||
val getter = (resolveToDescriptor() as VariableDescriptorWithAccessors).getter
|
||||
val delegateGetterResolvedCall = getter?.let { bindingContext[BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, it] }
|
||||
(delegateGetterResolvedCall?.resultingDescriptor?.source?.getPsi() as? KtDeclarationWithBody)?.passToProcessor()
|
||||
return
|
||||
}
|
||||
|
||||
initializer?.passToProcessor()
|
||||
|
||||
getter?.processFunction()
|
||||
|
||||
val isDefaultGetter = getter?.bodyExpression == null
|
||||
val isDefaultSetter = setter?.bodyExpression == null
|
||||
if (isDefaultGetter) {
|
||||
if (isDefaultSetter) {
|
||||
processAssignments()
|
||||
}
|
||||
else {
|
||||
setter!!.processBackingFieldAssignments()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtParameter.processParameter() {
|
||||
if (!canProcess()) return
|
||||
|
||||
val function = ownerFunction ?: return
|
||||
if (function.isOverridable()) return
|
||||
|
||||
if (function is KtPropertyAccessor && function.isSetter) {
|
||||
function.property.processAssignments()
|
||||
return
|
||||
}
|
||||
|
||||
val parameterDescriptor = resolveToDescriptor() as ValueParameterDescriptor
|
||||
|
||||
function.processCalls(parentUsage.scope.toSearchScope()) body@ {
|
||||
(function as? KtFunction)?.processCalls(parentUsage.scope.toSearchScope()) body@ {
|
||||
val refExpression = it.element as? KtExpression ?: return@body
|
||||
val callElement = refExpression.getParentOfTypeAndBranch<KtCallElement> { calleeExpression } ?: return@body
|
||||
val resolvedCall = callElement.getResolvedCall(callElement.analyze()) ?: return@body
|
||||
@@ -164,8 +203,9 @@ class InflowSlicer(
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtFunction.processFunction() {
|
||||
val pseudocode = pseudocodeCache[bodyExpression ?: return] ?: return
|
||||
private fun KtDeclarationWithBody.processFunction() {
|
||||
val bodyExpression = bodyExpression ?: return
|
||||
val pseudocode = pseudocodeCache[bodyExpression] ?: return
|
||||
pseudocode.traverse(TraversalOrder.FORWARD) { instr ->
|
||||
if (instr is ReturnValueInstruction && instr.subroutine == this) {
|
||||
(instr.returnExpressionIfAny?.returnedExpression ?: instr.element as? KtExpression)?.passToProcessorAsValue()
|
||||
@@ -181,6 +221,12 @@ class InflowSlicer(
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtExpression.isBackingFieldReference(): Boolean {
|
||||
return this is KtSimpleNameExpression &&
|
||||
getReferencedName() == SyntheticFieldDescriptor.NAME.asString() &&
|
||||
analyze()[BindingContext.REFERENCE_TARGET, this] is SyntheticFieldDescriptor
|
||||
}
|
||||
|
||||
private fun KtExpression.processExpression() {
|
||||
val lambda = when (this) {
|
||||
is KtLambdaExpression -> functionLiteral
|
||||
@@ -206,7 +252,19 @@ class InflowSlicer(
|
||||
}
|
||||
return
|
||||
}
|
||||
(createdAt.target.accessedDescriptor?.source?.getPsi() as? KtDeclaration)?.passToProcessor()
|
||||
val accessedDescriptor = createdAt.target.accessedDescriptor ?: return
|
||||
val accessedDeclaration = accessedDescriptor.source.getPsi() as? KtDeclaration ?: return
|
||||
if (accessedDescriptor is SyntheticFieldDescriptor) {
|
||||
val property = accessedDeclaration as? KtProperty ?: return
|
||||
if (accessedDescriptor.propertyDescriptor.setter?.isDefault ?: true) {
|
||||
property.processAssignments()
|
||||
}
|
||||
else {
|
||||
property.setter?.processBackingFieldAssignments()
|
||||
}
|
||||
return
|
||||
}
|
||||
accessedDeclaration.passToProcessor()
|
||||
}
|
||||
|
||||
is MergeInstruction -> createdAt.passInputsToProcessor()
|
||||
@@ -241,7 +299,7 @@ class InflowSlicer(
|
||||
when (element) {
|
||||
is KtProperty -> element.processProperty()
|
||||
is KtParameter -> element.processParameter()
|
||||
is KtFunction -> element.processFunction()
|
||||
is KtDeclarationWithBody -> element.processFunction()
|
||||
else -> element.processExpression()
|
||||
}
|
||||
}
|
||||
@@ -253,11 +311,8 @@ class OutflowSlicer(
|
||||
parentUsage: KotlinSliceUsage
|
||||
) : Slicer(element, processor, parentUsage) {
|
||||
private fun KtDeclaration.processVariable() {
|
||||
when (this) {
|
||||
is KtProperty -> if (!canProcess()) return
|
||||
is KtParameter -> if (!canProcess()) return
|
||||
else -> return
|
||||
}
|
||||
if (this is KtParameter && !canProcess()) return
|
||||
|
||||
val withDereferences = parentUsage.params.showInstanceDereferences
|
||||
processVariableAccesses(
|
||||
parentUsage.scope.toSearchScope(),
|
||||
@@ -368,6 +423,9 @@ class OutflowSlicer(
|
||||
when (element) {
|
||||
is KtProperty, is KtParameter -> (element as KtDeclaration).processVariable()
|
||||
is KtFunction -> element.processFunction()
|
||||
is KtPropertyAccessor -> if (element.isGetter) {
|
||||
element.property.processVariable()
|
||||
}
|
||||
else -> element.processExpression()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// FLOW: IN
|
||||
|
||||
class A(var b: Boolean) {
|
||||
var foo: Int
|
||||
set(value) {
|
||||
field = if (b) value else 0
|
||||
}
|
||||
|
||||
fun test() {
|
||||
val x = <caret>foo
|
||||
foo = 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
10 val x = <bold>foo</bold>
|
||||
4 var <bold>foo: Int</bold>
|
||||
6 field = <bold>if (b) value else 0</bold>
|
||||
6 field = if (b) <bold>value</bold> else 0
|
||||
5 set(<bold>value</bold>) {
|
||||
11 foo = <bold>1</bold>
|
||||
6 field = if (b) value else <bold>0</bold>
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// FLOW: IN
|
||||
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
object D {
|
||||
operator fun getValue(thisRef: Any?, property: KProperty<*>) = 1
|
||||
}
|
||||
|
||||
val foo: Int by D
|
||||
|
||||
fun test() {
|
||||
val <caret>x = foo
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
12 val <bold>x = foo</bold>
|
||||
12 val x = <bold>foo</bold>
|
||||
9 val <bold>foo: Int by D</bold>
|
||||
6 operator fun <bold>getValue(thisRef: Any?, property: KProperty<*>) = 1</bold>
|
||||
6 operator fun getValue(thisRef: Any?, property: KProperty<*>) = <bold>1</bold>
|
||||
@@ -0,0 +1,14 @@
|
||||
// FLOW: IN
|
||||
|
||||
class A(var b: Boolean) {
|
||||
var foo: Int
|
||||
get() = if (b) field else 0
|
||||
set(value) {
|
||||
field = if (b) value else 0
|
||||
}
|
||||
|
||||
fun test() {
|
||||
val x = <caret>foo
|
||||
foo = 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
11 val x = <bold>foo</bold>
|
||||
4 var <bold>foo: Int</bold>
|
||||
5 get() = <bold>if (b) field else 0</bold>
|
||||
5 get() = if (b) <bold>field</bold> else 0
|
||||
7 field = <bold>if (b) value else 0</bold>
|
||||
7 field = if (b) <bold>value</bold> else 0
|
||||
6 set(<bold>value</bold>) {
|
||||
12 foo = <bold>1</bold>
|
||||
7 field = if (b) value else <bold>0</bold>
|
||||
5 get() = if (b) field else <bold>0</bold>
|
||||
@@ -0,0 +1,8 @@
|
||||
// FLOW: IN
|
||||
|
||||
val foo: Int
|
||||
get() = 0
|
||||
|
||||
fun test() {
|
||||
val <caret>x = foo
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
7 val <bold>x = foo</bold>
|
||||
7 val x = <bold>foo</bold>
|
||||
3 val <bold>foo: Int</bold>
|
||||
4 get() = <bold>0</bold>
|
||||
@@ -0,0 +1,10 @@
|
||||
// FLOW: IN
|
||||
|
||||
val foo: Int
|
||||
get(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
fun test() {
|
||||
val <caret>x = foo
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
9 val <bold>x = foo</bold>
|
||||
9 val x = <bold>foo</bold>
|
||||
3 val <bold>foo: Int</bold>
|
||||
5 return <bold>0</bold>
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// FLOW: IN
|
||||
|
||||
class A(var b: Boolean) {
|
||||
var foo: Int
|
||||
get() = 1
|
||||
set(value) {
|
||||
field = value + 1
|
||||
}
|
||||
|
||||
fun test() {
|
||||
val x = <caret>foo
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
11 val x = <bold>foo</bold>
|
||||
4 var <bold>foo: Int</bold>
|
||||
5 get() = <bold>1</bold>
|
||||
@@ -0,0 +1,8 @@
|
||||
// FLOW: OUT
|
||||
|
||||
val foo: Int
|
||||
get() = <caret>0
|
||||
|
||||
fun test() {
|
||||
val x = foo
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
4 get() = <bold>0</bold>
|
||||
4 <bold>get() = 0</bold>
|
||||
7 val <bold>x = foo</bold>
|
||||
@@ -0,0 +1,10 @@
|
||||
// FLOW: OUT
|
||||
|
||||
val foo: Int
|
||||
get(): Int {
|
||||
return <caret>0
|
||||
}
|
||||
|
||||
fun test() {
|
||||
val x = foo
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
5 return <bold>0</bold>
|
||||
4 <bold>get(): Int {</bold>
|
||||
9 val <bold>x = foo</bold>
|
||||
@@ -54,6 +54,18 @@ public class SlicerTestGenerated extends AbstractSlicerTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inflow/defaultGetterFieldInSetter.kt")
|
||||
public void testInflow_DefaultGetterFieldInSetter() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/defaultGetterFieldInSetter.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inflow/delegateGetter.kt")
|
||||
public void testInflow_DelegateGetter() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/delegateGetter.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inflow/doubleLambdaResult.kt")
|
||||
public void testInflow_DoubleLambdaResult() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/doubleLambdaResult.kt");
|
||||
@@ -102,6 +114,24 @@ public class SlicerTestGenerated extends AbstractSlicerTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inflow/getterAndSetterUsingField.kt")
|
||||
public void testInflow_GetterAndSetterUsingField() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/getterAndSetterUsingField.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inflow/getterExpressionBody.kt")
|
||||
public void testInflow_GetterExpressionBody() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/getterExpressionBody.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inflow/getterReturnExpression.kt")
|
||||
public void testInflow_GetterReturnExpression() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/getterReturnExpression.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inflow/ifExpression.kt")
|
||||
public void testInflow_IfExpression() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/ifExpression.kt");
|
||||
@@ -168,6 +198,12 @@ public class SlicerTestGenerated extends AbstractSlicerTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inflow/noFieldInGetter.kt")
|
||||
public void testInflow_NoFieldInGetter() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/noFieldInGetter.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inflow/nonLocalReturn.kt")
|
||||
public void testInflow_NonLocalReturn() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/inflow/nonLocalReturn.kt");
|
||||
@@ -312,6 +348,18 @@ public class SlicerTestGenerated extends AbstractSlicerTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("outflow/getterExpressionBody.kt")
|
||||
public void testOutflow_GetterExpressionBody() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/getterExpressionBody.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("outflow/getterReturnExpression.kt")
|
||||
public void testOutflow_GetterReturnExpression() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/getterReturnExpression.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("outflow/getFunCalls.kt")
|
||||
public void testOutflow_GetFunCalls() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/slicer/outflow/getFunCalls.kt");
|
||||
|
||||
Reference in New Issue
Block a user