Refactoring: extract top-level functions from KotlinSteppingCommandProvider
(cherry picked from commit 2daac45)
This commit is contained in:
committed by
Nikolay Krasko
parent
dd42420a1e
commit
60fbcb7e38
+184
-179
@@ -72,13 +72,15 @@ class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() {
|
||||
val file = sourcePosition.file as? KtFile ?: return null
|
||||
if (sourcePosition.line < 0) return null
|
||||
|
||||
val containingFunction = sourcePosition.elementAt.parents.firstOrNull { it is KtNamedFunction && !it.isLocal } ?: return null
|
||||
val containingFunction = sourcePosition.elementAt.parents
|
||||
.filterIsInstance<KtNamedFunction>()
|
||||
.firstOrNull { !it.isLocal } ?: return null
|
||||
|
||||
val startLineNumber = containingFunction.getLineNumber(true)
|
||||
val endLineNumber = containingFunction.getLineNumber(false)
|
||||
val startLineNumber = containingFunction.getLineNumber(true) + 1
|
||||
val endLineNumber = containingFunction.getLineNumber(false) + 1
|
||||
if (startLineNumber > endLineNumber) return null
|
||||
|
||||
val linesRange = startLineNumber + 1..endLineNumber + 1
|
||||
val linesRange = startLineNumber..endLineNumber
|
||||
|
||||
val inlineArgumentsToSkip = getElementsToSkip(containingFunction as KtNamedFunction, sourcePosition) ?: return null
|
||||
|
||||
@@ -87,122 +89,6 @@ class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() {
|
||||
return DebuggerSteppingHelper.createStepOverCommand(suspendContext, ignoreBreakpoints, file, linesRange, inlineArgumentsToSkip, additionalElementsToSkip)
|
||||
}
|
||||
|
||||
private fun getElementsToSkip(containingFunction: KtNamedFunction, sourcePosition: SourcePosition): List<KtFunction>? {
|
||||
val inlineFunctionCalls = getInlineFunctionCallsIfAny(sourcePosition)
|
||||
if (!inlineFunctionCalls.isEmpty()) {
|
||||
val inlineArguments = getInlineArgumentsIfAny(inlineFunctionCalls)
|
||||
if (inlineArguments.isNotEmpty() && inlineArguments.all { !it.shouldNotUseStepOver(sourcePosition.elementAt) }) {
|
||||
return inlineArguments
|
||||
}
|
||||
|
||||
if (inlineArguments.isEmpty() && inlineFunctionCalls.all { !it.shouldNotUseStepOver(sourcePosition.elementAt) }) {
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
if (InlineUtil.isInline(containingFunction.resolveToDescriptor())) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun PsiElement.getAdditionalElementsToSkip(): List<PsiElement> {
|
||||
val result = arrayListOf<PsiElement>()
|
||||
val ifParent = getParentOfType<KtIfExpression>(false)
|
||||
if (ifParent != null) {
|
||||
if (ifParent.then.contains(this)) {
|
||||
ifParent.elseKeyword?.let { result.add(it) }
|
||||
ifParent.`else`?.let { result.add(it) }
|
||||
}
|
||||
}
|
||||
val tryParent = getParentOfType<KtTryExpression>(false)
|
||||
if (tryParent != null) {
|
||||
val catchClause = getParentOfType<KtCatchClause>(false)
|
||||
if (catchClause != null) {
|
||||
result.addAll(tryParent.catchClauses.filter { it != catchClause })
|
||||
}
|
||||
}
|
||||
|
||||
val whenEntry = getParentOfType<KtWhenEntry>(false)
|
||||
if (whenEntry != null) {
|
||||
if (whenEntry.expression.contains(this)) {
|
||||
val whenParent = whenEntry.getParentOfType<KtWhenExpression>(false)
|
||||
if (whenParent != null) {
|
||||
result.addAll(whenParent.entries.filter { it != whenEntry })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun PsiElement.shouldNotUseStepOver(elementAt: PsiElement): Boolean {
|
||||
val ifParent = getParentOfType<KtIfExpression>(false)
|
||||
if (ifParent != null) {
|
||||
// if (inlineFunCall()) {...}
|
||||
if (ifParent.condition.contains(this)) {
|
||||
return true
|
||||
}
|
||||
|
||||
/*
|
||||
<caret>if (...) inlineFunCall()
|
||||
else inlineFunCall()
|
||||
*/
|
||||
val ifParentElementAt = elementAt.getParentOfType<KtIfExpression>(false)
|
||||
if (ifParentElementAt == null) {
|
||||
if (ifParent.then.contains(this)) {
|
||||
return true
|
||||
}
|
||||
if (ifParent.`else`.contains(this)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val tryParent = getParentOfType<KtTryExpression>(false)
|
||||
if (tryParent != null) {
|
||||
/* try { inlineFunCall() }
|
||||
catch()...
|
||||
*/
|
||||
if (tryParent.tryBlock.contains(this)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
val whenEntry = getParentOfType<KtWhenEntry>(false)
|
||||
if (whenEntry != null) {
|
||||
// <caret>inlineFunCall -> ...
|
||||
if (whenEntry.conditions.any { it.contains(this) } ) {
|
||||
return true
|
||||
}
|
||||
|
||||
// <caret>1 == 2 -> inlineFunCall()
|
||||
if (whenEntry.expression.contains(this)) {
|
||||
val parentEntryElementAt = elementAt.getParentOfType<KtWhenEntry>(false) ?: return true
|
||||
return parentEntryElementAt == whenEntry &&
|
||||
whenEntry.conditions.any { it.contains(elementAt) }
|
||||
}
|
||||
}
|
||||
|
||||
val whileParent = getParentOfType<KtWhileExpression>(false)
|
||||
if (whileParent != null) {
|
||||
// while (inlineFunCall()) {...}
|
||||
if (whileParent.condition.contains(this)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// last statement in while
|
||||
return (whileParent.body as? KtBlockExpression)?.statements?.lastOrNull()?.getLineNumber() == elementAt.getLineNumber()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun PsiElement?.contains(element: PsiElement): Boolean {
|
||||
return this?.textRange?.contains(element.textRange) ?: false
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
fun getStepOutCommand(suspendContext: SuspendContextImpl, debugContext: DebuggerContextImpl): DebugProcessImpl.ResumeCommand? {
|
||||
return getStepOutCommand(suspendContext, debugContext.sourcePosition)
|
||||
@@ -228,79 +114,196 @@ class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() {
|
||||
|
||||
return DebuggerSteppingHelper.createStepOutCommand(suspendContext, true, inlineFunctions, inlinedArgument)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getInlineFunctionsIfAny(file: KtFile, offset: Int): List<KtNamedFunction> {
|
||||
val elementAt = file.findElementAt(offset) ?: return emptyList()
|
||||
val containingFunction = elementAt.getParentOfType<KtNamedFunction>(false) ?: return emptyList()
|
||||
private fun getElementsToSkip(containingFunction: KtNamedFunction, sourcePosition: SourcePosition): List<KtFunction>? {
|
||||
val inlineFunctionCalls = getInlineFunctionCallsIfAny(sourcePosition)
|
||||
if (!inlineFunctionCalls.isEmpty()) {
|
||||
val inlineArguments = getInlineArgumentsIfAny(inlineFunctionCalls)
|
||||
if (inlineArguments.isNotEmpty() && inlineArguments.all { !it.shouldNotUseStepOver(sourcePosition.elementAt) }) {
|
||||
return inlineArguments
|
||||
}
|
||||
|
||||
val descriptor = containingFunction.resolveToDescriptor()
|
||||
if (!InlineUtil.isInline(descriptor)) return emptyList()
|
||||
|
||||
val inlineFunctionsCalls = DebuggerUtils.analyzeElementWithInline(
|
||||
containingFunction.getResolutionFacade(),
|
||||
containingFunction.analyzeFully(),
|
||||
containingFunction,
|
||||
false
|
||||
).filterIsInstance<KtNamedFunction>()
|
||||
|
||||
return inlineFunctionsCalls
|
||||
}
|
||||
|
||||
private fun getInlineArgumentsIfAny(inlineFunctionCalls: List<KtCallExpression>): List<KtFunction> {
|
||||
return inlineFunctionCalls.flatMap {
|
||||
it.valueArguments
|
||||
.map { getArgumentExpression(it) }
|
||||
.filterIsInstance<KtFunction>()
|
||||
if (inlineArguments.isEmpty() && inlineFunctionCalls.all { !it.shouldNotUseStepOver(sourcePosition.elementAt) }) {
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getArgumentExpression(it: ValueArgument) = (it.getArgumentExpression() as? KtLambdaExpression)?.functionLiteral ?: it.getArgumentExpression()
|
||||
// Step over calls to lambda arguments in inline function while execution is already in that function
|
||||
if (InlineUtil.isInline(containingFunction.resolveToDescriptor())) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
private fun getInlineFunctionCallsIfAny(sourcePosition: SourcePosition): List<KtCallExpression> {
|
||||
val file = sourcePosition.file as? KtFile ?: return emptyList()
|
||||
val lineNumber = sourcePosition.line
|
||||
var elementAt = sourcePosition.elementAt
|
||||
return null
|
||||
}
|
||||
|
||||
var startOffset = file.getLineStartOffset(lineNumber) ?: elementAt.startOffset
|
||||
val endOffset = file.getLineEndOffset(lineNumber) ?: elementAt.endOffset
|
||||
private fun PsiElement.getAdditionalElementsToSkip(): List<PsiElement> {
|
||||
val result = arrayListOf<PsiElement>()
|
||||
val ifParent = getParentOfType<KtIfExpression>(false)
|
||||
if (ifParent != null) {
|
||||
if (ifParent.then.contains(this)) {
|
||||
ifParent.elseKeyword?.let { result.add(it) }
|
||||
ifParent.`else`?.let { result.add(it) }
|
||||
}
|
||||
}
|
||||
val tryParent = getParentOfType<KtTryExpression>(false)
|
||||
if (tryParent != null) {
|
||||
val catchClause = getParentOfType<KtCatchClause>(false)
|
||||
if (catchClause != null) {
|
||||
result.addAll(tryParent.catchClauses.filter { it != catchClause })
|
||||
}
|
||||
}
|
||||
|
||||
var topMostElement: PsiElement? = null
|
||||
while (topMostElement !is KtElement && startOffset < endOffset) {
|
||||
elementAt = file.findElementAt(startOffset)
|
||||
if (elementAt != null) {
|
||||
topMostElement = CodeInsightUtils.getTopmostElementAtOffset(elementAt, startOffset)
|
||||
val whenEntry = getParentOfType<KtWhenEntry>(false)
|
||||
if (whenEntry != null) {
|
||||
if (whenEntry.expression.contains(this)) {
|
||||
val whenParent = whenEntry.getParentOfType<KtWhenExpression>(false)
|
||||
if (whenParent != null) {
|
||||
result.addAll(whenParent.entries.filter { it != whenEntry })
|
||||
}
|
||||
startOffset++
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun PsiElement.shouldNotUseStepOver(elementAt: PsiElement): Boolean {
|
||||
val ifParent = getParentOfType<KtIfExpression>(false)
|
||||
if (ifParent != null) {
|
||||
// if (inlineFunCall()) {...}
|
||||
if (ifParent.condition.contains(this)) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (topMostElement !is KtElement) return emptyList()
|
||||
|
||||
val start = topMostElement.startOffset
|
||||
val end = topMostElement.endOffset
|
||||
|
||||
fun isInlineCall(expr: KtExpression): Boolean {
|
||||
val context = expr.analyze(BodyResolveMode.PARTIAL)
|
||||
val resolvedCall = expr.getResolvedCall(context) ?: return false
|
||||
return InlineUtil.isInline(resolvedCall.resultingDescriptor)
|
||||
}
|
||||
|
||||
val allInlineFunctionCalls = CodeInsightUtils.
|
||||
findElementsOfClassInRange(file, start, end, KtExpression::class.java)
|
||||
.map { KtPsiUtil.getParentCallIfPresent(it as KtExpression) }
|
||||
.filterIsInstance<KtCallExpression>()
|
||||
.filter { isInlineCall(it) }
|
||||
.toSet()
|
||||
|
||||
// It is necessary to check range because of multiline assign
|
||||
var linesRange = lineNumber..lineNumber
|
||||
return allInlineFunctionCalls.filter {
|
||||
val shouldInclude = it.getLineNumber() in linesRange
|
||||
if (shouldInclude) {
|
||||
linesRange = Math.min(linesRange.start, it.getLineNumber())..Math.max(linesRange.endInclusive, it.getLineNumber(false))
|
||||
/*
|
||||
<caret>if (...) inlineFunCall()
|
||||
else inlineFunCall()
|
||||
*/
|
||||
val ifParentElementAt = elementAt.getParentOfType<KtIfExpression>(false)
|
||||
if (ifParentElementAt == null) {
|
||||
if (ifParent.then.contains(this)) {
|
||||
return true
|
||||
}
|
||||
if (ifParent.`else`.contains(this)) {
|
||||
return true
|
||||
}
|
||||
shouldInclude
|
||||
}
|
||||
}
|
||||
|
||||
val tryParent = getParentOfType<KtTryExpression>(false)
|
||||
if (tryParent != null) {
|
||||
/* try { inlineFunCall() }
|
||||
catch()...
|
||||
*/
|
||||
if (tryParent.tryBlock.contains(this)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
val whenEntry = getParentOfType<KtWhenEntry>(false)
|
||||
if (whenEntry != null) {
|
||||
// <caret>inlineFunCall -> ...
|
||||
if (whenEntry.conditions.any { it.contains(this) } ) {
|
||||
return true
|
||||
}
|
||||
|
||||
// <caret>1 == 2 -> inlineFunCall()
|
||||
if (whenEntry.expression.contains(this)) {
|
||||
val parentEntryElementAt = elementAt.getParentOfType<KtWhenEntry>(false) ?: return true
|
||||
return parentEntryElementAt == whenEntry &&
|
||||
whenEntry.conditions.any { it.contains(elementAt) }
|
||||
}
|
||||
}
|
||||
|
||||
val whileParent = getParentOfType<KtWhileExpression>(false)
|
||||
if (whileParent != null) {
|
||||
// while (inlineFunCall()) {...}
|
||||
if (whileParent.condition.contains(this)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// last statement in while
|
||||
return (whileParent.body as? KtBlockExpression)?.statements?.lastOrNull()?.getLineNumber() == elementAt.getLineNumber()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun PsiElement?.contains(element: PsiElement): Boolean {
|
||||
return this?.textRange?.contains(element.textRange) ?: false
|
||||
}
|
||||
|
||||
private fun getInlineFunctionsIfAny(file: KtFile, offset: Int): List<KtNamedFunction> {
|
||||
val elementAt = file.findElementAt(offset) ?: return emptyList()
|
||||
val containingFunction = elementAt.getParentOfType<KtNamedFunction>(false) ?: return emptyList()
|
||||
|
||||
val descriptor = containingFunction.resolveToDescriptor()
|
||||
if (!InlineUtil.isInline(descriptor)) return emptyList()
|
||||
|
||||
val inlineFunctionsCalls = DebuggerUtils.analyzeElementWithInline(
|
||||
containingFunction.getResolutionFacade(),
|
||||
containingFunction.analyzeFully(),
|
||||
containingFunction,
|
||||
false
|
||||
).filterIsInstance<KtNamedFunction>()
|
||||
|
||||
return inlineFunctionsCalls
|
||||
}
|
||||
|
||||
private fun getInlineArgumentsIfAny(inlineFunctionCalls: List<KtCallExpression>): List<KtFunction> {
|
||||
return inlineFunctionCalls.flatMap {
|
||||
it.valueArguments
|
||||
.map { getArgumentExpression(it) }
|
||||
.filterIsInstance<KtFunction>()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getArgumentExpression(it: ValueArgument) = (it.getArgumentExpression() as? KtLambdaExpression)?.functionLiteral ?: it.getArgumentExpression()
|
||||
|
||||
private fun getInlineFunctionCallsIfAny(sourcePosition: SourcePosition): List<KtCallExpression> {
|
||||
val file = sourcePosition.file as? KtFile ?: return emptyList()
|
||||
val lineNumber = sourcePosition.line
|
||||
var elementAt = sourcePosition.elementAt
|
||||
|
||||
var startOffset = file.getLineStartOffset(lineNumber) ?: elementAt.startOffset
|
||||
val endOffset = file.getLineEndOffset(lineNumber) ?: elementAt.endOffset
|
||||
|
||||
var topMostElement: PsiElement? = null
|
||||
while (topMostElement !is KtElement && startOffset < endOffset) {
|
||||
elementAt = file.findElementAt(startOffset)
|
||||
if (elementAt != null) {
|
||||
topMostElement = CodeInsightUtils.getTopmostElementAtOffset(elementAt, startOffset)
|
||||
}
|
||||
startOffset++
|
||||
}
|
||||
|
||||
if (topMostElement !is KtElement) return emptyList()
|
||||
|
||||
val start = topMostElement.startOffset
|
||||
val end = topMostElement.endOffset
|
||||
|
||||
fun isInlineCall(expr: KtExpression): Boolean {
|
||||
val context = expr.analyze(BodyResolveMode.PARTIAL)
|
||||
val resolvedCall = expr.getResolvedCall(context) ?: return false
|
||||
return InlineUtil.isInline(resolvedCall.resultingDescriptor)
|
||||
}
|
||||
|
||||
val allInlineFunctionCalls = CodeInsightUtils.
|
||||
findElementsOfClassInRange(file, start, end, KtExpression::class.java)
|
||||
.map { KtPsiUtil.getParentCallIfPresent(it as KtExpression) }
|
||||
.filterIsInstance<KtCallExpression>()
|
||||
.filter { isInlineCall(it) }
|
||||
.toSet()
|
||||
|
||||
// It is necessary to check range because of multiline assign
|
||||
var linesRange = lineNumber..lineNumber
|
||||
return allInlineFunctionCalls.filter {
|
||||
val shouldInclude = it.getLineNumber() in linesRange
|
||||
if (shouldInclude) {
|
||||
linesRange = Math.min(linesRange.start, it.getLineNumber())..Math.max(linesRange.endInclusive, it.getLineNumber(false))
|
||||
}
|
||||
shouldInclude
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Action(val position: XSourcePositionImpl?) {
|
||||
@@ -362,6 +365,8 @@ fun getStepOverPosition(
|
||||
return Action.STEP_OVER()
|
||||
}
|
||||
|
||||
// Predict step over location by assuming that it will be next valid location in the range of current function.
|
||||
// This prediction doesn't work in branching position when there're several valid location after current position.
|
||||
val locations = computedReferenceType.allLineLocations()
|
||||
.dropWhile { it != location }
|
||||
.drop(1)
|
||||
|
||||
Reference in New Issue
Block a user