Create from usage: Support generation of Java fields from usages in Kotlin code

This commit is contained in:
Alexey Sedunov
2015-02-19 13:20:56 +03:00
parent 34d8e1c08c
commit 19fc9adffa
22 changed files with 194 additions and 7 deletions
@@ -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
}
@@ -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
}
@@ -0,0 +1,8 @@
// "Create extension property 'foo'" "true"
// ERROR: Unresolved reference: foo
fun test(): String? {
return A().foo
}
val A.foo: String?
@@ -0,0 +1,6 @@
// "Create extension property 'foo'" "true"
// ERROR: Unresolved reference: foo
fun test(): String? {
return A().<caret>foo
}
@@ -0,0 +1,8 @@
// "Create extension property 'foo'" "true"
// ERROR: Unresolved reference: foo
fun test(): String? {
return A().foo
}
val A.foo: String?
@@ -0,0 +1,6 @@
// "Create extension property 'foo'" "true"
// ERROR: Unresolved reference: foo
fun test(): String? {
return A().<caret>foo
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -0,0 +1,6 @@
// "Create property 'foo'" "true"
// ERROR: Unresolved reference: foo
fun test(): String? {
return A().<caret>foo
}
@@ -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 = ""
}
@@ -0,0 +1,6 @@
// "Create property 'foo'" "true"
// ERROR: Unresolved reference: foo
fun test() {
A().<caret>foo = ""
}
@@ -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);
}
}
}
}