Debugger: do not insert array in imports

This commit is contained in:
Natalia Ukhorskaya
2016-03-29 16:57:03 +03:00
parent 2ce9783214
commit 102edd5a0e
11 changed files with 213 additions and 32 deletions
+1
View File
@@ -27,6 +27,7 @@
- Configure Kotlin: show only changed files in the notification "Kotlin not configured" - Configure Kotlin: show only changed files in the notification "Kotlin not configured"
- Configure Kotlin: restore all changed files in undo action - Configure Kotlin: restore all changed files in undo action
- Convert java expression to kotlin when open Evaluate Expression dialog from Variables View - Convert java expression to kotlin when open Evaluate Expression dialog from Variables View
- Fix Evaluate Expression for expression created for array element
### Java to Kotlin converter ### Java to Kotlin converter
@@ -21,6 +21,7 @@ import com.intellij.debugger.engine.evaluation.CodeFragmentFactory
import com.intellij.debugger.engine.evaluation.CodeFragmentKind import com.intellij.debugger.engine.evaluation.CodeFragmentKind
import com.intellij.debugger.engine.evaluation.TextWithImports import com.intellij.debugger.engine.evaluation.TextWithImports
import com.intellij.debugger.jdi.StackFrameProxyImpl import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.intellij.ide.highlighter.JavaFileType
import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
@@ -28,6 +29,7 @@ import com.intellij.openapi.util.Key
import com.intellij.psi.* import com.intellij.psi.*
import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.PsiTypesUtil
import com.intellij.util.IncorrectOperationException import com.intellij.util.IncorrectOperationException
import com.intellij.util.concurrency.Semaphore import com.intellij.util.concurrency.Semaphore
import com.intellij.xdebugger.XDebuggerManager import com.intellij.xdebugger.XDebuggerManager
@@ -70,7 +72,7 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
project, project,
"fragment.kt", "fragment.kt",
item.text, item.text,
item.imports, initImports(item.imports),
contextElement contextElement
) )
} }
@@ -79,7 +81,7 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
project, project,
"fragment.kt", "fragment.kt",
item.text, item.text,
item.imports, initImports(item.imports),
contextElement contextElement
) )
} }
@@ -117,6 +119,28 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
return codeFragment return codeFragment
} }
private fun initImports(imports: String?): String? {
if (imports != null && !imports.isEmpty()) {
return imports.split(KtCodeFragment.IMPORT_SEPARATOR)
.mapNotNull { fixImportIfNeeded(it) }
.joinToString(KtCodeFragment.IMPORT_SEPARATOR)
}
return null
}
private fun fixImportIfNeeded(import: String): String? {
// skip arrays
if (import.endsWith("[]")) {
return fixImportIfNeeded(import.removeSuffix("[]").trim())
}
// skip primitive types
if (PsiTypesUtil.boxIfPossible(import) != import) {
return null
}
return import
}
private fun getWrappedContextElement(project: Project, context: PsiElement?) private fun getWrappedContextElement(project: Project, context: PsiElement?)
= wrapContextIfNeeded(project, getContextElement(context)) = wrapContextIfNeeded(project, getContextElement(context))
@@ -130,16 +154,28 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
null null
} }
val importList = try {
kotlinCodeFragment.importsAsImportList()?.let {
(PsiFileFactory.getInstance(project).createFileFromText(
"dummy.java", JavaFileType.INSTANCE, it.text
) as? PsiJavaFile)?.importList
}
}
catch(e: IncorrectOperationException) {
null
}
if (javaExpression != null) { if (javaExpression != null) {
var convertedFragment: KtExpressionCodeFragment? = null var convertedFragment: KtExpressionCodeFragment? = null
project.executeWriteCommand("Convert java expression to kotlin in Evaluate Expression") { project.executeWriteCommand("Convert java expression to kotlin in Evaluate Expression") {
val newText = javaExpression.j2kText() val newText = javaExpression.j2kText()
val newImports = importList?.j2kText()
if (newText != null) { if (newText != null) {
convertedFragment = KtExpressionCodeFragment( convertedFragment = KtExpressionCodeFragment(
project, project,
kotlinCodeFragment.name, kotlinCodeFragment.name,
newText, newText,
kotlinCodeFragment.importsToString(), newImports,
kotlinCodeFragment.context kotlinCodeFragment.context
) )
@@ -0,0 +1,31 @@
LineBreakpoint created at createExpressionCastToBuiltIn.kt:7
!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! createExpressionCastToBuiltIn.CreateExpressionCastToBuiltInKt
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
createExpressionCastToBuiltIn.kt:7
package createExpressionCastToBuiltIn
fun main(args: Array<String>) {
val primitiveArray = intArrayOf(1)
val stringArray = arrayOf("a")
//Breakpoint!
val a = 1
}
// PRINT_FRAME
// SKIP: value, hash, hash32
// DESCRIPTOR_VIEW_OPTIONS: NAME_EXPRESSION_RESULT
Compile bytecode for args
Compile bytecode for primitiveArray
Compile bytecode for primitiveArray[0]
Compile bytecode for stringArray
Compile bytecode for stringArray[0]
frame = main
local = args (expression = args)
local = primitiveArray (expression = primitiveArray)
element = 0 (expression = primitiveArray[0])
local = stringArray (expression = stringArray)
element = 0 (expression = stringArray[0])
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -20,7 +20,7 @@ fun main(args: Array<String>) {
} }
// PRINT_FRAME // PRINT_FRAME
// DESCRIPTOR_VIEW_OPTIONS: NAME_AND_EXPRESSION // DESCRIPTOR_VIEW_OPTIONS: NAME_EXPRESSION
frame = main frame = main
local = args (expression = args) local = args (expression = args)
@@ -0,0 +1,38 @@
LineBreakpoint created at createExpressionWithArray.kt:9
!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! createExpressionWithArray.CreateExpressionWithArrayKt
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
createExpressionWithArray.kt:9
package createExpressionWithArray
import forTests.MyJavaClass
fun main(args: Array<String>) {
val baseArray = arrayOf(MyJavaClass().getBaseClassValue())
val innerArray = arrayOf(MyJavaClass().getInnerClassValue())
//Breakpoint!
val a = 1
}
// PRINT_FRAME
// DESCRIPTOR_VIEW_OPTIONS: NAME_EXPRESSION_RESULT
Compile bytecode for args
Compile bytecode for baseArray
Compile bytecode for baseArray[0]
Compile bytecode for baseArray[0].i2
Compile bytecode for innerArray
Compile bytecode for innerArray[0]
Compile bytecode for (innerArray[0] as InnerClass).i
Compile bytecode for (innerArray[0] as InnerClass).i2
frame = main
local = args (expression = args)
local = baseArray (expression = baseArray)
element = 0 (expression = baseArray[0])
field = i2 (expression = baseArray[0].i2)
local = innerArray (expression = innerArray)
element = 0 (expression = innerArray[0])
field = i (expression = (innerArray[0] as InnerClass).i)
field = i2 (expression = (innerArray[0] as InnerClass).i2)
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,12 @@
package createExpressionCastToBuiltIn
fun main(args: Array<String>) {
val primitiveArray = intArrayOf(1)
val stringArray = arrayOf("a")
//Breakpoint!
val a = 1
}
// PRINT_FRAME
// SKIP: value, hash, hash32
// DESCRIPTOR_VIEW_OPTIONS: NAME_EXPRESSION_RESULT
@@ -16,4 +16,4 @@ fun main(args: Array<String>) {
} }
// PRINT_FRAME // PRINT_FRAME
// DESCRIPTOR_VIEW_OPTIONS: NAME_AND_EXPRESSION // DESCRIPTOR_VIEW_OPTIONS: NAME_EXPRESSION
@@ -0,0 +1,13 @@
package createExpressionWithArray
import forTests.MyJavaClass
fun main(args: Array<String>) {
val baseArray = arrayOf(MyJavaClass().getBaseClassValue())
val innerArray = arrayOf(MyJavaClass().getInnerClassValue())
//Breakpoint!
val a = 1
}
// PRINT_FRAME
// DESCRIPTOR_VIEW_OPTIONS: NAME_EXPRESSION_RESULT
@@ -23,4 +23,19 @@ public class MyJavaClass {
private static class PrivateJavaClass { private static class PrivateJavaClass {
public final int prop = 1; public final int prop = 1;
} }
public static class BaseClass {
public final int i2 = 1;
}
public BaseClass getBaseClassValue() {
return new BaseClass();
}
public BaseClass getInnerClassValue() {
return new InnerClass();
}
public static class InnerClass extends BaseClass {
public final int i = 1;
}
} }
@@ -24,12 +24,10 @@ import com.intellij.debugger.engine.evaluation.TextWithImports
import com.intellij.debugger.engine.evaluation.TextWithImportsImpl import com.intellij.debugger.engine.evaluation.TextWithImportsImpl
import com.intellij.debugger.engine.evaluation.expression.EvaluatorBuilderImpl import com.intellij.debugger.engine.evaluation.expression.EvaluatorBuilderImpl
import com.intellij.debugger.engine.events.DebuggerCommandImpl import com.intellij.debugger.engine.events.DebuggerCommandImpl
import com.intellij.debugger.impl.DebuggerContextImpl
import com.intellij.debugger.settings.NodeRendererSettings import com.intellij.debugger.settings.NodeRendererSettings
import com.intellij.debugger.ui.impl.watch.* import com.intellij.debugger.ui.impl.watch.*
import com.intellij.debugger.ui.tree.FieldDescriptor import com.intellij.debugger.ui.tree.*
import com.intellij.debugger.ui.tree.LocalVariableDescriptor
import com.intellij.debugger.ui.tree.StackFrameDescriptor
import com.intellij.debugger.ui.tree.StaticDescriptor
import com.intellij.execution.process.ProcessOutputTypes import com.intellij.execution.process.ProcessOutputTypes
import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.util.io.FileUtil
@@ -112,7 +110,7 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
createAdditionalBreakpoints(fileText) createAdditionalBreakpoints(fileText)
val shouldPrintFrame = isDirectiveDefined(fileText, "// PRINT_FRAME") val shouldPrintFrame = isDirectiveDefined(fileText, "// PRINT_FRAME")
val skipInPrintFrame = if (shouldPrintFrame) findLinesWithPrefixesRemoved(fileText, "// SKIP: ") else emptyList() val skipInPrintFrame = if (shouldPrintFrame) findListWithPrefixes(fileText, "// SKIP: ") else emptyList()
val descriptorViewOptions = DescriptorViewOptions.valueOf(findStringWithPrefixes(fileText, "// DESCRIPTOR_VIEW_OPTIONS: ") ?: "FULL") val descriptorViewOptions = DescriptorViewOptions.valueOf(findStringWithPrefixes(fileText, "// DESCRIPTOR_VIEW_OPTIONS: ") ?: "FULL")
val expressions = loadTestDirectivesPairs(fileText, "// EXPRESSION: ", "// RESULT: ") val expressions = loadTestDirectivesPairs(fileText, "// EXPRESSION: ", "// RESULT: ")
@@ -232,23 +230,24 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
internal class PrinterConfig( internal class PrinterConfig(
val variablesToSkipInPrintFrame: List<String> = emptyList(), val variablesToSkipInPrintFrame: List<String> = emptyList(),
val descriptorOptionsOptions: DescriptorViewOptions = DescriptorViewOptions.FULL val viewOptions: DescriptorViewOptions = DescriptorViewOptions.FULL
) { ) {
enum class DescriptorViewOptions { enum class DescriptorViewOptions {
FULL, FULL,
NAME_AND_EXPRESSION NAME_EXPRESSION,
NAME_EXPRESSION_RESULT
} }
fun shouldRenderSourcesPosition(): Boolean { fun shouldRenderSourcesPosition(): Boolean {
return when(descriptorOptionsOptions) { return when(viewOptions) {
DescriptorViewOptions.FULL -> true DescriptorViewOptions.FULL -> true
else -> false else -> false
} }
} }
fun shouldRenderExpression(): Boolean { fun shouldRenderExpression(): Boolean {
return when(descriptorOptionsOptions) { return when {
DescriptorViewOptions.NAME_AND_EXPRESSION -> true viewOptions.toString().contains("EXPRESSION") -> true
else -> false else -> false
} }
} }
@@ -256,10 +255,14 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
fun renderLabel(descriptor: NodeDescriptorImpl): String { fun renderLabel(descriptor: NodeDescriptorImpl): String {
return when { return when {
descriptor is WatchItemDescriptor -> descriptor.calcValueName() descriptor is WatchItemDescriptor -> descriptor.calcValueName()
descriptorOptionsOptions == DescriptorViewOptions.NAME_AND_EXPRESSION -> descriptor.name ?: descriptor.label viewOptions.toString().contains("NAME") -> descriptor.name ?: descriptor.label
else -> descriptor.label else -> descriptor.label
} }
} }
fun shouldComputeResultOfCreateExpression(): Boolean {
return viewOptions == DescriptorViewOptions.NAME_EXPRESSION_RESULT
}
} }
private inner class Printer(private val config: PrinterConfig) { private inner class Printer(private val config: PrinterConfig) {
@@ -309,19 +312,29 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
} }
if (config.shouldRenderExpression() && descriptor is ValueDescriptorImpl) { if (config.shouldRenderExpression() && descriptor is ValueDescriptorImpl) {
var expression: PsiExpression? = null val expression = invokeInManagerThread {
debuggerContext.debugProcess!!.managerThread.invokeAndWait(object : DebuggerCommandImpl() { descriptor.getTreeEvaluation((node as XValueNodeImpl).valueContainer as JavaValue, it) as? PsiExpression
override fun action() { }
expression = runReadAction {
descriptor.getTreeEvaluation((node as XValueNodeImpl).valueContainer as JavaValue, debuggerContext) as? PsiExpression if (expression != null) {
val text = TextWithImportsImpl(expression)
val imports = expression.getUserData(DebuggerTreeNodeExpression.ADDITIONAL_IMPORTS_KEY)?.joinToString { it } ?: ""
val codeFragment = KotlinCodeFragmentFactory().createPresentationCodeFragment(
TextWithImportsImpl(text.kind, text.text, text.imports + imports, text.fileType),
debuggerContext.sourcePosition.elementAt, project
)
val codeFragmentText = codeFragment.text
if (config.shouldComputeResultOfCreateExpression()) {
invokeInManagerThread {
it.suspendContext?.evaluate(
TextWithImportsImpl(text.kind, codeFragmentText, codeFragment.importsToString(), text.fileType),
null)
} }
} }
})
if (expression != null) { append(" (expression = $codeFragmentText)")
val text = KotlinCodeFragmentFactory().createPresentationCodeFragment(
TextWithImportsImpl(expression!!), debuggerContext.sourcePosition.elementAt, project
).text
append(" (expression = $text)")
} }
} }
append("\n") append("\n")
@@ -332,6 +345,16 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
return false return false
} }
private fun <T: Any> invokeInManagerThread(f: (DebuggerContextImpl) -> T?): T? {
var result: T? = null
debuggerContext.debugProcess!!.managerThread.invokeAndWait(object : DebuggerCommandImpl() {
override fun action() {
result = runReadAction { f(debuggerContext) }
}
})
return result
}
private fun getPrefix(descriptor: NodeDescriptorImpl): String { private fun getPrefix(descriptor: NodeDescriptorImpl): String {
val prefix = when (descriptor) { val prefix = when (descriptor) {
is StackFrameDescriptor -> "frame" is StackFrameDescriptor -> "frame"
@@ -340,6 +363,7 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
is StaticDescriptor -> "static" is StaticDescriptor -> "static"
is ThisDescriptorImpl -> "this" is ThisDescriptorImpl -> "this"
is FieldDescriptor -> "field" is FieldDescriptor -> "field"
is ArrayElementDescriptor -> "element"
is MessageDescriptor -> "" is MessageDescriptor -> ""
else -> "unknown" else -> "unknown"
} }
@@ -422,7 +446,11 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
return KotlinCodeFragmentFactory().createWrappingContext(text, labels, KotlinCodeFragmentFactory.getContextElement(contextElement), project)!! return KotlinCodeFragmentFactory().createWrappingContext(text, labels, KotlinCodeFragmentFactory.getContextElement(contextElement), project)!!
} }
private fun SuspendContextImpl.evaluate(text: String, codeFragmentKind: CodeFragmentKind, expectedResult: String) { private fun SuspendContextImpl.evaluate(text: String, codeFragmentKind: CodeFragmentKind, expectedResult: String?) {
return evaluate(TextWithImportsImpl(codeFragmentKind, text, "", KotlinFileType.INSTANCE), expectedResult)
}
private fun SuspendContextImpl.evaluate(item: TextWithImportsImpl, expectedResult: String?) {
runReadAction { runReadAction {
val sourcePosition = ContextUtil.getSourcePosition(this) val sourcePosition = ContextUtil.getSourcePosition(this)
val contextElement = createContextElement(this) val contextElement = createContextElement(this)
@@ -432,7 +460,7 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
try { try {
val evaluator = val evaluator =
EvaluatorBuilderImpl.build(TextWithImportsImpl(codeFragmentKind, text, "", KotlinFileType.INSTANCE), EvaluatorBuilderImpl.build(item,
contextElement, contextElement,
sourcePosition) sourcePosition)
@@ -441,11 +469,12 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
val value = evaluator.evaluate(this@AbstractKotlinEvaluateExpressionTest.evaluationContext) val value = evaluator.evaluate(this@AbstractKotlinEvaluateExpressionTest.evaluationContext)
val actualResult = value.asValue().asString() val actualResult = value.asValue().asString()
if (expectedResult != null) {
Assert.assertTrue("Evaluate expression returns wrong result for $text:\nexpected = $expectedResult\nactual = $actualResult\n", expectedResult == actualResult) Assert.assertTrue("Evaluate expression returns wrong result for ${item.text}:\nexpected = $expectedResult\nactual = $actualResult\n", expectedResult == actualResult)
}
} }
catch (e: EvaluateException) { catch (e: EvaluateException) {
Assert.assertTrue("Evaluate expression throws wrong exception for $text:\nexpected = $expectedResult\nactual = ${e.message}\n", expectedResult == e.message?.replaceFirst(ID_PART_REGEX, "id=ID")) Assert.assertTrue("Evaluate expression throws wrong exception for ${item.text}:\nexpected = $expectedResult\nactual = ${e.message}\n", expectedResult == e.message?.replaceFirst(ID_PART_REGEX, "id=ID"))
} }
} }
} }
@@ -501,6 +501,12 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
doSingleBreakpointTest(fileName); doSingleBreakpointTest(fileName);
} }
@TestMetadata("createExpressionWithArray.kt")
public void testCreateExpressionWithArray() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/createExpressionWithArray.kt");
doSingleBreakpointTest(fileName);
}
@TestMetadata("delegatedPropertyInClass.kt") @TestMetadata("delegatedPropertyInClass.kt")
public void testDelegatedPropertyInClass() throws Exception { public void testDelegatedPropertyInClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/delegatedPropertyInClass.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/delegatedPropertyInClass.kt");