From 7fada51c42acf0c5a5c7a75238ae281f0d09ae24 Mon Sep 17 00:00:00 2001 From: Leonid Startsev Date: Mon, 16 Sep 2019 14:51:12 +0300 Subject: [PATCH] Add quickfix for setting explicit public visibility for diagnostic reported in API mode --- .../jetbrains/kotlin/diagnostics/Errors.java | 4 +- .../rendering/DefaultErrorMessages.java | 4 +- .../checkers/ApiModeDeclarationChecker.kt | 45 +++++++------------ .../kotlin/idea/core/psiModificationUtils.kt | 5 +-- .../idea/quickfix/ChangeVisibilityFix.kt | 27 +++++++++-- .../kotlin/idea/quickfix/QuickFixRegistrar.kt | 2 + 6 files changed, 48 insertions(+), 39 deletions(-) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index c142b096082..74804baf353 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -207,9 +207,9 @@ public interface Errors { DiagnosticFactory2 DEPRECATED_MODIFIER_FOR_TARGET = DiagnosticFactory2.create(WARNING); DiagnosticFactory2 DEPRECATED_MODIFIER = DiagnosticFactory2.create(WARNING); DiagnosticFactory2 REDUNDANT_MODIFIER_FOR_TARGET = DiagnosticFactory2.create(WARNING); - DiagnosticFactory0 NO_EXPLICIT_VISIBILITY_IN_API_MODE = DiagnosticFactory0.create(ERROR); + DiagnosticFactory1 NO_EXPLICIT_VISIBILITY_IN_API_MODE = DiagnosticFactory1.create(ERROR); DiagnosticFactory0 NO_EXPLICIT_RETURN_TYPE_IN_API_MODE = DiagnosticFactory0.create(ERROR); - DiagnosticFactory0 NO_EXPLICIT_VISIBILITY_IN_API_MODE_MIGRATION = DiagnosticFactory0.create(WARNING); + DiagnosticFactory1 NO_EXPLICIT_VISIBILITY_IN_API_MODE_MIGRATION = DiagnosticFactory1.create(WARNING); DiagnosticFactory0 NO_EXPLICIT_RETURN_TYPE_IN_API_MODE_MIGRATION = DiagnosticFactory0.create(WARNING); DiagnosticFactory2 WRONG_MODIFIER_CONTAINING_DECLARATION = DiagnosticFactory2.create(ERROR); DiagnosticFactory2 DEPRECATED_MODIFIER_CONTAINING_DECLARATION = DiagnosticFactory2.create(WARNING); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java index 0bc7626bfe6..96776b7635e 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -119,9 +119,9 @@ public class DefaultErrorMessages { MAP.put(DEPRECATED_MODIFIER_FOR_TARGET, "Modifier ''{0}'' is deprecated for ''{1}''", TO_STRING, TO_STRING); MAP.put(DEPRECATED_MODIFIER, "Modifier ''{0}'' is deprecated, use ''{1}'' instead", TO_STRING, TO_STRING); MAP.put(REDUNDANT_MODIFIER_FOR_TARGET, "Modifier ''{0}'' is redundant for ''{1}''", TO_STRING, TO_STRING); - MAP.put(NO_EXPLICIT_VISIBILITY_IN_API_MODE, "This declaration is effectively public API and API mode is on, but no visibility is specified"); + MAP.put(NO_EXPLICIT_VISIBILITY_IN_API_MODE, "Declaration ''{0}'' is effectively public API and API mode is on, but no visibility is specified", NAME); MAP.put(NO_EXPLICIT_RETURN_TYPE_IN_API_MODE, "This declaration is effectively public API and API mode is on, but no explicit return type is specified"); - MAP.put(NO_EXPLICIT_VISIBILITY_IN_API_MODE_MIGRATION, "This declaration is effectively public API and API mode is on, but no visibility is specified"); + MAP.put(NO_EXPLICIT_VISIBILITY_IN_API_MODE_MIGRATION, "Declaration ''{0}'' is effectively public API and API mode is on, but no visibility is specified", NAME); MAP.put(NO_EXPLICIT_RETURN_TYPE_IN_API_MODE_MIGRATION, "This declaration is effectively public API and API mode is on, but no explicit return type is specified"); MAP.put(WRONG_MODIFIER_CONTAINING_DECLARATION, "Modifier ''{0}'' is not applicable inside ''{1}''", TO_STRING, TO_STRING); MAP.put(DEPRECATED_MODIFIER_CONTAINING_DECLARATION, "Modifier ''{0}'' is deprecated inside ''{1}''", TO_STRING, TO_STRING); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ApiModeDeclarationChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ApiModeDeclarationChecker.kt index 5a1fa3db44d..2ebb13fad83 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ApiModeDeclarationChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ApiModeDeclarationChecker.kt @@ -5,12 +5,10 @@ package org.jetbrains.kotlin.resolve.checkers -import com.intellij.psi.PsiElement import org.jetbrains.kotlin.config.AnalysisFlags import org.jetbrains.kotlin.config.ApiMode import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.descriptors.* -import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0 import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.diagnostics.reportDiagnosticOnce import org.jetbrains.kotlin.lexer.KtModifierKeywordToken @@ -35,19 +33,19 @@ class ApiModeDeclarationChecker : DeclarationChecker { private fun checkVisibilityModifier( state: ApiMode, declaration: KtDeclaration, - descriptor: DeclarationDescriptor, + descriptor: DeclarationDescriptorWithVisibility, context: DeclarationCheckerContext ) { val modifier = declaration.visibilityModifier()?.node?.elementType as? KtModifierKeywordToken if (modifier != null) return if (excludeForDiagnostic(descriptor)) return - context.selectDiagnosticAndReport( - declaration, - state, - Errors.NO_EXPLICIT_VISIBILITY_IN_API_MODE, - Errors.NO_EXPLICIT_VISIBILITY_IN_API_MODE_MIGRATION - ) + val diagnostic = + if (state == ApiMode.ENABLED) + Errors.NO_EXPLICIT_VISIBILITY_IN_API_MODE.on(declaration, descriptor) + else + Errors.NO_EXPLICIT_VISIBILITY_IN_API_MODE_MIGRATION.on(declaration, descriptor) + context.trace.reportDiagnosticOnce(diagnostic) } private fun checkExplicitReturnType( @@ -65,26 +63,15 @@ class ApiModeDeclarationChecker : DeclarationChecker { checkForInternal = false, checkForPrivate = false ) - if (shouldReport) context.selectDiagnosticAndReport( - declaration, - state, - Errors.NO_EXPLICIT_RETURN_TYPE_IN_API_MODE, - Errors.NO_EXPLICIT_RETURN_TYPE_IN_API_MODE_MIGRATION - ) - } - - private fun DeclarationCheckerContext.selectDiagnosticAndReport( - on: KtDeclaration, - state: ApiMode, - ifError: DiagnosticFactory0, - ifWarning: DiagnosticFactory0 - ) { - val diagnostic = - if (state == ApiMode.ENABLED) - ifError.on(on) - else - ifWarning.on(on) - trace.reportDiagnosticOnce(diagnostic) + if (shouldReport) { + val diagnostic = + if (state == ApiMode.ENABLED) + Errors.NO_EXPLICIT_RETURN_TYPE_IN_API_MODE.on(declaration) + else + Errors.NO_EXPLICIT_RETURN_TYPE_IN_API_MODE_MIGRATION + .on(declaration) + context.trace.reportDiagnosticOnce(diagnostic) + } } /** diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/psiModificationUtils.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/psiModificationUtils.kt index 5e0358fbfe4..9d73ff790b6 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/psiModificationUtils.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/psiModificationUtils.kt @@ -288,9 +288,8 @@ fun KtDeclaration.toDescriptor(): DeclarationDescriptor? { return descriptor } -//TODO: code style option whether to insert redundant 'public' keyword or not -fun KtModifierListOwner.setVisibility(visibilityModifier: KtModifierKeywordToken) { - if (this is KtDeclaration) { +fun KtModifierListOwner.setVisibility(visibilityModifier: KtModifierKeywordToken, addImplicitVisibilityModifier: Boolean = false) { + if (this is KtDeclaration && !addImplicitVisibilityModifier) { val defaultVisibilityKeyword = implicitVisibility() if (visibilityModifier == defaultVisibilityKeyword) { diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeVisibilityFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeVisibilityFix.kt index 9492d1a232d..7bf89c43b93 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeVisibilityFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeVisibilityFix.kt @@ -20,9 +20,12 @@ import com.intellij.codeInsight.intention.HighPriorityAction import com.intellij.codeInsight.intention.IntentionAction import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithVisibility import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.descriptors.Visibility +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1 import org.jetbrains.kotlin.idea.core.canBeInternal import org.jetbrains.kotlin.idea.core.canBePrivate import org.jetbrains.kotlin.idea.core.canBeProtected @@ -42,7 +45,8 @@ import org.jetbrains.kotlin.resolve.ExposedVisibilityChecker open class ChangeVisibilityFix( element: KtModifierListOwner, private val elementName: String, - private val visibilityModifier: KtModifierKeywordToken + private val visibilityModifier: KtModifierKeywordToken, + private val addImplicitVisibilityModifier: Boolean = false ) : KotlinQuickFixAction(element) { override fun getText() = "Make '$elementName' $visibilityModifier" @@ -52,9 +56,9 @@ open class ChangeVisibilityFix( val pointer = element?.createSmartPointer() val originalElement = pointer?.element if (originalElement is KtDeclaration) { - originalElement.runOnExpectAndAllActuals(useOnSelf = true) { it.setVisibility(visibilityModifier) } + originalElement.runOnExpectAndAllActuals(useOnSelf = true) { it.setVisibility(visibilityModifier, addImplicitVisibilityModifier) } } else { - originalElement?.setVisibility(visibilityModifier) + originalElement?.setVisibility(visibilityModifier, addImplicitVisibilityModifier) } val propertyAccessor = pointer?.element as? KtPropertyAccessor @@ -112,4 +116,21 @@ open class ChangeVisibilityFix( } } } + + object SetExplicitVisibilityFactory : KotlinIntentionActionsFactory() { + @Suppress("UNCHECKED_CAST") + override fun doCreateActions(diagnostic: Diagnostic): List { + val factory = diagnostic.factory as DiagnosticFactory1<*, DeclarationDescriptor> + val descriptor = factory.cast(diagnostic).a as? DeclarationDescriptorWithVisibility ?: return emptyList() + val element = diagnostic.psiElement as? KtModifierListOwner ?: return emptyList() + return listOf( + ChangeVisibilityFix( + element, + descriptor.name.asString(), + KtTokens.PUBLIC_KEYWORD, + addImplicitVisibilityModifier = true + ) + ) + } + } } \ 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 2680476a83f..d8c1fb8bda9 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt @@ -151,6 +151,8 @@ class QuickFixRegistrar : QuickFixContributor { FORBIDDEN_BINARY_MOD.registerFactory(removeModifierFactory) FORBIDDEN_BINARY_MOD.registerFactory(RenameModToRemFix.Factory) + NO_EXPLICIT_VISIBILITY_IN_API_MODE.registerFactory(ChangeVisibilityFix.SetExplicitVisibilityFactory) + NO_EXPLICIT_VISIBILITY_IN_API_MODE_MIGRATION.registerFactory(ChangeVisibilityFix.SetExplicitVisibilityFactory) NO_EXPLICIT_RETURN_TYPE_IN_API_MODE.registerActions(SpecifyTypeExplicitlyFix()) NO_EXPLICIT_RETURN_TYPE_IN_API_MODE_MIGRATION.registerActions(SpecifyTypeExplicitlyFix())