Change Signature: Support configurable Change Signature for Java code

#KT-5923 Fixed
This commit is contained in:
Alexey Sedunov
2015-06-26 17:17:30 +03:00
parent 9e82411e78
commit 5319e9e761
14 changed files with 290 additions and 79 deletions
@@ -50,7 +50,7 @@ public abstract class CallableRefactoring<T: CallableDescriptor>(
val project: Project,
val callableDescriptor: T,
val bindingContext: BindingContext,
val commandName: String?) {
val commandName: String) {
private val LOG = Logger.getInstance(javaClass<CallableRefactoring<*>>())
private val kind = (callableDescriptor as? CallableMemberDescriptor)?.getKind() ?: CallableMemberDescriptor.Kind.DECLARATION
@@ -353,12 +353,16 @@ public fun ChangeInfo.toJetChangeInfo(originalChangeSignatureDescriptor: JetMeth
val currentType = parameterDescriptors[i].getType()
val defaultValueText = info.getDefaultValue()
val defaultValueExpr = if (getLanguage().`is`(JavaLanguage.INSTANCE) && !defaultValueText.isNullOrEmpty()) {
PsiElementFactory.SERVICE.getInstance(method.getProject())
.createExpressionFromText(defaultValueText!!, null)
.j2k()
}
else null
val defaultValueExpr =
when {
info is KotlinAwareJavaParameterInfoImpl -> info.kotlinDefaultValue
getLanguage().`is`(JavaLanguage.INSTANCE) && !defaultValueText.isNullOrEmpty() -> {
PsiElementFactory.SERVICE.getInstance(method.getProject())
.createExpressionFromText(defaultValueText!!, null)
.j2k()
}
else -> null
}
with(JetParameterInfo(callableDescriptor = functionDescriptor,
originalIndex = oldIndex,
@@ -16,22 +16,36 @@
package org.jetbrains.kotlin.idea.refactoring.changeSignature
import com.intellij.lang.java.JavaLanguage
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.Messages
import com.intellij.psi.PsiElement
import com.intellij.refactoring.changeSignature.ChangeSignatureHandler
import com.intellij.psi.PsiElementFactory
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiType
import com.intellij.refactoring.BaseRefactoringProcessor
import com.intellij.refactoring.changeSignature.*
import com.intellij.refactoring.util.CanonicalTypes
import com.intellij.testFramework.LightVirtualFile
import com.intellij.util.VisibilityUtil
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.asJava.KotlinLightMethod
import org.jetbrains.kotlin.asJava.getRepresentativeLightMethod
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.idea.JetFileType
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.core.refactoring.createJavaClass
import org.jetbrains.kotlin.idea.core.refactoring.createJavaMethod
import org.jetbrains.kotlin.idea.core.refactoring.toPsiFile
import org.jetbrains.kotlin.idea.refactoring.CallableRefactoring
import org.jetbrains.kotlin.psi.JetClass
import org.jetbrains.kotlin.psi.JetFunction
import org.jetbrains.kotlin.psi.JetParameter
import org.jetbrains.kotlin.psi.JetProperty
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.OverrideResolver
@@ -68,36 +82,109 @@ public class JetChangeSignature(project: Project,
val configuration: JetChangeSignatureConfiguration,
bindingContext: BindingContext,
val defaultValueContext: PsiElement,
commandName: String?): CallableRefactoring<CallableDescriptor>(project, callableDescriptor, bindingContext, commandName) {
commandName: String?): CallableRefactoring<CallableDescriptor>(project,
callableDescriptor,
bindingContext,
commandName ?: ChangeSignatureHandler.REFACTORING_NAME) {
private val LOG = Logger.getInstance(javaClass<JetChangeSignature>())
override fun forcePerformForSelectedFunctionOnly() = configuration.forcePerformForSelectedFunctionOnly()
private fun runSilentRefactoring(descriptor: JetMethodDescriptor) {
val commandName = commandName ?: ChangeSignatureHandler.REFACTORING_NAME
val processor = when (descriptor.baseDeclaration) {
val baseDeclaration = descriptor.baseDeclaration
val processor = when (baseDeclaration) {
is JetFunction, is JetClass -> {
JetChangeSignatureDialog.createRefactoringProcessorForSilentChangeSignature(project, commandName, descriptor, defaultValueContext)
}
is JetProperty, is JetParameter -> {
JetChangePropertySignatureDialog.createProcessorForSilentRefactoring(project, commandName, descriptor)
}
else -> throw AssertionError("Unexpected declaration: ${descriptor.baseDeclaration.getElementTextWithContext()}")
is PsiMethod -> {
if (baseDeclaration.getLanguage() != JavaLanguage.INSTANCE) {
Messages.showErrorDialog("Can't change signature of ${baseDeclaration.getLanguage().getDisplayName()} method", commandName)
return
}
ChangeSignatureProcessor(project, getPreviewInfoForJavaMethod(descriptor).second)
}
else -> throw AssertionError("Unexpected declaration: ${baseDeclaration.getElementTextWithContext()}")
}
processor.run()
}
private fun runInteractiveRefactoring(descriptor: JetMethodDescriptor) {
val dialog = when (descriptor.baseDeclaration) {
val baseDeclaration = descriptor.baseDeclaration
val dialog = when (baseDeclaration) {
is JetFunction, is JetClass -> JetChangeSignatureDialog(project, descriptor, defaultValueContext, commandName)
is JetProperty, is JetParameter -> JetChangePropertySignatureDialog(project, descriptor, commandName)
else -> throw AssertionError("Unexpected declaration: ${descriptor.baseDeclaration.getElementTextWithContext()}")
is PsiMethod -> {
// No changes are made from Kotlin side: just run foreign refactoring
if (descriptor is JetChangeSignatureData) {
ChangeSignatureUtil.invokeChangeSignatureOn(baseDeclaration, project)
return
}
if (baseDeclaration.getLanguage() != JavaLanguage.INSTANCE) {
Messages.showErrorDialog("Can't change signature of ${baseDeclaration.getLanguage().getDisplayName()} method", commandName)
return
}
val (preview, javaChangeInfo) = getPreviewInfoForJavaMethod(descriptor)
object: JavaChangeSignatureDialog(project, JavaMethodDescriptor(preview), false, null) {
override fun createRefactoringProcessor(): BaseRefactoringProcessor? {
val processor = super.createRefactoringProcessor()
(processor as? ChangeSignatureProcessor)?.getChangeInfo()?.updateMethod(javaChangeInfo.getMethod())
return processor
}
}
}
else -> throw AssertionError("Unexpected declaration: ${baseDeclaration.getElementTextWithContext()}")
}
dialog.show()
}
private fun getPreviewInfoForJavaMethod(descriptor: JetMethodDescriptor): Pair<PsiMethod, JavaChangeInfo> {
val originalMethod = descriptor.baseDeclaration as PsiMethod
val contextFile = defaultValueContext.getContainingFile() as JetFile
// Generate new Java method signature from the Kotlin point of view
val ktChangeInfo = JetChangeInfo(methodDescriptor = descriptor, context = defaultValueContext)
val ktSignature = ktChangeInfo.getNewSignature(descriptor.originalPrimaryCallable)
val dummyFileText = with(StringBuilder()) {
contextFile.getPackageDirective()?.let { append(it.getText()).append("\n") }
append("class Dummy {\n").append(ktSignature).append("{}\n}")
toString()
}
val dummyFile = LightVirtualFile("dummy.kt", JetFileType.INSTANCE, dummyFileText).toPsiFile(project) as JetFile
val dummyDeclaration = (dummyFile.getDeclarations().first() as JetClass).getBody()!!.getDeclarations().first()
// Convert to PsiMethod which can be used in Change Signature dialog
val containingClass = PsiElementFactory.SERVICE.getInstance(project).createClass("Dummy")
val preview = createJavaMethod(dummyDeclaration.getRepresentativeLightMethod()!!, containingClass)
// Create JavaChangeInfo based on new signature
// TODO: Support visibility change
val visibility = VisibilityUtil.getVisibilityModifier(originalMethod.getModifierList())
val returnType = CanonicalTypes.createTypeWrapper(preview.getReturnType() ?: PsiType.VOID)
val params = (preview.getParameterList().getParameters() zip ktChangeInfo.getNewParameters()).map {
val (param, paramInfo) = it
// Keep original default value for proper update of Kotlin usages
KotlinAwareJavaParameterInfoImpl(paramInfo.getOldIndex(), param.getName(), param.getType(), paramInfo.defaultValueForCall)
}.toTypedArray()
return preview to JavaChangeInfoImpl(visibility,
originalMethod,
preview.getName(),
returnType,
params,
arrayOf<ThrownExceptionInfo>(),
false,
emptySet<PsiMethod>(),
emptySet<PsiMethod>())
}
override fun performRefactoring(descriptorsForChange: Collection<CallableDescriptor>) {
val adjustedDescriptor = adjustDescriptor(descriptorsForChange) ?: return
@@ -0,0 +1,28 @@
/*
* 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.refactoring.changeSignature
import com.intellij.psi.PsiType
import com.intellij.refactoring.changeSignature.ParameterInfoImpl
import org.jetbrains.kotlin.psi.JetExpression
public class KotlinAwareJavaParameterInfoImpl(
oldParameterIndex: Int,
name: String,
type: PsiType,
val kotlinDefaultValue: JetExpression?
): ParameterInfoImpl(oldParameterIndex, name, type, kotlinDefaultValue?.getText() ?: "")
@@ -16,80 +16,68 @@
package org.jetbrains.kotlin.idea.core.refactoring
import com.intellij.openapi.util.Key
import com.intellij.openapi.roots.JavaProjectRootsUtil
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.refactoring.util.ConflictsUtil
import com.intellij.psi.PsiFileFactory
import org.jetbrains.kotlin.idea.core.getPackage
import org.jetbrains.kotlin.idea.JetFileType
import com.intellij.openapi.project.Project
import java.io.File
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.psi.*
import java.util.ArrayList
import com.intellij.openapi.application.ApplicationManager
import com.intellij.refactoring.BaseRefactoringProcessor.ConflictsInTestsException
import com.intellij.refactoring.ui.ConflictsDialog
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode
import com.intellij.ide.util.PsiElementListCellRenderer
import com.intellij.openapi.ui.popup.JBPopup
import com.intellij.openapi.ui.popup.PopupChooserBuilder
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.util.TextRange
import com.intellij.codeInsight.daemon.impl.quickfix.CreateFromUsageUtils
import com.intellij.codeInsight.unwrap.RangeSplitter
import com.intellij.codeInsight.unwrap.UnwrapHandler
import com.intellij.openapi.editor.markup.TextAttributes
import com.intellij.openapi.editor.markup.HighlighterTargetArea
import com.intellij.openapi.editor.markup.RangeHighlighter
import java.util.Collections
import com.intellij.ide.util.PsiElementListCellRenderer
import com.intellij.lang.java.JavaLanguage
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.colors.EditorColors
import com.intellij.openapi.editor.colors.EditorColorsManager
import com.intellij.ui.components.JBList
import com.intellij.openapi.editor.markup.HighlighterTargetArea
import com.intellij.openapi.editor.markup.RangeHighlighter
import com.intellij.openapi.editor.markup.TextAttributes
import com.intellij.openapi.options.ConfigurationException
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.JavaProjectRootsUtil
import com.intellij.openapi.ui.popup.JBPopup
import com.intellij.openapi.ui.popup.JBPopupAdapter
import com.intellij.openapi.ui.popup.LightweightWindowEvent
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
import com.intellij.psi.PsiNamedElement
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import com.intellij.openapi.ui.popup.PopupChooserBuilder
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.TextRange
import com.intellij.openapi.util.text.StringUtil
import javax.swing.Icon
import org.jetbrains.kotlin.idea.util.string.collapseSpaces
import org.jetbrains.kotlin.asJava.KotlinLightMethod
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import com.intellij.psi.PsiPackage
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import com.intellij.refactoring.util.RefactoringUIUtil
import org.jetbrains.kotlin.idea.caches.resolve.getJavaMemberDescriptor
import com.intellij.refactoring.changeSignature.ChangeSignatureUtil
import org.jetbrains.kotlin.asJava.LightClassUtil
import com.intellij.util.VisibilityUtil
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind
import org.jetbrains.kotlin.resolve.OverridingUtil
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import com.intellij.lang.java.JavaLanguage
import com.intellij.codeInsight.daemon.impl.quickfix.CreateFromUsageUtils
import com.intellij.openapi.options.ConfigurationException
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.*
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.refactoring.BaseRefactoringProcessor.ConflictsInTestsException
import com.intellij.refactoring.changeSignature.ChangeSignatureUtil
import com.intellij.refactoring.listeners.RefactoringEventData
import com.intellij.refactoring.listeners.RefactoringEventListener
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import com.intellij.refactoring.ui.ConflictsDialog
import com.intellij.refactoring.util.ConflictsUtil
import com.intellij.refactoring.util.RefactoringUIUtil
import com.intellij.ui.components.JBList
import com.intellij.util.VisibilityUtil
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.asJava.KotlinLightMethod
import org.jetbrains.kotlin.asJava.LightClassUtil
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.JetFileType
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getJavaMemberDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.core.getPackage
import org.jetbrains.kotlin.idea.j2k.IdeaResolverForConverter
import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.idea.util.string.collapseSpaces
import org.jetbrains.kotlin.j2k.ConverterSettings
import org.jetbrains.kotlin.j2k.IdeaReferenceSearcher
import org.jetbrains.kotlin.j2k.JavaToKotlinConverter
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.AnalyzingUtils
import org.jetbrains.kotlin.resolve.BindingContext
import java.io.File
import java.util.ArrayList
import java.util.Collections
import javax.swing.Icon
fun <T: Any> PsiElement.getAndRemoveCopyableUserData(key: Key<T>): T? {
val data = getCopyableUserData(key)
@@ -113,14 +101,14 @@ fun createKotlinFile(fileName: String, targetDir: PsiDirectory): JetFile {
public fun File.toVirtualFile(): VirtualFile? = LocalFileSystem.getInstance().findFileByIoFile(this)
public fun File.toPsiFile(project: Project): PsiFile? {
return toVirtualFile()?.let { vfile -> PsiManager.getInstance(project).findFile(vfile) }
}
public fun File.toPsiFile(project: Project): PsiFile? = toVirtualFile()?.toPsiFile(project)
public fun File.toPsiDirectory(project: Project): PsiDirectory? {
return toVirtualFile()?.let { vfile -> PsiManager.getInstance(project).findDirectory(vfile) }
}
public fun VirtualFile.toPsiFile(project: Project): PsiFile? = PsiManager.getInstance(project).findFile(this)
public fun PsiElement.getUsageContext(): PsiElement {
return when (this) {
is JetElement -> PsiTreeUtil.getParentOfType(this, javaClass<JetNamedDeclaration>(), javaClass<JetFile>())!!
@@ -0,0 +1,15 @@
class Foo {
static void foo(int n, int m, int i) {
}
}
class Test {
static void test() {
Foo.foo();
Foo.foo(1);
Foo.foo(1, 2, 3);
Foo.foo(1, 2, 3);
Foo.foo(4, 5, 6);
}
}
@@ -0,0 +1,9 @@
// "Add parameter to function 'foo'" "true"
// DISABLE-ERRORS
fun test() {
Foo.foo()
Foo.foo(1)
Foo.foo(1, 2, 3)
Foo.foo(1, 2, 3<caret>)
Foo.foo(4, 5, 6)
}
@@ -0,0 +1,15 @@
class Foo {
static void foo(int n, int m) {
}
}
class Test {
static void test() {
Foo.foo();
Foo.foo(1);
Foo.foo(1, 2);
Foo.foo(1, 2, 3);
Foo.foo(4, 5, 6);
}
}
@@ -0,0 +1,9 @@
// "Add parameter to function 'foo'" "true"
// DISABLE-ERRORS
fun test() {
Foo.foo()
Foo.foo(1)
Foo.foo(1, 2)
Foo.foo(1, 2, 3<caret>)
Foo.foo(4, 5, 6)
}
@@ -0,0 +1,14 @@
class Foo {
static void foo(int n) {
}
}
class Test {
static void test() {
Foo.foo();
Foo.foo(1);
Foo.foo(1);
Foo.foo(3);
}
}
@@ -0,0 +1,8 @@
// "Remove parameter 'm'" "true"
// DISABLE-ERRORS
fun test() {
Foo.foo()
Foo.foo(1<caret>)
Foo.foo(1)
Foo.foo(3)
}
@@ -0,0 +1,14 @@
class Foo {
static void foo(int n, int m) {
}
}
class Test {
static void test() {
Foo.foo();
Foo.foo(1);
Foo.foo(1, 2);
Foo.foo(3, 4);
}
}
@@ -0,0 +1,8 @@
// "Remove parameter 'm'" "true"
// DISABLE-ERRORS
fun test() {
Foo.foo()
Foo.foo(1<caret>)
Foo.foo(1, 2)
Foo.foo(3, 4)
}
@@ -250,6 +250,12 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ChangeSignature extends AbstractQuickFixMultiFileTest {
@TestMetadata("addJavaMethodParameter.before.Main.kt")
public void testAddJavaMethodParameter() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/changeSignature/addJavaMethodParameter.before.Main.kt");
doTestWithExtraFile(fileName);
}
@TestMetadata("addParameterWithImport.before.Main.kt")
public void testAddParameterWithImport() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/changeSignature/addParameterWithImport.before.Main.kt");
@@ -265,6 +271,12 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/changeSignature/matchFunctionLiteralWithSAMType.before.Main.kt");
doTestWithExtraFile(fileName);
}
@TestMetadata("removeJavaMethodParameter.before.Main.kt")
public void testRemoveJavaMethodParameter() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/changeSignature/removeJavaMethodParameter.before.Main.kt");
doTestWithExtraFile(fileName);
}
}
@TestMetadata("idea/testData/quickfix/createFromUsage")