diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/CommonCompilerArguments.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/CommonCompilerArguments.kt index 1fa73fe5451..61a853d21b7 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/CommonCompilerArguments.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/CommonCompilerArguments.kt @@ -316,6 +316,14 @@ abstract class CommonCompilerArguments : CommonToolArguments() { @Argument(value = "-Xdisable-default-scripting-plugin", description = "Do not enable scripting plugin by default") var disableDefaultScriptingPlugin: Boolean by FreezableVar(false) + @Argument( + value = "-Xapi-mode", + valueDescription = "{enable|disable|migration}", + description = "Enable api mode, force compiler to report errors an all public API declarations without explicit visibility.\n" + + "Use 'migration' level to issue warnings instead of errors." + ) + var apiMode: String by FreezableVar(ApiMode.DISABLED.state) + open fun configureAnalysisFlags(collector: MessageCollector): MutableMap, Any> { return HashMap, Any>().apply { put(AnalysisFlags.skipMetadataVersionCheck, skipMetadataVersionCheck) @@ -324,6 +332,10 @@ abstract class CommonCompilerArguments : CommonToolArguments() { put(AnalysisFlags.useExperimental, useExperimental?.toList().orEmpty()) put(AnalysisFlags.explicitApiVersion, apiVersion != null) put(AnalysisFlags.allowResultReturnType, allowResultReturnType) + ApiMode.fromString(apiMode)?.also { put(AnalysisFlags.apiMode, it) } ?: collector.report( + CompilerMessageSeverity.ERROR, + "Unknown value for parameter -Xapi-mode: '$apiMode'. Value should be one of ${ApiMode.availableValues()}" + ) } } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/config/AnalysisFlags.kt b/compiler/frontend/src/org/jetbrains/kotlin/config/AnalysisFlags.kt index aefadcc1dda..d7e72035cd6 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/config/AnalysisFlags.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/config/AnalysisFlags.kt @@ -27,6 +27,9 @@ object AnalysisFlags { @JvmStatic val allowResultReturnType by AnalysisFlag.Delegates.Boolean + @JvmStatic + val apiMode by AnalysisFlag.Delegates.ApiModeDisabledByDefault + @JvmStatic val constraintSystemForOverloadResolution by AnalysisFlag.Delegates.ConstraintSystemForOverloadResolution diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index 87a95a82a6b..07cb7bda75a 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -207,6 +207,8 @@ 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); + DiagnosticFactory0 NO_EXPLICIT_VISIBILITY_IN_API_MODE_MIGRATION = DiagnosticFactory0.create(WARNING); DiagnosticFactory2 WRONG_MODIFIER_CONTAINING_DECLARATION = DiagnosticFactory2.create(ERROR); DiagnosticFactory2 DEPRECATED_MODIFIER_CONTAINING_DECLARATION = DiagnosticFactory2.create(WARNING); DiagnosticFactory1 ILLEGAL_INLINE_PARAMETER_MODIFIER = DiagnosticFactory1.create(ERROR); 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 72b77756837..e7e0ee4ca03 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -119,6 +119,8 @@ 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_MIGRATION, "This declaration is effectively public API and API mode is on, but no visibility 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); MAP.put(ILLEGAL_INLINE_PARAMETER_MODIFIER, "Modifier ''{0}'' is allowed only for function parameters of an inline function", TO_STRING); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/PlatformConfiguratorBase.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/PlatformConfiguratorBase.kt index 57157ebc914..c04f3d017b6 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/PlatformConfiguratorBase.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/PlatformConfiguratorBase.kt @@ -32,6 +32,7 @@ private val DEFAULT_DECLARATION_CHECKERS = listOf( ReservedMembersAndConstructsForInlineClass(), ResultClassInReturnTypeChecker(), LocalVariableTypeParametersChecker(), + ApiModeDeclarationChecker(), TailrecFunctionChecker, TrailingCommaDeclarationChecker ) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ApiModeDeclarationChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ApiModeDeclarationChecker.kt new file mode 100644 index 00000000000..d4879630a33 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ApiModeDeclarationChecker.kt @@ -0,0 +1,61 @@ +/* + * 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.resolve.checkers + +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.Errors +import org.jetbrains.kotlin.diagnostics.reportDiagnosticOnce +import org.jetbrains.kotlin.lexer.KtModifierKeywordToken +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.psiUtil.visibilityModifier +import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyPublicApi + +class ApiModeDeclarationChecker : DeclarationChecker { + override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) { + val state = isEnabled(context.languageVersionSettings) + if (state == ApiMode.DISABLED) return + + val isApi = (descriptor as? DeclarationDescriptorWithVisibility)?.isEffectivelyPublicApi ?: return + if (!isApi) return + val modifier = declaration.visibilityModifier()?.node?.elementType as? KtModifierKeywordToken + if (modifier != null) return + + if (excludeForDiagnostic(descriptor)) return + val diagnostic = + if (state == ApiMode.ENABLED) + Errors.NO_EXPLICIT_VISIBILITY_IN_API_MODE.on(declaration) + else + Errors.NO_EXPLICIT_VISIBILITY_IN_API_MODE_MIGRATION.on(declaration) + context.trace.reportDiagnosticOnce(diagnostic) + } + + /** + * Exclusion list: + * 1. Primary constructors of public API classes + * 3. Members of public API interfaces + * 4. do not report overrides of public API? effectively, this means 'no report on overrides at all' + * + * Do we need something like @PublicApiFile to disable (or invert) this inspection per-file? + */ + private fun excludeForDiagnostic(descriptor: DeclarationDescriptor): Boolean { + /* 1. */ if ((descriptor as? ClassConstructorDescriptor)?.isPrimary == true) return true + val isMemberOfPublicInterface = + (descriptor.containingDeclaration as? ClassDescriptor)?.let { DescriptorUtils.isInterface(it) && it.effectiveVisibility().publicApi } + /* 3. */ if (descriptor is CallableDescriptor && isMemberOfPublicInterface == true) return true + /* 4. */ if ((descriptor as? CallableDescriptor)?.overriddenDescriptors?.isNotEmpty() == true) return true + return false + } + + companion object { + fun isEnabled(settings: LanguageVersionSettings): ApiMode { + return settings.getFlag(AnalysisFlags.apiMode) + } + } +} \ No newline at end of file diff --git a/compiler/testData/cli/js/jsExtraHelp.out b/compiler/testData/cli/js/jsExtraHelp.out index 8c6c973a21e..da809920555 100644 --- a/compiler/testData/cli/js/jsExtraHelp.out +++ b/compiler/testData/cli/js/jsExtraHelp.out @@ -11,6 +11,9 @@ where advanced options include: -Xtyped-arrays Translate primitive arrays to JS typed arrays -Xallow-kotlin-package Allow compiling code in package 'kotlin' and allow not requiring kotlin.stdlib in module-info -Xallow-result-return-type Allow compiling code when `kotlin.Result` is used as a return type + -Xapi-mode={enable|disable|migration} + Enable api mode, force compiler to report errors an all public API declarations without explicit visibility. + Use 'migration' level to issue warnings instead of errors. -Xcheck-phase-conditions Check pre- and postconditions on phases -Xcheck-sticky-phase-conditions Run sticky condition checks on subsequent phases as well. Implies -Xcheck-phase-conditions diff --git a/compiler/testData/cli/jvm/extraHelp.out b/compiler/testData/cli/jvm/extraHelp.out index fd6e5cfe92c..6486e3f4ad3 100644 --- a/compiler/testData/cli/jvm/extraHelp.out +++ b/compiler/testData/cli/jvm/extraHelp.out @@ -70,6 +70,9 @@ where advanced options include: -Xuse-type-table Use type table in metadata serialization -Xallow-kotlin-package Allow compiling code in package 'kotlin' and allow not requiring kotlin.stdlib in module-info -Xallow-result-return-type Allow compiling code when `kotlin.Result` is used as a return type + -Xapi-mode={enable|disable|migration} + Enable api mode, force compiler to report errors an all public API declarations without explicit visibility. + Use 'migration' level to issue warnings instead of errors. -Xcheck-phase-conditions Check pre- and postconditions on phases -Xcheck-sticky-phase-conditions Run sticky condition checks on subsequent phases as well. Implies -Xcheck-phase-conditions diff --git a/compiler/util/src/org/jetbrains/kotlin/config/AnalysisFlag.kt b/compiler/util/src/org/jetbrains/kotlin/config/AnalysisFlag.kt index e886794305e..9c2b89adecc 100644 --- a/compiler/util/src/org/jetbrains/kotlin/config/AnalysisFlag.kt +++ b/compiler/util/src/org/jetbrains/kotlin/config/AnalysisFlag.kt @@ -38,6 +38,10 @@ class AnalysisFlag internal constructor( operator fun provideDelegate(instance: Any?, property: KProperty<*>) = Delegate(property.name, JvmDefaultMode.DISABLE) } + object ApiModeDisabledByDefault { + operator fun provideDelegate(instance: Any?, property: KProperty<*>) = Delegate(property.name, ApiMode.DISABLED) + } + object ListOfStrings { operator fun provideDelegate(instance: Any?, property: KProperty<*>) = Delegate(property.name, emptyList()) } diff --git a/compiler/util/src/org/jetbrains/kotlin/config/ApiMode.kt b/compiler/util/src/org/jetbrains/kotlin/config/ApiMode.kt new file mode 100644 index 00000000000..d13d6fb13d5 --- /dev/null +++ b/compiler/util/src/org/jetbrains/kotlin/config/ApiMode.kt @@ -0,0 +1,18 @@ +/* + * 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.config + +enum class ApiMode(val state: String) { + DISABLED("disable"), + ENABLED("enable"), + MIGRATION("migration"); + + companion object { + fun fromString(string: String): ApiMode? = values().find { it.state == string } + + fun availableValues() = values().joinToString(prefix = "{", postfix = "}") { it.state } + } +} \ No newline at end of file