Line markers for recursive calls.

This commit is contained in:
Evgeny Gerashchenko
2015-04-02 01:46:45 +03:00
parent 06c05193a6
commit 6041d566d9
16 changed files with 285 additions and 0 deletions
+1
View File
@@ -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"/>
@@ -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);
}
}
}