Debugger: do not insert array in imports
This commit is contained in:
@@ -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
|
||||
+12
@@ -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
|
||||
Vendored
+1
-1
@@ -16,4 +16,4 @@ fun main(args: Array<String>) {
|
||||
}
|
||||
|
||||
// PRINT_FRAME
|
||||
// DESCRIPTOR_VIEW_OPTIONS: NAME_AND_EXPRESSION
|
||||
// DESCRIPTOR_VIEW_OPTIONS: NAME_EXPRESSION
|
||||
|
||||
Vendored
+13
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+56
-27
@@ -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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user