diff --git a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/AddModifierFix.kt b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/AddModifierFix.kt new file mode 100644 index 00000000000..5b38e72357e --- /dev/null +++ b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/AddModifierFix.kt @@ -0,0 +1,114 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.quickfix + +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.kotlin.idea.KotlinBundle +import org.jetbrains.kotlin.idea.inspections.KotlinUniversalQuickFix +import org.jetbrains.kotlin.lexer.KtModifierKeywordToken +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.containingClass +import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject + +open class AddModifierFix( + element: KtModifierListOwner, + protected val modifier: KtModifierKeywordToken +) : KotlinCrossLanguageQuickFixAction(element), KotlinUniversalQuickFix { + override fun getText(): String { + val element = element ?: return "" + if (modifier in modalityModifiers || modifier in KtTokens.VISIBILITY_MODIFIERS || modifier == KtTokens.CONST_KEYWORD) { + return KotlinBundle.message("fix.add.modifier.text", RemoveModifierFix.getElementName(element), modifier.value) + } + return KotlinBundle.message("fix.add.modifier.text.generic", modifier.value) + } + + override fun getFamilyName() = KotlinBundle.message("fix.add.modifier.family") + + protected fun invokeOnElement(element: KtModifierListOwner?) { + element?.addModifier(modifier) + + if (modifier == KtTokens.ABSTRACT_KEYWORD && (element is KtProperty || element is KtNamedFunction)) { + element.containingClass()?.run { + if (!hasModifier(KtTokens.ABSTRACT_KEYWORD) && !hasModifier(KtTokens.SEALED_KEYWORD)) { + addModifier(KtTokens.ABSTRACT_KEYWORD) + } + } + } + } + + override fun invokeImpl(project: Project, editor: Editor?, file: PsiFile) { + val originalElement = element + invokeOnElement(originalElement) + } + + // TODO: consider checking if this fix is available by testing if the [element] can be refactored by calling + // FIR version of [org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtilKt#canRefactor] + override fun isAvailableImpl(project: Project, editor: Editor?, file: PsiFile): Boolean = element != null + + interface Factory { + fun createFactory(modifier: KtModifierKeywordToken): QuickFixesPsiBasedFactory { + return createFactory(modifier, KtModifierListOwner::class.java) + } + + fun createFactory( + modifier: KtModifierKeywordToken, + modifierOwnerClass: Class + ): QuickFixesPsiBasedFactory { + return quickFixesPsiBasedFactory { e -> + val modifierListOwner = + PsiTreeUtil.getParentOfType(e, modifierOwnerClass, false) ?: return@quickFixesPsiBasedFactory emptyList() + listOfNotNull(createIfApplicable(modifierListOwner, modifier)) + } + } + + fun createIfApplicable(modifierListOwner: KtModifierListOwner, modifier: KtModifierKeywordToken): AddModifierFix? { + when (modifier) { + KtTokens.ABSTRACT_KEYWORD, KtTokens.OPEN_KEYWORD -> { + if (modifierListOwner is KtObjectDeclaration) return null + if (modifierListOwner is KtEnumEntry) return null + if (modifierListOwner is KtDeclaration && modifierListOwner !is KtClass) { + val parentClassOrObject = modifierListOwner.containingClassOrObject ?: return null + if (parentClassOrObject is KtObjectDeclaration) return null + if (parentClassOrObject is KtEnumEntry) return null + } + if (modifier == KtTokens.ABSTRACT_KEYWORD + && modifierListOwner is KtClass + && modifierListOwner.hasModifier(KtTokens.INLINE_KEYWORD) + ) return null + } + KtTokens.INNER_KEYWORD -> { + if (modifierListOwner is KtObjectDeclaration) return null + if (modifierListOwner is KtClass) { + if (modifierListOwner.isInterface() || + modifierListOwner.isSealed() || + modifierListOwner.isEnum() || + modifierListOwner.isData() || + modifierListOwner.isAnnotation() + ) return null + } + } + } + return AddModifierFix(modifierListOwner, modifier) + } + + fun createModifierFix( + element: KtModifierListOwner, + modifier: KtModifierKeywordToken + ): T + } + + companion object : Factory { + private val modalityModifiers = setOf(KtTokens.ABSTRACT_KEYWORD, KtTokens.OPEN_KEYWORD, KtTokens.FINAL_KEYWORD) + override fun createModifierFix(element: KtModifierListOwner, modifier: KtModifierKeywordToken): AddModifierFix = + AddModifierFix(element, modifier) + + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/AddModifierFixMpp.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/AddModifierFixMpp.kt index feff1271644..dc98d290348 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/AddModifierFixMpp.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/AddModifierFixMpp.kt @@ -25,48 +25,26 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Errors -import org.jetbrains.kotlin.idea.KotlinBundle import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny -import org.jetbrains.kotlin.idea.core.quickfix.QuickFixUtil -import org.jetbrains.kotlin.idea.inspections.KotlinUniversalQuickFix import org.jetbrains.kotlin.idea.refactoring.canRefactor import org.jetbrains.kotlin.idea.util.runOnExpectAndAllActuals import org.jetbrains.kotlin.lexer.KtModifierKeywordToken import org.jetbrains.kotlin.lexer.KtTokens.* -import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.containingClass -import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtModifierListOwner +import org.jetbrains.kotlin.psi.KtTypeReference import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.types.TypeUtils +/** Similar to [AddModifierFix] but with multi-platform support. */ open class AddModifierFixMpp( element: KtModifierListOwner, - protected val modifier: KtModifierKeywordToken -) : KotlinCrossLanguageQuickFixAction(element), KotlinUniversalQuickFix { - override fun getText(): String { - val element = element ?: return "" - if (modifier in modalityModifiers || modifier in VISIBILITY_MODIFIERS || modifier == CONST_KEYWORD) { - return KotlinBundle.message("fix.add.modifier.text", RemoveModifierFix.getElementName(element), modifier.value) - } - return KotlinBundle.message("fix.add.modifier.text.generic", modifier.value) - } - - override fun getFamilyName() = KotlinBundle.message("fix.add.modifier.family") - - private fun invokeOnElement(element: KtModifierListOwner?) { - element?.addModifier(modifier) - - if (modifier == ABSTRACT_KEYWORD && (element is KtProperty || element is KtNamedFunction)) { - element.containingClass()?.run { - if (!hasModifier(ABSTRACT_KEYWORD) && !hasModifier(SEALED_KEYWORD)) { - addModifier(ABSTRACT_KEYWORD) - } - } - } - } + modifier: KtModifierKeywordToken +) : AddModifierFix(element, modifier) { override fun invokeImpl(project: Project, editor: Editor?, file: PsiFile) { val originalElement = element @@ -82,59 +60,12 @@ open class AddModifierFixMpp( return element.canRefactor() } - companion object { - + companion object : Factory { private fun KtModifierKeywordToken.isMultiplatformPersistent(): Boolean = this in MODALITY_MODIFIERS || this == INLINE_KEYWORD - private val modalityModifiers = setOf(ABSTRACT_KEYWORD, OPEN_KEYWORD, FINAL_KEYWORD) - - fun createFactory(modifier: KtModifierKeywordToken): KotlinSingleIntentionActionFactory { - return createFactory(modifier, KtModifierListOwner::class.java) - } - - fun createFactory( - modifier: KtModifierKeywordToken, - modifierOwnerClass: Class - ): KotlinSingleIntentionActionFactory { - return object : KotlinSingleIntentionActionFactory() { - public override fun createAction(diagnostic: Diagnostic): IntentionAction? { - val modifierListOwner = QuickFixUtil.getParentElementOfType(diagnostic, modifierOwnerClass) ?: return null - return createIfApplicable(modifierListOwner, modifier) - } - } - } - - fun createIfApplicable(modifierListOwner: KtModifierListOwner, modifier: KtModifierKeywordToken): AddModifierFixMpp? { - when (modifier) { - ABSTRACT_KEYWORD, OPEN_KEYWORD -> { - if (modifierListOwner is KtObjectDeclaration) return null - if (modifierListOwner is KtEnumEntry) return null - if (modifierListOwner is KtDeclaration && modifierListOwner !is KtClass) { - val parentClassOrObject = modifierListOwner.containingClassOrObject ?: return null - if (parentClassOrObject is KtObjectDeclaration) return null - if (parentClassOrObject is KtEnumEntry) return null - } - if (modifier == ABSTRACT_KEYWORD - && modifierListOwner is KtClass - && modifierListOwner.hasModifier(INLINE_KEYWORD) - ) return null - } - INNER_KEYWORD -> { - if (modifierListOwner is KtObjectDeclaration) return null - if (modifierListOwner is KtClass) { - if (modifierListOwner.isInterface() || - modifierListOwner.isSealed() || - modifierListOwner.isEnum() || - modifierListOwner.isData() || - modifierListOwner.isAnnotation() - ) return null - } - } - } - return AddModifierFixMpp(modifierListOwner, modifier) - } - + override fun createModifierFix(element: KtModifierListOwner, modifier: KtModifierKeywordToken): AddModifierFixMpp = + AddModifierFixMpp(element, modifier) } object MakeClassOpenFactory : KotlinSingleIntentionActionFactory() {