Implement 'Skip simple getters' for debugger
This commit is contained in:
@@ -515,6 +515,7 @@
|
||||
<debugger.javaBreakpointHandlerFactory implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpointHandlerFactory"/>
|
||||
<debugger.javaBreakpointHandlerFactory implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinLineBreakpointHandlerFactory"/>
|
||||
<debugger.jvmSteppingCommandProvider implementation="org.jetbrains.kotlin.idea.debugger.stepping.KotlinSteppingCommandProvider"/>
|
||||
<debugger.simplePropertyGetterProvider implementation="org.jetbrains.kotlin.idea.debugger.stepping.KotlinSimpleGetterProvider"/>
|
||||
|
||||
<codeInsight.implementMethod language="kotlin" implementationClass="org.jetbrains.kotlin.idea.core.overrideImplement.ImplementMembersHandler"/>
|
||||
<codeInsight.overrideMethod language="kotlin" implementationClass="org.jetbrains.kotlin.idea.core.overrideImplement.OverrideMembersHandler"/>
|
||||
|
||||
@@ -53,12 +53,15 @@ import org.jetbrains.kotlin.fileClasses.internalNameWithoutInnerClasses
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyzeAndGetResult
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFullyAndGetResult
|
||||
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
|
||||
import org.jetbrains.kotlin.idea.core.refactoring.getLineStartOffset
|
||||
import org.jetbrains.kotlin.idea.debugger.breakpoints.getLambdasAtLineIfAny
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinCodeFragmentFactory
|
||||
import org.jetbrains.kotlin.idea.decompiler.classFile.KtClsFile
|
||||
import org.jetbrains.kotlin.idea.search.usagesSearch.isImportUsage
|
||||
import org.jetbrains.kotlin.idea.util.DebuggerUtils
|
||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
@@ -125,12 +128,30 @@ public class KotlinPositionManager(private val myDebugProcess: DebugProcess) : M
|
||||
if (lambdaOrFunIfInside != null) {
|
||||
return SourcePosition.createFromElement(lambdaOrFunIfInside.bodyExpression!!)
|
||||
}
|
||||
val property = getParameterIfInConstructor(location, psiFile, lineNumber)
|
||||
if (property != null) {
|
||||
return SourcePosition.createFromElement(property)
|
||||
}
|
||||
return SourcePosition.createFromLine(psiFile, lineNumber)
|
||||
}
|
||||
|
||||
throw NoDataException.INSTANCE
|
||||
}
|
||||
|
||||
private fun getParameterIfInConstructor(location: Location, file: KtFile, lineNumber: Int): KtParameter? {
|
||||
val lineStartOffset = file.getLineStartOffset(lineNumber) ?: return null
|
||||
val elementAt = file.findElementAt(lineStartOffset)
|
||||
val contextElement = KotlinCodeFragmentFactory.getContextElement(elementAt)
|
||||
val methodName = location.method().name()
|
||||
if (contextElement is KtClass && JvmAbi.isGetterName(methodName)) {
|
||||
val parameterForGetter = contextElement.getPrimaryConstructor()?.valueParameters?.firstOrNull() {
|
||||
it.hasValOrVar() && it.name != null && JvmAbi.getterName(it.name!!) == methodName
|
||||
} ?: return null
|
||||
return parameterForGetter
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getLambdaOrFunIfInside(location: Location, file: KtFile, lineNumber: Int): KtFunction? {
|
||||
val currentLocationFqName = location.declaringType().name()
|
||||
if (currentLocationFqName == null) return null
|
||||
|
||||
+81
-71
@@ -180,81 +180,13 @@ private fun getExpressionToAddDebugExpressionBefore(tmpFile: KtFile, contextElem
|
||||
}
|
||||
|
||||
private fun addDebugExpressionBeforeContextElement(codeFragment: KtCodeFragment, contextElement: PsiElement): List<KtExpression> {
|
||||
val psiFactory = KtPsiFactory(codeFragment)
|
||||
|
||||
fun insertNewInitializer(classBody: KtClassBody): PsiElement? {
|
||||
val initializer = psiFactory.createAnonymousInitializer()
|
||||
val newInitializer = (classBody.addAfter(initializer, classBody.firstChild) as KtAnonymousInitializer)
|
||||
val block = newInitializer.body as KtBlockExpression?
|
||||
return block?.lastChild
|
||||
}
|
||||
|
||||
val elementBefore = when {
|
||||
contextElement is KtFile -> {
|
||||
val fakeFunction = psiFactory.createFunction("fun _debug_fun_() {}")
|
||||
contextElement.add(psiFactory.createNewLine())
|
||||
val newFakeFun = contextElement.add(fakeFunction) as KtNamedFunction
|
||||
newFakeFun.bodyExpression!!.lastChild
|
||||
}
|
||||
contextElement is KtProperty && !contextElement.isLocal -> {
|
||||
val delegateExpressionOrInitializer = contextElement.delegateExpressionOrInitializer
|
||||
if (delegateExpressionOrInitializer != null) {
|
||||
wrapInRunFun(delegateExpressionOrInitializer)
|
||||
}
|
||||
else {
|
||||
val getter = contextElement.getter!!
|
||||
if (!getter.hasBlockBody()) {
|
||||
wrapInRunFun(getter.bodyExpression!!)
|
||||
}
|
||||
else {
|
||||
(getter.bodyExpression as KtBlockExpression).statements.first()
|
||||
}
|
||||
}
|
||||
}
|
||||
contextElement is KtPrimaryConstructor -> {
|
||||
val classOrObject = contextElement.getContainingClassOrObject()
|
||||
insertNewInitializer(classOrObject.getOrCreateBody())
|
||||
}
|
||||
contextElement is KtClassOrObject -> {
|
||||
insertNewInitializer(contextElement.getOrCreateBody())
|
||||
}
|
||||
contextElement is KtFunctionLiteral -> {
|
||||
val block = contextElement.bodyExpression!!
|
||||
block.statements.firstOrNull() ?: block.lastChild
|
||||
}
|
||||
contextElement is KtDeclarationWithBody && !contextElement.hasBody()-> {
|
||||
val block = psiFactory.createBlock("")
|
||||
val newBlock = contextElement.add(block) as KtBlockExpression
|
||||
newBlock.rBrace
|
||||
}
|
||||
contextElement is KtDeclarationWithBody && !contextElement.hasBlockBody()-> {
|
||||
wrapInRunFun(contextElement.bodyExpression!!)
|
||||
}
|
||||
contextElement is KtDeclarationWithBody && contextElement.hasBlockBody()-> {
|
||||
val block = contextElement.bodyExpression as KtBlockExpression
|
||||
val last = block.statements.lastOrNull()
|
||||
if (last is KtReturnExpression)
|
||||
last
|
||||
else
|
||||
block.rBrace
|
||||
}
|
||||
contextElement is KtWhenEntry -> {
|
||||
val entryExpression = contextElement.expression
|
||||
if (entryExpression is KtBlockExpression) {
|
||||
entryExpression.statements.firstOrNull() ?: entryExpression.lastChild
|
||||
}
|
||||
else {
|
||||
wrapInRunFun(entryExpression!!)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
contextElement
|
||||
}
|
||||
}
|
||||
val elementBefore = findElementBefore(contextElement)
|
||||
|
||||
val parent = elementBefore?.parent
|
||||
if (parent == null || elementBefore == null) return emptyList()
|
||||
|
||||
val psiFactory = KtPsiFactory(codeFragment)
|
||||
|
||||
parent.addBefore(psiFactory.createNewLine(), elementBefore)
|
||||
|
||||
fun insertExpression(expr: KtElement?): List<KtExpression> {
|
||||
@@ -282,6 +214,84 @@ private fun addDebugExpressionBeforeContextElement(codeFragment: KtCodeFragment,
|
||||
return insertExpression(debugExpression)
|
||||
}
|
||||
|
||||
private fun findElementBefore(contextElement: PsiElement): PsiElement? {
|
||||
val psiFactory = KtPsiFactory(contextElement)
|
||||
|
||||
fun insertNewInitializer(classBody: KtClassBody): PsiElement? {
|
||||
val initializer = psiFactory.createAnonymousInitializer()
|
||||
val newInitializer = (classBody.addAfter(initializer, classBody.firstChild) as KtAnonymousInitializer)
|
||||
val block = newInitializer.body as KtBlockExpression?
|
||||
return block?.lastChild
|
||||
}
|
||||
|
||||
return when {
|
||||
contextElement is KtFile -> {
|
||||
val fakeFunction = psiFactory.createFunction("fun _debug_fun_() {}")
|
||||
contextElement.add(psiFactory.createNewLine())
|
||||
val newFakeFun = contextElement.add(fakeFunction) as KtNamedFunction
|
||||
newFakeFun.bodyExpression!!.lastChild
|
||||
}
|
||||
contextElement is KtProperty && !contextElement.isLocal -> {
|
||||
val delegateExpressionOrInitializer = contextElement.delegateExpressionOrInitializer
|
||||
if (delegateExpressionOrInitializer != null) {
|
||||
wrapInRunFun(delegateExpressionOrInitializer)
|
||||
}
|
||||
else {
|
||||
val getter = contextElement.getter!!
|
||||
if (!getter.hasBlockBody()) {
|
||||
wrapInRunFun(getter.bodyExpression!!)
|
||||
}
|
||||
else {
|
||||
(getter.bodyExpression as KtBlockExpression).statements.first()
|
||||
}
|
||||
}
|
||||
}
|
||||
contextElement is KtParameter -> {
|
||||
val ownerFunction = contextElement.ownerFunction!!
|
||||
findElementBefore(ownerFunction)
|
||||
}
|
||||
contextElement is KtPrimaryConstructor -> {
|
||||
val classOrObject = contextElement.getContainingClassOrObject()
|
||||
insertNewInitializer(classOrObject.getOrCreateBody())
|
||||
}
|
||||
contextElement is KtClassOrObject -> {
|
||||
insertNewInitializer(contextElement.getOrCreateBody())
|
||||
}
|
||||
contextElement is KtFunctionLiteral -> {
|
||||
val block = contextElement.bodyExpression!!
|
||||
block.statements.firstOrNull() ?: block.lastChild
|
||||
}
|
||||
contextElement is KtDeclarationWithBody && !contextElement.hasBody() -> {
|
||||
val block = psiFactory.createBlock("")
|
||||
val newBlock = contextElement.add(block) as KtBlockExpression
|
||||
newBlock.rBrace
|
||||
}
|
||||
contextElement is KtDeclarationWithBody && !contextElement.hasBlockBody() -> {
|
||||
wrapInRunFun(contextElement.bodyExpression!!)
|
||||
}
|
||||
contextElement is KtDeclarationWithBody && contextElement.hasBlockBody() -> {
|
||||
val block = contextElement.bodyExpression as KtBlockExpression
|
||||
val last = block.statements.lastOrNull()
|
||||
if (last is KtReturnExpression)
|
||||
last
|
||||
else
|
||||
block.rBrace
|
||||
}
|
||||
contextElement is KtWhenEntry -> {
|
||||
val entryExpression = contextElement.expression
|
||||
if (entryExpression is KtBlockExpression) {
|
||||
entryExpression.statements.firstOrNull() ?: entryExpression.lastChild
|
||||
}
|
||||
else {
|
||||
wrapInRunFun(entryExpression!!)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
contextElement
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun replaceByRunFunction(expression: KtExpression): KtCallExpression {
|
||||
val callExpression = KtPsiFactory(expression).createExpression("run { \n${expression.text} \n}") as KtCallExpression
|
||||
val replaced = expression.replaced(callExpression)
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.engine.SimplePropertyGetterProvider
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
|
||||
class KotlinSimpleGetterProvider : SimplePropertyGetterProvider {
|
||||
override fun isInsideSimpleGetter(element: PsiElement): Boolean {
|
||||
// class A(val a: Int)
|
||||
if (element is KtParameter) {
|
||||
return true
|
||||
}
|
||||
|
||||
val accessor = PsiTreeUtil.getParentOfType(element, KtPropertyAccessor::class.java)
|
||||
if (accessor != null && accessor.isGetter) {
|
||||
val body = accessor.bodyExpression
|
||||
return when (body) {
|
||||
// val a: Int get() { return field }
|
||||
is KtBlockExpression -> {
|
||||
val returnedExpression = (body.statements.singleOrNull() as? KtReturnExpression)?.returnedExpression ?: return false
|
||||
returnedExpression.textMatches("field")
|
||||
}
|
||||
// val a: Int get() = field
|
||||
is KtExpression -> body.textMatches("field")
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
val property = PsiTreeUtil.getParentOfType(element, KtProperty::class.java)
|
||||
// val a = foo()
|
||||
if (property != null) {
|
||||
return property.getter == null
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
LineBreakpoint created at onGetter.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! onGetter.OnGetterKt
|
||||
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||
onGetter.kt:9
|
||||
onGetter.kt:12
|
||||
Compile bytecode for prop
|
||||
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||
|
||||
Process finished with exit code 0
|
||||
@@ -0,0 +1,8 @@
|
||||
LineBreakpoint created at skipSimpleGetter.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! skipSimpleGetter.SkipSimpleGetterKt
|
||||
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||
skipSimpleGetter.kt:6
|
||||
skipSimpleGetter.kt:7
|
||||
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||
|
||||
Process finished with exit code 0
|
||||
@@ -0,0 +1,12 @@
|
||||
package onGetter
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val a = A(1)
|
||||
// EXPRESSION: prop
|
||||
// RESULT: 1: I
|
||||
// STEP_INTO: 1
|
||||
//Breakpoint!
|
||||
a.prop
|
||||
}
|
||||
|
||||
class A(val prop: Int)
|
||||
@@ -0,0 +1,25 @@
|
||||
package skipSimpleGetter
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val a = A(1)
|
||||
//Breakpoint!
|
||||
a.a1 + a.a2 + a.a3 + a.a4
|
||||
}
|
||||
|
||||
class A(val a4: Int) {
|
||||
// only init
|
||||
val a1 = 1
|
||||
|
||||
// simple get, expression body
|
||||
val a2: Int = 1
|
||||
get() = field
|
||||
|
||||
// simple get, block body
|
||||
val a3: Int = 1
|
||||
get() {
|
||||
return field
|
||||
}
|
||||
}
|
||||
|
||||
// STEP_INTO: 21
|
||||
// SKIP_GETTERS: true
|
||||
@@ -241,6 +241,12 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest {
|
||||
doStepIntoTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("skipSimpleGetter.kt")
|
||||
public void testSkipSimpleGetter() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepInto/skipSimpleGetter.kt");
|
||||
doStepIntoTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("stepIntoFromInlineFun.kt")
|
||||
public void testStepIntoFromInlineFun() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepInto/stepIntoFromInlineFun.kt");
|
||||
|
||||
@@ -48,7 +48,7 @@ public class MockLocation implements Location {
|
||||
|
||||
@Override
|
||||
public Method method() {
|
||||
throw new UnsupportedOperationException();
|
||||
return new MockMethod();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
import com.sun.jdi.Method
|
||||
|
||||
class MockMethod : Method {
|
||||
override fun name() = ""
|
||||
|
||||
override fun isSynthetic() = throw UnsupportedOperationException()
|
||||
override fun isFinal() = throw UnsupportedOperationException()
|
||||
override fun isStatic() = throw UnsupportedOperationException()
|
||||
override fun declaringType() = throw UnsupportedOperationException()
|
||||
override fun signature() = throw UnsupportedOperationException()
|
||||
override fun genericSignature() = throw UnsupportedOperationException()
|
||||
override fun variables() = throw UnsupportedOperationException()
|
||||
override fun variablesByName(name: String?) = throw UnsupportedOperationException()
|
||||
override fun bytecodes() = throw UnsupportedOperationException()
|
||||
override fun isBridge() = throw UnsupportedOperationException()
|
||||
override fun isObsolete() = throw UnsupportedOperationException()
|
||||
override fun isSynchronized() = throw UnsupportedOperationException()
|
||||
override fun allLineLocations() = throw UnsupportedOperationException()
|
||||
override fun allLineLocations(stratum: String?, sourceName: String?) = throw UnsupportedOperationException()
|
||||
override fun isNative() = throw UnsupportedOperationException()
|
||||
override fun locationOfCodeIndex(codeIndex: Long) = throw UnsupportedOperationException()
|
||||
override fun arguments() = throw UnsupportedOperationException()
|
||||
override fun isAbstract() = throw UnsupportedOperationException()
|
||||
override fun isVarArgs() = throw UnsupportedOperationException()
|
||||
override fun returnTypeName() = throw UnsupportedOperationException()
|
||||
override fun argumentTypes( ) = throw UnsupportedOperationException()
|
||||
override fun isConstructor() = throw UnsupportedOperationException()
|
||||
override fun locationsOfLine(lineNumber: Int) = throw UnsupportedOperationException()
|
||||
override fun locationsOfLine(stratum: String?, sourceName: String?, lineNumber: Int) = throw UnsupportedOperationException()
|
||||
override fun argumentTypeNames() = throw UnsupportedOperationException()
|
||||
override fun returnType() = throw UnsupportedOperationException()
|
||||
override fun isStaticInitializer() = throw UnsupportedOperationException()
|
||||
override fun location() = throw UnsupportedOperationException()
|
||||
override fun compareTo(other: Method?) = throw UnsupportedOperationException()
|
||||
override fun isPackagePrivate() = throw UnsupportedOperationException()
|
||||
override fun isPrivate() = throw UnsupportedOperationException()
|
||||
override fun isProtected() = throw UnsupportedOperationException()
|
||||
override fun isPublic() = throw UnsupportedOperationException()
|
||||
override fun modifiers() = throw UnsupportedOperationException()
|
||||
override fun virtualMachine() = throw UnsupportedOperationException()
|
||||
}
|
||||
+6
@@ -223,6 +223,12 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
|
||||
doSingleBreakpointTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("onGetter.kt")
|
||||
public void testOnGetter() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/onGetter.kt");
|
||||
doSingleBreakpointTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("onObjectHeader.kt")
|
||||
public void testOnObjectHeader() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/onObjectHeader.kt");
|
||||
|
||||
Reference in New Issue
Block a user