From 4ecd3726b356a98a66ea76215756df7d7c4891d5 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Wed, 10 Dec 2014 15:54:59 +0300 Subject: [PATCH] Create parameter from usage: Support named arguments --- .../plugin/quickfix/QuickFixRegistrar.java | 2 + .../createClass/CreateClassFromUsageFix.kt | 1 + .../createClass/createClassUtils.kt | 15 +--- .../CreateParameterActionFactory.kt | 71 +++++++------------ ...teParameterByNamedArgumentActionFactory.kt | 66 +++++++++++++++++ .../CreateParameterFromUsageFix.kt | 54 ++++++++++++++ .../plugin/refactoring/jetRefactoringUtil.kt | 13 ++++ .../afterNamedArgInConstructorCall.kt | 9 +++ .../parameter/afterNamedArgInFunctionCall.kt | 9 +++ .../beforeNamedArgInConstructorCall.kt | 9 +++ .../parameter/beforeNamedArgInFunctionCall.kt | 9 +++ .../quickfix/QuickFixTestGenerated.java | 12 ++++ 12 files changed, 209 insertions(+), 61 deletions(-) create mode 100644 idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createVariable/CreateParameterByNamedArgumentActionFactory.kt create mode 100644 idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createVariable/CreateParameterFromUsageFix.kt create mode 100644 idea/testData/quickfix/createFromUsage/createVariable/parameter/afterNamedArgInConstructorCall.kt create mode 100644 idea/testData/quickfix/createFromUsage/createVariable/parameter/afterNamedArgInFunctionCall.kt create mode 100644 idea/testData/quickfix/createFromUsage/createVariable/parameter/beforeNamedArgInConstructorCall.kt create mode 100644 idea/testData/quickfix/createFromUsage/createVariable/parameter/beforeNamedArgInFunctionCall.kt diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixRegistrar.java b/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixRegistrar.java index 5ba77442aa9..b682c6be237 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixRegistrar.java +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixRegistrar.java @@ -252,6 +252,8 @@ public class QuickFixRegistrar { QuickFixes.factories.put(UNRESOLVED_REFERENCE_WRONG_RECEIVER, CreateParameterActionFactory.INSTANCE$); QuickFixes.factories.put(EXPRESSION_EXPECTED_PACKAGE_FOUND, CreateParameterActionFactory.INSTANCE$); + QuickFixes.factories.put(NAMED_PARAMETER_NOT_FOUND, CreateParameterByNamedArgumentActionFactory.INSTANCE$); + QuickFixes.factories.put(FUNCTION_EXPECTED, CreateInvokeFunctionActionFactory.INSTANCE$); QuickFixFactoryForTypeMismatchError factoryForTypeMismatchError = new QuickFixFactoryForTypeMismatchError(); diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createClass/CreateClassFromUsageFix.kt b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createClass/CreateClassFromUsageFix.kt index 206221a3a2d..416089b8d9a 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createClass/CreateClassFromUsageFix.kt +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createClass/CreateClassFromUsageFix.kt @@ -41,6 +41,7 @@ import com.intellij.openapi.module.ModuleUtilCore import com.intellij.ide.util.DirectoryChooserUtil import java.util.HashMap import com.intellij.openapi.application.ApplicationManager +import org.jetbrains.jet.plugin.refactoring.canRefactor enum class ClassKind(val keyword: String, val description: String) { PLAIN_CLASS: ClassKind("class", "class") diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createClass/createClassUtils.kt b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createClass/createClassUtils.kt index de2a21cd05d..1707dfe0f0d 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createClass/createClassUtils.kt +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createClass/createClassUtils.kt @@ -28,9 +28,7 @@ import com.intellij.codeInsight.daemon.quickFix.CreateClassOrPackageFix import org.jetbrains.jet.plugin.quickfix.DelegatingIntentionAction import org.jetbrains.jet.plugin.JetBundle import com.intellij.psi.PsiPackage -import com.intellij.psi.PsiDirectory -import org.jetbrains.jet.lang.psi.JetElement -import org.jetbrains.jet.plugin.util.ProjectRootsUtil +import org.jetbrains.jet.plugin.refactoring.canRefactor private fun String.checkClassName(): Boolean = isNotEmpty() && Character.isUpperCase(first()) @@ -95,17 +93,6 @@ private fun JetExpression.getInheritableTypeInfo( } } -private fun PsiElement.canRefactor(): Boolean { - return when (this) { - is PsiPackage -> - getDirectories().any { it.canRefactor() } - is JetElement, is PsiDirectory -> - isWritable() && ProjectRootsUtil.isInSource(element = this, includeLibrarySources = false) - else -> - false - } -} - private fun JetSimpleNameExpression.getCreatePackageFixIfApplicable(targetParent: PsiElement): IntentionAction? { val name = getReferencedName() if (!name.checkPackageName()) return null diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createVariable/CreateParameterActionFactory.kt b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createVariable/CreateParameterActionFactory.kt index 0c05574bff2..fb72062e06d 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createVariable/CreateParameterActionFactory.kt +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createVariable/CreateParameterActionFactory.kt @@ -1,9 +1,5 @@ package org.jetbrains.jet.plugin.quickfix.createFromUsage.createVariable -import org.jetbrains.jet.plugin.quickfix.createFromUsage.CreateFromUsageFixBase -import org.jetbrains.jet.plugin.JetBundle -import com.intellij.openapi.project.Project -import com.intellij.openapi.editor.Editor import org.jetbrains.jet.lang.psi.JetFile import org.jetbrains.jet.plugin.quickfix.JetSingleIntentionActionFactory import org.jetbrains.jet.lang.diagnostics.Diagnostic @@ -16,9 +12,6 @@ import org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder.guessTy import org.jetbrains.jet.lang.resolve.BindingContext import org.jetbrains.jet.lang.descriptors.ClassDescriptor import org.jetbrains.jet.lang.descriptors.FunctionDescriptor -import org.jetbrains.jet.plugin.refactoring.changeSignature.runChangeSignature -import org.jetbrains.jet.plugin.refactoring.changeSignature.JetChangeSignatureConfiguration -import org.jetbrains.jet.plugin.refactoring.changeSignature.JetChangeSignatureData import org.jetbrains.jet.plugin.refactoring.changeSignature.JetParameterInfo import com.intellij.psi.PsiElement import org.jetbrains.jet.lang.psi.JetPropertyAccessor @@ -43,28 +36,6 @@ import org.jetbrains.jet.plugin.caches.resolve.analyzeFullyAndGetResult import org.jetbrains.jet.utils.addToStdlib.firstIsInstanceOrNull object CreateParameterActionFactory: JetSingleIntentionActionFactory() { - private fun JetType.hasTypeParametersToAdd(functionDescriptor: FunctionDescriptor, context: BindingContext): Boolean { - val typeParametersToAdd = LinkedHashSet(getTypeParameters()) - typeParametersToAdd.removeAll(functionDescriptor.getTypeParameters()) - if (typeParametersToAdd.isEmpty()) return false - - val scope = when(functionDescriptor) { - is ConstructorDescriptor -> { - val classDescriptor = (functionDescriptor as? ConstructorDescriptor)?.getContainingDeclaration() - (classDescriptor as? ClassDescriptorWithResolutionScopes)?.getScopeForClassHeaderResolution() - } - - is FunctionDescriptor -> { - val function = DescriptorToSourceUtils.descriptorToDeclaration(functionDescriptor) as? JetFunction - function?.let { context[BindingContext.RESOLUTION_SCOPE, it.getBodyExpression()] } - } - - else -> null - } ?: return true - - return typeParametersToAdd.any { scope.getClassifier(it.getName()) != it } - } - override fun createAction(diagnostic: Diagnostic): IntentionAction? { val result = (diagnostic.getPsiFile() as? JetFile)?.analyzeFullyAndGetResult() ?: return null val context = result.bindingContext @@ -75,7 +46,7 @@ object CreateParameterActionFactory: JetSingleIntentionActionFactory() { val varExpected = refExpr.getAssignmentByLHS() != null val paramType = refExpr.getExpressionForTypeGuess().guessTypes(context, result.moduleDescriptor).let { - when (it.size) { + when (it.size()) { 0 -> KotlinBuiltIns.getInstance().getAnyType() 1 -> it.first() else -> return null @@ -116,22 +87,28 @@ object CreateParameterActionFactory: JetSingleIntentionActionFactory() { if (paramType.hasTypeParametersToAdd(functionDescriptor, context)) return null - return object: CreateFromUsageFixBase(refExpr) { - override fun getText(): String { - return JetBundle.message("create.parameter.from.usage", refExpr.getReferencedName()) - } - - override fun invoke(project: Project, editor: Editor?, file: JetFile?) { - val config = object : JetChangeSignatureConfiguration { - override fun configure(changeSignatureData: JetChangeSignatureData, bindingContext: BindingContext) { - changeSignatureData.addParameter(parameterInfo) - } - - override fun performSilently(affectedFunctions: Collection): Boolean = false - } - - runChangeSignature(project, functionDescriptor, config, context, refExpr, getText()) - } - } + return CreateParameterFromUsageFix(functionDescriptor, context, parameterInfo, refExpr) } } + +fun JetType.hasTypeParametersToAdd(functionDescriptor: FunctionDescriptor, context: BindingContext): Boolean { + val typeParametersToAdd = LinkedHashSet(getTypeParameters()) + typeParametersToAdd.removeAll(functionDescriptor.getTypeParameters()) + if (typeParametersToAdd.isEmpty()) return false + + val scope = when(functionDescriptor) { + is ConstructorDescriptor -> { + val classDescriptor = functionDescriptor.getContainingDeclaration() as? ClassDescriptorWithResolutionScopes + classDescriptor?.getScopeForClassHeaderResolution() + } + + is FunctionDescriptor -> { + val function = DescriptorToSourceUtils.descriptorToDeclaration(functionDescriptor) as? JetFunction + function?.let { context[BindingContext.RESOLUTION_SCOPE, it.getBodyExpression()] } + } + + else -> null + } ?: return true + + return typeParametersToAdd.any { scope.getClassifier(it.getName()) != it } +} diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createVariable/CreateParameterByNamedArgumentActionFactory.kt b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createVariable/CreateParameterByNamedArgumentActionFactory.kt new file mode 100644 index 00000000000..ce514fb2a4d --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createVariable/CreateParameterByNamedArgumentActionFactory.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2010-2014 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.jet.plugin.quickfix.createFromUsage.createVariable + +import org.jetbrains.jet.plugin.quickfix.JetSingleIntentionActionFactory +import org.jetbrains.jet.lang.diagnostics.Diagnostic +import com.intellij.codeInsight.intention.IntentionAction +import org.jetbrains.jet.lang.psi.JetFile +import org.jetbrains.jet.plugin.caches.resolve.analyzeFullyAndGetResult +import org.jetbrains.jet.plugin.quickfix.QuickFixUtil +import org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder.guessTypes +import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns +import org.jetbrains.jet.plugin.refactoring.changeSignature.JetParameterInfo +import org.jetbrains.jet.lang.psi.JetValueArgument +import org.jetbrains.jet.lang.psi.JetCallElement +import org.jetbrains.jet.lang.psi.psiUtil.getStrictParentOfType +import org.jetbrains.jet.lang.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.jet.plugin.codeInsight.DescriptorToDeclarationUtil +import org.jetbrains.jet.plugin.refactoring.canRefactor +import org.jetbrains.jet.lang.psi.JetFunction +import org.jetbrains.jet.lang.psi.JetClass +import org.jetbrains.jet.lang.descriptors.FunctionDescriptor + +public object CreateParameterByNamedArgumentActionFactory: JetSingleIntentionActionFactory() { + override fun createAction(diagnostic: Diagnostic): IntentionAction? { + val result = (diagnostic.getPsiFile() as? JetFile)?.analyzeFullyAndGetResult() ?: return null + val context = result.bindingContext + + val argument = QuickFixUtil.getParentElementOfType(diagnostic, javaClass()) ?: return null + val name = argument.getArgumentName()?.getText() ?: return null + val argumentExpression = argument.getArgumentExpression() + + val callElement = argument.getStrictParentOfType() ?: return null + val functionDescriptor = callElement.getResolvedCall(context)?.getResultingDescriptor() as? FunctionDescriptor ?: return null + val callable = DescriptorToDeclarationUtil.getDeclaration(callElement.getProject(), functionDescriptor) ?: return null + if (!((callable is JetFunction || callable is JetClass) && callable.canRefactor())) return null + + val anyType = KotlinBuiltIns.getInstance().getAnyType() + val paramType = argumentExpression?.guessTypes(context, result.moduleDescriptor)?.let { + when (it.size()) { + 0 -> anyType + 1 -> it.first() + else -> return null + } + } ?: anyType + if (paramType.hasTypeParametersToAdd(functionDescriptor, context)) return null + + val parameterInfo = JetParameterInfo(name, paramType) + parameterInfo.setDefaultValueText(argumentExpression.getText() ?: "") + return CreateParameterFromUsageFix(functionDescriptor, context, parameterInfo, argument) + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createVariable/CreateParameterFromUsageFix.kt b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createVariable/CreateParameterFromUsageFix.kt new file mode 100644 index 00000000000..64d53e8f79e --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createVariable/CreateParameterFromUsageFix.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2010-2014 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.jet.plugin.quickfix.createFromUsage.createVariable + +import org.jetbrains.jet.lang.psi.JetElement +import org.jetbrains.jet.plugin.quickfix.createFromUsage.CreateFromUsageFixBase +import org.jetbrains.jet.plugin.JetBundle +import com.intellij.openapi.project.Project +import com.intellij.openapi.editor.Editor +import org.jetbrains.jet.lang.psi.JetFile +import org.jetbrains.jet.plugin.refactoring.changeSignature.JetChangeSignatureConfiguration +import org.jetbrains.jet.plugin.refactoring.changeSignature.JetChangeSignatureData +import org.jetbrains.jet.lang.resolve.BindingContext +import com.intellij.psi.PsiElement +import org.jetbrains.jet.plugin.refactoring.changeSignature.runChangeSignature +import org.jetbrains.jet.plugin.refactoring.changeSignature.JetParameterInfo +import org.jetbrains.jet.lang.descriptors.FunctionDescriptor + +public class CreateParameterFromUsageFix( + val functionDescriptor: FunctionDescriptor, + val bindingContext: BindingContext, + val parameterInfo: JetParameterInfo, + val defaultValueContext: JetElement +): CreateFromUsageFixBase(defaultValueContext) { + override fun getText(): String { + return JetBundle.message("create.parameter.from.usage", parameterInfo.getName()) + } + + override fun invoke(project: Project, editor: Editor?, file: JetFile?) { + val config = object : JetChangeSignatureConfiguration { + override fun configure(changeSignatureData: JetChangeSignatureData, bindingContext: BindingContext) { + changeSignatureData.addParameter(parameterInfo) + } + + override fun performSilently(affectedFunctions: Collection): Boolean = false + } + + runChangeSignature(project, functionDescriptor, config, bindingContext, defaultValueContext, getText()) + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt index b580d10e06d..45a1bbe8c9f 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt @@ -72,6 +72,8 @@ import org.jetbrains.jet.lang.resolve.OverridingUtil import org.jetbrains.jet.lang.psi.psiUtil.getParentOfType import org.jetbrains.jet.lang.psi.psiUtil.getNonStrictParentOfType import org.jetbrains.jet.lang.psi.psiUtil.getStrictParentOfType +import com.intellij.psi.PsiPackage +import org.jetbrains.jet.plugin.util.ProjectRootsUtil fun PsiElement.getAndRemoveCopyableUserData(key: Key): T? { val data = getCopyableUserData(key) @@ -381,4 +383,15 @@ public fun comparePossiblyOverridingDescriptors(currentDescriptor: DeclarationDe } return false +} + +public fun PsiElement.canRefactor(): Boolean { + return when (this) { + is PsiPackage -> + getDirectories().any { it.canRefactor() } + is JetElement, is PsiDirectory -> + isWritable() && ProjectRootsUtil.isInSource(element = this, includeLibrarySources = false) + else -> + false + } } \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createVariable/parameter/afterNamedArgInConstructorCall.kt b/idea/testData/quickfix/createFromUsage/createVariable/parameter/afterNamedArgInConstructorCall.kt new file mode 100644 index 00000000000..113bba62aba --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createVariable/parameter/afterNamedArgInConstructorCall.kt @@ -0,0 +1,9 @@ +// "Create parameter 's'" "true" + +class Foo(val n: Int, s: String) { + +} + +fun bar() { + Foo(n = 1, s = "2") +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createVariable/parameter/afterNamedArgInFunctionCall.kt b/idea/testData/quickfix/createFromUsage/createVariable/parameter/afterNamedArgInFunctionCall.kt new file mode 100644 index 00000000000..a42774270ac --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createVariable/parameter/afterNamedArgInFunctionCall.kt @@ -0,0 +1,9 @@ +// "Create parameter 's'" "true" + +fun foo(n: Int, s: String) { + +} + +fun bar() { + foo(n = 1, s = "2") +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createVariable/parameter/beforeNamedArgInConstructorCall.kt b/idea/testData/quickfix/createFromUsage/createVariable/parameter/beforeNamedArgInConstructorCall.kt new file mode 100644 index 00000000000..669c5b06908 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createVariable/parameter/beforeNamedArgInConstructorCall.kt @@ -0,0 +1,9 @@ +// "Create parameter 's'" "true" + +class Foo(val n: Int) { + +} + +fun bar() { + Foo(n = 1, s = "2") +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createVariable/parameter/beforeNamedArgInFunctionCall.kt b/idea/testData/quickfix/createFromUsage/createVariable/parameter/beforeNamedArgInFunctionCall.kt new file mode 100644 index 00000000000..1b342da1086 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createVariable/parameter/beforeNamedArgInFunctionCall.kt @@ -0,0 +1,9 @@ +// "Create parameter 's'" "true" + +fun foo(n: Int) { + +} + +fun bar() { + foo(n = 1, s = "2") +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java index 9161fc8127e..a9aae763170 100644 --- a/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java @@ -2399,6 +2399,18 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { doTest(fileName); } + @TestMetadata("beforeNamedArgInConstructorCall.kt") + public void testNamedArgInConstructorCall() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/parameter/beforeNamedArgInConstructorCall.kt"); + doTest(fileName); + } + + @TestMetadata("beforeNamedArgInFunctionCall.kt") + public void testNamedArgInFunctionCall() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/parameter/beforeNamedArgInFunctionCall.kt"); + doTest(fileName); + } + @TestMetadata("beforeNullableType.kt") public void testNullableType() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/parameter/beforeNullableType.kt");