From c262f17d75db97ec38fc146aa5ade3c536810d92 Mon Sep 17 00:00:00 2001 From: Tianyu Geng Date: Tue, 12 Oct 2021 02:01:45 +0300 Subject: [PATCH] FIR checker: report violations related to API mode --- .../diagnostics/KtFirDataClassConverters.kt | 24 +++ .../api/fir/diagnostics/KtFirDiagnostics.kt | 16 ++ .../fir/diagnostics/KtFirDiagnosticsImpl.kt | 20 +++ .../diagnostics/FirDiagnosticsList.kt | 6 + .../model/RegularDiagnosticData.kt | 3 +- .../fir/analysis/diagnostics/FirErrors.kt | 4 + .../checkers/CommonDeclarationCheckers.kt | 1 + .../FirExplicitApiDeclarationChecker.kt | 146 ++++++++++++++++++ .../diagnostics/FirDefaultErrorMessages.kt | 9 ++ .../LightTreePositioningStrategies.kt | 43 ++++++ .../SourceElementPositioningStrategies.kt | 11 +- .../testsWithExplicitApi/annotations.fir.kt | 47 ------ .../tests/testsWithExplicitApi/annotations.kt | 1 + .../tests/testsWithExplicitApi/classes.fir.kt | 47 ------ .../tests/testsWithExplicitApi/classes.kt | 3 +- .../companionObject.fir.kt | 20 --- .../testsWithExplicitApi/companionObject.kt | 1 + .../testsWithExplicitApi/constructors.fir.kt | 17 -- .../testsWithExplicitApi/constructors.kt | 1 + .../testsWithExplicitApi/inlineClasses.fir.kt | 8 - .../testsWithExplicitApi/inlineClasses.kt | 3 +- .../testsWithExplicitApi/interfaces.fir.kt | 35 ----- .../tests/testsWithExplicitApi/interfaces.kt | 1 + .../testsWithExplicitApi/properties.fir.kt | 23 --- .../tests/testsWithExplicitApi/properties.kt | 1 + .../testsWithExplicitApi/publishedApi.fir.kt | 7 - .../testsWithExplicitApi/publishedApi.kt | 1 + .../testsWithExplicitApi/toplevel.fir.kt | 12 -- .../tests/testsWithExplicitApi/toplevel.kt | 1 + .../test/runners/AbstractFirDiagnosticTest.kt | 8 + 30 files changed, 298 insertions(+), 222 deletions(-) create mode 100644 compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/syntax/FirExplicitApiDeclarationChecker.kt delete mode 100644 compiler/testData/diagnostics/tests/testsWithExplicitApi/annotations.fir.kt delete mode 100644 compiler/testData/diagnostics/tests/testsWithExplicitApi/classes.fir.kt delete mode 100644 compiler/testData/diagnostics/tests/testsWithExplicitApi/companionObject.fir.kt delete mode 100644 compiler/testData/diagnostics/tests/testsWithExplicitApi/constructors.fir.kt delete mode 100644 compiler/testData/diagnostics/tests/testsWithExplicitApi/inlineClasses.fir.kt delete mode 100644 compiler/testData/diagnostics/tests/testsWithExplicitApi/interfaces.fir.kt delete mode 100644 compiler/testData/diagnostics/tests/testsWithExplicitApi/properties.fir.kt delete mode 100644 compiler/testData/diagnostics/tests/testsWithExplicitApi/publishedApi.fir.kt delete mode 100644 compiler/testData/diagnostics/tests/testsWithExplicitApi/toplevel.fir.kt diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt index 5993e39f11e..462390bf55f 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt @@ -1199,6 +1199,30 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert token, ) } + add(FirErrors.NO_EXPLICIT_VISIBILITY_IN_API_MODE) { firDiagnostic -> + NoExplicitVisibilityInApiModeImpl( + firDiagnostic as FirPsiDiagnostic, + token, + ) + } + add(FirErrors.NO_EXPLICIT_VISIBILITY_IN_API_MODE_WARNING) { firDiagnostic -> + NoExplicitVisibilityInApiModeWarningImpl( + firDiagnostic as FirPsiDiagnostic, + token, + ) + } + add(FirErrors.NO_EXPLICIT_RETURN_TYPE_IN_API_MODE) { firDiagnostic -> + NoExplicitReturnTypeInApiModeImpl( + firDiagnostic as FirPsiDiagnostic, + token, + ) + } + add(FirErrors.NO_EXPLICIT_RETURN_TYPE_IN_API_MODE_WARNING) { firDiagnostic -> + NoExplicitReturnTypeInApiModeWarningImpl( + firDiagnostic as FirPsiDiagnostic, + token, + ) + } add(FirErrors.INLINE_CLASS_NOT_TOP_LEVEL) { firDiagnostic -> InlineClassNotTopLevelImpl( firDiagnostic as FirPsiDiagnostic, diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt index 2f10a464a8f..d5f030d2752 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt @@ -867,6 +867,22 @@ sealed class KtFirDiagnostic : KtDiagnosticWithPsi { abstract val message: String } + abstract class NoExplicitVisibilityInApiMode : KtFirDiagnostic() { + override val diagnosticClass get() = NoExplicitVisibilityInApiMode::class + } + + abstract class NoExplicitVisibilityInApiModeWarning : KtFirDiagnostic() { + override val diagnosticClass get() = NoExplicitVisibilityInApiModeWarning::class + } + + abstract class NoExplicitReturnTypeInApiMode : KtFirDiagnostic() { + override val diagnosticClass get() = NoExplicitReturnTypeInApiMode::class + } + + abstract class NoExplicitReturnTypeInApiModeWarning : KtFirDiagnostic() { + override val diagnosticClass get() = NoExplicitReturnTypeInApiModeWarning::class + } + abstract class InlineClassNotTopLevel : KtFirDiagnostic() { override val diagnosticClass get() = InlineClassNotTopLevel::class } diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt index 99dce0274d9..45db5883452 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt @@ -1036,6 +1036,26 @@ internal class InapplicableOperatorModifierImpl( override val token: ValidityToken, ) : KtFirDiagnostic.InapplicableOperatorModifier(), KtAbstractFirDiagnostic +internal class NoExplicitVisibilityInApiModeImpl( + override val firDiagnostic: FirPsiDiagnostic, + override val token: ValidityToken, +) : KtFirDiagnostic.NoExplicitVisibilityInApiMode(), KtAbstractFirDiagnostic + +internal class NoExplicitVisibilityInApiModeWarningImpl( + override val firDiagnostic: FirPsiDiagnostic, + override val token: ValidityToken, +) : KtFirDiagnostic.NoExplicitVisibilityInApiModeWarning(), KtAbstractFirDiagnostic + +internal class NoExplicitReturnTypeInApiModeImpl( + override val firDiagnostic: FirPsiDiagnostic, + override val token: ValidityToken, +) : KtFirDiagnostic.NoExplicitReturnTypeInApiMode(), KtAbstractFirDiagnostic + +internal class NoExplicitReturnTypeInApiModeWarningImpl( + override val firDiagnostic: FirPsiDiagnostic, + override val token: ValidityToken, +) : KtFirDiagnostic.NoExplicitReturnTypeInApiModeWarning(), KtAbstractFirDiagnostic + internal class InlineClassNotTopLevelImpl( override val firDiagnostic: FirPsiDiagnostic, override val token: ValidityToken, diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt index a832f3c7c19..11ccd85bb76 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt @@ -381,6 +381,12 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") { val INAPPLICABLE_OPERATOR_MODIFIER by error(PositioningStrategy.OPERATOR_MODIFIER) { parameter("message") } + + val NO_EXPLICIT_VISIBILITY_IN_API_MODE by error(PositioningStrategy.DECLARATION_START_TO_NAME) + val NO_EXPLICIT_VISIBILITY_IN_API_MODE_WARNING by warning(PositioningStrategy.DECLARATION_START_TO_NAME) + + val NO_EXPLICIT_RETURN_TYPE_IN_API_MODE by error(PositioningStrategy.DECLARATION_NAME) + val NO_EXPLICIT_RETURN_TYPE_IN_API_MODE_WARNING by warning(PositioningStrategy.DECLARATION_NAME) } val INLINE_CLASSES by object : DiagnosticGroup("Inline classes") { diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/model/RegularDiagnosticData.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/model/RegularDiagnosticData.kt index 3d533ebf64f..711996b688e 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/model/RegularDiagnosticData.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/model/RegularDiagnosticData.kt @@ -108,8 +108,9 @@ enum class PositioningStrategy(private val strategy: String? = null) { ENUM_MODIFIER, FIELD_KEYWORD, TAILREC_MODIFIER, - IMPORT_ALIAS, PROPERTY_DELEGATE, + IMPORT_ALIAS, + DECLARATION_START_TO_NAME, ; diff --git a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt index f4ed3ab9124..a059b46f8bf 100644 --- a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt +++ b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt @@ -284,6 +284,10 @@ object FirErrors { val WRONG_MODIFIER_CONTAINING_DECLARATION by error2() val DEPRECATED_MODIFIER_CONTAINING_DECLARATION by warning2() val INAPPLICABLE_OPERATOR_MODIFIER by error1(SourceElementPositioningStrategies.OPERATOR_MODIFIER) + val NO_EXPLICIT_VISIBILITY_IN_API_MODE by error0(SourceElementPositioningStrategies.DECLARATION_START_TO_NAME) + val NO_EXPLICIT_VISIBILITY_IN_API_MODE_WARNING by warning0(SourceElementPositioningStrategies.DECLARATION_START_TO_NAME) + val NO_EXPLICIT_RETURN_TYPE_IN_API_MODE by error0(SourceElementPositioningStrategies.DECLARATION_NAME) + val NO_EXPLICIT_RETURN_TYPE_IN_API_MODE_WARNING by warning0(SourceElementPositioningStrategies.DECLARATION_NAME) // Inline classes val INLINE_CLASS_NOT_TOP_LEVEL by error0(SourceElementPositioningStrategies.INLINE_OR_VALUE_MODIFIER) diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonDeclarationCheckers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonDeclarationCheckers.kt index 617abae8d2c..e708abca6f8 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonDeclarationCheckers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonDeclarationCheckers.kt @@ -29,6 +29,7 @@ object CommonDeclarationCheckers : DeclarationCheckers() { FirExpectActualDeclarationChecker, FirInvalidAndDangerousCharactersChecker, FirAmbiguousAnonymousTypeChecker, + FirExplicitApiDeclarationChecker, ) override val functionCheckers: Set diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/syntax/FirExplicitApiDeclarationChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/syntax/FirExplicitApiDeclarationChecker.kt new file mode 100644 index 00000000000..3f87f67fb9c --- /dev/null +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/syntax/FirExplicitApiDeclarationChecker.kt @@ -0,0 +1,146 @@ +/* + * 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.fir.analysis.checkers.syntax + +import org.jetbrains.kotlin.KtNodeTypes +import org.jetbrains.kotlin.config.AnalysisFlags +import org.jetbrains.kotlin.config.ExplicitApiMode +import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.fir.FirFakeSourceElementKind +import org.jetbrains.kotlin.fir.FirRealSourceElementKind +import org.jetbrains.kotlin.fir.FirSourceElement +import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext +import org.jetbrains.kotlin.fir.analysis.checkers.declaration.isLocalMember +import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors +import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn +import org.jetbrains.kotlin.fir.analysis.getChild +import org.jetbrains.kotlin.fir.declarations.* +import org.jetbrains.kotlin.fir.declarations.impl.FirPrimaryConstructor +import org.jetbrains.kotlin.fir.declarations.utils.effectiveVisibility +import org.jetbrains.kotlin.fir.declarations.utils.isData +import org.jetbrains.kotlin.fir.declarations.utils.isOverride +import org.jetbrains.kotlin.fir.languageVersionSettings +import org.jetbrains.kotlin.fir.resolve.transformers.publishedApiEffectiveVisibility +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.KtDeclaration + +object FirExplicitApiDeclarationChecker : FirDeclarationSyntaxChecker() { + private val codeFragmentTypes = + setOf(KtNodeTypes.BLOCK_CODE_FRAGMENT, KtNodeTypes.EXPRESSION_CODE_FRAGMENT, KtNodeTypes.TYPE_CODE_FRAGMENT) + + override fun checkLightTree(element: FirDeclaration, source: FirSourceElement, context: CheckerContext, reporter: DiagnosticReporter) { + if ((source.kind !is FirRealSourceElementKind && source.kind != FirFakeSourceElementKind.PropertyFromParameter) || + element !is FirMemberDeclaration + ) { + return + } + val state = context.session.languageVersionSettings.getFlag(AnalysisFlags.explicitApiMode) + if (state == ExplicitApiMode.DISABLED) return + // Enum entries do not have visibilities + if (element is FirEnumEntry) return + if (!element.effectiveVisibility.publicApi && element.publishedApiEffectiveVisibility == null) return + + checkVisibilityModifier(state, element, source, context, reporter) + checkExplicitReturnType(state, element, source, context, reporter) + } + + private fun checkVisibilityModifier( + state: ExplicitApiMode, + declaration: FirMemberDeclaration, + source: FirSourceElement, + context: CheckerContext, + reporter: DiagnosticReporter + ) { + val visibilityModifier = source.getChild(KtNodeTypes.MODIFIER_LIST)?.getChild(KtTokens.VISIBILITY_MODIFIERS) + if (visibilityModifier != null) return + + if (explicitVisibilityIsNotRequired(declaration, context)) return + val factory = if (state == ExplicitApiMode.STRICT) + FirErrors.NO_EXPLICIT_VISIBILITY_IN_API_MODE + else + FirErrors.NO_EXPLICIT_VISIBILITY_IN_API_MODE_WARNING + reporter.reportOn(source, factory, context) + } + + /** + * Exclusion list: + * 1. Primary constructors of public API classes + * 2. Properties of data classes in public API + * 3. Overrides of public API. Effectively, this means 'no report on overrides at all' + * 4. Getters and setters (because getters can't change visibility and setter-only explicit visibility looks ugly) + * 5. Properties of annotations in public API + * 6. Value parameter declaration + * + * TODO: Do we need something like @PublicApiFile to disable (or invert) this inspection per-file? + */ + private fun explicitVisibilityIsNotRequired(declaration: FirMemberDeclaration, context: CheckerContext): Boolean { + return when (declaration) { + // 1, + is FirPrimaryConstructor, + // 4 + is FirPropertyAccessor, + // 6 + is FirValueParameter -> true + is FirCallableDeclaration -> { + val containingClass = context.containingDeclarations.lastOrNull() as? FirRegularClass + // 2, 5 + if (declaration is FirProperty && + containingClass != null && + (containingClass.isData || containingClass.classKind == ClassKind.ANNOTATION_CLASS) + ) { + return true + } + + // 3 + declaration.isOverride + } + else -> false + } + } + + private fun checkExplicitReturnType( + state: ExplicitApiMode, + declaration: FirMemberDeclaration, + source: FirSourceElement, + context: CheckerContext, + reporter: DiagnosticReporter + ) { + if (declaration !is FirCallableDeclaration) return + if (!returnTypeCheckIsApplicable(source, context)) return + + val shouldReport = returnTypeRequired(declaration, context) + if (shouldReport) { + val factory = + if (state == ExplicitApiMode.STRICT) + FirErrors.NO_EXPLICIT_RETURN_TYPE_IN_API_MODE + else + FirErrors.NO_EXPLICIT_RETURN_TYPE_IN_API_MODE_WARNING + reporter.reportOn(source, factory, context) + } + } + + private fun returnTypeCheckIsApplicable(source: FirSourceElement, context: CheckerContext): Boolean { + // Note that by default getChild uses `depth = -1`, which would find all descendents. + if (source.getChild(KtNodeTypes.TYPE_REFERENCE, depth = 1) != null) return false + // Do not check if the containing file is not a physical file. + val containingFile = context.containingDeclarations.first() + if (containingFile.source?.elementType in codeFragmentTypes) return false + + return when (source.elementType) { + // Only require return type if the function is defined via `=`. If it has a block body or is abstract, we don't require return + // type because not declaring means it returns `Unit`. + KtNodeTypes.FUN -> source.getChild(KtTokens.EQ, depth = 1) != null + KtNodeTypes.PROPERTY -> true + else -> false + } + } + + private fun returnTypeRequired(declaration: FirCallableDeclaration, context: CheckerContext): Boolean { + // If current declaration is local or it's a member in a local declaration (local class, etc), then we do not require return type. + return !declaration.isLocalMember && context.containingDeclarations.lastOrNull()?.isLocalMember != true + } +} \ No newline at end of file diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt index 7f25082d3fc..ab5026be9ed 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt @@ -308,6 +308,10 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NO_ACTUAL_CLASS_M import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NO_ACTUAL_FOR_EXPECT import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NO_COMPANION_OBJECT import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NO_ELSE_IN_WHEN +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NO_EXPLICIT_RETURN_TYPE_IN_API_MODE +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NO_EXPLICIT_RETURN_TYPE_IN_API_MODE_WARNING +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NO_EXPLICIT_VISIBILITY_IN_API_MODE +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NO_EXPLICIT_VISIBILITY_IN_API_MODE_WARNING import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NO_GET_METHOD import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NO_RECEIVER_ALLOWED import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NO_RETURN_IN_FUNCTION_WITH_BLOCK_BODY @@ -799,6 +803,11 @@ class FirDefaultErrorMessages { map.put(DEPRECATED_MODIFIER_PAIR, "Modifier ''{0}'' is deprecated in presence of ''{1}''", TO_STRING, TO_STRING) map.put(DEPRECATED_MODIFIER_FOR_TARGET, "Modifier ''{0}'' is deprecated for ''{1}''", TO_STRING, STRING) map.put(REDUNDANT_MODIFIER_FOR_TARGET, "Modifier ''{0}'' is redundant for ''{1}''", TO_STRING, STRING) + map.put(NO_EXPLICIT_VISIBILITY_IN_API_MODE, "Visibility must be specified in explicit API mode"); + map.put(NO_EXPLICIT_RETURN_TYPE_IN_API_MODE, "Return type must be specified in explicit API mode"); + map.put(NO_EXPLICIT_VISIBILITY_IN_API_MODE_WARNING, "Visibility must be specified in explicit API mode"); + map.put(NO_EXPLICIT_RETURN_TYPE_IN_API_MODE_WARNING, "Return type must be specified in explicit API mode"); + map.put(INCOMPATIBLE_MODIFIERS, "Modifier ''{0}'' is incompatible with ''{1}''", TO_STRING, TO_STRING) map.put(REDUNDANT_OPEN_IN_INTERFACE, "Modifier 'open' is redundant for abstract interface members") map.put(WRONG_MODIFIER_TARGET, "Modifier ''{0}'' is not applicable to ''{1}''", TO_STRING, STRING) diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/LightTreePositioningStrategies.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/LightTreePositioningStrategies.kt index c26634698db..c36411ff39c 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/LightTreePositioningStrategies.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/LightTreePositioningStrategies.kt @@ -148,6 +148,43 @@ object LightTreePositioningStrategies { } } + val DECLARATION_START_TO_NAME: LightTreePositioningStrategy = object : LightTreePositioningStrategy() { + + private fun FlyweightCapableTreeStructure.firstNonCommentNonAnnotationLeaf(node: LighterASTNode): LighterASTNode? { + val childrenArray = getChildrenArray(node).filterNotNull() + // this is leaf + if (childrenArray.isEmpty()) return node + for (child in childrenArray) { + val childTokenType = child.tokenType ?: return null + if (childTokenType in KtTokens.WHITE_SPACE_OR_COMMENT_BIT_SET || childTokenType == KtNodeTypes.ANNOTATION_ENTRY) { + continue + } + return firstNonCommentNonAnnotationLeaf(child) ?: continue + } + return null + } + + override fun mark( + node: LighterASTNode, + startOffset: Int, + endOffset: Int, + tree: FlyweightCapableTreeStructure + ): List { + val startNode = tree.firstNonCommentNonAnnotationLeaf(node) ?: node + val nameIdentifier = tree.nameIdentifier(node) + return if (nameIdentifier != null) { + markRange(startNode, nameIdentifier, startOffset, endOffset, tree, node) + } else { + val endNode = when (node.tokenType) { + KtNodeTypes.PRIMARY_CONSTRUCTOR, KtNodeTypes.SECONDARY_CONSTRUCTOR -> tree.constructorKeyword(node) + KtNodeTypes.OBJECT_DECLARATION -> tree.objectKeyword(node) + else -> return DEFAULT.mark(node, startOffset, endOffset, tree) + } + markRange(startNode, endNode ?: node, startOffset, endOffset, tree, node) + } + } + } + val DECLARATION_NAME: LightTreePositioningStrategy = object : LightTreePositioningStrategy() { override fun mark( node: LighterASTNode, @@ -1083,6 +1120,12 @@ fun LighterASTNode.isExpression(): Boolean { } } +fun FlyweightCapableTreeStructure.getChildrenArray(node: LighterASTNode): Array { + val childrenRef = Ref>() + getChildren(node, childrenRef) + return childrenRef.get() ?: emptyArray() +} + /** * @param locateReferencedName whether to remove any nested parentheses while locating the reference element. This is useful for diagnostics * on super and unresolved references. For example, with the following, only the part inside the parentheses should be highlighted. diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/SourceElementPositioningStrategies.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/SourceElementPositioningStrategies.kt index 7109fbc96eb..0b958d0044d 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/SourceElementPositioningStrategies.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/SourceElementPositioningStrategies.kt @@ -330,13 +330,18 @@ object SourceElementPositioningStrategies { PositioningStrategies.TAILREC_MODIFIER ) + val PROPERTY_DELEGATE = SourceElementPositioningStrategy( + LightTreePositioningStrategies.PROPERTY_DELEGATE, + PositioningStrategies.PROPERTY_DELEGATE + ) + val IMPORT_ALIAS = SourceElementPositioningStrategy( LightTreePositioningStrategies.IMPORT_ALIAS, PositioningStrategies.IMPORT_ALIAS ) - val PROPERTY_DELEGATE = SourceElementPositioningStrategy( - LightTreePositioningStrategies.PROPERTY_DELEGATE, - PositioningStrategies.PROPERTY_DELEGATE + val DECLARATION_START_TO_NAME = SourceElementPositioningStrategy( + LightTreePositioningStrategies.DECLARATION_START_TO_NAME, + PositioningStrategies.DECLARATION_START_TO_NAME ) } diff --git a/compiler/testData/diagnostics/tests/testsWithExplicitApi/annotations.fir.kt b/compiler/testData/diagnostics/tests/testsWithExplicitApi/annotations.fir.kt deleted file mode 100644 index 5fb4373d009..00000000000 --- a/compiler/testData/diagnostics/tests/testsWithExplicitApi/annotations.fir.kt +++ /dev/null @@ -1,47 +0,0 @@ -// SKIP_TXT - -annotation class A - -@Target( - AnnotationTarget.CLASS, - AnnotationTarget.PROPERTY, - AnnotationTarget.CONSTRUCTOR, - AnnotationTarget.FUNCTION -) -public annotation class B - -annotation class C(val a: String) - -/** - * Foo1 KDoc - */ -@B -class Foo1() {} - -public class Foo2() { - /** - * KDoc for methodWithAnnotations - */ - @B - fun methodWithAnnotations() {} - - /** - * Property KDoc - */ - @B - var simple: Int = 10 -} - -public open class ClassWithOpen { - /** - * constructor KDoc - */ - @B - constructor() {} - - /** - * KDoc for openAnnotatedMethod - */ - @B - open fun openAnnotatedMethod() {} -} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/testsWithExplicitApi/annotations.kt b/compiler/testData/diagnostics/tests/testsWithExplicitApi/annotations.kt index a8db31f4ea1..8c427d9646e 100644 --- a/compiler/testData/diagnostics/tests/testsWithExplicitApi/annotations.kt +++ b/compiler/testData/diagnostics/tests/testsWithExplicitApi/annotations.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // SKIP_TXT annotation class A diff --git a/compiler/testData/diagnostics/tests/testsWithExplicitApi/classes.fir.kt b/compiler/testData/diagnostics/tests/testsWithExplicitApi/classes.fir.kt deleted file mode 100644 index b94ba0effef..00000000000 --- a/compiler/testData/diagnostics/tests/testsWithExplicitApi/classes.fir.kt +++ /dev/null @@ -1,47 +0,0 @@ -// SKIP_TXT - -/** - * KDoc for Foo1 - */ -class Foo1() {} - -public class Foo2() { - /** - * KDoc for method - */ - fun method() {} - - /** - * KDoc for method2 - */ - public fun method2() {} - private fun method3() {} - - fun implicit() = 10 - public fun implicit2() = 10 - public fun implicit3(): Int = 10 -} - -public open class ClassWithOpen() { - /** - * KDoc for method - */ - fun method() {} - - /** - * KDoc for openMethod - */ - open fun openMethod() {} -} - -public data class FooData(val i: Int, val s: String) - -data class FooData2(val i: Int, val s: String) - -public class WithNested { - class Nested {} - inner class Inner {} -} - -enum class Foo { A, B } -public enum class Bar { A, B } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/testsWithExplicitApi/classes.kt b/compiler/testData/diagnostics/tests/testsWithExplicitApi/classes.kt index 7aabf5e2a0f..4fac964150b 100644 --- a/compiler/testData/diagnostics/tests/testsWithExplicitApi/classes.kt +++ b/compiler/testData/diagnostics/tests/testsWithExplicitApi/classes.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // SKIP_TXT /** @@ -44,4 +45,4 @@ public class WithNested { } enum class Foo { A, B } -public enum class Bar { A, B } \ No newline at end of file +public enum class Bar { A, B } diff --git a/compiler/testData/diagnostics/tests/testsWithExplicitApi/companionObject.fir.kt b/compiler/testData/diagnostics/tests/testsWithExplicitApi/companionObject.fir.kt deleted file mode 100644 index fb806d2ac0f..00000000000 --- a/compiler/testData/diagnostics/tests/testsWithExplicitApi/companionObject.fir.kt +++ /dev/null @@ -1,20 +0,0 @@ -// SKIP_TXT - -public class Bar { - companion object {} -} - -public class Bar2 { - companion object MyCompanion {} -} - -public class Bar3 { - /** - * Nested object KDoc - */ - object NestedObject {} -} - -data class FooData2(val i: Int, val s: String) { - object NestedObject {} -} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/testsWithExplicitApi/companionObject.kt b/compiler/testData/diagnostics/tests/testsWithExplicitApi/companionObject.kt index 6109fe28b21..3e151b5932b 100644 --- a/compiler/testData/diagnostics/tests/testsWithExplicitApi/companionObject.kt +++ b/compiler/testData/diagnostics/tests/testsWithExplicitApi/companionObject.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // SKIP_TXT public class Bar { diff --git a/compiler/testData/diagnostics/tests/testsWithExplicitApi/constructors.fir.kt b/compiler/testData/diagnostics/tests/testsWithExplicitApi/constructors.fir.kt deleted file mode 100644 index fc5b06b9848..00000000000 --- a/compiler/testData/diagnostics/tests/testsWithExplicitApi/constructors.fir.kt +++ /dev/null @@ -1,17 +0,0 @@ -// SKIP_TXT - -public class Foo1 () {} -public class Foo2 constructor() {} -public class Foo3 public constructor() {} -public class Foo4 private constructor() {} - -public class Foo5 { - /** - * constructor KDoc - */ - constructor() {} -} - -public class Foo6 { - public constructor() {} -} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/testsWithExplicitApi/constructors.kt b/compiler/testData/diagnostics/tests/testsWithExplicitApi/constructors.kt index 1274e474233..29c1eb7582b 100644 --- a/compiler/testData/diagnostics/tests/testsWithExplicitApi/constructors.kt +++ b/compiler/testData/diagnostics/tests/testsWithExplicitApi/constructors.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // SKIP_TXT public class Foo1 () {} diff --git a/compiler/testData/diagnostics/tests/testsWithExplicitApi/inlineClasses.fir.kt b/compiler/testData/diagnostics/tests/testsWithExplicitApi/inlineClasses.fir.kt deleted file mode 100644 index 85234994011..00000000000 --- a/compiler/testData/diagnostics/tests/testsWithExplicitApi/inlineClasses.fir.kt +++ /dev/null @@ -1,8 +0,0 @@ -// !DIAGNOSTICS: -EXPERIMENTAL_FEATURE_WARNING -// !LANGUAGE: -JvmInlineValueClasses -// SKIP_TXT - -inline class Value1(val inner: Int) -public inline class Value2(val inner: Int) -inline class Value3(public val inner: Int) -public inline class Value4(public val inner: Int) \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/testsWithExplicitApi/inlineClasses.kt b/compiler/testData/diagnostics/tests/testsWithExplicitApi/inlineClasses.kt index 4ac8337119e..b97598739d8 100644 --- a/compiler/testData/diagnostics/tests/testsWithExplicitApi/inlineClasses.kt +++ b/compiler/testData/diagnostics/tests/testsWithExplicitApi/inlineClasses.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // !DIAGNOSTICS: -EXPERIMENTAL_FEATURE_WARNING // !LANGUAGE: -JvmInlineValueClasses // SKIP_TXT @@ -5,4 +6,4 @@ inline class Value1(val inner: Int) public inline class Value2(val inner: Int) inline class Value3(public val inner: Int) -public inline class Value4(public val inner: Int) \ No newline at end of file +public inline class Value4(public val inner: Int) diff --git a/compiler/testData/diagnostics/tests/testsWithExplicitApi/interfaces.fir.kt b/compiler/testData/diagnostics/tests/testsWithExplicitApi/interfaces.fir.kt deleted file mode 100644 index 865fc8dd083..00000000000 --- a/compiler/testData/diagnostics/tests/testsWithExplicitApi/interfaces.fir.kt +++ /dev/null @@ -1,35 +0,0 @@ -// SKIP_TXT - -interface I1 { - fun i() -} - -public interface I2 { - fun i() -} - -public interface I3 { - public fun i() - public val v: Int -} - -public interface I4 { - public fun i(): Int - public val v: Int -} - -public class Impl: I3 { - override fun i() {} - override val v: Int - get() = 10 -} - -public class Impl2: I4 { - override fun i() = 10 - override val v = 10 -} - -private class PrivateImpl: I4 { - override fun i() = 10 - override val v = 10 -} diff --git a/compiler/testData/diagnostics/tests/testsWithExplicitApi/interfaces.kt b/compiler/testData/diagnostics/tests/testsWithExplicitApi/interfaces.kt index 4ca83833ec4..92a758453a7 100644 --- a/compiler/testData/diagnostics/tests/testsWithExplicitApi/interfaces.kt +++ b/compiler/testData/diagnostics/tests/testsWithExplicitApi/interfaces.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // SKIP_TXT interface I1 { diff --git a/compiler/testData/diagnostics/tests/testsWithExplicitApi/properties.fir.kt b/compiler/testData/diagnostics/tests/testsWithExplicitApi/properties.fir.kt deleted file mode 100644 index c92e0da56a6..00000000000 --- a/compiler/testData/diagnostics/tests/testsWithExplicitApi/properties.fir.kt +++ /dev/null @@ -1,23 +0,0 @@ -// SKIP_TXT - -public class Foo(val bar: Int, private var bar2: String, internal var bar3: Long, public var bar4: Int) { - /** - * Property KDoc - */ - var simple: Int = 10 - public var simple2: Int = 10 - - val withGetter: Int - get() = 10 - - public val withGetter2: Int - get() = 10 - - var getterAndSetter: Int = 10 - get() = field - set(v) { field = v } - - public var getterAndSetter2: Int = 10 - get() = field - set(v) { field = v } -} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/testsWithExplicitApi/properties.kt b/compiler/testData/diagnostics/tests/testsWithExplicitApi/properties.kt index fdbd7c2b141..f8d8f9faae0 100644 --- a/compiler/testData/diagnostics/tests/testsWithExplicitApi/properties.kt +++ b/compiler/testData/diagnostics/tests/testsWithExplicitApi/properties.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // SKIP_TXT public class Foo(val bar: Int, private var bar2: String, internal var bar3: Long, public var bar4: Int) { diff --git a/compiler/testData/diagnostics/tests/testsWithExplicitApi/publishedApi.fir.kt b/compiler/testData/diagnostics/tests/testsWithExplicitApi/publishedApi.fir.kt deleted file mode 100644 index 7827b84d9de..00000000000 --- a/compiler/testData/diagnostics/tests/testsWithExplicitApi/publishedApi.fir.kt +++ /dev/null @@ -1,7 +0,0 @@ -// SKIP_TXT -// WITH_RUNTIME - -public class A { - @PublishedApi - internal fun foo() = 1 -} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/testsWithExplicitApi/publishedApi.kt b/compiler/testData/diagnostics/tests/testsWithExplicitApi/publishedApi.kt index fbfaf04ef18..742b80dc8f5 100644 --- a/compiler/testData/diagnostics/tests/testsWithExplicitApi/publishedApi.kt +++ b/compiler/testData/diagnostics/tests/testsWithExplicitApi/publishedApi.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // SKIP_TXT // WITH_RUNTIME diff --git a/compiler/testData/diagnostics/tests/testsWithExplicitApi/toplevel.fir.kt b/compiler/testData/diagnostics/tests/testsWithExplicitApi/toplevel.fir.kt deleted file mode 100644 index 9644173e86a..00000000000 --- a/compiler/testData/diagnostics/tests/testsWithExplicitApi/toplevel.fir.kt +++ /dev/null @@ -1,12 +0,0 @@ -// SKIP_TXT - -/** - * foo KDoc - */ -fun foo() {} - -public fun foo2() {} - -fun bar() = 10 -public fun bar2() = 10 -public fun bar3(): Int = 10 diff --git a/compiler/testData/diagnostics/tests/testsWithExplicitApi/toplevel.kt b/compiler/testData/diagnostics/tests/testsWithExplicitApi/toplevel.kt index 54e92fd6f88..f21255b1dca 100644 --- a/compiler/testData/diagnostics/tests/testsWithExplicitApi/toplevel.kt +++ b/compiler/testData/diagnostics/tests/testsWithExplicitApi/toplevel.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // SKIP_TXT /** diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/runners/AbstractFirDiagnosticTest.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/runners/AbstractFirDiagnosticTest.kt index e9e11426a72..0fff77cf636 100644 --- a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/runners/AbstractFirDiagnosticTest.kt +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/runners/AbstractFirDiagnosticTest.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.test.runners +import org.jetbrains.kotlin.config.ExplicitApiMode import org.jetbrains.kotlin.platform.jvm.JvmPlatforms import org.jetbrains.kotlin.test.Constructor import org.jetbrains.kotlin.test.TestJdkKind @@ -17,6 +18,7 @@ import org.jetbrains.kotlin.test.directives.FirDiagnosticsDirectives.WITH_EXTEND import org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives.JDK_KIND import org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives.WITH_REFLECT import org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives.WITH_STDLIB +import org.jetbrains.kotlin.test.directives.LanguageSettingsDirectives import org.jetbrains.kotlin.test.directives.LanguageSettingsDirectives.LANGUAGE import org.jetbrains.kotlin.test.frontend.fir.FirFailingTestSuppressor import org.jetbrains.kotlin.test.frontend.fir.FirFrontendFacade @@ -108,6 +110,12 @@ fun TestConfigurationBuilder.baseFirDiagnosticTestConfiguration( } } + forTestsMatching("compiler/testData/diagnostics/tests/testsWithExplicitApi/*") { + defaultDirectives { + LanguageSettingsDirectives.EXPLICIT_API_MODE with ExplicitApiMode.STRICT + } + } + forTestsMatching( "compiler/fir/analysis-tests/testData/resolve/extendedCheckers/*" or "compiler/testData/diagnostics/tests/controlFlowAnalysis/deadCode/*"