Create from usage: Support generation of Java fields from usages in Kotlin code
This commit is contained in:
+15
-4
@@ -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
|
||||
}
|
||||
|
||||
+9
-2
@@ -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?) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// "Create extension property 'foo'" "true"
|
||||
// ERROR: Unresolved reference: foo
|
||||
|
||||
fun test(): String? {
|
||||
return A().foo
|
||||
}
|
||||
|
||||
val A.foo: String?
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// "Create extension property 'foo'" "true"
|
||||
// ERROR: Unresolved reference: foo
|
||||
|
||||
fun test(): String? {
|
||||
return A().<caret>foo
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
class A {
|
||||
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// "Create extension property 'foo'" "true"
|
||||
// ERROR: Unresolved reference: foo
|
||||
|
||||
fun test(): String? {
|
||||
return A().foo
|
||||
}
|
||||
|
||||
val A.foo: String?
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// "Create extension property 'foo'" "true"
|
||||
// ERROR: Unresolved reference: foo
|
||||
|
||||
fun test(): String? {
|
||||
return A().<caret>foo
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
class A {
|
||||
|
||||
}
|
||||
+8
@@ -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().<caret>foo
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
class A {
|
||||
|
||||
}
|
||||
+10
@@ -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.<caret>foo
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
interface A {
|
||||
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
class A {
|
||||
|
||||
@Nullable
|
||||
public final String foo;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Create property 'foo'" "true"
|
||||
// ERROR: Unresolved reference: foo
|
||||
|
||||
fun test(): String? {
|
||||
return A().foo
|
||||
}
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// "Create property 'foo'" "true"
|
||||
// ERROR: Unresolved reference: foo
|
||||
|
||||
fun test(): String? {
|
||||
return A().<caret>foo
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
class A {
|
||||
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
class A {
|
||||
|
||||
@NotNull
|
||||
public String foo;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Create property 'foo'" "true"
|
||||
// ERROR: Unresolved reference: foo
|
||||
|
||||
fun test() {
|
||||
A().foo = ""
|
||||
}
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// "Create property 'foo'" "true"
|
||||
// ERROR: Unresolved reference: foo
|
||||
|
||||
fun test() {
|
||||
A().<caret>foo = ""
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
class A {
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user