[jvm-lang] java: port _Create property from usage_ action

- make it create template for both getter and setter;
- make it work with type parameter guesser;
- add tests!
This commit is contained in:
Daniil Ovchinnikov
2018-02-12 17:45:12 +03:00
parent 64292fd235
commit fbe959bf47
28 changed files with 633 additions and 32 deletions
@@ -1,4 +1,4 @@
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight.daemon.impl.analysis;
import com.intellij.codeInsight.ExceptionUtil;
@@ -498,6 +498,7 @@ public class HighlightMethodUtil {
else {
QuickFixAction.registerQuickFixAction(highlightInfo, range, QUICK_FIX_FACTORY.createCreateMethodFromUsageFix(methodCall));
QuickFixAction.registerQuickFixAction(highlightInfo, range, QUICK_FIX_FACTORY.createCreateAbstractMethodFromUsageFix(methodCall));
QuickFixAction.registerQuickFixAction(highlightInfo, range, QUICK_FIX_FACTORY.createCreateGetterSetterPropertyFromUsageFix(methodCall));
}
}
@@ -849,7 +850,6 @@ public class HighlightMethodUtil {
QuickFixAction.registerQuickFixAction(highlightInfo, fixRange, QUICK_FIX_FACTORY.createCreateConstructorFromSuperFix(methodCall));
QuickFixAction.registerQuickFixAction(highlightInfo, fixRange, QUICK_FIX_FACTORY.createCreateConstructorFromThisFix(methodCall));
QuickFixAction.registerQuickFixAction(highlightInfo, fixRange, QUICK_FIX_FACTORY.createCreatePropertyFromUsageFix(methodCall));
QuickFixAction.registerQuickFixAction(highlightInfo, fixRange, QUICK_FIX_FACTORY.createCreateGetterSetterPropertyFromUsageFix(methodCall));
CandidateInfo[] methodCandidates = resolveHelper.getReferencedMethodCandidates(methodCall, false);
CastMethodArgumentFix.REGISTRAR.registerCastActions(methodCandidates, methodCall, highlightInfo, fixRange);
PermuteArgumentsFix.registerFix(highlightInfo, methodCall, methodCandidates, fixRange);
@@ -32,3 +32,9 @@ object CreateEnumConstantActionGroup : JvmActionGroup {
return message("create.enum.constant.from.usage.text", requireNotNull(data?.entityName))
}
}
object CreatePropertyActionGroup : JvmActionGroup {
override fun getDisplayText(data: JvmActionGroup.RenderData?): String {
return message("create.property.from.usage.text", requireNotNull(data?.entityName))
}
}
@@ -101,6 +101,8 @@ create.abstract.method.from.usage.full.text=Create abstract method ''{0}'' in ''
create.parameter.from.usage.family=Create parameter from Usage
create.parameter.from.usage.text=Create parameter ''{0}''
create.property.from.usage.family=Create property From Usage
create.property.from.usage.text=Create property ''{0}''
create.property.from.usage.full.text=Create property ''{0}'' in ''{1}''
create.getter=Create Getter
create.setter=Create Setter
defer.final.assignment.with.temp.family=Defer final assignment with temp
@@ -85,6 +85,9 @@ class JavaElementActionsFactory(private val renderer: JavaElementRenderer) : Jvm
if (!staticMethodRequested && javaClass.hasModifierProperty(PsiModifier.ABSTRACT) && !javaClass.isInterface) {
result += CreateMethodAction(javaClass, request, true)
}
if (!javaClass.isInterface) {
result += CreatePropertyAction(javaClass, request)
}
return result
}
@@ -0,0 +1,269 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.lang.java.actions
import com.intellij.codeInsight.CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement
import com.intellij.codeInsight.ExpectedTypeInfo
import com.intellij.codeInsight.daemon.QuickFixBundle.message
import com.intellij.codeInsight.daemon.impl.quickfix.CreateFromUsageBaseFix.positionCursor
import com.intellij.codeInsight.daemon.impl.quickfix.CreateFromUsageUtils.ParameterNameExpression
import com.intellij.codeInsight.daemon.impl.quickfix.GuessTypeParameters
import com.intellij.codeInsight.generation.GenerateMembersUtil.generateSimpleGetterPrototype
import com.intellij.codeInsight.generation.GenerateMembersUtil.generateSimpleSetterPrototype
import com.intellij.codeInsight.template.*
import com.intellij.codeInsight.template.impl.TemplateState
import com.intellij.codeInsight.template.impl.VariableNode
import com.intellij.lang.java.beans.PropertyKind
import com.intellij.lang.java.beans.PropertyKind.*
import com.intellij.lang.java.request.CreateMethodFromJavaUsageRequest
import com.intellij.lang.jvm.JvmModifier
import com.intellij.lang.jvm.actions.CreateMethodRequest
import com.intellij.lang.jvm.actions.CreatePropertyActionGroup
import com.intellij.lang.jvm.actions.JvmActionGroup
import com.intellij.lang.jvm.actions.JvmGroupIntentionAction
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.*
import com.intellij.psi.codeStyle.JavaCodeStyleManager
import com.intellij.psi.codeStyle.VariableKind
import com.intellij.psi.presentation.java.ClassPresentationUtil.getNameForClass
import com.intellij.psi.util.PropertyUtilBase.getAccessorName
import com.intellij.psi.util.PropertyUtilBase.getPropertyNameAndKind
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.PsiUtil.setModifierProperty
import com.intellij.util.component1
import com.intellij.util.component2
import com.intellij.util.toNotNull
/**
* This action renders a property (field + getter + setter) in Java class when getter or a setter is requested.
*/
internal class CreatePropertyAction(
target: PsiClass,
override val request: CreateMethodRequest
) : CreateMemberAction(target, request), JvmGroupIntentionAction {
companion object {
private const val FIELD_VARIABLE = "FIELD_NAME_VARIABLE"
private const val SETTER_PARAM_NAME = "SETTER_PARAM_NAME"
}
override fun getActionGroup(): JvmActionGroup = CreatePropertyActionGroup
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile?): Boolean {
if (!super.isAvailable(project, editor, file)) return false
val accessorName = request.methodName
if (!PsiNameHelper.getInstance(project).isIdentifier(accessorName)) return false
val (propertyName: String, propertyKind: PropertyKind) = doGetPropertyInfo() ?: return false
if (propertyName == null || propertyName.isEmpty() || propertyKind == null) return false
// check parameters count
when (propertyKind) {
GETTER, BOOLEAN_GETTER -> if (request.parameters.isNotEmpty()) return false
SETTER -> if (request.parameters.size != 1) return false
}
if (target.findMethodsByName(accessorName, false).isNotEmpty()) return false
val counterPart = when (propertyKind) {
GETTER, BOOLEAN_GETTER -> SETTER
SETTER -> {
val expectedType = request.parameters.single().second.singleOrNull()
if (expectedType != null && PsiType.BOOLEAN == JvmPsiConversionHelper.getInstance(project).convertType(expectedType.theType)) {
BOOLEAN_GETTER
}
else {
GETTER
}
}
}
return target.findMethodsByName(getAccessorName(propertyName, counterPart), false).isEmpty()
}
private fun doGetPropertyInfo() = getPropertyNameAndKind(request.methodName)
private val propertyInfo: com.intellij.openapi.util.Pair<String, PropertyKind> get() = requireNotNull(doGetPropertyInfo()).toNotNull()
override fun getFamilyName(): String = message("create.property.from.usage.family")
override fun getRenderData() = JvmActionGroup.RenderData { propertyInfo.first }
override fun getText(): String = message("create.property.from.usage.full.text", propertyInfo.first, getNameForClass(target, false))
override fun invoke(project: Project, editor: Editor?, file: PsiFile?) {
val factory = JavaPsiFacade.getInstance(project).elementFactory
val codeStyleManager = JavaCodeStyleManager.getInstance(project)!!
val (propertyName, propertyKind) = propertyInfo
val target = target
val isStatic = JvmModifier.STATIC in request.modifiers
val suggestedFieldName = run {
val kind = if (isStatic) VariableKind.STATIC_FIELD else VariableKind.FIELD
codeStyleManager.propertyNameToVariableName(propertyName, kind)
}
fun insertPrototypes(): Pair<PsiMethod, PsiMethod> {
val prototypeType = if (propertyKind == BOOLEAN_GETTER) PsiType.BOOLEAN else PsiType.VOID
val field = factory.createField(suggestedFieldName, prototypeType).setStatic(isStatic)
val getterPrototype = generateSimpleGetterPrototype(field)
val setterPrototype = generateSimpleSetterPrototype(field, target)
return if (propertyKind == SETTER) {
// Technology isn't there yet. See related: WEB-26575.
// We can't recalculate template segments which start before the current segment,
// so we add the setter before the getter.
val setter = forcePsiPostprocessAndRestoreElement(target.add(setterPrototype)) as PsiMethod
val getter = forcePsiPostprocessAndRestoreElement(target.add(getterPrototype)) as PsiMethod
getter to setter
}
else {
val getter = forcePsiPostprocessAndRestoreElement(target.add(getterPrototype)) as PsiMethod
val setter = forcePsiPostprocessAndRestoreElement(target.add(setterPrototype)) as PsiMethod
getter to setter
}
}
val (getter, setter) = insertPrototypes()
val getterBody = requireNotNull(getter.body) { getter.text }
val getterFieldRefElement = run {
val getterReturnStatement = getterBody.statements.single() as PsiReturnStatement
val getterReference = getterReturnStatement.returnValue as PsiReferenceExpression
requireNotNull(getterReference.referenceNameElement) { getter.text }
}
val getterTypeElement = requireNotNull(getter.returnTypeElement) { getter.text }
val setterBody = requireNotNull(setter.body) { setter.text }
val setterAssignment = run {
val setterAssignmentStatement = setterBody.statements.singleOrNull() as? PsiExpressionStatement
requireNotNull(setterAssignmentStatement?.expression as? PsiAssignmentExpression) { setter.text }
}
val setterFieldRefElement = run {
val setterReference = setterAssignment.lExpression as PsiReferenceExpression
requireNotNull(setterReference.referenceNameElement) { setter.text }
}
val setterTypeElement = requireNotNull(setter.parameterList.parameters.single().typeElement) { setter.text }
val setterParamNameElement = requireNotNull(setter.parameterList.parameters.single().nameIdentifier) { setter.text }
val setterParamRefElement = requireNotNull(setterAssignment.rExpression) { setter.text }
val expectedTypes: List<ExpectedTypeInfo> = when (propertyKind) {
PropertyKind.GETTER -> extractExpectedTypes(project, request.returnType)
PropertyKind.BOOLEAN_GETTER -> listOf(PsiType.BOOLEAN.toExpectedType())
PropertyKind.SETTER -> extractExpectedTypes(project, request.parameters.single().second)
}
fun TemplateBuilder.createTemplateContext(): TemplateContext {
val substitutor = request.targetSubstitutor.toPsiSubstitutor(project)
val guesserContext = (request as? CreateMethodFromJavaUsageRequest)?.context
val guesser = GuessTypeParameters(project, factory, this, substitutor)
return TemplateContext(project, factory, target, this, guesser, guesserContext)
}
val targetFile = target.containingFile
val targetDocument = requireNotNull(targetFile.viewProvider.document)
val targetEditor = positionCursor(project, targetFile, target) ?: return
/**
* Given user want to create a property from a getter reference, such as getFoo.
* In this case we insert both dummy getter and dummy setter.
*
* 1. We add input window on the getter name element, and copy its contents to setter name element.
* This is done via VariableNode.
*
* 2. We add input window on the getter type element, and copy its contents to the setter type element.
* Problem is that input type element could contain multiple input windows within it,
* so we have to track the whole type element range to copy it,
* there is no way to do it via TemplateBuilder, so we track it via RangeExpression.
*
* 3. Setter parameter name template is added in any case.
*/
fun TemplateBuilderImpl.setupTemplate(
inputTypeElement: PsiTypeElement,
inputNameElement: PsiElement,
mirrorTypeElement: PsiTypeElement,
mirrorNameElement: PsiElement,
endElement: PsiElement?,
context: PsiMethod
): RangeExpression {
val templateTypeElement = createTemplateContext().setupTypeElement(inputTypeElement, expectedTypes)
val typeExpression = RangeExpression(targetDocument, templateTypeElement.textRange)
replaceElement(mirrorTypeElement, typeExpression, false) // copy type text to mirror
val fieldExpression = FieldExpression(project, target, context, suggestedFieldName) { typeExpression.text }
replaceElement(inputNameElement, FIELD_VARIABLE, fieldExpression, true)
replaceElement(mirrorNameElement, VariableNode(FIELD_VARIABLE, null), false) // copy field name to mirror
val setterParameterExpression = ParameterNameExpression(
codeStyleManager.suggestVariableName(VariableKind.PARAMETER, propertyName, null, null).names
)
replaceElement(setterParamNameElement, SETTER_PARAM_NAME, setterParameterExpression, true)
replaceElement(setterParamRefElement, VariableNode(SETTER_PARAM_NAME, null), false) // copy setter parameter name to mirror
endElement?.let(::setEndVariableAfter)
return typeExpression
}
val builder = TemplateBuilderImpl(target)
val typeExpression = if (propertyKind == SETTER) {
builder.setupTemplate(
inputTypeElement = setterTypeElement,
inputNameElement = setterFieldRefElement,
mirrorTypeElement = getterTypeElement,
mirrorNameElement = getterFieldRefElement,
endElement = setterBody.lBrace,
context = setter
)
}
else {
builder.setupTemplate(
inputTypeElement = getterTypeElement,
inputNameElement = getterFieldRefElement,
mirrorTypeElement = setterTypeElement,
mirrorNameElement = setterFieldRefElement,
endElement = getterBody.lBrace,
context = getter
)
}
val template = builder.buildInlineTemplate().apply {
isToShortenLongNames = true
}
val listener = object : TemplateEditingAdapter() {
override fun beforeTemplateFinished(state: TemplateState, template: Template, brokenOff: Boolean) {
if (brokenOff) return
CommandProcessor.getInstance().runUndoTransparentAction {
runWriteAction {
insertMissingField(state)
}
}
}
fun insertMissingField(state: TemplateState) {
val userFieldName = state.getVariableValue(FIELD_VARIABLE)?.text ?: return
if (!PsiNameHelper.getInstance(project).isIdentifier(userFieldName)) return
val element = targetFile.findElementAt(state.editor.caretModel.offset)
val aClass = PsiTreeUtil.getParentOfType(element, PsiClass::class.java) ?: return
if (aClass.findFieldByName(userFieldName, false) != null) return
// we want to create a field if there is no field with the name entered by the user
val userType = factory.createTypeFromText(typeExpression.text, aClass)
val userField = factory.createField(userFieldName, userType).setStatic(isStatic)
aClass.add(userField)
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(state.editor.document)
}
}
TemplateManager.getInstance(project).startTemplate(targetEditor, template, listener)
}
private fun <T : PsiModifierListOwner> T.setStatic(isStatic: Boolean) = apply {
setModifierProperty(this, PsiModifier.STATIC, isStatic)
}
}
@@ -0,0 +1,58 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.lang.java.actions
import com.intellij.codeInsight.completion.JavaLookupElementBuilder
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.codeInsight.template.Expression
import com.intellij.codeInsight.template.ExpressionContext
import com.intellij.codeInsight.template.Result
import com.intellij.codeInsight.template.TextResult
import com.intellij.icons.AllIcons
import com.intellij.openapi.project.Project
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.intellij.psi.util.createSmartPointer
import com.intellij.ui.LayeredIcon
import javax.swing.Icon
internal class FieldExpression(
project: Project,
target: PsiClass,
typeContext: PsiElement,
private val fieldName: String,
private val typeText: () -> String
) : Expression() {
companion object {
private val newFieldIcon: Icon = LayeredIcon.create(AllIcons.Nodes.Field, AllIcons.Actions.New)
}
private val myClassPointer = target.createSmartPointer(project)
private val myTypeContextPointer = typeContext.createSmartPointer(project)
private val myFactory = JavaPsiFacade.getElementFactory(project)
override fun calculateResult(context: ExpressionContext): Result? = TextResult(fieldName)
override fun calculateQuickResult(context: ExpressionContext): Result? = calculateResult(context)
override fun calculateLookupItems(context: ExpressionContext): Array<LookupElement> {
val psiClass = myClassPointer.element ?: return LookupElement.EMPTY_ARRAY
val typeContext = myTypeContextPointer.element ?: return LookupElement.EMPTY_ARRAY
val userType = myFactory.createTypeFromText(typeText(), typeContext)
val result = LinkedHashSet<LookupElement>()
if (psiClass.findFieldByName(fieldName, false) == null) {
result += LookupElementBuilder.create(fieldName).withIcon(newFieldIcon).withTypeText(userType.presentableText)
}
for (field in psiClass.fields) {
val fieldType = field.type
if (userType == fieldType) {
result += JavaLookupElementBuilder.forField(field).withTypeText(fieldType.presentableText)
}
}
return if (result.size < 2) LookupElement.EMPTY_ARRAY else result.toTypedArray()
}
}
@@ -0,0 +1,33 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.lang.java.actions
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.template.Expression
import com.intellij.codeInsight.template.ExpressionContext
import com.intellij.codeInsight.template.Result
import com.intellij.codeInsight.template.TextResult
import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.RangeMarker
import com.intellij.openapi.util.TextRange
/**
* This expression copies text from the specified range.
*/
internal class RangeExpression(
private val document: Document,
range: TextRange
) : Expression() {
private val marker: RangeMarker = document.createRangeMarker(range).also {
it.isGreedyToLeft = true
it.isGreedyToRight = true
}
val text: String get() = document.getText(TextRange.create(marker))
override fun calculateResult(context: ExpressionContext): Result? = TextResult(text)
override fun calculateQuickResult(context: ExpressionContext): Result? = calculateResult(context)
override fun calculateLookupItems(context: ExpressionContext): Array<out LookupElement> = LookupElement.EMPTY_ARRAY
}
@@ -1,8 +1,8 @@
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.lang.java.actions
import com.intellij.codeInsight.ExpectedTypeInfo
import com.intellij.codeInsight.ExpectedTypesProvider
import com.intellij.codeInsight.ExpectedTypesProvider.createInfo
import com.intellij.codeInsight.TailType
import com.intellij.lang.java.JavaLanguage
import com.intellij.lang.java.request.ExpectedJavaType
@@ -64,7 +64,7 @@ private fun toExpectedTypeInfo(project: Project, expectedType: ExpectedType): Ex
if (expectedType is ExpectedJavaType) return expectedType.info
val helper = JvmPsiConversionHelper.getInstance(project)
val psiType = helper.convertType(expectedType.theType) ?: return null
return ExpectedTypesProvider.createInfo(psiType, expectedType.theKind.infoKind(), psiType, TailType.NONE)
return createInfo(psiType, expectedType.theKind.infoKind(), psiType, TailType.NONE)
}
@ExpectedTypeInfo.Type
@@ -84,3 +84,5 @@ internal inline fun extractNames(suggestedNames: SuggestedNameInfo?, defaultName
val names = (suggestedNames ?: SuggestedNameInfo.NULL_INFO).names
return if (names.isEmpty()) arrayOf(defaultName()) else names
}
internal fun PsiType.toExpectedType() = createInfo(this, ExpectedTypeInfo.TYPE_STRICTLY, this, TailType.NONE)
@@ -1,6 +1,7 @@
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.lang.java.actions
import com.intellij.codeInsight.ExpectedTypeInfo
import com.intellij.codeInsight.daemon.impl.quickfix.CreateFromUsageUtils
import com.intellij.codeInsight.daemon.impl.quickfix.GuessTypeParameters
import com.intellij.codeInsight.template.TemplateBuilder
@@ -44,9 +45,12 @@ internal fun TemplateContext.setupParameters(method: PsiMethod, parameters: Expe
}
internal fun TemplateContext.setupTypeElement(typeElement: PsiTypeElement?, types: ExpectedTypes) {
typeElement ?: return
val expectedTypes = extractExpectedTypes(project, types).toTypedArray()
guesser.setupTypeElement(typeElement, expectedTypes, guesserContext, targetClass)
setupTypeElement(typeElement ?: return, extractExpectedTypes(project, types))
}
@JvmName("setupTypeElementJ")
internal fun TemplateContext.setupTypeElement(typeElement: PsiTypeElement, types: List<ExpectedTypeInfo>): PsiTypeElement {
return guesser.setupTypeElement(typeElement, types.toTypedArray(), guesserContext, targetClass)
}
internal fun TemplateContext.setupParameterName(parameter: PsiParameter, names: Array<out String>) {
@@ -0,0 +1,18 @@
// "Create property" "true"
class JC {
private boolean foo;
public boolean isFoo() {<caret>
return foo;
}
public void setFoo(boolean foo) {
this.foo = foo;
}
}
class Main {
void usage(JC jc) {
jc.isFoo();
}
}
@@ -1,9 +1,8 @@
// "Create property" "true"
class Calculator {
int i;
public void printError() {
setI(0);
setI(0);
}
public void setI(int i) {
@@ -3,7 +3,7 @@ class Calculator {
private Object i;
{
setI(() -> {});
setI(() -> {});
}
public void setI(Object i) {
@@ -3,7 +3,7 @@ class Calculator {
private int i;
public void printError() {
setI(0);
setI(0);
}
public void setI(int i) {
@@ -0,0 +1,8 @@
// "Create property" "true"
class JC {}
class Main {
void usage(JC jc) {
jc.<caret>isFoo();
}
}
@@ -2,7 +2,7 @@
class Calculator {
int i;
public void printError() {
set<caret>I(0);
set<caret>I(0);
}
public int getI() {return i;}
}
@@ -2,6 +2,6 @@
class Calculator {
int i;
public void printError() {
set<caret>I(0);
set<caret>I(0);
}
}
@@ -0,0 +1,10 @@
// "Create property" "false"
class JC {
void setFoo(boolean a) {}
}
class Main {
void usage(JC jc) {
jc.<caret>isFoo();
}
}
@@ -0,0 +1,8 @@
// "Create property" "false"
class JC {}
class Main {
void usage(JC jc) {
jc.<caret>isFoo("1");
}
}
@@ -0,0 +1,10 @@
// "Create property" "false"
class JC {
void setFoo(int a) {}
}
class Main {
void usage(JC jc) {
jc.<caret>getFoo();
}
}
@@ -0,0 +1,8 @@
// "Create property" "false"
class JC {}
class Main {
void usage(JC jc) {
jc.<caret>getFoo("1");
}
}
@@ -0,0 +1,8 @@
// "Create property" "false"
class JC {}
class Main {
void usage(JC jc) {
jc.<caret>setFoo("1", "2");
}
}
@@ -0,0 +1,8 @@
// "Create property" "false"
class JC {}
class Main {
void usage(JC jc) {
jc.<caret>setFoo();
}
}
@@ -0,0 +1,8 @@
// "Create property" "false"
interface I {}
class Main {
void usage(I i) {
i.<caret>setFoo("hello");
}
}
@@ -1,6 +1,6 @@
// "Create property" "true"
class Calculator {
{
set<caret>I(() -> {});
set<caret>I(() -> {});
}
}
@@ -1,6 +1,6 @@
// "Create property" "true"
class Calculator {
public void printError() {
set<caret>I(0);
set<caret>I(0);
}
}
@@ -0,0 +1,128 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.java.codeInsight.daemon.quickFix
import com.intellij.codeInsight.template.impl.TemplateManagerImpl
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
import groovy.transform.CompileStatic
import static com.intellij.codeInsight.template.impl.TemplateManagerImpl.setTemplateTesting
@CompileStatic
class CreatePropertyFromUsageTemplateTest extends LightCodeInsightFixtureTestCase {
void 'test template from getter'() {
myFixture.configureByText "_.java", '''\
class JC<T> {}
class Main {
java.util.List<String> usage(JC<String> jc) {
return jc.<caret>getFoo();
}
}
'''
setTemplateTesting project, testRootDisposable
myFixture.launchAction(myFixture.findSingleIntention("Create property 'foo' in 'JC'"))
// check initial template
myFixture.checkResult '''\
import java.util.List;
class JC<T> {
public <selection>List</selection><T> getFoo() {
return foo;
}
public void setFoo(List<T> foo) {
this.foo = foo;
}
}
class Main {
java.util.List<String> usage(JC<String> jc) {
return jc.getFoo();
}
}
'''
// type into getter type template window
myFixture.type "Foo\t"
// check that setter type changes too
myFixture.checkResult '''\
import java.util.List;
class JC<T> {
public Foo<<selection>T</selection>> getFoo() {
return foo;
}
public void setFoo(Foo<T> foo) {
this.foo = foo;
}
}
class Main {
java.util.List<String> usage(JC<String> jc) {
return jc.getFoo();
}
}
'''
// go to getter name reference and change it to bar
myFixture.type "\tbar"
// check that setter name reference changes too
myFixture.checkResult '''\
import java.util.List;
class JC<T> {
public Foo<T> getFoo() {
return bar<caret>;
}
public void setFoo(Foo<T> foo) {
this.bar = foo;
}
}
class Main {
java.util.List<String> usage(JC<String> jc) {
return jc.getFoo();
}
}
'''
// go to setter parameter name and it to param
myFixture.type "\tparam"
myFixture.checkResult '''\
import java.util.List;
class JC<T> {
public Foo<T> getFoo() {
return bar;
}
public void setFoo(Foo<T> param<caret>) {
this.bar = param;
}
}
class Main {
java.util.List<String> usage(JC<String> jc) {
return jc.getFoo();
}
}
'''
TemplateManagerImpl.getTemplateState(editor).gotoEnd(false)
// check that new field is created with user-defined type and name
myFixture.checkResult '''\
import java.util.List;
class JC<T> {
private Foo<T> bar;
public Foo<T> getFoo() {<caret>
return bar;
}
public void setFoo(Foo<T> param) {
this.bar = param;
}
}
class Main {
java.util.List<String> usage(JC<String> jc) {
return jc.getFoo();
}
}
'''
}
}
@@ -1,22 +1,11 @@
/*
* Copyright 2000-2017 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.
*/
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.java.codeInsight.daemon.quickFix;
import com.intellij.codeInsight.daemon.quickFix.ActionHint;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.NotNull;
public class CreatePropertyFromUsageTest extends LightQuickFixParameterizedTestCase {
public void test() { doAllTests(); }
@@ -30,4 +19,9 @@ public class CreatePropertyFromUsageTest extends LightQuickFixParameterizedTestC
protected LanguageLevel getLanguageLevel() {
return LanguageLevel.JDK_1_8;
}
@Override
protected ActionHint parseActionHintImpl(@NotNull PsiFile file, @NotNull String contents) {
return ActionHint.parse(file, contents, false);
}
}
@@ -0,0 +1,17 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
@file:JvmName("KotlinUtils")
package com.intellij.util
import com.intellij.openapi.util.Pair as JBPair
operator fun <A> JBPair<A, *>.component1(): A = first
operator fun <A> JBPair<*, A>.component2(): A = second
// This function helps to get rid of platform types.
fun <A : Any, B : Any> JBPair<A?, B?>.toNotNull(): JBPair<A, B> {
requireNotNull(first)
requireNotNull(second)
@Suppress("UNCHECKED_CAST")
return this as JBPair<A, B>
}