Line markers for recursive calls.
This commit is contained in:
@@ -353,6 +353,7 @@
|
||||
<programRunner implementation="org.jetbrains.kotlin.idea.k2jsrun.K2JSBrowserProgramRunner"/>
|
||||
<configurationProducer implementation="org.jetbrains.kotlin.idea.run.JetRunConfigurationProducer"/>
|
||||
<codeInsight.lineMarkerProvider language="jet" implementationClass="org.jetbrains.kotlin.idea.highlighter.JetLineMarkerProvider"/>
|
||||
<codeInsight.lineMarkerProvider language="jet" implementationClass="org.jetbrains.kotlin.idea.highlighter.KotlinRecursiveCallLineMarkerProvider"/>
|
||||
<codeInsight.lineMarkerProvider language="JAVA"
|
||||
implementationClass="org.jetbrains.kotlin.idea.ktSignature.KotlinSignatureInJavaMarkerProvider"/>
|
||||
<iconProvider implementation="org.jetbrains.kotlin.idea.JetIconProvider"/>
|
||||
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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.highlighter
|
||||
|
||||
import com.intellij.codeHighlighting.Pass
|
||||
import com.intellij.codeInsight.daemon.LineMarkerInfo
|
||||
import com.intellij.codeInsight.daemon.LineMarkerProvider
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.editor.markup.GutterIconRenderer
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parents
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.isInlined
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
|
||||
import java.util.HashSet
|
||||
|
||||
public class KotlinRecursiveCallLineMarkerProvider() : LineMarkerProvider {
|
||||
override fun getLineMarkerInfo(element: PsiElement) = null
|
||||
|
||||
override fun collectSlowLineMarkers(elements: MutableList<PsiElement>, result: MutableCollection<LineMarkerInfo<*>>) {
|
||||
val markedLineNumbers = HashSet<Int>()
|
||||
for (element in elements) {
|
||||
ProgressManager.checkCanceled()
|
||||
if (element is JetElement) {
|
||||
val lineNumber = element.getLineNumber()
|
||||
if (lineNumber !in markedLineNumbers && isRecursiveCall(element)) {
|
||||
markedLineNumbers.add(lineNumber)
|
||||
result.add(RecursiveMethodCallMarkerInfo(element))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getEnclosingFunction(element: JetElement): JetNamedFunction? {
|
||||
for (parent in element.parents(false)) {
|
||||
when (parent) {
|
||||
is JetFunctionLiteral -> if (!parent.isInlined(parent.analyze())) return null
|
||||
is JetNamedFunction -> return parent
|
||||
is JetClassOrObject -> return null
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun isRecursiveCall(element: JetElement): Boolean {
|
||||
if (!(element is JetSimpleNameExpression && element.getParent() is JetCallExpression || element is JetArrayAccessExpression))
|
||||
return false
|
||||
val enclosingFunction = getEnclosingFunction(element) ?: return false
|
||||
|
||||
val bindingContext = element.analyze()
|
||||
val enclosingFunctionDescriptor = bindingContext[BindingContext.FUNCTION, enclosingFunction] ?: return false
|
||||
|
||||
val call = bindingContext[BindingContext.CALL, element] ?: return false
|
||||
val resolvedCall = bindingContext[BindingContext.RESOLVED_CALL, call] ?: return false
|
||||
|
||||
if (resolvedCall.getCandidateDescriptor().getOriginal() != enclosingFunctionDescriptor) return false
|
||||
|
||||
val extensionReceiver = resolvedCall.getExtensionReceiver()
|
||||
if (extensionReceiver is ExpressionReceiver) {
|
||||
val thisTarget = (extensionReceiver.getExpression() as? JetThisExpression)?.getInstanceReference()?.getReference()?.resolve()
|
||||
if (thisTarget != enclosingFunction) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
val dispatchReceiver = resolvedCall.getDispatchReceiver()
|
||||
if (dispatchReceiver is ExpressionReceiver) {
|
||||
val thisTarget = (dispatchReceiver.getExpression() as? JetThisExpression)?.getInstanceReference()?.getReference()?.resolve()
|
||||
if (thisTarget != enclosingFunction.getNonStrictParentOfType<JetClassOrObject>()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private class RecursiveMethodCallMarkerInfo(callElement: JetElement)
|
||||
: LineMarkerInfo<JetElement>(
|
||||
callElement,
|
||||
callElement.getTextRange(),
|
||||
AllIcons.Gutter.RecursiveMethod,
|
||||
Pass.UPDATE_OVERRIDEN_MARKERS,
|
||||
{ "Recursive call" },
|
||||
null,
|
||||
GutterIconRenderer.Alignment.RIGHT
|
||||
) {
|
||||
|
||||
override fun createGutterRenderer(): GutterIconRenderer? {
|
||||
return object : LineMarkerInfo.LineMarkerGutterIconRenderer<JetElement>(this) {
|
||||
override fun getClickAction() = null // to place breakpoint on mouse click
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun PsiElement.getLineNumber(): Int {
|
||||
return PsiDocumentManager.getInstance(getProject()).getDocument(getContainingFile())!!.getLineNumber(getTextOffset())
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun Any.get(a: Int) {
|
||||
if (a > 0) {
|
||||
<lineMarker>this[a - 1]</lineMarker>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun Any.f(a: Int) {
|
||||
"".f(1)
|
||||
this.<lineMarker>f</lineMarker>(2)
|
||||
<lineMarker>f</lineMarker>(3)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun <T> f(a: Int) {
|
||||
<lineMarker>f</lineMarker><String>(55)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun f(a: Int) {
|
||||
run {
|
||||
<lineMarker>f</lineMarker>(a - 1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun outer(a: Int) {
|
||||
SwingUtilities.invokeLater {
|
||||
outer(a - 1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fun f(a: Int) {
|
||||
class Local {
|
||||
fun member() {
|
||||
f(11)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun f(a: Int) {
|
||||
val x = ::f
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fun outer(a: Int) {
|
||||
fun local(a: Int) {
|
||||
if (a > 0) {
|
||||
outer(a - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
class F {
|
||||
fun f(a: Int, other: F) {
|
||||
if (a > 0) {
|
||||
other.f(a - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fun f(a: Int): Int {
|
||||
if (a > 0) {
|
||||
<lineMarker>f</lineMarker>(a - 1) + f(a + 1)
|
||||
}
|
||||
|
||||
return <lineMarker>f</lineMarker>(a)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun f(a: Int) {
|
||||
if (a > 0) {
|
||||
<lineMarker descr="Recursive call">f</lineMarker>(a - 1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
open class <lineMarker></lineMarker>Super {
|
||||
open fun <lineMarker></lineMarker>f(a: Int) {
|
||||
}
|
||||
}
|
||||
|
||||
class F: Super() {
|
||||
override fun <lineMarker></lineMarker>f(a: Int) {
|
||||
if (a > 0) {
|
||||
super.f(a - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
class F {
|
||||
fun f(a: Int) {
|
||||
if (a > 0) {
|
||||
this.<lineMarker>f</lineMarker>(a - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import java.util.regex.Pattern;
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@InnerTestClasses({
|
||||
LineMarkersTestGenerated.OverrideImplement.class,
|
||||
LineMarkersTestGenerated.RecursiveCall.class,
|
||||
})
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public class LineMarkersTestGenerated extends AbstractLineMarkersTest {
|
||||
@@ -179,4 +180,91 @@ public class LineMarkersTestGenerated extends AbstractLineMarkersTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/codeInsight/lineMarker/recursiveCall")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class RecursiveCall extends AbstractLineMarkersTest {
|
||||
public void testAllFilesPresentInRecursiveCall() throws Exception {
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/codeInsight/lineMarker/recursiveCall"), Pattern.compile("^(.+)\\.kt$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("conventionCall.kt")
|
||||
public void testConventionCall() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/lineMarker/recursiveCall/conventionCall.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("extension.kt")
|
||||
public void testExtension() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/lineMarker/recursiveCall/extension.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("generic.kt")
|
||||
public void testGeneric() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/lineMarker/recursiveCall/generic.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inInlinedLambda.kt")
|
||||
public void testInInlinedLambda() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/lineMarker/recursiveCall/inInlinedLambda.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inLambda.kt")
|
||||
public void testInLambda() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/lineMarker/recursiveCall/inLambda.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("localClass.kt")
|
||||
public void testLocalClass() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/lineMarker/recursiveCall/localClass.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("methodReference.kt")
|
||||
public void testMethodReference() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/lineMarker/recursiveCall/methodReference.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("nested.kt")
|
||||
public void testNested() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/lineMarker/recursiveCall/nested.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("otherQualifier.kt")
|
||||
public void testOtherQualifier() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/lineMarker/recursiveCall/otherQualifier.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("severalCallsInOneLine.kt")
|
||||
public void testSeveralCallsInOneLine() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/lineMarker/recursiveCall/severalCallsInOneLine.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("simple.kt")
|
||||
public void testSimple() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/lineMarker/recursiveCall/simple.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("superQualifier.kt")
|
||||
public void testSuperQualifier() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/lineMarker/recursiveCall/superQualifier.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("thisQualifier.kt")
|
||||
public void testThisQualifier() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/lineMarker/recursiveCall/thisQualifier.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user