From 2daedc98b5c66fa0b92ad1a3856df73f6948b1b8 Mon Sep 17 00:00:00 2001 From: Toshiaki Kameyama Date: Sat, 28 Dec 2019 15:38:05 +0900 Subject: [PATCH] KT-35648 RemoveArgumentNameIntention: Support MixedNamedArgumentsInTheirOwnPosition - ^KT-35648 Fixed --- .../completion/NamedArgumentCompletion.kt | 26 +++++++----- .../intentions/RemoveArgumentNameIntention.kt | 42 ++++++------------- .../namedArgumentBefore.kt | 6 +++ .../namedArgumentBefore.kt.after | 6 +++ .../namedArgumentBefore2.kt | 6 +++ .../namedArgumentBefore2.kt.after | 6 +++ .../notInPosition.kt | 7 ++++ .../notInPosition2.kt | 7 ++++ .../removeArgumentName/namedArgumentBefore.kt | 3 +- .../intentions/IntentionTestGenerated.java | 33 +++++++++++++++ 10 files changed, 102 insertions(+), 40 deletions(-) create mode 100644 idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/namedArgumentBefore.kt create mode 100644 idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/namedArgumentBefore.kt.after create mode 100644 idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/namedArgumentBefore2.kt create mode 100644 idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/namedArgumentBefore2.kt.after create mode 100644 idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/notInPosition.kt create mode 100644 idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/notInPosition2.kt diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/NamedArgumentCompletion.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/NamedArgumentCompletion.kt index c5d99e8db66..ec80e368081 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/NamedArgumentCompletion.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/NamedArgumentCompletion.kt @@ -34,17 +34,11 @@ import java.util.* object NamedArgumentCompletion { fun isOnlyNamedArgumentExpected(nameExpression: KtSimpleNameExpression, resolutionFacade: ResolutionFacade): Boolean { val thisArgument = nameExpression.parent as? KtValueArgument ?: return false + if (thisArgument.isNamed()) return false + val resolvedCall = thisArgument.getStrictParentOfType()?.resolveToCall(resolutionFacade) ?: return false - val callElement = thisArgument.getStrictParentOfType() ?: return false - val argumentsBeforeThis = callElement.valueArguments.takeWhile { it != thisArgument } - - if (!nameExpression.languageVersionSettings.supportsFeature(LanguageFeature.MixedNamedArgumentsInTheirOwnPosition)) { - return argumentsBeforeThis.any { it.isNamed() } - } - - val resolvedCall = callElement.resolveToCall(resolutionFacade) ?: return false - return argumentsBeforeThis.any { it.isNamed() && !it.placedOnItsOwnPositionInCall(resolvedCall) } + return !thisArgument.canBeUsedWithoutNameInCall(resolvedCall) } fun complete(collector: LookupElementsCollector, expectedInfos: Collection, callType: CallType<*>) { @@ -83,6 +77,18 @@ object NamedArgumentCompletion { } } +/** + * Checks whether argument in the [resolvedCall] can be used without its name (as positional argument). + */ +fun KtValueArgument.canBeUsedWithoutNameInCall(resolvedCall: ResolvedCall): Boolean { + val argumentsBeforeThis = resolvedCall.call.valueArguments.takeWhile { it != this } + return if (languageVersionSettings.supportsFeature(LanguageFeature.MixedNamedArgumentsInTheirOwnPosition)) { + argumentsBeforeThis.none { it.isNamed() && !it.placedOnItsOwnPositionInCall(resolvedCall) } + } else { + argumentsBeforeThis.none { it.isNamed() } + } +} + /** * Checks whether argument in the [resolvedCall] is on the same position as it listed in the callable definition. * @@ -99,6 +105,6 @@ object NamedArgumentCompletion { * ) * ``` */ -private fun ValueArgument.placedOnItsOwnPositionInCall(resolvedCall: ResolvedCall): Boolean { +fun ValueArgument.placedOnItsOwnPositionInCall(resolvedCall: ResolvedCall): Boolean { return resolvedCall.getParameterForArgument(this)?.index == resolvedCall.call.valueArguments.indexOf(this) } diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/RemoveArgumentNameIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/RemoveArgumentNameIntention.kt index d0be91b11df..403f8af3a83 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/RemoveArgumentNameIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/RemoveArgumentNameIntention.kt @@ -1,17 +1,6 @@ /* - * 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. + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ package org.jetbrains.kotlin.idea.intentions @@ -19,33 +8,32 @@ package org.jetbrains.kotlin.idea.intentions import com.intellij.openapi.editor.Editor import com.intellij.openapi.util.TextRange import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall +import org.jetbrains.kotlin.idea.completion.canBeUsedWithoutNameInCall +import org.jetbrains.kotlin.idea.completion.placedOnItsOwnPositionInCall import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType import org.jetbrains.kotlin.psi.psiUtil.startOffset +import org.jetbrains.kotlin.resolve.calls.callUtil.getParameterForArgument import org.jetbrains.kotlin.resolve.calls.components.isVararg -import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch class RemoveArgumentNameIntention : SelfTargetingRangeIntention(KtValueArgument::class.java, "Remove argument name") { override fun applicabilityRange(element: KtValueArgument): TextRange? { if (!element.isNamed()) return null + val resolvedCall = element.getStrictParentOfType()?.resolveToCall() ?: return null - val argumentList = element.parent as? KtValueArgumentList ?: return null - val arguments = argumentList.arguments - if (arguments.asSequence().takeWhile { it != element }.any { it.isNamed() }) return null + if (!element.placedOnItsOwnPositionInCall(resolvedCall)) return null + if (!element.canBeUsedWithoutNameInCall(resolvedCall)) return null - val callExpr = argumentList.parent as? KtCallElement ?: return null - val argumentMatch = callExpr.resolveToArgumentMatch(element) ?: return null - if (argumentMatch.valueParameter.index != arguments.indexOf(element)) return null - - val expression = element.getArgumentExpression() ?: return null - return TextRange(element.startOffset, expression.startOffset) + val argumentExpression = element.getArgumentExpression() ?: return null + return TextRange(element.startOffset, argumentExpression.startOffset) } override fun applyTo(element: KtValueArgument, editor: Editor?) { val argumentExpr = element.getArgumentExpression() ?: return val argumentList = element.parent as? KtValueArgumentList ?: return - val callExpr = argumentList.parent as? KtCallElement ?: return + val resolvedCall = (argumentList.parent as? KtCallElement)?.resolveToCall() ?: return val psiFactory = KtPsiFactory(element) - if (argumentExpr is KtCollectionLiteralExpression && callExpr.resolveToArgumentMatch(element)?.valueParameter?.isVararg == true) { + if (argumentExpr is KtCollectionLiteralExpression && resolvedCall.getParameterForArgument(element)?.isVararg == true) { argumentExpr.getInnerExpressions() .map { psiFactory.createArgument(it) } .reversed() @@ -56,8 +44,4 @@ class RemoveArgumentNameIntention : SelfTargetingRangeIntention element.replace(newArgument) } } - - private fun KtCallElement.resolveToArgumentMatch(element: KtValueArgument): ArgumentMatch? { - return resolveToCall()?.getArgumentMapping(element) as? ArgumentMatch ?: return null - } } \ No newline at end of file diff --git a/idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/namedArgumentBefore.kt b/idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/namedArgumentBefore.kt new file mode 100644 index 00000000000..8a2395377b8 --- /dev/null +++ b/idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/namedArgumentBefore.kt @@ -0,0 +1,6 @@ +// COMPILER_ARGUMENTS: -XXLanguage:+MixedNamedArgumentsInTheirOwnPosition +fun foo(s: String, b: Boolean) {} + +fun bar() { + foo(s = "", b = true) +} \ No newline at end of file diff --git a/idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/namedArgumentBefore.kt.after b/idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/namedArgumentBefore.kt.after new file mode 100644 index 00000000000..038e846f144 --- /dev/null +++ b/idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/namedArgumentBefore.kt.after @@ -0,0 +1,6 @@ +// COMPILER_ARGUMENTS: -XXLanguage:+MixedNamedArgumentsInTheirOwnPosition +fun foo(s: String, b: Boolean) {} + +fun bar() { + foo(s = "", true) +} \ No newline at end of file diff --git a/idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/namedArgumentBefore2.kt b/idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/namedArgumentBefore2.kt new file mode 100644 index 00000000000..c33710b4cb6 --- /dev/null +++ b/idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/namedArgumentBefore2.kt @@ -0,0 +1,6 @@ +// COMPILER_ARGUMENTS: -XXLanguage:+MixedNamedArgumentsInTheirOwnPosition +fun foo(name1: Int, name2: Int, name3: Int) {} + +fun usage() { + foo(1, name2 = 2, name3 = 3) +} \ No newline at end of file diff --git a/idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/namedArgumentBefore2.kt.after b/idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/namedArgumentBefore2.kt.after new file mode 100644 index 00000000000..7a0d9fbd0fd --- /dev/null +++ b/idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/namedArgumentBefore2.kt.after @@ -0,0 +1,6 @@ +// COMPILER_ARGUMENTS: -XXLanguage:+MixedNamedArgumentsInTheirOwnPosition +fun foo(name1: Int, name2: Int, name3: Int) {} + +fun usage() { + foo(1, name2 = 2, 3) +} \ No newline at end of file diff --git a/idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/notInPosition.kt b/idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/notInPosition.kt new file mode 100644 index 00000000000..df3318bd34d --- /dev/null +++ b/idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/notInPosition.kt @@ -0,0 +1,7 @@ +// COMPILER_ARGUMENTS: +XXLanguage:+MixedNamedArgumentsInTheirOwnPosition +// IS_APPLICABLE: false +fun foo(name1: Int, name2: Int, name3: Int) {} + +fun usage() { + foo(name2 = 2, name1 = 1, name3 = 3) +} diff --git a/idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/notInPosition2.kt b/idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/notInPosition2.kt new file mode 100644 index 00000000000..e373894ddd7 --- /dev/null +++ b/idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/notInPosition2.kt @@ -0,0 +1,7 @@ +// COMPILER_ARGUMENTS: +XXLanguage:+MixedNamedArgumentsInTheirOwnPosition +// IS_APPLICABLE: false +fun foo(name1: Int, name2: Int, name3: Int) {} + +fun usage() { + foo(name2 = 2, name1 = 1, name3 = 3) +} \ No newline at end of file diff --git a/idea/testData/intentions/removeArgumentName/namedArgumentBefore.kt b/idea/testData/intentions/removeArgumentName/namedArgumentBefore.kt index 62a354eeb57..516030a974d 100644 --- a/idea/testData/intentions/removeArgumentName/namedArgumentBefore.kt +++ b/idea/testData/intentions/removeArgumentName/namedArgumentBefore.kt @@ -1,5 +1,6 @@ +// COMPILER_ARGUMENTS: -XXLanguage:-MixedNamedArgumentsInTheirOwnPosition // IS_APPLICABLE: false -fun foo(s: String, b: Boolean){} +fun foo(s: String, b: Boolean) {} fun bar() { foo(s = "", b = true) diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index 45d4f3d1b94..2e9e5db275c 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -12711,6 +12711,39 @@ public class IntentionTestGenerated extends AbstractIntentionTest { public void testSuperClassConstructor() throws Exception { runTest("idea/testData/intentions/removeArgumentName/superClassConstructor.kt"); } + + @TestMetadata("idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class MixedNamedArgumentsInTheirOwnPosition extends AbstractIntentionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInMixedNamedArgumentsInTheirOwnPosition() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), null, true); + } + + @TestMetadata("namedArgumentBefore.kt") + public void testNamedArgumentBefore() throws Exception { + runTest("idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/namedArgumentBefore.kt"); + } + + @TestMetadata("namedArgumentBefore2.kt") + public void testNamedArgumentBefore2() throws Exception { + runTest("idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/namedArgumentBefore2.kt"); + } + + @TestMetadata("notInPosition.kt") + public void testNotInPosition() throws Exception { + runTest("idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/notInPosition.kt"); + } + + @TestMetadata("notInPosition2.kt") + public void testNotInPosition2() throws Exception { + runTest("idea/testData/intentions/removeArgumentName/MixedNamedArgumentsInTheirOwnPosition/notInPosition2.kt"); + } + } } @TestMetadata("idea/testData/intentions/removeBraces")