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: restore all changed files in undo action
- 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
@@ -21,6 +21,7 @@ import com.intellij.debugger.engine.evaluation.CodeFragmentFactory
import com.intellij.debugger.engine.evaluation.CodeFragmentKind
import com.intellij.debugger.engine.evaluation.TextWithImports
import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.intellij.ide.highlighter.JavaFileType
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project
@@ -28,6 +29,7 @@ import com.intellij.openapi.util.Key
import com.intellij.psi.*
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.PsiTypesUtil
import com.intellij.util.IncorrectOperationException
import com.intellij.util.concurrency.Semaphore
import com.intellij.xdebugger.XDebuggerManager
@@ -70,7 +72,7 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
project,
"fragment.kt",
item.text,
item.imports,
initImports(item.imports),
contextElement
)
}
@@ -79,7 +81,7 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
project,
"fragment.kt",
item.text,
item.imports,
initImports(item.imports),
contextElement
)
}
@@ -117,6 +119,28 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
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?)
= wrapContextIfNeeded(project, getContextElement(context))
@@ -130,16 +154,28 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
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) {
var convertedFragment: KtExpressionCodeFragment? = null
project.executeWriteCommand("Convert java expression to kotlin in Evaluate Expression") {
val newText = javaExpression.j2kText()
val newImports = importList?.j2kText()
if (newText != null) {
convertedFragment = KtExpressionCodeFragment(
project,
kotlinCodeFragment.name,
newText,
kotlinCodeFragment.importsToString(),
newImports,
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
// DESCRIPTOR_VIEW_OPTIONS: NAME_AND_EXPRESSION
// DESCRIPTOR_VIEW_OPTIONS: NAME_EXPRESSION
frame = main
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
// 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 {
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.expression.EvaluatorBuilderImpl
import com.intellij.debugger.engine.events.DebuggerCommandImpl
import com.intellij.debugger.impl.DebuggerContextImpl
import com.intellij.debugger.settings.NodeRendererSettings
import com.intellij.debugger.ui.impl.watch.*
import com.intellij.debugger.ui.tree.FieldDescriptor
import com.intellij.debugger.ui.tree.LocalVariableDescriptor
import com.intellij.debugger.ui.tree.StackFrameDescriptor
import com.intellij.debugger.ui.tree.StaticDescriptor
import com.intellij.debugger.ui.tree.*
import com.intellij.execution.process.ProcessOutputTypes
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.io.FileUtil
@@ -112,7 +110,7 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
createAdditionalBreakpoints(fileText)
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 expressions = loadTestDirectivesPairs(fileText, "// EXPRESSION: ", "// RESULT: ")
@@ -232,23 +230,24 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
internal class PrinterConfig(
val variablesToSkipInPrintFrame: List<String> = emptyList(),
val descriptorOptionsOptions: DescriptorViewOptions = DescriptorViewOptions.FULL
val viewOptions: DescriptorViewOptions = DescriptorViewOptions.FULL
) {
enum class DescriptorViewOptions {
FULL,
NAME_AND_EXPRESSION
NAME_EXPRESSION,
NAME_EXPRESSION_RESULT
}
fun shouldRenderSourcesPosition(): Boolean {
return when(descriptorOptionsOptions) {
return when(viewOptions) {
DescriptorViewOptions.FULL -> true
else -> false
}
}
fun shouldRenderExpression(): Boolean {
return when(descriptorOptionsOptions) {
DescriptorViewOptions.NAME_AND_EXPRESSION -> true
return when {
viewOptions.toString().contains("EXPRESSION") -> true
else -> false
}
}
@@ -256,10 +255,14 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
fun renderLabel(descriptor: NodeDescriptorImpl): String {
return when {
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
}
}
fun shouldComputeResultOfCreateExpression(): Boolean {
return viewOptions == DescriptorViewOptions.NAME_EXPRESSION_RESULT
}
}
private inner class Printer(private val config: PrinterConfig) {
@@ -309,19 +312,29 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
}
if (config.shouldRenderExpression() && descriptor is ValueDescriptorImpl) {
var expression: PsiExpression? = null
debuggerContext.debugProcess!!.managerThread.invokeAndWait(object : DebuggerCommandImpl() {
override fun action() {
expression = runReadAction {
descriptor.getTreeEvaluation((node as XValueNodeImpl).valueContainer as JavaValue, debuggerContext) as? PsiExpression
val expression = invokeInManagerThread {
descriptor.getTreeEvaluation((node as XValueNodeImpl).valueContainer as JavaValue, it) 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) {
val text = KotlinCodeFragmentFactory().createPresentationCodeFragment(
TextWithImportsImpl(expression!!), debuggerContext.sourcePosition.elementAt, project
).text
append(" (expression = $text)")
append(" (expression = $codeFragmentText)")
}
}
append("\n")
@@ -332,6 +345,16 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
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 {
val prefix = when (descriptor) {
is StackFrameDescriptor -> "frame"
@@ -340,6 +363,7 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
is StaticDescriptor -> "static"
is ThisDescriptorImpl -> "this"
is FieldDescriptor -> "field"
is ArrayElementDescriptor -> "element"
is MessageDescriptor -> ""
else -> "unknown"
}
@@ -422,7 +446,11 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
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 {
val sourcePosition = ContextUtil.getSourcePosition(this)
val contextElement = createContextElement(this)
@@ -432,7 +460,7 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
try {
val evaluator =
EvaluatorBuilderImpl.build(TextWithImportsImpl(codeFragmentKind, text, "", KotlinFileType.INSTANCE),
EvaluatorBuilderImpl.build(item,
contextElement,
sourcePosition)
@@ -441,11 +469,12 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
val value = evaluator.evaluate(this@AbstractKotlinEvaluateExpressionTest.evaluationContext)
val actualResult = value.asValue().asString()
Assert.assertTrue("Evaluate expression returns wrong result for $text:\nexpected = $expectedResult\nactual = $actualResult\n", expectedResult == actualResult)
if (expectedResult != null) {
Assert.assertTrue("Evaluate expression returns wrong result for ${item.text}:\nexpected = $expectedResult\nactual = $actualResult\n", expectedResult == actualResult)
}
}
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);
}
@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")
public void testDelegatedPropertyInClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/delegatedPropertyInClass.kt");