JvmCommonIntentionActionsFactory switched from UAST to @JvmCommon-annotated API

This commit is contained in:
Nicolay Mitropolsky
2017-07-05 19:37:23 +03:00
parent 8e69e8748e
commit cfeb6c005e
5 changed files with 142 additions and 56 deletions
@@ -17,13 +17,13 @@ package com.intellij.codeInsight.intention
import com.intellij.lang.Language
import com.intellij.lang.LanguageExtension
import com.intellij.psi.PsiModifier
import com.intellij.psi.PsiType
import com.intellij.psi.PsiTypeParameter
import com.intellij.openapi.components.ServiceManager
import com.intellij.psi.*
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.uast.UClass
import org.jetbrains.uast.UDeclaration
import org.jetbrains.uast.UParameter
import org.jetbrains.uast.UElement
import org.jetbrains.uast.UastContext
/**
* Extension Point provides language-abstracted code modifications for JVM-based languages.
@@ -38,18 +38,22 @@ import org.jetbrains.uast.UParameter
@ApiStatus.Experimental
abstract class JvmCommonIntentionActionsFactory {
open fun createChangeModifierAction(declaration: UDeclaration,
open fun createChangeModifierAction(declaration: @JvmCommon PsiModifierListOwner,
@PsiModifier.ModifierConstant modifier: String,
shouldPresent: Boolean): IntentionAction? = null
shouldPresent: Boolean): IntentionAction? =
//Fallback if Uast-version of method is overridden
createChangeModifierAction(declaration.asUast<UDeclaration>(), modifier, shouldPresent)
open fun createAddCallableMemberActions(info: MethodInsertionInfo): List<IntentionAction> = emptyList()
open fun createAddBeanPropertyActions(uClass: UClass,
open fun createAddBeanPropertyActions(psiClass: @JvmCommon PsiClass,
propertyName: String,
@PsiModifier.ModifierConstant visibilityModifier: String,
propertyType: PsiType,
setterRequired: Boolean,
getterRequired: Boolean): List<IntentionAction> = emptyList()
getterRequired: Boolean): List<IntentionAction> =
//Fallback if Uast-version of method is overridden
createAddBeanPropertyActions(psiClass.asUast<UClass>(), propertyName, visibilityModifier, propertyType, setterRequired, getterRequired)
companion object : LanguageExtension<JvmCommonIntentionActionsFactory>(
"com.intellij.codeInsight.intention.jvmCommonIntentionActionsFactory") {
@@ -58,49 +62,78 @@ abstract class JvmCommonIntentionActionsFactory {
override fun forLanguage(l: Language): JvmCommonIntentionActionsFactory? = super.forLanguage(l)
}
//A fallback to old api
@Deprecated("use or/and override @JvmCommon-version of this method instead")
open fun createChangeModifierAction(declaration: UDeclaration,
@PsiModifier.ModifierConstant modifier: String,
shouldPresent: Boolean): IntentionAction? = null
@Deprecated("use or/and override @JvmCommon-version of this method instead")
open fun createAddBeanPropertyActions(uClass: UClass,
propertyName: String,
@PsiModifier.ModifierConstant visibilityModifier: String,
propertyType: PsiType,
setterRequired: Boolean,
getterRequired: Boolean): List<IntentionAction> = emptyList()
}
@ApiStatus.Experimental
sealed class MethodInsertionInfo(
val containingClass: UClass,
val targetClass: @JvmCommon PsiClass,
@PsiModifier.ModifierConstant
val modifiers: List<String> = emptyList(),
val typeParams: List<PsiTypeParameter> = emptyList(),
val parameters: List<UParameter> = emptyList()
val parameters: List<@JvmCommon PsiParameter> = emptyList()
) {
@Deprecated("use `targetClass`", ReplaceWith("targetClass"))
val containingClass: UClass
get() = targetClass.asUast<UClass>()
companion object {
@JvmStatic
fun constructorInfo(containingClass: UClass, parameters: List<UParameter>) =
Constructor(containingClass = containingClass, parameters = parameters)
fun constructorInfo(targetClass: @JvmCommon PsiClass, parameters: List<@JvmCommon PsiParameter>) =
Constructor(targetClass = targetClass, parameters = parameters)
@JvmStatic
fun simpleMethodInfo(containingClass: UClass,
fun simpleMethodInfo(containingClass: @JvmCommon PsiClass,
methodName: String,
@PsiModifier.ModifierConstant modifier: String,
returnType: PsiType,
parameters: List<UParameter>) =
parameters: List<@JvmCommon PsiParameter>) =
Method(name = methodName,
modifiers = listOf(modifier),
containingClass = containingClass,
targetClass = containingClass,
returnType = returnType,
parameters = parameters)
}
class Method(
containingClass: UClass,
targetClass: @JvmCommon PsiClass,
val name: String,
modifiers: List<String> = emptyList(),
typeParams: List<PsiTypeParameter> = emptyList(),
typeParams: List<@JvmCommon PsiTypeParameter> = emptyList(),
val returnType: PsiType,
parameters: List<UParameter> = emptyList(),
parameters: List<@JvmCommon PsiParameter> = emptyList(),
val isAbstract: Boolean = false
) : MethodInsertionInfo(containingClass, modifiers, typeParams, parameters)
) : MethodInsertionInfo(targetClass, modifiers, typeParams, parameters)
class Constructor(
containingClass: UClass,
targetClass: @JvmCommon PsiClass,
modifiers: List<String> = emptyList(),
typeParams: List<PsiTypeParameter> = emptyList(),
parameters: List<UParameter> = emptyList()
) : MethodInsertionInfo(containingClass, modifiers, typeParams, parameters)
typeParams: List<@JvmCommon PsiTypeParameter> = emptyList(),
parameters: List<@JvmCommon PsiParameter> = emptyList()
) : MethodInsertionInfo(targetClass, modifiers, typeParams, parameters)
}
@Deprecated("remove after kotlin plugin will be ported")
private inline fun <reified T : UElement> PsiElement.asUast(): T = when (this) {
is T -> this
else -> this.let { ServiceManager.getService(project, UastContext::class.java).convertElement(this, null, T::class.java) as T? }
?: throw UnsupportedOperationException("cant convert $this to ${T::class}")
}
@@ -17,8 +17,8 @@ package com.intellij.codeInspection.inheritance
import com.intellij.CommonBundle
import com.intellij.codeInsight.daemon.QuickFixBundle
import com.intellij.codeInsight.intention.JvmCommonIntentionActionsFactory
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.codeInsight.intention.JvmCommonIntentionActionsFactory
import com.intellij.codeInspection.*
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger
@@ -185,7 +185,8 @@ class ImplicitSubclassInspection : AbstractBaseUastLocalInspectionTool() {
shouldPresent: Boolean,
actionsList: SmartList<IntentionAction>) {
if (declaration.modifierList?.hasModifierProperty(name) != shouldPresent) {
(actionsFactory.createChangeModifierAction(declaration, name, shouldPresent))?.let {
(actionsFactory.createChangeModifierAction(declaration as @com.intellij.psi.JvmCommon PsiModifierListOwner, name,
shouldPresent))?.let {
actionsList.add(it)
}
}
@@ -22,46 +22,47 @@ import com.intellij.codeInsight.intention.AbstractIntentionAction
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.codeInsight.intention.JvmCommonIntentionActionsFactory
import com.intellij.codeInsight.intention.MethodInsertionInfo
import com.intellij.lang.java.JavaLanguage
import com.intellij.openapi.application.runWriteAction
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.impl.beanProperties.CreateJavaBeanPropertyFix
import com.intellij.psi.util.PsiFormatUtil
import com.intellij.psi.util.PsiFormatUtilBase
import com.intellij.util.VisibilityUtil
import org.jetbrains.annotations.NotNull
import org.jetbrains.uast.UClass
import org.jetbrains.uast.UDeclaration
import org.jetbrains.uast.UParameter
class JavaCommonIntentionActionsFactory : JvmCommonIntentionActionsFactory() {
override fun createChangeModifierAction(declaration: UDeclaration, modifier: String, shouldPresent: Boolean): IntentionAction {
override fun createChangeModifierAction(declaration: @JvmCommon PsiModifierListOwner,
modifier: String,
shouldPresent: Boolean): IntentionAction {
return ModifierFix(declaration.modifierList, modifier, shouldPresent, false)
}
override fun createAddCallableMemberActions(info: MethodInsertionInfo): List<IntentionAction> {
return when (info) {
is MethodInsertionInfo.Method -> with(info) {
createAddMethodAction(containingClass, name, modifiers.joinToString(" "), returnType, parameters)
createAddMethodAction(targetClass, name, modifiers.joinToString(" "), returnType, parameters)
?.let { listOf(it) } ?: emptyList()
}
is MethodInsertionInfo.Constructor ->
listOf(AddConstructorFix(info.containingClass.psi, info.parameters.map { it.psi }))
listOf(AddConstructorFix(info.targetClass.javaPsi(), info.parameters.map { it.javaPsi() }))
}
}
private fun createAddMethodAction(uClass: UClass,
private fun createAddMethodAction(psiClass: @JvmCommon PsiClass,
methodName: String,
@PsiModifier.ModifierConstant @NotNull visibilityModifier: String,
returnType: PsiType,
parameters: List<UParameter>): IntentionAction? {
val paramsString = parameters.mapIndexed { i, t -> "${t.type.presentableText} ${t.name ?: "arg$i"}" }.joinToString()
parameters: List<@JvmCommon PsiParameter>): IntentionAction? {
val paramsString = parameters.mapIndexed { i, t -> "${t.type.canonicalText} ${t.name ?: "arg$i"}" }.joinToString()
val signatureString =
"${VisibilityUtil.getVisibilityString(visibilityModifier)} ${returnType.presentableText} $methodName($paramsString){}"
val targetClassPointer = SmartPointerManager.getInstance(uClass.project).createSmartPsiElementPointer(uClass.psi)
"${VisibilityUtil.getVisibilityString(visibilityModifier)} ${returnType.canonicalText} $methodName($paramsString){}"
val targetClassPointer = SmartPointerManager.getInstance(psiClass.project).createSmartPsiElementPointer(psiClass.javaPsi())
return object : AbstractIntentionAction() {
private val text = targetClassPointer.element?.let { psiClass ->
@@ -77,17 +78,30 @@ class JavaCommonIntentionActionsFactory : JvmCommonIntentionActionsFactory() {
override fun getText(): String = text
override fun invoke(project: Project, editor: Editor?, file: PsiFile) {
targetClassPointer.element?.let { targetClass ->
targetClass.add(createMethod(targetClass))
val targetClass = targetClassPointer.element ?: return
runWriteAction {
val addedMethod = targetClass.add(createMethod(targetClass))
JavaCodeStyleManager.getInstance(project).shortenClassReferences(addedMethod)
}
}
private fun createMethod(targetClass: PsiClass): PsiMethod = PsiElementFactory.SERVICE.getInstance(targetClass.project)
.createMethodFromText(signatureString, targetClass)
private fun createMethod(targetClass: PsiClass): PsiMethod {
val elementFactory = JVMElementFactories.getFactory(targetClass.language, targetClass.project) // it could be Groovy
?: JavaPsiFacade.getElementFactory(targetClass.project)
return elementFactory.createMethodFromText(signatureString, targetClass)
}
}
}
override fun createAddBeanPropertyActions(uClass: UClass,
private inline fun <reified T : PsiElement> T.javaPsi(): T {
assert(language == JavaLanguage.INSTANCE)
return when (this) {
is org.jetbrains.uast.UElement -> psi as T
else -> this
}
}
override fun createAddBeanPropertyActions(psiClass: @JvmCommon PsiClass,
propertyName: String,
@PsiModifier.ModifierConstant visibilityModifier: String,
propertyType: PsiType,
@@ -95,20 +109,20 @@ class JavaCommonIntentionActionsFactory : JvmCommonIntentionActionsFactory() {
getterRequired: Boolean): List<IntentionAction> {
if (getterRequired && setterRequired)
return listOf<IntentionAction>(
CreateJavaBeanPropertyFix(uClass.psi, propertyName, propertyType, getterRequired, setterRequired,
CreateJavaBeanPropertyFix(psiClass.javaPsi(), propertyName, propertyType, getterRequired, setterRequired,
true),
CreateJavaBeanPropertyFix(uClass.psi, propertyName, propertyType, getterRequired, setterRequired,
CreateJavaBeanPropertyFix(psiClass.javaPsi(), propertyName, propertyType, getterRequired, setterRequired,
false))
if (getterRequired || setterRequired)
return listOf<IntentionAction>(
CreateJavaBeanPropertyFix(uClass.psi, propertyName, propertyType, getterRequired, setterRequired,
CreateJavaBeanPropertyFix(psiClass.javaPsi(), propertyName, propertyType, getterRequired, setterRequired,
true),
CreateJavaBeanPropertyFix(uClass.psi, propertyName, propertyType, getterRequired, setterRequired,
CreateJavaBeanPropertyFix(psiClass.javaPsi(), propertyName, propertyType, getterRequired, setterRequired,
false),
CreateJavaBeanPropertyFix(uClass.psi, propertyName, propertyType, true, true, true))
CreateJavaBeanPropertyFix(psiClass.javaPsi(), propertyName, propertyType, true, true, true))
return listOf<IntentionAction>(
CreateJavaBeanPropertyFix(uClass.psi, propertyName, propertyType, getterRequired, setterRequired, true))
CreateJavaBeanPropertyFix(psiClass.javaPsi(), propertyName, propertyType, getterRequired, setterRequired, true))
}
}
@@ -25,8 +25,6 @@ import com.intellij.psi.search.GlobalSearchScope;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.uast.UClass;
import org.jetbrains.uast.UastContextKt;
import static com.intellij.psi.CommonClassNames.JAVA_LANG_STRING;
import static com.intellij.util.ArrayUtil.toObjectArray;
@@ -35,14 +33,14 @@ import static com.intellij.util.ArrayUtil.toObjectArray;
public class CreateBeanPropertyFixes {
public static LocalQuickFix[] createFixes(String propertyName,
@NotNull PsiClass psiClass,
@NotNull @JvmCommon PsiClass psiClass,
@Nullable PsiType type,
final boolean createSetter) {
return IntentionWrapper.wrapToQuickFixes(createActions(propertyName, psiClass, type, createSetter), psiClass.getContainingFile());
}
public static IntentionAction[] createActions(String propertyName,
@NotNull PsiClass psiClass,
@NotNull @JvmCommon PsiClass psiClass,
@Nullable PsiType type,
final boolean createSetter) {
if (psiClass instanceof PsiCompiledElement) return IntentionAction.EMPTY_ARRAY;
@@ -55,9 +53,8 @@ public class CreateBeanPropertyFixes {
}
JvmCommonIntentionActionsFactory factory = JvmCommonIntentionActionsFactory.forLanguage(psiClass.getLanguage());
if (factory == null) return IntentionAction.EMPTY_ARRAY;
UClass uClass = UastContextKt.toUElement(psiClass, UClass.class);
if (uClass == null) return IntentionAction.EMPTY_ARRAY;
return toObjectArray(factory.createAddBeanPropertyActions(uClass, propertyName, PsiModifier.PUBLIC, type, createSetter, !createSetter),
IntentionAction.class);
return toObjectArray(
factory.createAddBeanPropertyActions(psiClass, propertyName, PsiModifier.PUBLIC, type, createSetter, !createSetter),
IntentionAction.class);
}
}
@@ -0,0 +1,41 @@
/*
* 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.
*/
package com.intellij.psi;
import org.jetbrains.annotations.ApiStatus;
import java.lang.annotation.*;
/**
* Annotation for marking {@link PsiElement} subclasses usages that they will be used in
* language-abstracted way:
* <ul>
* <li>No modification methods should be directly called on such instances</li>
* <li>Usages should be aware of {@link PsiElement#getLanguage()}, and not be hardcoded to dedicated language</li>
* <li>Instances could be "virtual" and don't have physical representations</li>
* <li>Complex operations should be performed via corresponding {@link com.intellij.lang.LanguageExtensionPoint} or similar API</li>
* <li>{@link org.jetbrains.uast.UClass}-like instances should be supported</li>
* </ul>
* <b>Note:</b> this annotation considered as kind-of "transitional" until generic language-independent API will be implemented
*/
@ApiStatus.Experimental
@Documented
@Retention(RetentionPolicy.CLASS)
@Target({
ElementType.TYPE, ElementType.PARAMETER, ElementType.FIELD, ElementType.TYPE_USE, ElementType.TYPE_PARAMETER
})
public @interface JvmCommon {
}