mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-18 09:34:34 +07:00
[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:
+2
-2
@@ -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
|
||||
|
||||
+3
@@ -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>) {
|
||||
|
||||
+18
@@ -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
-2
@@ -1,9 +1,8 @@
|
||||
// "Create property" "true"
|
||||
class Calculator {
|
||||
int i;
|
||||
|
||||
public void printError() {
|
||||
setI(0);
|
||||
setI(0);
|
||||
}
|
||||
|
||||
public void setI(int i) {
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ class Calculator {
|
||||
private Object i;
|
||||
|
||||
{
|
||||
setI(() -> {});
|
||||
setI(() -> {});
|
||||
}
|
||||
|
||||
public void setI(Object i) {
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ class Calculator {
|
||||
private int i;
|
||||
|
||||
public void printError() {
|
||||
setI(0);
|
||||
setI(0);
|
||||
}
|
||||
|
||||
public void setI(int i) {
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// "Create property" "true"
|
||||
class JC {}
|
||||
|
||||
class Main {
|
||||
void usage(JC jc) {
|
||||
jc.<caret>isFoo();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
class Calculator {
|
||||
int i;
|
||||
public void printError() {
|
||||
set<caret>I(0);
|
||||
set<caret>I(0);
|
||||
}
|
||||
public int getI() {return i;}
|
||||
}
|
||||
|
||||
+1
-1
@@ -2,6 +2,6 @@
|
||||
class Calculator {
|
||||
int i;
|
||||
public void printError() {
|
||||
set<caret>I(0);
|
||||
set<caret>I(0);
|
||||
}
|
||||
}
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// "Create property" "false"
|
||||
class JC {
|
||||
void setFoo(boolean a) {}
|
||||
}
|
||||
|
||||
class Main {
|
||||
void usage(JC jc) {
|
||||
jc.<caret>isFoo();
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// "Create property" "false"
|
||||
class JC {}
|
||||
|
||||
class Main {
|
||||
void usage(JC jc) {
|
||||
jc.<caret>isFoo("1");
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// "Create property" "false"
|
||||
class JC {
|
||||
void setFoo(int a) {}
|
||||
}
|
||||
|
||||
class Main {
|
||||
void usage(JC jc) {
|
||||
jc.<caret>getFoo();
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// "Create property" "false"
|
||||
class JC {}
|
||||
|
||||
class Main {
|
||||
void usage(JC jc) {
|
||||
jc.<caret>getFoo("1");
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// "Create property" "false"
|
||||
class JC {}
|
||||
|
||||
class Main {
|
||||
void usage(JC jc) {
|
||||
jc.<caret>setFoo("1", "2");
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// "Create property" "false"
|
||||
class JC {}
|
||||
|
||||
class Main {
|
||||
void usage(JC jc) {
|
||||
jc.<caret>setFoo();
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// "Create property" "false"
|
||||
interface I {}
|
||||
|
||||
class Main {
|
||||
void usage(I i) {
|
||||
i.<caret>setFoo("hello");
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
// "Create property" "true"
|
||||
class Calculator {
|
||||
{
|
||||
set<caret>I(() -> {});
|
||||
set<caret>I(() -> {});
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
// "Create property" "true"
|
||||
class Calculator {
|
||||
public void printError() {
|
||||
set<caret>I(0);
|
||||
set<caret>I(0);
|
||||
}
|
||||
}
|
||||
|
||||
+128
@@ -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();
|
||||
}
|
||||
}
|
||||
'''
|
||||
}
|
||||
}
|
||||
+9
-15
@@ -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>
|
||||
}
|
||||
Reference in New Issue
Block a user