diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableBuilder.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableBuilder.kt index 7a4c9a8e500..15b1627d95a 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableBuilder.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableBuilder.kt @@ -72,6 +72,8 @@ import com.intellij.psi.PsiMethod import com.intellij.openapi.fileEditor.OpenFileDescriptor import com.intellij.psi.codeStyle.JavaCodeStyleManager import com.intellij.psi.PsiModifier +import com.intellij.psi.PsiField +import com.intellij.psi.PsiMember import org.jetbrains.kotlin.resolve.scopes.ChainedScope import org.jetbrains.kotlin.resolve.scopes.WritableScopeImpl import org.jetbrains.kotlin.resolve.scopes.RedeclarationHandler @@ -764,7 +766,7 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { val project = declaration.getProject() - val newJavaMethod: PsiMethod = when (declaration) { + val newJavaMember: PsiMember = when (declaration) { is JetNamedFunction -> { val method = createJavaMethod(declaration, targetClass) @@ -774,16 +776,25 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { method } + is JetProperty -> { + if (targetClass.isInterface()) return false + createJavaField(declaration, targetClass) + } else -> return false } declaration.delete() - JavaCodeStyleManager.getInstance(project).shortenClassReferences(newJavaMethod); + JavaCodeStyleManager.getInstance(project).shortenClassReferences(newJavaMember); val descriptor = OpenFileDescriptor(project, targetClass.getContainingFile().getVirtualFile()) - val targetEditor = FileEditorManager.getInstance(project).openTextEditor(descriptor, true) - CreateFromUsageUtils.setupEditor(newJavaMethod, targetEditor) + val targetEditor = FileEditorManager.getInstance(project).openTextEditor(descriptor, true)!! + + when (newJavaMember) { + is PsiMethod -> CreateFromUsageUtils.setupEditor(newJavaMember, targetEditor) + is PsiField -> targetEditor.getCaretModel().moveToOffset(newJavaMember.getTextRange().getEndOffset() - 1) + } + targetEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE) return true } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateCallableFromUsageFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateCallableFromUsageFix.kt index fa1580aa668..6b74bd4c3d6 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateCallableFromUsageFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateCallableFromUsageFix.kt @@ -84,8 +84,15 @@ public class CreateCallableFromUsageFix( val project = file.getProject() val callableBuilder = CallableBuilderConfiguration(callableInfos, element as JetExpression, file, null, isExtension).createBuilder() - val receiverTypeCandidates = callableBuilder.computeTypeCandidates(callableInfo.receiverTypeInfo) - return receiverTypeCandidates.any { getDeclarationIfApplicable(project, it) != null } + val receiverTypeCandidates = callableBuilder.computeTypeCandidates(callableInfos.first().receiverTypeInfo) + val isProperty = callableInfos.any { it.kind == CallableKind.PROPERTY } + return receiverTypeCandidates.any { + val declaration = getDeclarationIfApplicable(project, it) + when { + isProperty && declaration is PsiClass && declaration.isInterface() -> false + else -> declaration != null + } + } } override fun invoke(project: Project, editor: Editor?, file: JetFile?) { diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt index 0ea470323d7..8dda88bff47 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt @@ -81,6 +81,8 @@ import com.intellij.psi.PsiTypeParameterList import com.intellij.refactoring.changeSignature.ChangeSignatureUtil import com.intellij.psi.PsiModifierList import org.jetbrains.kotlin.asJava.LightClassUtil +import com.intellij.psi.PsiField +import com.intellij.util.VisibilityUtil import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind import org.jetbrains.kotlin.resolve.OverridingUtil @@ -488,3 +490,22 @@ public fun createJavaMethod(function: JetNamedFunction, targetClass: PsiClass): return method } + +fun createJavaField(property: JetProperty, targetClass: PsiClass): PsiField { + val template = LightClassUtil.getLightClassPropertyMethods(property).getGetter() + ?: throw AssertionError("Can't generate light method: ${JetPsiUtil.getElementTextWithContext(property)}") + + val factory = PsiElementFactory.SERVICE.getInstance(template.getProject()) + val field = targetClass.add(factory.createField(property.getName(), template.getReturnType())) as PsiField + + with(field.getModifierList()) { + val templateModifiers = template.getModifierList() + setModifierProperty(VisibilityUtil.getVisibilityModifier(templateModifiers), true) + if (!property.isVar()) { + setModifierProperty(PsiModifier.FINAL, true) + } + copyModifierListItems(templateModifiers, this, false) + } + + return field +} diff --git a/idea/testData/quickfix/createFromUsage/createVariable/property/extensionValOnGroovyType.after.kt b/idea/testData/quickfix/createFromUsage/createVariable/property/extensionValOnGroovyType.after.kt new file mode 100644 index 00000000000..7f994d2b30d --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createVariable/property/extensionValOnGroovyType.after.kt @@ -0,0 +1,8 @@ +// "Create extension property 'foo'" "true" +// ERROR: Unresolved reference: foo + +fun test(): String? { + return A().foo +} + +val A.foo: String? diff --git a/idea/testData/quickfix/createFromUsage/createVariable/property/extensionValOnGroovyType.before.Main.kt b/idea/testData/quickfix/createFromUsage/createVariable/property/extensionValOnGroovyType.before.Main.kt new file mode 100644 index 00000000000..0d06c67640b --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createVariable/property/extensionValOnGroovyType.before.Main.kt @@ -0,0 +1,6 @@ +// "Create extension property 'foo'" "true" +// ERROR: Unresolved reference: foo + +fun test(): String? { + return A().foo +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createVariable/property/extensionValOnGroovyType.before.data.Sample.groovy b/idea/testData/quickfix/createFromUsage/createVariable/property/extensionValOnGroovyType.before.data.Sample.groovy new file mode 100644 index 00000000000..1b4568a8f26 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createVariable/property/extensionValOnGroovyType.before.data.Sample.groovy @@ -0,0 +1,3 @@ +class A { + +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createVariable/property/extensionValOnJavaType.after.kt b/idea/testData/quickfix/createFromUsage/createVariable/property/extensionValOnJavaType.after.kt new file mode 100644 index 00000000000..7f994d2b30d --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createVariable/property/extensionValOnJavaType.after.kt @@ -0,0 +1,8 @@ +// "Create extension property 'foo'" "true" +// ERROR: Unresolved reference: foo + +fun test(): String? { + return A().foo +} + +val A.foo: String? diff --git a/idea/testData/quickfix/createFromUsage/createVariable/property/extensionValOnJavaType.before.Main.kt b/idea/testData/quickfix/createFromUsage/createVariable/property/extensionValOnJavaType.before.Main.kt new file mode 100644 index 00000000000..0d06c67640b --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createVariable/property/extensionValOnJavaType.before.Main.kt @@ -0,0 +1,6 @@ +// "Create extension property 'foo'" "true" +// ERROR: Unresolved reference: foo + +fun test(): String? { + return A().foo +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createVariable/property/extensionValOnJavaType.before.data.Sample.java b/idea/testData/quickfix/createFromUsage/createVariable/property/extensionValOnJavaType.before.data.Sample.java new file mode 100644 index 00000000000..1b4568a8f26 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createVariable/property/extensionValOnJavaType.before.data.Sample.java @@ -0,0 +1,3 @@ +class A { + +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createVariable/property/valOnGroovyType.before.Main.kt b/idea/testData/quickfix/createFromUsage/createVariable/property/valOnGroovyType.before.Main.kt new file mode 100644 index 00000000000..4d4a60331df --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createVariable/property/valOnGroovyType.before.Main.kt @@ -0,0 +1,8 @@ +// "Create property 'foo'" "false" +// ACTION: Convert to expression body +// ACTION: Create extension property 'foo' +// ERROR: Unresolved reference: foo + +fun test(): String? { + return A().foo +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createVariable/property/valOnGroovyType.before.data.Sample.groovy b/idea/testData/quickfix/createFromUsage/createVariable/property/valOnGroovyType.before.data.Sample.groovy new file mode 100644 index 00000000000..1b4568a8f26 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createVariable/property/valOnGroovyType.before.data.Sample.groovy @@ -0,0 +1,3 @@ +class A { + +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createVariable/property/valOnJavaInterface.before.Main.kt b/idea/testData/quickfix/createFromUsage/createVariable/property/valOnJavaInterface.before.Main.kt new file mode 100644 index 00000000000..13ebd61d7ef --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createVariable/property/valOnJavaInterface.before.Main.kt @@ -0,0 +1,10 @@ +// "Create property 'foo'" "false" +// ACTION: Convert to expression body +// ACTION: Disable 'Convert to Expression Body' +// ACTION: Edit intention settings +// ACTION: Create extension property 'foo' +// ERROR: Unresolved reference: foo + +fun test(a: A): String? { + return a.foo +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createVariable/property/valOnJavaInterface.before.data.Sample.java b/idea/testData/quickfix/createFromUsage/createVariable/property/valOnJavaInterface.before.data.Sample.java new file mode 100644 index 00000000000..84e5f4303b8 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createVariable/property/valOnJavaInterface.before.data.Sample.java @@ -0,0 +1,3 @@ +interface A { + +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createVariable/property/valOnJavaType.after.data.Sample.java b/idea/testData/quickfix/createFromUsage/createVariable/property/valOnJavaType.after.data.Sample.java new file mode 100644 index 00000000000..4c27f89dccc --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createVariable/property/valOnJavaType.after.data.Sample.java @@ -0,0 +1,7 @@ +import org.jetbrains.annotations.Nullable; + +class A { + + @Nullable + public final String foo; +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createVariable/property/valOnJavaType.after.kt b/idea/testData/quickfix/createFromUsage/createVariable/property/valOnJavaType.after.kt new file mode 100644 index 00000000000..54f47725fa9 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createVariable/property/valOnJavaType.after.kt @@ -0,0 +1,7 @@ +// "Create property 'foo'" "true" +// ERROR: Unresolved reference: foo + +fun test(): String? { + return A().foo +} + diff --git a/idea/testData/quickfix/createFromUsage/createVariable/property/valOnJavaType.before.Main.kt b/idea/testData/quickfix/createFromUsage/createVariable/property/valOnJavaType.before.Main.kt new file mode 100644 index 00000000000..6a82aa30f02 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createVariable/property/valOnJavaType.before.Main.kt @@ -0,0 +1,6 @@ +// "Create property 'foo'" "true" +// ERROR: Unresolved reference: foo + +fun test(): String? { + return A().foo +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createVariable/property/valOnJavaType.before.data.Sample.java b/idea/testData/quickfix/createFromUsage/createVariable/property/valOnJavaType.before.data.Sample.java new file mode 100644 index 00000000000..1b4568a8f26 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createVariable/property/valOnJavaType.before.data.Sample.java @@ -0,0 +1,3 @@ +class A { + +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createVariable/property/varOnJavaType.after.data.Sample.java b/idea/testData/quickfix/createFromUsage/createVariable/property/varOnJavaType.after.data.Sample.java new file mode 100644 index 00000000000..c8c90d54453 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createVariable/property/varOnJavaType.after.data.Sample.java @@ -0,0 +1,7 @@ +import org.jetbrains.annotations.NotNull; + +class A { + + @NotNull + public String foo; +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createVariable/property/varOnJavaType.after.kt b/idea/testData/quickfix/createFromUsage/createVariable/property/varOnJavaType.after.kt new file mode 100644 index 00000000000..2fd5e6c8810 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createVariable/property/varOnJavaType.after.kt @@ -0,0 +1,7 @@ +// "Create property 'foo'" "true" +// ERROR: Unresolved reference: foo + +fun test() { + A().foo = "" +} + diff --git a/idea/testData/quickfix/createFromUsage/createVariable/property/varOnJavaType.before.Main.kt b/idea/testData/quickfix/createFromUsage/createVariable/property/varOnJavaType.before.Main.kt new file mode 100644 index 00000000000..b7106bfabc3 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createVariable/property/varOnJavaType.before.Main.kt @@ -0,0 +1,6 @@ +// "Create property 'foo'" "true" +// ERROR: Unresolved reference: foo + +fun test() { + A().foo = "" +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createVariable/property/varOnJavaType.before.data.Sample.java b/idea/testData/quickfix/createFromUsage/createVariable/property/varOnJavaType.before.data.Sample.java new file mode 100644 index 00000000000..1b4568a8f26 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createVariable/property/varOnJavaType.before.data.Sample.java @@ -0,0 +1,3 @@ +class A { + +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java index 7ccc94b56b4..29a1669be6c 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java @@ -361,7 +361,7 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes @TestMetadata("idea/testData/quickfix/createFromUsage/createVariable") @TestDataPath("$PROJECT_ROOT") - @InnerTestClasses({CreateVariable.Parameter.class}) + @InnerTestClasses({CreateVariable.Parameter.class, CreateVariable.Property.class}) @RunWith(JUnit3RunnerWithInners.class) public static class CreateVariable extends AbstractQuickFixMultiFileTest { public void testAllFilesPresentInCreateVariable() throws Exception { @@ -383,6 +383,50 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes } } + @TestMetadata("idea/testData/quickfix/createFromUsage/createVariable/property") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Property extends AbstractQuickFixMultiFileTest { + public void testAllFilesPresentInProperty() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/createFromUsage/createVariable/property"), Pattern.compile("^(\\w+)\\.before\\.Main\\.kt$"), true); + } + + @TestMetadata("extensionValOnGroovyType.before.Main.kt") + public void testExtensionValOnGroovyType() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/property/extensionValOnGroovyType.before.Main.kt"); + doTestWithExtraFile(fileName); + } + + @TestMetadata("extensionValOnJavaType.before.Main.kt") + public void testExtensionValOnJavaType() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/property/extensionValOnJavaType.before.Main.kt"); + doTestWithExtraFile(fileName); + } + + @TestMetadata("valOnGroovyType.before.Main.kt") + public void testValOnGroovyType() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/property/valOnGroovyType.before.Main.kt"); + doTestWithExtraFile(fileName); + } + + @TestMetadata("valOnJavaInterface.before.Main.kt") + public void testValOnJavaInterface() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/property/valOnJavaInterface.before.Main.kt"); + doTestWithExtraFile(fileName); + } + + @TestMetadata("valOnJavaType.before.Main.kt") + public void testValOnJavaType() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/property/valOnJavaType.before.Main.kt"); + doTestWithExtraFile(fileName); + } + + @TestMetadata("varOnJavaType.before.Main.kt") + public void testVarOnJavaType() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/property/varOnJavaType.before.Main.kt"); + doTestWithExtraFile(fileName); + } + } } }