diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java index affe998d4280..af0c10d965c6 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java @@ -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); diff --git a/java/java-analysis-impl/src/com/intellij/lang/jvm/actions/commonActionTypes.kt b/java/java-analysis-impl/src/com/intellij/lang/jvm/actions/commonActionTypes.kt index 7b2056ae5733..e074cf3b142c 100644 --- a/java/java-analysis-impl/src/com/intellij/lang/jvm/actions/commonActionTypes.kt +++ b/java/java-analysis-impl/src/com/intellij/lang/jvm/actions/commonActionTypes.kt @@ -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)) + } +} diff --git a/java/java-analysis-impl/src/messages/QuickFixBundle.properties b/java/java-analysis-impl/src/messages/QuickFixBundle.properties index fb18aaf255d0..119c3069706d 100644 --- a/java/java-analysis-impl/src/messages/QuickFixBundle.properties +++ b/java/java-analysis-impl/src/messages/QuickFixBundle.properties @@ -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 diff --git a/java/java-impl/src/com/intellij/codeInsight/intention/impl/JavaElementActionsFactory.kt b/java/java-impl/src/com/intellij/codeInsight/intention/impl/JavaElementActionsFactory.kt index e9ede5a1f0bf..e423f62c87fa 100644 --- a/java/java-impl/src/com/intellij/codeInsight/intention/impl/JavaElementActionsFactory.kt +++ b/java/java-impl/src/com/intellij/codeInsight/intention/impl/JavaElementActionsFactory.kt @@ -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 } diff --git a/java/java-impl/src/com/intellij/lang/java/actions/CreatePropertyAction.kt b/java/java-impl/src/com/intellij/lang/java/actions/CreatePropertyAction.kt new file mode 100644 index 000000000000..4962b2ae53c9 --- /dev/null +++ b/java/java-impl/src/com/intellij/lang/java/actions/CreatePropertyAction.kt @@ -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 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 { + 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 = 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.setStatic(isStatic: Boolean) = apply { + setModifierProperty(this, PsiModifier.STATIC, isStatic) + } +} diff --git a/java/java-impl/src/com/intellij/lang/java/actions/FieldExpression.kt b/java/java-impl/src/com/intellij/lang/java/actions/FieldExpression.kt new file mode 100644 index 000000000000..8cb02c7b23ec --- /dev/null +++ b/java/java-impl/src/com/intellij/lang/java/actions/FieldExpression.kt @@ -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 { + 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() + 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() + } +} diff --git a/java/java-impl/src/com/intellij/lang/java/actions/RangeExpression.kt b/java/java-impl/src/com/intellij/lang/java/actions/RangeExpression.kt new file mode 100644 index 000000000000..89e4119bd8b2 --- /dev/null +++ b/java/java-impl/src/com/intellij/lang/java/actions/RangeExpression.kt @@ -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 = LookupElement.EMPTY_ARRAY +} diff --git a/java/java-impl/src/com/intellij/lang/java/actions/jvmPsiUtil.kt b/java/java-impl/src/com/intellij/lang/java/actions/jvmPsiUtil.kt index 239a63f8bcab..b3dad806634d 100644 --- a/java/java-impl/src/com/intellij/lang/java/actions/jvmPsiUtil.kt +++ b/java/java-impl/src/com/intellij/lang/java/actions/jvmPsiUtil.kt @@ -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) diff --git a/java/java-impl/src/com/intellij/lang/java/actions/templates.kt b/java/java-impl/src/com/intellij/lang/java/actions/templates.kt index de658cb9d9bf..5ac94445e5ca 100644 --- a/java/java-impl/src/com/intellij/lang/java/actions/templates.kt +++ b/java/java-impl/src/com/intellij/lang/java/actions/templates.kt @@ -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): PsiTypeElement { + return guesser.setupTypeElement(typeElement, types.toTypedArray(), guesserContext, targetClass) } internal fun TemplateContext.setupParameterName(parameter: PsiParameter, names: Array) { diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/afterBooleanPropertyFromGetter.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/afterBooleanPropertyFromGetter.java new file mode 100644 index 000000000000..bf99c535c7a5 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/afterBooleanPropertyFromGetter.java @@ -0,0 +1,18 @@ +// "Create property" "true" +class JC { + private boolean foo; + + public boolean isFoo() { + return foo; + } + + public void setFoo(boolean foo) { + this.foo = foo; + } +} + +class Main { + void usage(JC jc) { + jc.isFoo(); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/afterFieldExist.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/afterFieldExist.java index 4887621e2dd9..802c8f61beb2 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/afterFieldExist.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/afterFieldExist.java @@ -1,9 +1,8 @@ // "Create property" "true" class Calculator { int i; - public void printError() { - setI(0); + setI(0); } public void setI(int i) { diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/afterPassedLambda.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/afterPassedLambda.java index a019ca5a4403..ca51dbd0060a 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/afterPassedLambda.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/afterPassedLambda.java @@ -3,7 +3,7 @@ class Calculator { private Object i; { - setI(() -> {}); + setI(() -> {}); } public void setI(Object i) { diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/afterSimple.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/afterSimple.java index 8ef4b57983d9..2004dc31c3e7 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/afterSimple.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/afterSimple.java @@ -3,7 +3,7 @@ class Calculator { private int i; public void printError() { - setI(0); + setI(0); } public void setI(int i) { diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeBooleanPropertyFromGetter.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeBooleanPropertyFromGetter.java new file mode 100644 index 000000000000..cd4166e4d689 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeBooleanPropertyFromGetter.java @@ -0,0 +1,8 @@ +// "Create property" "true" +class JC {} + +class Main { + void usage(JC jc) { + jc.isFoo(); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeFieldAndGetterExist.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeFieldAndGetterExist.java index 805c1da80b33..ae90e0461ae5 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeFieldAndGetterExist.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeFieldAndGetterExist.java @@ -2,7 +2,7 @@ class Calculator { int i; public void printError() { - setI(0); + setI(0); } public int getI() {return i;} } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeFieldExist.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeFieldExist.java index 1349d9d08394..4d2427e9b954 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeFieldExist.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeFieldExist.java @@ -2,6 +2,6 @@ class Calculator { int i; public void printError() { - setI(0); + setI(0); } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeFromBooleanGetterWhenSetterExists.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeFromBooleanGetterWhenSetterExists.java new file mode 100644 index 000000000000..b858c1337663 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeFromBooleanGetterWhenSetterExists.java @@ -0,0 +1,10 @@ +// "Create property" "false" +class JC { + void setFoo(boolean a) {} +} + +class Main { + void usage(JC jc) { + jc.isFoo(); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeFromBooleanGetterWithArg.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeFromBooleanGetterWithArg.java new file mode 100644 index 000000000000..c9fcb7eed0f5 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeFromBooleanGetterWithArg.java @@ -0,0 +1,8 @@ +// "Create property" "false" +class JC {} + +class Main { + void usage(JC jc) { + jc.isFoo("1"); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeFromGetterWhenSetterExists.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeFromGetterWhenSetterExists.java new file mode 100644 index 000000000000..17b88f74cb17 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeFromGetterWhenSetterExists.java @@ -0,0 +1,10 @@ +// "Create property" "false" +class JC { + void setFoo(int a) {} +} + +class Main { + void usage(JC jc) { + jc.getFoo(); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeFromGetterWithArg.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeFromGetterWithArg.java new file mode 100644 index 000000000000..a81bbaa25005 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeFromGetterWithArg.java @@ -0,0 +1,8 @@ +// "Create property" "false" +class JC {} + +class Main { + void usage(JC jc) { + jc.getFoo("1"); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeFromSetterWithTwoArgs.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeFromSetterWithTwoArgs.java new file mode 100644 index 000000000000..b6a5213dac26 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeFromSetterWithTwoArgs.java @@ -0,0 +1,8 @@ +// "Create property" "false" +class JC {} + +class Main { + void usage(JC jc) { + jc.setFoo("1", "2"); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeFromSetterWithoutArgs.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeFromSetterWithoutArgs.java new file mode 100644 index 000000000000..fb052397694f --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeFromSetterWithoutArgs.java @@ -0,0 +1,8 @@ +// "Create property" "false" +class JC {} + +class Main { + void usage(JC jc) { + jc.setFoo(); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeInInterface.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeInInterface.java new file mode 100644 index 000000000000..8feb13ec3122 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeInInterface.java @@ -0,0 +1,8 @@ +// "Create property" "false" +interface I {} + +class Main { + void usage(I i) { + i.setFoo("hello"); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforePassedLambda.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforePassedLambda.java index 04aae73fe98e..491422e3ba04 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforePassedLambda.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforePassedLambda.java @@ -1,6 +1,6 @@ // "Create property" "true" class Calculator { { - setI(() -> {}); + setI(() -> {}); } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeSimple.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeSimple.java index bb36a18e3514..971552cdfa84 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeSimple.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createPropertyFromUsage/beforeSimple.java @@ -1,6 +1,6 @@ // "Create property" "true" class Calculator { public void printError() { - setI(0); + setI(0); } } diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/CreatePropertyFromUsageTemplateTest.groovy b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/CreatePropertyFromUsageTemplateTest.groovy new file mode 100644 index 000000000000..dc9a2f351a2f --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/CreatePropertyFromUsageTemplateTest.groovy @@ -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 {} +class Main { + java.util.List usage(JC jc) { + return jc.getFoo(); + } +} +''' + setTemplateTesting project, testRootDisposable + myFixture.launchAction(myFixture.findSingleIntention("Create property 'foo' in 'JC'")) + // check initial template + myFixture.checkResult '''\ +import java.util.List; + +class JC { + public List getFoo() { + return foo; + } + + public void setFoo(List foo) { + this.foo = foo; + } +} +class Main { + java.util.List usage(JC 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 { + public Foo<T> getFoo() { + return foo; + } + + public void setFoo(Foo foo) { + this.foo = foo; + } +} +class Main { + java.util.List usage(JC 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 { + public Foo getFoo() { + return bar; + } + + public void setFoo(Foo foo) { + this.bar = foo; + } +} +class Main { + java.util.List usage(JC jc) { + return jc.getFoo(); + } +} +''' + // go to setter parameter name and it to param + myFixture.type "\tparam" + myFixture.checkResult '''\ +import java.util.List; + +class JC { + public Foo getFoo() { + return bar; + } + + public void setFoo(Foo param) { + this.bar = param; + } +} +class Main { + java.util.List usage(JC 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 { + private Foo bar; + + public Foo getFoo() { + return bar; + } + + public void setFoo(Foo param) { + this.bar = param; + } +} +class Main { + java.util.List usage(JC jc) { + return jc.getFoo(); + } +} +''' + } +} diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/CreatePropertyFromUsageTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/CreatePropertyFromUsageTest.java index d5b729fb5757..731fa6405dd7 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/CreatePropertyFromUsageTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/CreatePropertyFromUsageTest.java @@ -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); + } } diff --git a/platform/lang-impl/src/com/intellij/util/KtUtils.kt b/platform/lang-impl/src/com/intellij/util/KtUtils.kt new file mode 100644 index 000000000000..47c3a6bcc0a1 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/util/KtUtils.kt @@ -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 JBPair.component1(): A = first +operator fun JBPair<*, A>.component2(): A = second + +// This function helps to get rid of platform types. +fun JBPair.toNotNull(): JBPair { + requireNotNull(first) + requireNotNull(second) + @Suppress("UNCHECKED_CAST") + return this as JBPair +}