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.openapi.fileEditor.OpenFileDescriptor
|
||||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager
|
import com.intellij.psi.codeStyle.JavaCodeStyleManager
|
||||||
import com.intellij.psi.PsiModifier
|
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.ChainedScope
|
||||||
import org.jetbrains.kotlin.resolve.scopes.WritableScopeImpl
|
import org.jetbrains.kotlin.resolve.scopes.WritableScopeImpl
|
||||||
import org.jetbrains.kotlin.resolve.scopes.RedeclarationHandler
|
import org.jetbrains.kotlin.resolve.scopes.RedeclarationHandler
|
||||||
@@ -764,7 +766,7 @@ class CallableBuilder(val config: CallableBuilderConfiguration) {
|
|||||||
|
|
||||||
val project = declaration.getProject()
|
val project = declaration.getProject()
|
||||||
|
|
||||||
val newJavaMethod: PsiMethod = when (declaration) {
|
val newJavaMember: PsiMember = when (declaration) {
|
||||||
is JetNamedFunction -> {
|
is JetNamedFunction -> {
|
||||||
val method = createJavaMethod(declaration, targetClass)
|
val method = createJavaMethod(declaration, targetClass)
|
||||||
|
|
||||||
@@ -774,16 +776,25 @@ class CallableBuilder(val config: CallableBuilderConfiguration) {
|
|||||||
|
|
||||||
method
|
method
|
||||||
}
|
}
|
||||||
|
is JetProperty -> {
|
||||||
|
if (targetClass.isInterface()) return false
|
||||||
|
createJavaField(declaration, targetClass)
|
||||||
|
}
|
||||||
else -> return false
|
else -> return false
|
||||||
}
|
}
|
||||||
|
|
||||||
declaration.delete()
|
declaration.delete()
|
||||||
|
|
||||||
JavaCodeStyleManager.getInstance(project).shortenClassReferences(newJavaMethod);
|
JavaCodeStyleManager.getInstance(project).shortenClassReferences(newJavaMember);
|
||||||
|
|
||||||
val descriptor = OpenFileDescriptor(project, targetClass.getContainingFile().getVirtualFile())
|
val descriptor = OpenFileDescriptor(project, targetClass.getContainingFile().getVirtualFile())
|
||||||
val targetEditor = FileEditorManager.getInstance(project).openTextEditor(descriptor, true)
|
val targetEditor = FileEditorManager.getInstance(project).openTextEditor(descriptor, true)!!
|
||||||
CreateFromUsageUtils.setupEditor(newJavaMethod, targetEditor)
|
|
||||||
|
when (newJavaMember) {
|
||||||
|
is PsiMethod -> CreateFromUsageUtils.setupEditor(newJavaMember, targetEditor)
|
||||||
|
is PsiField -> targetEditor.getCaretModel().moveToOffset(newJavaMember.getTextRange().getEndOffset() - 1)
|
||||||
|
}
|
||||||
|
targetEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-2
@@ -84,8 +84,15 @@ public class CreateCallableFromUsageFix(
|
|||||||
val project = file.getProject()
|
val project = file.getProject()
|
||||||
val callableBuilder =
|
val callableBuilder =
|
||||||
CallableBuilderConfiguration(callableInfos, element as JetExpression, file, null, isExtension).createBuilder()
|
CallableBuilderConfiguration(callableInfos, element as JetExpression, file, null, isExtension).createBuilder()
|
||||||
val receiverTypeCandidates = callableBuilder.computeTypeCandidates(callableInfo.receiverTypeInfo)
|
val receiverTypeCandidates = callableBuilder.computeTypeCandidates(callableInfos.first().receiverTypeInfo)
|
||||||
return receiverTypeCandidates.any { getDeclarationIfApplicable(project, it) != null }
|
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?) {
|
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.refactoring.changeSignature.ChangeSignatureUtil
|
||||||
import com.intellij.psi.PsiModifierList
|
import com.intellij.psi.PsiModifierList
|
||||||
import org.jetbrains.kotlin.asJava.LightClassUtil
|
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
|
||||||
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind
|
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind
|
||||||
import org.jetbrains.kotlin.resolve.OverridingUtil
|
import org.jetbrains.kotlin.resolve.OverridingUtil
|
||||||
@@ -488,3 +490,22 @@ public fun createJavaMethod(function: JetNamedFunction, targetClass: PsiClass):
|
|||||||
|
|
||||||
return method
|
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")
|
@TestMetadata("idea/testData/quickfix/createFromUsage/createVariable")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@InnerTestClasses({CreateVariable.Parameter.class})
|
@InnerTestClasses({CreateVariable.Parameter.class, CreateVariable.Property.class})
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
public static class CreateVariable extends AbstractQuickFixMultiFileTest {
|
public static class CreateVariable extends AbstractQuickFixMultiFileTest {
|
||||||
public void testAllFilesPresentInCreateVariable() throws Exception {
|
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