From a71e9d7f12baf67209633cec6ea61533d47024bd Mon Sep 17 00:00:00 2001 From: "Roman.Ivanov" Date: Fri, 1 Jun 2018 10:25:15 +0700 Subject: [PATCH] fix first call in chain detection : IDEA-192777 --- .../codeInsight/hints/MethodChainHintsPass.kt | 30 ++++- .../daemon/inlays/MethodChainHintsTest.kt | 116 ++++++++++++++++++ 2 files changed, 141 insertions(+), 5 deletions(-) create mode 100644 java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/inlays/MethodChainHintsTest.kt diff --git a/java/java-impl/src/com/intellij/codeInsight/hints/MethodChainHintsPass.kt b/java/java-impl/src/com/intellij/codeInsight/hints/MethodChainHintsPass.kt index 2375ee7f51bb..1b569b7780f6 100644 --- a/java/java-impl/src/com/intellij/codeInsight/hints/MethodChainHintsPass.kt +++ b/java/java-impl/src/com/intellij/codeInsight/hints/MethodChainHintsPass.kt @@ -23,11 +23,8 @@ class MethodChainHintsPass( override fun collectElementHints(element: PsiElement, collector: (offset: Int, hint: String) -> Unit) { val call = element as? PsiMethodCallExpression ?: return - val qualifier = call.methodExpression.qualifierExpression - if (qualifier != null && qualifier is PsiMethodCallExpression) { - val callSibling = qualifier.nextSibling - if (callSibling is PsiWhiteSpace && callSibling.textContains('\n')) return // Not first call - } + if (!isFirstCall(call)) return + val next = call.nextSibling if (!(next is PsiWhiteSpace && next.textContains('\n'))) return val chain = collectChain(call) @@ -50,6 +47,29 @@ class MethodChainHintsPass( } } + private fun isFirstCall(call: PsiMethodCallExpression): Boolean { + val document = myEditor.document + val callLine = document.getLineNumber(call.argumentList.textOffset) + + val callForQualifier = ExpressionUtils.getCallForQualifier(call) + if (callForQualifier == null || + document.getLineNumber(callForQualifier.argumentList.textOffset) == callLine) return false + + val firstQualifierCall = call.methodExpression.qualifier as? PsiMethodCallExpression + if (firstQualifierCall != null) { + if (document.getLineNumber(firstQualifierCall.argumentList.textOffset) != callLine) return false + var currentQualifierCall: PsiMethodCallExpression = firstQualifierCall + while (true) { + val qualifier = currentQualifierCall.methodExpression.qualifier + if (qualifier == null) return false + if (qualifier !is PsiMethodCallExpression) return true + if (document.getLineNumber(qualifier.argumentList.textOffset) != callLine) return false + currentQualifierCall = qualifier + } + } + return true + } + private fun collectChain(call: PsiMethodCallExpression): List { val chain = mutableListOf(call) var current = call diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/inlays/MethodChainHintsTest.kt b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/inlays/MethodChainHintsTest.kt new file mode 100644 index 000000000000..8e2ba59c9226 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/inlays/MethodChainHintsTest.kt @@ -0,0 +1,116 @@ +/* + * Copyright 2000-2018 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 com.intellij.java.codeInsight.daemon.inlays + +import com.intellij.codeInsight.daemon.impl.HintRenderer +import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase +import org.intellij.lang.annotations.Language + +class MethodChainHintsTest: LightCodeInsightFixtureTestCase() { + fun check(@Language("Java") text: String) { + myFixture.configureByText("A.java", text) + myFixture.testInlays({ (it.renderer as HintRenderer).text }, { it.renderer is HintRenderer }) + } + + fun `test plain builder`() { + check(""" +public class Chains { + static class A { + B b() {return null;} + C c() {return null;} + } + + static class B { + A a() {return null;} + C c() {return null;} + } + + static class C { + B b() {return null;} + A a() {return null;} + } + + public static void main(String[] args) { + new A() + .b() + .c() + .a() + .c(); + } +}""") + } + + fun `test duplicated builder`() { + // IDEA-192777 + check("""public class Chains { + static class A { + B b() {return null;} + C c() {return null;} + } + + static class B { + A a() {return null;} + C c() {return null;} + } + + static class C { + B b() {return null;} + A a() {return null;} + } + + public static void main(String[] args) { + new A() + .b() + .c().b() + .a() + .c() + .b() + .c(); + } +} +""") + } + + fun `test duplicated call with reference qualifier`() { + check("""public class Chains { + static class A { + B b() {return null;} + C c() {return null;} + } + + static class B { + A a() {return null;} + C c() {return null;} + } + + static class C { + B b() {return null;} + A a() {return null;} + } + + public static void main(String[] args) { + A a = new A(); + a.b().c() + .a() + .b() + .a() + .c() + .b(); + } +} +""") + } +} \ No newline at end of file