diff --git a/idea/idea-frontend-independent/resources-en/messages/KotlinBundleIndependent.properties b/idea/idea-frontend-independent/resources-en/messages/KotlinBundleIndependent.properties index 7d2460d460b..4e223ee9b58 100644 --- a/idea/idea-frontend-independent/resources-en/messages/KotlinBundleIndependent.properties +++ b/idea/idea-frontend-independent/resources-en/messages/KotlinBundleIndependent.properties @@ -61,4 +61,8 @@ find.usages.type.super.type.qualifier=Super type qualifier find.usages.type.receiver=Receiver find.usages.type.delegate=Delegate find.usages.type.packageDirective=Package directive -find.usages.type.packageMemberAccess=Package member access \ No newline at end of file +find.usages.type.packageMemberAccess=Package member access + +and.delete.initializer=\ and delete initializer +change.to.val=Change to val +change.to.var=Change to var \ No newline at end of file diff --git a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/ChangeVariableMutabilityFix.kt b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/ChangeVariableMutabilityFix.kt new file mode 100644 index 00000000000..c03c529824a --- /dev/null +++ b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/ChangeVariableMutabilityFix.kt @@ -0,0 +1,86 @@ +/* + * 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 org.jetbrains.kotlin.idea.KotlinBundleIndependent +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType + +class ChangeVariableMutabilityFix( + element: KtValVarKeywordOwner, + private val makeVar: Boolean, + private val actionText: String? = null, + private val deleteInitializer: Boolean = false +) : KotlinPsiOnlyQuickFixAction(element) { + + override fun getText() = actionText + ?: (if (makeVar) KotlinBundleIndependent.message("change.to.var") else KotlinBundleIndependent.message("change.to.val")) + + if (deleteInitializer) KotlinBundleIndependent.message("and.delete.initializer") else "" + + override fun getFamilyName(): String = text + + override fun isAvailable(project: Project, editor: Editor?, file: KtFile): Boolean { + val element = element ?: return false + val valOrVar = element.valOrVarKeyword?.node?.elementType ?: return false + return (valOrVar == KtTokens.VAR_KEYWORD) != makeVar + } + + override fun invoke(project: Project, editor: Editor?, file: KtFile) { + val element = element ?: return + val factory = KtPsiFactory(project) + val newKeyword = if (makeVar) factory.createVarKeyword() else factory.createValKeyword() + element.valOrVarKeyword!!.replace(newKeyword) + if (deleteInitializer) { + (element as? KtProperty)?.initializer = null + } + if (makeVar) { + (element as? KtModifierListOwner)?.removeModifier(KtTokens.CONST_KEYWORD) + } + } + + companion object { + val VAL_WITH_SETTER_FACTORY: QuickFixesPsiBasedFactory = + quickFixesPsiBasedFactory { psiElement: KtPropertyAccessor -> + listOf(ChangeVariableMutabilityFix(psiElement.property, true)) + } + + val VAR_OVERRIDDEN_BY_VAL_FACTORY: QuickFixesPsiBasedFactory = + quickFixesPsiBasedFactory { psiElement: PsiElement -> + when (psiElement) { + is KtProperty, is KtParameter -> listOf(ChangeVariableMutabilityFix(psiElement as KtValVarKeywordOwner, true)) + else -> emptyList() + } + } + + val VAR_ANNOTATION_PARAMETER_FACTORY: QuickFixesPsiBasedFactory = + quickFixesPsiBasedFactory { psiElement: KtParameter -> + listOf(ChangeVariableMutabilityFix(psiElement, false)) + } + + val LATEINIT_VAL_FACTORY: QuickFixesPsiBasedFactory = + quickFixesPsiBasedFactory { psiElement: PsiElement -> + val property = psiElement.getStrictParentOfType() ?: return@quickFixesPsiBasedFactory emptyList() + if (property.valOrVarKeyword.text != "val") { + emptyList() + } else { + listOf(ChangeVariableMutabilityFix(property, makeVar = true)) + } + } + + val MUST_BE_INITIALIZED_FACTORY: QuickFixesPsiBasedFactory = + quickFixesPsiBasedFactory { psiElement: PsiElement -> + val property = psiElement as? KtProperty ?: return@quickFixesPsiBasedFactory emptyList() + val getter = property.getter ?: return@quickFixesPsiBasedFactory emptyList() + if (!getter.hasBody()) return@quickFixesPsiBasedFactory emptyList() + if (getter.hasBlockBody() && property.typeReference == null) return@quickFixesPsiBasedFactory emptyList() + listOf(ChangeVariableMutabilityFix(property, makeVar = false)) + } + } +} \ No newline at end of file diff --git a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/KotlinPsiOnlyQuickFixAction.kt b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/KotlinPsiOnlyQuickFixAction.kt new file mode 100644 index 00000000000..15538d25929 --- /dev/null +++ b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/KotlinPsiOnlyQuickFixAction.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2010-2019 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.codeInsight.FileModificationService +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import org.jetbrains.kotlin.psi.KtFile + +abstract class KotlinPsiOnlyQuickFixAction(element: T) : QuickFixActionBase(element) { + protected open fun isAvailable(project: Project, editor: Editor?, file: KtFile) = true + + override fun isAvailableImpl(project: Project, editor: Editor?, file: PsiFile): Boolean { + val ktFile = file as? KtFile ?: return false + return isAvailable(project, editor, ktFile) + } + + final override fun invoke(project: Project, editor: Editor?, file: PsiFile) { + val element = element ?: return + if (file is KtFile && FileModificationService.getInstance().prepareFileForWrite(element.containingFile)) { + invoke(project, editor, file) + } + } + + protected abstract operator fun invoke(project: Project, editor: Editor?, file: KtFile) + + override fun startInWriteAction() = true +} diff --git a/idea/resources-en/messages/KotlinBundle.properties b/idea/resources-en/messages/KotlinBundle.properties index 533ae28b198..1228cc0f613 100644 --- a/idea/resources-en/messages/KotlinBundle.properties +++ b/idea/resources-en/messages/KotlinBundle.properties @@ -1040,9 +1040,6 @@ replace.with.publishedapi.bridge.call=Replace with @PublishedApi bridge call replace.with.generated.publishedapi.bridge.call.0=Replace with generated @PublishedApi bridge call ''{0}'' convert.sealed.sub.class.to.object.fix.family.name=Convert sealed sub-class to object generate.identity.equals.fix.family.name=Generate equals \\& hashCode by identity -and.delete.initializer=\ and delete initializer -change.to.val=Change to val -change.to.var=Change to var change.type.of.0.to.1=Change type of {0} to ''{1}'' change.type.to.0=Change type to ''{0}'' base.property.0=base property {0} diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeVariableMutabilityFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeVariableMutabilityFix.kt index 44e3c7189b8..546c39a2a5f 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeVariableMutabilityFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeVariableMutabilityFix.kt @@ -17,128 +17,44 @@ package org.jetbrains.kotlin.idea.quickfix import com.intellij.codeInsight.intention.IntentionAction -import com.intellij.openapi.editor.Editor -import com.intellij.openapi.project.Project -import com.intellij.psi.PsiElement import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1 import org.jetbrains.kotlin.diagnostics.Errors -import org.jetbrains.kotlin.idea.KotlinBundle +import org.jetbrains.kotlin.idea.KotlinBundleIndependent import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createCallable.CreatePropertyDelegateAccessorsActionFactory -import org.jetbrains.kotlin.lexer.KtModifierKeywordToken import org.jetbrains.kotlin.lexer.KtTokens -import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.psi.KtValVarKeywordOwner import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils import org.jetbrains.kotlin.util.OperatorNameConventions -class ChangeVariableMutabilityFix( - element: KtValVarKeywordOwner, - private val makeVar: Boolean, - private val actionText: String? = null, - private val deleteInitializer: Boolean = false -) : KotlinQuickFixAction(element) { - - override fun getText() = actionText - ?: (if (makeVar) KotlinBundle.message("change.to.var") else KotlinBundle.message("change.to.val")) + - if (deleteInitializer) KotlinBundle.message("and.delete.initializer") else "" - - override fun getFamilyName(): String = text - - override fun isAvailable(project: Project, editor: Editor?, file: KtFile): Boolean { - val element = element ?: return false - val valOrVar = element.valOrVarKeyword?.node?.elementType ?: return false - return (valOrVar == KtTokens.VAR_KEYWORD) != makeVar - } - - override fun invoke(project: Project, editor: Editor?, file: KtFile) { - val element = element ?: return - val factory = KtPsiFactory(project) - val newKeyword = if (makeVar) factory.createVarKeyword() else factory.createValKeyword() - element.valOrVarKeyword!!.replace(newKeyword) - if (deleteInitializer) { - (element as? KtProperty)?.initializer = null - } - if (makeVar) { - (element as? KtModifierListOwner)?.removeModifier(KtTokens.CONST_KEYWORD) - } - } - - companion object { - val VAL_WITH_SETTER_FACTORY: KotlinSingleIntentionActionFactory = object : KotlinSingleIntentionActionFactory() { - override fun createAction(diagnostic: Diagnostic): IntentionAction? { - val accessor = diagnostic.psiElement as KtPropertyAccessor - return ChangeVariableMutabilityFix(accessor.property, true) - } - } - - class ReassignmentActionFactory(val factory: DiagnosticFactory1<*, DeclarationDescriptor>) : KotlinSingleIntentionActionFactory() { - override fun createAction(diagnostic: Diagnostic): IntentionAction? { - val propertyDescriptor = factory.cast(diagnostic).a - val declaration = - DescriptorToSourceUtils.descriptorToDeclaration(propertyDescriptor) as? KtValVarKeywordOwner ?: return null - return ChangeVariableMutabilityFix(declaration, true) - } - } - - val VAL_REASSIGNMENT_FACTORY = ReassignmentActionFactory(Errors.VAL_REASSIGNMENT) - - val CAPTURED_VAL_INITIALIZATION_FACTORY = ReassignmentActionFactory(Errors.CAPTURED_VAL_INITIALIZATION) - - val CAPTURED_MEMBER_VAL_INITIALIZATION_FACTORY = ReassignmentActionFactory(Errors.CAPTURED_MEMBER_VAL_INITIALIZATION) - - val VAR_OVERRIDDEN_BY_VAL_FACTORY: QuickFixesPsiBasedFactory = - quickFixesPsiBasedFactory { psiElement: PsiElement -> - when (psiElement) { - is KtProperty, is KtParameter -> listOf(ChangeVariableMutabilityFix(psiElement as KtValVarKeywordOwner, true)) - else -> emptyList() - } - } - - val VAR_ANNOTATION_PARAMETER_FACTORY: KotlinSingleIntentionActionFactory = object : KotlinSingleIntentionActionFactory() { - override fun createAction(diagnostic: Diagnostic): IntentionAction? { - val element = diagnostic.psiElement as KtParameter - return ChangeVariableMutabilityFix(element, false) - } - } - - val LATEINIT_VAL_FACTORY = object : KotlinSingleIntentionActionFactory() { - override fun createAction(diagnostic: Diagnostic): IntentionAction? { - val lateinitElement = Errors.INAPPLICABLE_LATEINIT_MODIFIER.cast(diagnostic).psiElement - val property = lateinitElement.getStrictParentOfType() ?: return null - if (property.valOrVarKeyword.text != "val") return null - return ChangeVariableMutabilityFix(property, makeVar = true) - } - } - - val CONST_VAL_FACTORY = object : KotlinSingleIntentionActionFactory() { - override fun createAction(diagnostic: Diagnostic): IntentionAction? { - val (modifier, element) = Errors.WRONG_MODIFIER_TARGET.cast(diagnostic).run { a to psiElement } - if (modifier != KtTokens.CONST_KEYWORD) return null - val property = element.getStrictParentOfType() ?: return null - return ChangeVariableMutabilityFix(property, makeVar = false) - } - } - - val DELEGATED_PROPERTY_VAL_FACTORY = object : KotlinSingleIntentionActionFactory() { - override fun createAction(diagnostic: Diagnostic): IntentionAction? { - val element = Errors.DELEGATE_SPECIAL_FUNCTION_MISSING.cast(diagnostic).psiElement - val property = element.getStrictParentOfType() ?: return null - val info = CreatePropertyDelegateAccessorsActionFactory.extractFixData(property, diagnostic).singleOrNull() ?: return null - if (info.name != OperatorNameConventions.SET_VALUE.asString()) return null - return ChangeVariableMutabilityFix(property, makeVar = false, actionText = KotlinBundle.message("change.to.val")) - } - } - - val MUST_BE_INITIALIZED_FACTORY = object : KotlinSingleIntentionActionFactory() { - override fun createAction(diagnostic: Diagnostic): IntentionAction? { - val property = Errors.MUST_BE_INITIALIZED.cast(diagnostic).psiElement as? KtProperty ?: return null - val getter = property.getter ?: return null - if (!getter.hasBody()) return null - if (getter.hasBlockBody() && property.typeReference == null) return null - return ChangeVariableMutabilityFix(property, makeVar = false) - } - } +class ReassignmentActionFactory(val factory: DiagnosticFactory1<*, DeclarationDescriptor>) : KotlinSingleIntentionActionFactory() { + override fun createAction(diagnostic: Diagnostic): IntentionAction? { + val propertyDescriptor = factory.cast(diagnostic).a + val declaration = + DescriptorToSourceUtils.descriptorToDeclaration(propertyDescriptor) as? KtValVarKeywordOwner ?: return null + return ChangeVariableMutabilityFix(declaration, true) } } + +// TODO: Move this to idea-fir-independent +object ConstValFactory : KotlinSingleIntentionActionFactory() { + override fun createAction(diagnostic: Diagnostic): IntentionAction? { + val (modifier, element) = Errors.WRONG_MODIFIER_TARGET.cast(diagnostic).run { a to psiElement } + if (modifier != KtTokens.CONST_KEYWORD) return null + val property = element.getStrictParentOfType() ?: return null + return ChangeVariableMutabilityFix(property, makeVar = false) + } +} + +object DelegatedPropertyValFactory : KotlinSingleIntentionActionFactory() { + override fun createAction(diagnostic: Diagnostic): IntentionAction? { + val element = Errors.DELEGATE_SPECIAL_FUNCTION_MISSING.cast(diagnostic).psiElement + val property = element.getStrictParentOfType() ?: return null + val info = CreatePropertyDelegateAccessorsActionFactory.extractFixData(property, diagnostic).singleOrNull() ?: return null + if (info.name != OperatorNameConventions.SET_VALUE.asString()) return null + return ChangeVariableMutabilityFix(property, makeVar = false, actionText = KotlinBundleIndependent.message("change.to.val")) + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt index 184884ceaa7..bb41288f250 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt @@ -142,7 +142,7 @@ class QuickFixRegistrar : QuickFixContributor { removeModifierFactory ) REDUNDANT_MODIFIER_IN_GETTER.registerFactory(removeRedundantModifierFactory) - WRONG_MODIFIER_TARGET.registerFactory(removeModifierFactory, ChangeVariableMutabilityFix.CONST_VAL_FACTORY) + WRONG_MODIFIER_TARGET.registerFactory(removeModifierFactory, ConstValFactory) DEPRECATED_MODIFIER.registerFactory(ReplaceModifierFix) REDUNDANT_MODIFIER_FOR_TARGET.registerFactory(removeModifierFactory) WRONG_MODIFIER_CONTAINING_DECLARATION.registerFactory(removeModifierFactory) @@ -222,10 +222,10 @@ class QuickFixRegistrar : QuickFixContributor { VAL_WITH_SETTER.registerFactory(ChangeVariableMutabilityFix.VAL_WITH_SETTER_FACTORY) VAL_REASSIGNMENT.registerFactory( - ChangeVariableMutabilityFix.VAL_REASSIGNMENT_FACTORY, LiftAssignmentOutOfTryFix, AssignToPropertyFix + ReassignmentActionFactory(VAL_REASSIGNMENT), LiftAssignmentOutOfTryFix, AssignToPropertyFix ) - CAPTURED_VAL_INITIALIZATION.registerFactory(ChangeVariableMutabilityFix.CAPTURED_VAL_INITIALIZATION_FACTORY) - CAPTURED_MEMBER_VAL_INITIALIZATION.registerFactory(ChangeVariableMutabilityFix.CAPTURED_MEMBER_VAL_INITIALIZATION_FACTORY) + CAPTURED_VAL_INITIALIZATION.registerFactory(ReassignmentActionFactory(CAPTURED_VAL_INITIALIZATION)) + CAPTURED_MEMBER_VAL_INITIALIZATION.registerFactory(ReassignmentActionFactory(CAPTURED_MEMBER_VAL_INITIALIZATION)) VAR_OVERRIDDEN_BY_VAL.registerFactory(ChangeVariableMutabilityFix.VAR_OVERRIDDEN_BY_VAL_FACTORY) VAR_ANNOTATION_PARAMETER.registerFactory(ChangeVariableMutabilityFix.VAR_ANNOTATION_PARAMETER_FACTORY) @@ -418,7 +418,7 @@ class QuickFixRegistrar : QuickFixContributor { CreateDataClassPropertyFromDestructuringActionFactory ) - DELEGATE_SPECIAL_FUNCTION_MISSING.registerFactory(ChangeVariableMutabilityFix.DELEGATED_PROPERTY_VAL_FACTORY) + DELEGATE_SPECIAL_FUNCTION_MISSING.registerFactory(DelegatedPropertyValFactory) DELEGATE_SPECIAL_FUNCTION_MISSING.registerFactory(CreatePropertyDelegateAccessorsActionFactory) DELEGATE_SPECIAL_FUNCTION_NONE_APPLICABLE.registerFactory(CreatePropertyDelegateAccessorsActionFactory)