From 33e34c759ddc05fe3943354ee459b5c819dd8a96 Mon Sep 17 00:00:00 2001 From: Evgeny Gerashchenko Date: Thu, 2 Apr 2015 22:38:12 +0300 Subject: [PATCH] KT-2190 Highlight usages invoked on "throw" or "return" should highlight all exit-points of function #KT-2190 fixed --- .../kotlin/generators/tests/GenerateTests.kt | 5 + idea/src/META-INF/plugin.xml | 1 + ...KotlinHighlightExitPointsHandlerFactory.kt | 93 +++++++++++++++ idea/testData/exitPoints/inline1.kt | 15 +++ idea/testData/exitPoints/inline2.kt | 15 +++ .../testData/exitPoints/inlineLocalReturn1.kt | 14 +++ .../testData/exitPoints/inlineLocalReturn2.kt | 14 +++ idea/testData/exitPoints/localFunction1.kt | 14 +++ idea/testData/exitPoints/localFunction2.kt | 15 +++ .../testData/exitPoints/localFunctionThrow.kt | 18 +++ idea/testData/exitPoints/notInline1.kt | 15 +++ idea/testData/exitPoints/notInline2.kt | 16 +++ idea/testData/exitPoints/simple.kt | 11 ++ idea/testData/exitPoints/throw1.kt | 11 ++ idea/testData/exitPoints/throw2.kt | 11 ++ .../AbstractHighlightExitPointsTest.kt | 35 ++++++ .../HighlightExitPointsTestGenerated.java | 110 ++++++++++++++++++ 17 files changed, 413 insertions(+) create mode 100644 idea/src/org/jetbrains/kotlin/idea/highlighter/KotlinHighlightExitPointsHandlerFactory.kt create mode 100644 idea/testData/exitPoints/inline1.kt create mode 100644 idea/testData/exitPoints/inline2.kt create mode 100644 idea/testData/exitPoints/inlineLocalReturn1.kt create mode 100644 idea/testData/exitPoints/inlineLocalReturn2.kt create mode 100644 idea/testData/exitPoints/localFunction1.kt create mode 100644 idea/testData/exitPoints/localFunction2.kt create mode 100644 idea/testData/exitPoints/localFunctionThrow.kt create mode 100644 idea/testData/exitPoints/notInline1.kt create mode 100644 idea/testData/exitPoints/notInline2.kt create mode 100644 idea/testData/exitPoints/simple.kt create mode 100644 idea/testData/exitPoints/throw1.kt create mode 100644 idea/testData/exitPoints/throw2.kt create mode 100644 idea/tests/org/jetbrains/kotlin/idea/highlighter/AbstractHighlightExitPointsTest.kt create mode 100644 idea/tests/org/jetbrains/kotlin/idea/highlighter/HighlightExitPointsTestGenerated.java diff --git a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index 8ad78b237b0..d94ce695085 100644 --- a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -67,6 +67,7 @@ import org.jetbrains.kotlin.idea.folding.AbstractKotlinFoldingTest import org.jetbrains.kotlin.idea.hierarchy.AbstractHierarchyTest import org.jetbrains.kotlin.idea.highlighter.AbstractDiagnosticMessageJsTest import org.jetbrains.kotlin.idea.highlighter.AbstractDiagnosticMessageTest +import org.jetbrains.kotlin.idea.highlighter.AbstractHighlightExitPointsTest import org.jetbrains.kotlin.idea.highlighter.AbstractHighlightingTest import org.jetbrains.kotlin.idea.imports.AbstractOptimizeImportsTest import org.jetbrains.kotlin.idea.intentions.AbstractIntentionTest @@ -595,6 +596,10 @@ fun main(args: Array) { model("copyPaste/imports", pattern = """^([^\.]+)\.kt$""", testMethod = "doTestCut", testClassName = "Cut", recursive = false) } + testClass(javaClass()) { + model("exitPoints") + } + testClass(javaClass()) { model("codeInsight/lineMarker") } diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index c06c05dfde8..5f8a785e112 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -370,6 +370,7 @@ + ? { + if (target is LeafPsiElement && (target.getElementType() in RETURN_AND_THROW)) { + val returnOrThrow: JetExpression = PsiTreeUtil.getParentOfType(target, javaClass(), javaClass()) + return MyHandler(editor, file, returnOrThrow) + } + return null + } + + private class MyHandler(editor: Editor, file: PsiFile, val target: JetExpression) : HighlightUsagesHandlerBase(editor, file) { + override fun getTargets() = listOf(target) + + override fun selectTargets(targets: MutableList, selectionConsumer: Consumer>) { + selectionConsumer.consume(targets) + } + + override fun computeUsages(targets: MutableList?) { + val relevantFunction = target.getRelevantFunction() + relevantFunction?.accept(object : JetVisitorVoid() { + override fun visitJetElement(element: JetElement) { + element.acceptChildren(this) + } + + private fun visitReturnOrThrow(expression: JetExpression) { + if (expression.getRelevantFunction() == relevantFunction) { + addOccurrence(expression) + } + } + + override fun visitReturnExpression(expression: JetReturnExpression) { + visitReturnOrThrow(expression) + } + + override fun visitThrowExpression(expression: JetThrowExpression) { + visitReturnOrThrow(expression) + } + }) + } + } +} + +private fun JetExpression.getRelevantFunction(): JetFunction? { + if (this is JetReturnExpression) { + (this.getTargetLabel()?.getReference()?.resolve() as? JetFunction)?.let { return it } + } + for (parent in parents(false)) { + when (parent) { + is JetFunctionLiteral -> if (!parent.isInlined((parent.getParent() as JetFunctionLiteralExpression).analyze())) return parent + is JetNamedFunction -> return parent + } + } + return null +} \ No newline at end of file diff --git a/idea/testData/exitPoints/inline1.kt b/idea/testData/exitPoints/inline1.kt new file mode 100644 index 00000000000..92828746f33 --- /dev/null +++ b/idea/testData/exitPoints/inline1.kt @@ -0,0 +1,15 @@ +fun f(a: Int): Int { + if (a < 5) { + run { + return 1 + } + } + else { + return 2 + } +} + +inline public fun run(f: () -> T): T { } + +//HIGHLIGHTED: return 1 +//HIGHLIGHTED: return 2 \ No newline at end of file diff --git a/idea/testData/exitPoints/inline2.kt b/idea/testData/exitPoints/inline2.kt new file mode 100644 index 00000000000..e2451dbef9d --- /dev/null +++ b/idea/testData/exitPoints/inline2.kt @@ -0,0 +1,15 @@ +fun f(a: Int): Int { + if (a < 5) { + run { + return 1 + } + } + else { + return 2 + } +} + +inline public fun run(f: () -> T): T { } + +//HIGHLIGHTED: return 1 +//HIGHLIGHTED: return 2 \ No newline at end of file diff --git a/idea/testData/exitPoints/inlineLocalReturn1.kt b/idea/testData/exitPoints/inlineLocalReturn1.kt new file mode 100644 index 00000000000..81eefdbf07b --- /dev/null +++ b/idea/testData/exitPoints/inlineLocalReturn1.kt @@ -0,0 +1,14 @@ +fun f(a: Int): Int { + if (a < 5) { + run { + return@run 1 + } + } + else { + return 2 + } +} + +inline public fun run(f: () -> T): T { } + +//HIGHLIGHTED: return@run 1 diff --git a/idea/testData/exitPoints/inlineLocalReturn2.kt b/idea/testData/exitPoints/inlineLocalReturn2.kt new file mode 100644 index 00000000000..bb3f2a3d815 --- /dev/null +++ b/idea/testData/exitPoints/inlineLocalReturn2.kt @@ -0,0 +1,14 @@ +fun f(a: Int): Int { + if (a < 5) { + run { + return@run 1 + } + } + else { + return 2 + } +} + +inline public fun run(f: () -> T): T { } + +//HIGHLIGHTED: return 2 \ No newline at end of file diff --git a/idea/testData/exitPoints/localFunction1.kt b/idea/testData/exitPoints/localFunction1.kt new file mode 100644 index 00000000000..9f346f1a606 --- /dev/null +++ b/idea/testData/exitPoints/localFunction1.kt @@ -0,0 +1,14 @@ +fun f(a: Int): Int { + fun localFun() { + return + } + + if (a < 5) { + return 1 + } + else { + return 2 + } +} + +//HIGHLIGHTED: return diff --git a/idea/testData/exitPoints/localFunction2.kt b/idea/testData/exitPoints/localFunction2.kt new file mode 100644 index 00000000000..002d16e216d --- /dev/null +++ b/idea/testData/exitPoints/localFunction2.kt @@ -0,0 +1,15 @@ +fun f(a: Int): Int { + fun localFun() { + return + } + + if (a < 5) { + return 1 + } + else { + return 2 + } +} + +//HIGHLIGHTED: return 1 +//HIGHLIGHTED: return 2 diff --git a/idea/testData/exitPoints/localFunctionThrow.kt b/idea/testData/exitPoints/localFunctionThrow.kt new file mode 100644 index 00000000000..1bc9e8cc922 --- /dev/null +++ b/idea/testData/exitPoints/localFunctionThrow.kt @@ -0,0 +1,18 @@ +fun f(a: Int): Int { + fun localFun() { + if (a > 5) { + return + } + throw Error() + } + + if (a < 5) { + return 1 + } + else { + throw Exception() + } +} + +//HIGHLIGHTED: return +//HIGHLIGHTED: throw Error() diff --git a/idea/testData/exitPoints/notInline1.kt b/idea/testData/exitPoints/notInline1.kt new file mode 100644 index 00000000000..6e13ea5e3ca --- /dev/null +++ b/idea/testData/exitPoints/notInline1.kt @@ -0,0 +1,15 @@ +import javax.swing.SwingUtilities + +fun f(a: Int): Int { + if (a < 5) { + SwingUtilities.invokeLater(fun (): Unit { + return + }) + return 1 + } + else { + return 2 + } +} + +//HIGHLIGHTED: return diff --git a/idea/testData/exitPoints/notInline2.kt b/idea/testData/exitPoints/notInline2.kt new file mode 100644 index 00000000000..771d4d85c34 --- /dev/null +++ b/idea/testData/exitPoints/notInline2.kt @@ -0,0 +1,16 @@ +import javax.swing.SwingUtilities + +fun f(a: Int): Int { + if (a < 5) { + SwingUtilities.invokeLater(fun (): Unit { + return + }) + return 1 + } + else { + return 2 + } +} + +//HIGHLIGHTED: return 1 +//HIGHLIGHTED: return 2 diff --git a/idea/testData/exitPoints/simple.kt b/idea/testData/exitPoints/simple.kt new file mode 100644 index 00000000000..b176ab00190 --- /dev/null +++ b/idea/testData/exitPoints/simple.kt @@ -0,0 +1,11 @@ +fun f(a: Int): Int { + if (a < 5) { + return 1 + } + else { + return 2 + } +} + +//HIGHLIGHTED: return 1 +//HIGHLIGHTED: return 2 \ No newline at end of file diff --git a/idea/testData/exitPoints/throw1.kt b/idea/testData/exitPoints/throw1.kt new file mode 100644 index 00000000000..ad5bc6d6e9b --- /dev/null +++ b/idea/testData/exitPoints/throw1.kt @@ -0,0 +1,11 @@ +fun f(a: Int): Int { + if (a < 5) { + return 1 + } + else { + throw Error() + } +} + +//HIGHLIGHTED: return 1 +//HIGHLIGHTED: throw Error() \ No newline at end of file diff --git a/idea/testData/exitPoints/throw2.kt b/idea/testData/exitPoints/throw2.kt new file mode 100644 index 00000000000..610b9bfc2d0 --- /dev/null +++ b/idea/testData/exitPoints/throw2.kt @@ -0,0 +1,11 @@ +fun f(a: Int): Int { + if (a < 5) { + return 1 + } + else { + throw Error() + } +} + +//HIGHLIGHTED: return 1 +//HIGHLIGHTED: throw Error() \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/highlighter/AbstractHighlightExitPointsTest.kt b/idea/tests/org/jetbrains/kotlin/idea/highlighter/AbstractHighlightExitPointsTest.kt new file mode 100644 index 00000000000..1c1782a5205 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/highlighter/AbstractHighlightExitPointsTest.kt @@ -0,0 +1,35 @@ +/* + * 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.codeInsight.highlighting.HighlightUsagesHandler +import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase +import org.jetbrains.kotlin.test.InTextDirectivesUtils +import kotlin.test.assertEquals + +public abstract class AbstractHighlightExitPointsTest : LightCodeInsightFixtureTestCase() { + public fun doTest(testDataPath: String) { + myFixture.configureByFile(testDataPath) + HighlightUsagesHandler.invoke(myFixture.getProject(), myFixture.getEditor(), myFixture.getFile()); + + val text = myFixture.getFile().getText() + val expectedToBeHighlighted = InTextDirectivesUtils.findLinesWithPrefixesRemoved(text, "//HIGHLIGHTED:") + val highlighters = myFixture.getEditor().getMarkupModel().getAllHighlighters() + val actual = highlighters.map { text.substring(it.getStartOffset(), it.getEndOffset()) } + assertEquals(expectedToBeHighlighted, actual) + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/highlighter/HighlightExitPointsTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/highlighter/HighlightExitPointsTestGenerated.java new file mode 100644 index 00000000000..e25cdf18cb3 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/highlighter/HighlightExitPointsTestGenerated.java @@ -0,0 +1,110 @@ +/* + * 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.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.InnerTestClasses; +import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.test.JetTestUtils; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("idea/testData/exitPoints") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class HighlightExitPointsTestGenerated extends AbstractHighlightExitPointsTest { + public void testAllFilesPresentInExitPoints() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/exitPoints"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("inline1.kt") + public void testInline1() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/exitPoints/inline1.kt"); + doTest(fileName); + } + + @TestMetadata("inline2.kt") + public void testInline2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/exitPoints/inline2.kt"); + doTest(fileName); + } + + @TestMetadata("inlineLocalReturn1.kt") + public void testInlineLocalReturn1() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/exitPoints/inlineLocalReturn1.kt"); + doTest(fileName); + } + + @TestMetadata("inlineLocalReturn2.kt") + public void testInlineLocalReturn2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/exitPoints/inlineLocalReturn2.kt"); + doTest(fileName); + } + + @TestMetadata("localFunction1.kt") + public void testLocalFunction1() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/exitPoints/localFunction1.kt"); + doTest(fileName); + } + + @TestMetadata("localFunction2.kt") + public void testLocalFunction2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/exitPoints/localFunction2.kt"); + doTest(fileName); + } + + @TestMetadata("localFunctionThrow.kt") + public void testLocalFunctionThrow() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/exitPoints/localFunctionThrow.kt"); + doTest(fileName); + } + + @TestMetadata("notInline1.kt") + public void testNotInline1() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/exitPoints/notInline1.kt"); + doTest(fileName); + } + + @TestMetadata("notInline2.kt") + public void testNotInline2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/exitPoints/notInline2.kt"); + doTest(fileName); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/exitPoints/simple.kt"); + doTest(fileName); + } + + @TestMetadata("throw1.kt") + public void testThrow1() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/exitPoints/throw1.kt"); + doTest(fileName); + } + + @TestMetadata("throw2.kt") + public void testThrow2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/exitPoints/throw2.kt"); + doTest(fileName); + } +}