Support smart step into for inline function arguments
This commit is contained in:
@@ -19,55 +19,58 @@ package org.jetbrains.kotlin.idea.debugger.stepping
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.engine.BreakpointStepMethodFilter
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.LambdaMethodFilter
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.psi.PsiElementFactory
|
||||
import com.intellij.util.Range
|
||||
import com.sun.jdi.Location
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.idea.core.refactoring.isMultiLine
|
||||
import org.jetbrains.kotlin.idea.debugger.isInsideInlineArgument
|
||||
import org.jetbrains.kotlin.psi.KtBlockExpression
|
||||
import org.jetbrains.kotlin.psi.KtFunctionLiteralExpression
|
||||
import org.jetbrains.kotlin.psi.KtFunction
|
||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||
|
||||
public class KotlinLambdaMethodFilter(
|
||||
lambda: KtFunctionLiteralExpression,
|
||||
private val myCallingExpressionLines: Range<Int>
|
||||
private val lambda: KtFunction,
|
||||
private val myCallingExpressionLines: Range<Int>,
|
||||
private val isInline: Boolean
|
||||
): BreakpointStepMethodFilter {
|
||||
private val myFirstStatementPosition: SourcePosition?
|
||||
private val myLastStatementLine: Int
|
||||
|
||||
init {
|
||||
var firstStatementPosition: SourcePosition? = null
|
||||
var lastStatementPosition: SourcePosition? = null
|
||||
val body = lambda.getBodyExpression()!!
|
||||
val statements = body.getStatements()
|
||||
if (statements.isNotEmpty()) {
|
||||
firstStatementPosition = SourcePosition.createFromElement(statements.first())
|
||||
if (firstStatementPosition != null) {
|
||||
val lastStatement = statements.last()
|
||||
lastStatementPosition = SourcePosition.createFromOffset(firstStatementPosition.getFile(), lastStatement.getTextRange().getEndOffset())
|
||||
if (lambda.isMultiLine()) {
|
||||
var firstStatementPosition: SourcePosition? = null
|
||||
var lastStatementPosition: SourcePosition? = null
|
||||
val body = lambda.bodyExpression as KtBlockExpression
|
||||
val statements = body.statements
|
||||
if (statements.isNotEmpty()) {
|
||||
firstStatementPosition = SourcePosition.createFromElement(statements.first())
|
||||
if (firstStatementPosition != null) {
|
||||
val lastStatement = statements.last()
|
||||
lastStatementPosition = SourcePosition.createFromOffset(firstStatementPosition.file, lastStatement.textRange.endOffset)
|
||||
}
|
||||
}
|
||||
myFirstStatementPosition = firstStatementPosition
|
||||
myLastStatementLine = if (lastStatementPosition != null) lastStatementPosition.line else -1
|
||||
}
|
||||
else {
|
||||
myFirstStatementPosition = SourcePosition.createFromElement(lambda)
|
||||
myLastStatementLine = myFirstStatementPosition!!.line
|
||||
}
|
||||
myFirstStatementPosition = firstStatementPosition
|
||||
myLastStatementLine = if (lastStatementPosition != null) lastStatementPosition.getLine() else -1
|
||||
}
|
||||
|
||||
override fun getBreakpointPosition(): SourcePosition? {
|
||||
return myFirstStatementPosition
|
||||
}
|
||||
|
||||
override fun getLastStatementLine(): Int {
|
||||
return myLastStatementLine
|
||||
}
|
||||
override fun getBreakpointPosition() = myFirstStatementPosition
|
||||
override fun getLastStatementLine() = myLastStatementLine
|
||||
|
||||
override fun locationMatches(process: DebugProcessImpl, location: Location): Boolean {
|
||||
val method = location.method()
|
||||
|
||||
if (isInline) {
|
||||
return isInsideInlineArgument(lambda, location, process)
|
||||
}
|
||||
|
||||
return isLambdaName(method.name())
|
||||
}
|
||||
|
||||
override fun getCallingExpressionLines(): Range<Int>? {
|
||||
return myCallingExpressionLines
|
||||
}
|
||||
override fun getCallingExpressionLines() = if (isInline) Range(0, 999) else myCallingExpressionLines
|
||||
|
||||
companion object {
|
||||
public fun isLambdaName(name: String?): Boolean {
|
||||
|
||||
+5
-9
@@ -16,27 +16,23 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.actions.MethodSmartStepTarget
|
||||
import com.intellij.debugger.actions.SmartStepTarget
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiMethod
|
||||
import com.intellij.util.Range
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.idea.KotlinIcons
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.psi.KtFunctionLiteralExpression
|
||||
import org.jetbrains.kotlin.psi.KtFunction
|
||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||
import javax.swing.Icon
|
||||
|
||||
public class KotlinLambdaSmartStepTarget(
|
||||
label: String,
|
||||
highlightElement: KtFunctionLiteralExpression,
|
||||
lines: Range<Int>
|
||||
highlightElement: KtFunction,
|
||||
lines: Range<Int>,
|
||||
val isInline: Boolean
|
||||
): SmartStepTarget(label, highlightElement, true, lines) {
|
||||
override fun getIcon() = KotlinIcons.LAMBDA
|
||||
|
||||
fun getLambda() = getHighlightElement() as KtFunctionLiteralExpression
|
||||
fun getLambda() = getHighlightElement() as KtFunction
|
||||
|
||||
companion object {
|
||||
fun calcLabel(descriptor: DeclarationDescriptor, paramName: Name): String {
|
||||
|
||||
@@ -66,20 +66,33 @@ public class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() {
|
||||
element.accept(object: KtTreeVisitorVoid() {
|
||||
|
||||
override fun visitFunctionLiteralExpression(expression: KtFunctionLiteralExpression) {
|
||||
val context = expression.analyze()
|
||||
val resolvedCall = expression.getParentCall(context).getResolvedCall(context)
|
||||
if (resolvedCall != null && !InlineUtil.isInline(resolvedCall.getResultingDescriptor())) {
|
||||
val arguments = resolvedCall.getValueArguments()
|
||||
recordFunctionLiteral(expression.functionLiteral)
|
||||
}
|
||||
|
||||
override fun visitNamedFunction(function: KtNamedFunction) {
|
||||
if (!recordFunctionLiteral(function)) {
|
||||
super.visitNamedFunction(function)
|
||||
}
|
||||
}
|
||||
|
||||
private fun recordFunctionLiteral(function: KtFunction): Boolean {
|
||||
val context = function.analyze()
|
||||
val resolvedCall = function.getParentCall(context).getResolvedCall(context)
|
||||
if (resolvedCall != null) {
|
||||
val arguments = resolvedCall.valueArguments
|
||||
for ((param, argument) in arguments) {
|
||||
if (argument.getArguments().any { it.getArgumentExpression() == expression}) {
|
||||
val label = KotlinLambdaSmartStepTarget.calcLabel(resolvedCall.getResultingDescriptor(), param.getName())
|
||||
result.add(KotlinLambdaSmartStepTarget(label, expression, lines))
|
||||
break
|
||||
if (argument.arguments.any { getArgumentExpression(it) == function }) {
|
||||
val label = KotlinLambdaSmartStepTarget.calcLabel(resolvedCall.resultingDescriptor, param.name)
|
||||
result.add(KotlinLambdaSmartStepTarget(label, function, lines, InlineUtil.isInline(resolvedCall.resultingDescriptor)))
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun getArgumentExpression(it: ValueArgument) = (it.getArgumentExpression() as? KtFunctionLiteralExpression)?.functionLiteral ?: it.getArgumentExpression()
|
||||
|
||||
override fun visitObjectLiteralExpression(expression: KtObjectLiteralExpression) {
|
||||
// skip calls in object declarations
|
||||
}
|
||||
@@ -181,7 +194,7 @@ public class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() {
|
||||
override fun createMethodFilter(stepTarget: SmartStepTarget?): MethodFilter? {
|
||||
return when (stepTarget) {
|
||||
is KotlinMethodSmartStepTarget -> KotlinBasicStepMethodFilter(stepTarget.resolvedElement, stepTarget.getCallingExpressionLines()!!)
|
||||
is KotlinLambdaSmartStepTarget -> KotlinLambdaMethodFilter(stepTarget.getLambda(), stepTarget.getCallingExpressionLines()!! )
|
||||
is KotlinLambdaSmartStepTarget -> KotlinLambdaMethodFilter(stepTarget.getLambda(), stepTarget.getCallingExpressionLines()!!, stepTarget.isInline)
|
||||
else -> super.createMethodFilter(stepTarget)
|
||||
}
|
||||
}
|
||||
|
||||
+6
-4
@@ -38,6 +38,7 @@ import org.jetbrains.kotlin.idea.util.DebuggerUtils
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parents
|
||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||
@@ -68,7 +69,7 @@ public class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() {
|
||||
val file = sourcePosition.file as? KtFile ?: return null
|
||||
if (sourcePosition.line < 0) return null
|
||||
|
||||
val containingFunction = sourcePosition.elementAt.getParentOfType<KtNamedFunction>(false) ?: return null
|
||||
val containingFunction = sourcePosition.elementAt.parents.firstOrNull { it is KtNamedFunction && !it.isLocal } ?: return null
|
||||
|
||||
val startLineNumber = containingFunction.getLineNumber(true)
|
||||
val endLineNumber = containingFunction.getLineNumber(false)
|
||||
@@ -221,12 +222,13 @@ public class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() {
|
||||
private fun getInlineArgumentsIfAny(inlineFunctionCalls: List<KtCallExpression>): List<KtFunction> {
|
||||
return inlineFunctionCalls.flatMap {
|
||||
it.valueArguments
|
||||
.map { it.getArgumentExpression() }
|
||||
.filterIsInstance<KtFunctionLiteralExpression>()
|
||||
.map { it.functionLiteral }
|
||||
.map { getArgumentExpression(it) }
|
||||
.filterIsInstance<KtFunction>()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getArgumentExpression(it: ValueArgument) = (it.getArgumentExpression() as? KtFunctionLiteralExpression)?.functionLiteral ?: it.getArgumentExpression()
|
||||
|
||||
private fun getInlineFunctionCallsIfAny(sourcePosition: SourcePosition): List<KtCallExpression> {
|
||||
val file = sourcePosition.file as? KtFile ?: return emptyList()
|
||||
val lineNumber = sourcePosition.line
|
||||
|
||||
@@ -4,4 +4,4 @@ fun foo() {
|
||||
|
||||
inline fun f1(f: () -> Unit) {}
|
||||
|
||||
// EXISTS: f1(() -> Unit)
|
||||
// EXISTS: f1(() -> Unit), f1: f.invoke()
|
||||
@@ -0,0 +1,7 @@
|
||||
fun foo() {
|
||||
<caret>f1(fun () { })
|
||||
}
|
||||
|
||||
inline fun f1(f: () -> Unit) {}
|
||||
|
||||
// EXISTS: f1(() -> Unit), f1: f.invoke()
|
||||
@@ -0,0 +1,13 @@
|
||||
LineBreakpoint created at smartStepIntoInlinedFunLiteral.kt:6
|
||||
!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !OUTPUT_PATH!;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! smartStepIntoInlinedFunLiteral.SmartStepIntoInlinedFunLiteralKt
|
||||
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||
smartStepIntoInlinedFunLiteral.kt:6
|
||||
smartStepIntoInlinedFunLiteral.kt:9
|
||||
smartStepIntoInlinedFunLiteral.kt:10
|
||||
smartStepIntoInlinedFunLiteral.kt:13
|
||||
smartStepIntoInlinedFunLiteral.kt:14
|
||||
smartStepIntoInlinedFunLiteral.kt:18
|
||||
smartStepIntoInlinedFunLiteral.kt:20
|
||||
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||
|
||||
Process finished with exit code 0
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
LineBreakpoint created at smartStepIntoInlinedFunctionalExpression.kt:6
|
||||
!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !OUTPUT_PATH!;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! smartStepIntoInlinedFunctionalExpression.SmartStepIntoInlinedFunctionalExpressionKt
|
||||
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||
smartStepIntoInlinedFunctionalExpression.kt:6
|
||||
smartStepIntoInlinedFunctionalExpression.kt:9
|
||||
smartStepIntoInlinedFunctionalExpression.kt:10
|
||||
smartStepIntoInlinedFunctionalExpression.kt:13
|
||||
smartStepIntoInlinedFunctionalExpression.kt:14
|
||||
smartStepIntoInlinedFunctionalExpression.kt:18
|
||||
smartStepIntoInlinedFunctionalExpression.kt:20
|
||||
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||
|
||||
Process finished with exit code 0
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package smartStepIntoInlinedFunLiteral
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val array = arrayOf(1, 2)
|
||||
//Breakpoint!
|
||||
val myClass = MyClass()
|
||||
|
||||
// smart step into f2.invoke(), one-line lambda
|
||||
myClass.f1 { test() }
|
||||
.f2 { test() }
|
||||
|
||||
// smart step into map.invoke(), multiline lambda
|
||||
array.map {
|
||||
it *2
|
||||
}
|
||||
|
||||
// smart step into filter.invoke()
|
||||
array.map { it * 2 }
|
||||
.filter {
|
||||
it > 2
|
||||
}
|
||||
}
|
||||
|
||||
class MyClass {
|
||||
inline fun f1(f1Param: () -> Unit): MyClass {
|
||||
test()
|
||||
f1Param()
|
||||
return this
|
||||
}
|
||||
|
||||
inline fun f2(f1Param: () -> Unit): MyClass {
|
||||
test()
|
||||
f1Param()
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
fun test() {}
|
||||
|
||||
// STEP_OVER: 1
|
||||
// SMART_STEP_INTO_BY_INDEX: 4
|
||||
// STEP_OVER: 1
|
||||
// SMART_STEP_INTO_BY_INDEX: 2
|
||||
// STEP_OVER: 1
|
||||
// SMART_STEP_INTO_BY_INDEX: 4
|
||||
Vendored
+45
@@ -0,0 +1,45 @@
|
||||
package smartStepIntoInlinedFunctionalExpression
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val array = arrayOf(1, 2)
|
||||
//Breakpoint!
|
||||
val myClass = MyClass()
|
||||
|
||||
// smart step into f2.invoke(), one-line lambda
|
||||
myClass.f1(fun () { test() })
|
||||
.f2(fun () { test() })
|
||||
|
||||
// smart step into map.invoke(), multiline lambda
|
||||
array.map(fun (it): Int {
|
||||
return it * 2
|
||||
})
|
||||
|
||||
// smart step into filter.invoke()
|
||||
array.map(fun (it): Int { return it * 2 })
|
||||
.filter(fun (it): Boolean {
|
||||
return it > 2
|
||||
})
|
||||
}
|
||||
|
||||
class MyClass {
|
||||
inline fun f1(f1Param: () -> Unit): MyClass {
|
||||
test()
|
||||
f1Param()
|
||||
return this
|
||||
}
|
||||
|
||||
inline fun f2(f1Param: () -> Unit): MyClass {
|
||||
test()
|
||||
f1Param()
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
fun test() {}
|
||||
|
||||
// STEP_OVER: 1
|
||||
// SMART_STEP_INTO_BY_INDEX: 4
|
||||
// STEP_OVER: 1
|
||||
// SMART_STEP_INTO_BY_INDEX: 2
|
||||
// STEP_OVER: 1
|
||||
// SMART_STEP_INTO_BY_INDEX: 4
|
||||
@@ -27,7 +27,6 @@ import com.intellij.debugger.impl.DebuggerContextImpl
|
||||
import com.intellij.debugger.impl.PositionUtil
|
||||
import com.intellij.debugger.settings.DebuggerSettings
|
||||
import com.intellij.debugger.ui.breakpoints.BreakpointManager
|
||||
import com.intellij.debugger.ui.breakpoints.LineBreakpoint
|
||||
import com.intellij.execution.process.ProcessOutputTypes
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.roots.JdkOrderEntry
|
||||
@@ -43,6 +42,8 @@ import com.intellij.xdebugger.breakpoints.XBreakpoint
|
||||
import com.intellij.xdebugger.breakpoints.XBreakpointProperties
|
||||
import com.intellij.xdebugger.breakpoints.XBreakpointType
|
||||
import com.intellij.xdebugger.breakpoints.XLineBreakpointType
|
||||
import com.sun.jdi.request.StepRequest
|
||||
import org.jetbrains.kotlin.idea.core.refactoring.getLineNumber
|
||||
import org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpoint
|
||||
import org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpointType
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.*
|
||||
@@ -162,6 +163,7 @@ abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() {
|
||||
}
|
||||
|
||||
when {
|
||||
!line.startsWith("//") -> return
|
||||
line.startsWith("// STEP_INTO: ") -> repeat("// STEP_INTO: ") { stepInto(this) }
|
||||
line.startsWith("// STEP_OUT: ") -> repeat("// STEP_OUT: ") { doStepOut() }
|
||||
line.startsWith("// STEP_OVER: ") -> repeat("// STEP_OVER: ") { doStepOver() }
|
||||
@@ -188,14 +190,11 @@ abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() {
|
||||
}
|
||||
|
||||
private fun createSmartStepIntoFilters(): List<MethodFilter> {
|
||||
val breakpointManager = DebuggerManagerEx.getInstanceEx(getProject())?.getBreakpointManager()
|
||||
val breakpoint = breakpointManager?.getBreakpoints()?.first { it is LineBreakpoint }
|
||||
|
||||
val line = (breakpoint as LineBreakpoint).getLineIndex()
|
||||
val contextElement = ContextUtil.getContextElement(evaluationContext)!!
|
||||
val line = runReadAction { contextElement.getLineNumber() }
|
||||
|
||||
return runReadAction {
|
||||
val containingFile = breakpoint.getPsiFile()
|
||||
if (containingFile == null) throw AssertionError("Couldn't find file for breakpoint at the line $line")
|
||||
val containingFile = contextElement.containingFile
|
||||
|
||||
val position = MockSourcePosition(_file = containingFile, _line = line)
|
||||
|
||||
@@ -204,7 +203,7 @@ abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() {
|
||||
stepTargets.filterIsInstance<SmartStepTarget>().map {
|
||||
stepTarget ->
|
||||
when (stepTarget) {
|
||||
is KotlinLambdaSmartStepTarget -> KotlinLambdaMethodFilter(stepTarget.getLambda(), stepTarget.getCallingExpressionLines()!!)
|
||||
is KotlinLambdaSmartStepTarget -> KotlinLambdaMethodFilter(stepTarget.getLambda(), stepTarget.getCallingExpressionLines()!!, stepTarget.isInline)
|
||||
is KotlinMethodSmartStepTarget -> KotlinBasicStepMethodFilter(stepTarget.resolvedElement, stepTarget.getCallingExpressionLines()!!)
|
||||
is MethodSmartStepTarget -> BasicStepMethodFilter(stepTarget.getMethod(), stepTarget.getCallingExpressionLines())
|
||||
else -> null
|
||||
|
||||
@@ -523,6 +523,18 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest {
|
||||
doCustomTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("smartStepIntoInlinedFunLiteral.kt")
|
||||
public void testSmartStepIntoInlinedFunLiteral() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoInlinedFunLiteral.kt");
|
||||
doCustomTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("smartStepIntoInlinedFunctionalExpression.kt")
|
||||
public void testSmartStepIntoInlinedFunctionalExpression() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoInlinedFunctionalExpression.kt");
|
||||
doCustomTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("stepIntoStdlibInlineFun2step.kt")
|
||||
public void testStepIntoStdlibInlineFun2step() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/stepIntoStdlibInlineFun2step.kt");
|
||||
|
||||
@@ -125,6 +125,12 @@ public class SmartStepIntoTestGenerated extends AbstractSmartStepIntoTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inlinedFunctionalExpression.kt")
|
||||
public void testInlinedFunctionalExpression() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/smartStepInto/inlinedFunctionalExpression.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("invoke.kt")
|
||||
public void testInvoke() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/smartStepInto/invoke.kt");
|
||||
|
||||
Reference in New Issue
Block a user