From 4dd95e5640c59e8b2502aca3a8948b5d6d3b876e Mon Sep 17 00:00:00 2001 From: Mikhail Glukhikh Date: Wed, 29 Aug 2018 18:31:36 +0300 Subject: [PATCH] Introduce OptionalExpectationInspection It works at INFORMATION level only and is intended to form quick-fixes to add relevant actual declarations. #KT-25533 Fixed --- .../ExpectedActualDeclarationChecker.kt | 4 +- .../OptionalExpectation.html | 5 ++ idea/src/META-INF/plugin.xml | 9 +++ idea/src/META-INF/plugin.xml.173 | 9 +++ idea/src/META-INF/plugin.xml.181 | 9 +++ idea/src/META-INF/plugin.xml.183 | 9 +++ idea/src/META-INF/plugin.xml.as31 | 9 +++ idea/src/META-INF/plugin.xml.as32 | 9 +++ idea/src/META-INF/plugin.xml.as33 | 9 +++ .../OptionalExpectationInspection.kt | 64 +++++++++++++++++++ .../quickfix/expectactual/CreateActualFix.kt | 17 +++++ .../common/common.kt | 11 ++++ .../common/common.kt.after | 11 ++++ .../annotationOptionalExpectation/jvm/Ann.kt | 1 + .../jvm/Ann.kt.after | 5 ++ .../QuickFixMultiModuleTestGenerated.java | 5 ++ 16 files changed, 184 insertions(+), 2 deletions(-) create mode 100644 idea/resources/inspectionDescriptions/OptionalExpectation.html create mode 100644 idea/src/org/jetbrains/kotlin/idea/inspections/OptionalExpectationInspection.kt create mode 100644 idea/testData/multiModuleQuickFix/annotationOptionalExpectation/common/common.kt create mode 100644 idea/testData/multiModuleQuickFix/annotationOptionalExpectation/common/common.kt.after create mode 100644 idea/testData/multiModuleQuickFix/annotationOptionalExpectation/jvm/Ann.kt create mode 100644 idea/testData/multiModuleQuickFix/annotationOptionalExpectation/jvm/Ann.kt.after diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExpectedActualDeclarationChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExpectedActualDeclarationChecker.kt index 3496f6ea0bf..ef517891a73 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExpectedActualDeclarationChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExpectedActualDeclarationChecker.kt @@ -40,7 +40,7 @@ import org.jetbrains.kotlin.utils.ifEmpty import java.io.File object ExpectedActualDeclarationChecker : DeclarationChecker { - internal val OPTIONAL_EXPECTATION_FQ_NAME = FqName("kotlin.OptionalExpectation") + val OPTIONAL_EXPECTATION_FQ_NAME = FqName("kotlin.OptionalExpectation") override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) { if (!context.languageVersionSettings.supportsFeature(LanguageFeature.MultiPlatformProjects)) return @@ -129,7 +129,7 @@ object ExpectedActualDeclarationChecker : DeclarationChecker { .safeAs() ?.run { VfsUtilCore.virtualToIoFile(psiFile.virtualFile) } - private fun Map>.allStrongIncompatibilities(): Boolean = + fun Map>.allStrongIncompatibilities(): Boolean = this.keys.all { it is Incompatible && it.kind == Compatibility.IncompatibilityKind.STRONG } private fun checkActualDeclarationHasExpected( diff --git a/idea/resources/inspectionDescriptions/OptionalExpectation.html b/idea/resources/inspectionDescriptions/OptionalExpectation.html new file mode 100644 index 00000000000..cdbad207a79 --- /dev/null +++ b/idea/resources/inspectionDescriptions/OptionalExpectation.html @@ -0,0 +1,5 @@ + + +This special inspection reports optionally expected annotations without actual annotation in some platform modules. + + \ No newline at end of file diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 5722e3ef5ad..3db890e5b73 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -3057,6 +3057,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. language="kotlin" /> + + diff --git a/idea/src/META-INF/plugin.xml.173 b/idea/src/META-INF/plugin.xml.173 index 7525613130e..4078cfd1bc1 100644 --- a/idea/src/META-INF/plugin.xml.173 +++ b/idea/src/META-INF/plugin.xml.173 @@ -3055,6 +3055,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. language="kotlin" /> + + diff --git a/idea/src/META-INF/plugin.xml.181 b/idea/src/META-INF/plugin.xml.181 index 8c4cf32e794..f7cb015ad90 100644 --- a/idea/src/META-INF/plugin.xml.181 +++ b/idea/src/META-INF/plugin.xml.181 @@ -3056,6 +3056,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. language="kotlin" /> + + diff --git a/idea/src/META-INF/plugin.xml.183 b/idea/src/META-INF/plugin.xml.183 index 6aa779aec33..150c49d79a6 100644 --- a/idea/src/META-INF/plugin.xml.183 +++ b/idea/src/META-INF/plugin.xml.183 @@ -3057,6 +3057,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. language="kotlin" /> + + diff --git a/idea/src/META-INF/plugin.xml.as31 b/idea/src/META-INF/plugin.xml.as31 index 58a6447c25a..5a533016773 100644 --- a/idea/src/META-INF/plugin.xml.as31 +++ b/idea/src/META-INF/plugin.xml.as31 @@ -3055,6 +3055,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. language="kotlin" /> + + diff --git a/idea/src/META-INF/plugin.xml.as32 b/idea/src/META-INF/plugin.xml.as32 index 69283c1219e..9eb8d4d95ff 100644 --- a/idea/src/META-INF/plugin.xml.as32 +++ b/idea/src/META-INF/plugin.xml.as32 @@ -3055,6 +3055,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. language="kotlin" /> + + diff --git a/idea/src/META-INF/plugin.xml.as33 b/idea/src/META-INF/plugin.xml.as33 index b343fc381d0..d809f7abbb5 100644 --- a/idea/src/META-INF/plugin.xml.as33 +++ b/idea/src/META-INF/plugin.xml.as33 @@ -3057,6 +3057,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. language="kotlin" /> + + diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/OptionalExpectationInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/OptionalExpectationInspection.kt new file mode 100644 index 00000000000..e6a6d9ccf71 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/OptionalExpectationInspection.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.inspections + +import com.intellij.codeInspection.IntentionWrapper +import com.intellij.codeInspection.LocalInspectionToolSession +import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.codeInspection.ProblemsHolder +import org.jetbrains.kotlin.analyzer.ModuleInfo +import org.jetbrains.kotlin.idea.caches.project.ModuleSourceInfo +import org.jetbrains.kotlin.idea.caches.project.implementingDescriptors +import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny +import org.jetbrains.kotlin.idea.quickfix.expectactual.CreateActualClassFix +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.classOrObjectVisitor +import org.jetbrains.kotlin.psi.psiUtil.hasExpectModifier +import org.jetbrains.kotlin.resolve.MultiTargetPlatform +import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker +import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker.allStrongIncompatibilities +import org.jetbrains.kotlin.resolve.descriptorUtil.module +import org.jetbrains.kotlin.resolve.getMultiTargetPlatform +import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver + +class OptionalExpectationInspection : AbstractKotlinInspection() { + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession) = + classOrObjectVisitor(fun(classOrObject: KtClassOrObject) { + if (classOrObject !is KtClass || !classOrObject.isAnnotation()) return + if (!classOrObject.hasExpectModifier()) return + + val descriptor = classOrObject.resolveToDescriptorIfAny() ?: return + if (!descriptor.annotations.hasAnnotation(ExpectedActualDeclarationChecker.OPTIONAL_EXPECTATION_FQ_NAME)) return + + val implementingModules = classOrObject.findModuleDescriptor().implementingDescriptors + if (implementingModules.isEmpty()) return + + for (actualModuleDescriptor in implementingModules) { + val compatibility = ExpectedActualResolver.findActualForExpected(descriptor, actualModuleDescriptor) ?: continue + if (!compatibility.allStrongIncompatibilities() && + (ExpectedActualResolver.Compatibility.Compatible in compatibility || + !compatibility.values.flatMapTo( + hashSetOf() + ) { it }.all { actual -> + val expectedOnes = ExpectedActualResolver.findExpectedForActual(actual, descriptor.module) + expectedOnes != null && ExpectedActualResolver.Compatibility.Compatible in expectedOnes.keys + }) + ) continue + val platform = (actualModuleDescriptor.getMultiTargetPlatform() as? MultiTargetPlatform.Specific) ?: continue + val displayedName = actualModuleDescriptor.getCapability(ModuleInfo.Capability)?.displayedName ?: "" + val actualModule = (actualModuleDescriptor.getCapability(ModuleInfo.Capability) as? ModuleSourceInfo)?.module ?: continue + holder.registerProblem( + classOrObject.nameIdentifier ?: classOrObject, + "Optionally expected annotation has no actual annotation in module $displayedName for platform ${platform.platform}", + // NB: some highlighting is not suggested for this inspection + ProblemHighlightType.INFORMATION, + IntentionWrapper(CreateActualClassFix(classOrObject, actualModule, platform), classOrObject.containingFile) + ) + } + }) +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateActualFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateActualFix.kt index 3c33e5f1f21..ecf6980d835 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateActualFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateActualFix.kt @@ -43,10 +43,13 @@ import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.idea.util.actualsForExpected import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.hasExpectModifier import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.MultiTargetPlatform +import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker +import org.jetbrains.kotlin.resolve.checkers.ExperimentalUsageChecker import org.jetbrains.kotlin.resolve.getMultiTargetPlatform sealed class CreateActualFix( @@ -280,6 +283,14 @@ internal fun KtPsiFactory.generateClassOrObjectByExpectedClass( actualEntry.replace(createSuperTypeCallEntry("${actualEntry.typeReference!!.text}()")) } } + if (actualClass.isAnnotation()) { + actualClass.annotationEntries.zip(expectedClass.annotationEntries).forEach { (actualEntry, expectedEntry) -> + val annotationDescriptor = context.get(BindingContext.ANNOTATION, expectedEntry) ?: return@forEach + if (annotationDescriptor.fqName in forbiddenAnnotationFqNames) { + actualEntry.delete() + } + } + } declLoop@ for (expectedDeclaration in expectedClass.declarations.filter { !it.exists() }) { val descriptor = expectedDeclaration.toDescriptor() ?: continue @@ -319,6 +330,12 @@ internal fun KtPsiFactory.generateClassOrObjectByExpectedClass( } } +private val forbiddenAnnotationFqNames = setOf( + ExpectedActualDeclarationChecker.OPTIONAL_EXPECTATION_FQ_NAME, + FqName("kotlin.ExperimentalMultiplatform"), + ExperimentalUsageChecker.USE_EXPERIMENTAL_FQ_NAME +) + private fun KtPsiFactory.generateFunction( project: Project, expectedFunction: KtFunction, diff --git a/idea/testData/multiModuleQuickFix/annotationOptionalExpectation/common/common.kt b/idea/testData/multiModuleQuickFix/annotationOptionalExpectation/common/common.kt new file mode 100644 index 00000000000..c6e22e9584b --- /dev/null +++ b/idea/testData/multiModuleQuickFix/annotationOptionalExpectation/common/common.kt @@ -0,0 +1,11 @@ +// "Create actual annotation class for module testModule_JVM (JVM)" "true" +// TOOL: org.jetbrains.kotlin.idea.inspections.OptionalExpectationInspection + +package kotlin + +annotation class ExperimentalMultiplatform +annotation class OptionalExpectation + +@ExperimentalMultiplatform +@OptionalExpectation +expect annotation class Ann \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/annotationOptionalExpectation/common/common.kt.after b/idea/testData/multiModuleQuickFix/annotationOptionalExpectation/common/common.kt.after new file mode 100644 index 00000000000..c6e22e9584b --- /dev/null +++ b/idea/testData/multiModuleQuickFix/annotationOptionalExpectation/common/common.kt.after @@ -0,0 +1,11 @@ +// "Create actual annotation class for module testModule_JVM (JVM)" "true" +// TOOL: org.jetbrains.kotlin.idea.inspections.OptionalExpectationInspection + +package kotlin + +annotation class ExperimentalMultiplatform +annotation class OptionalExpectation + +@ExperimentalMultiplatform +@OptionalExpectation +expect annotation class Ann \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/annotationOptionalExpectation/jvm/Ann.kt b/idea/testData/multiModuleQuickFix/annotationOptionalExpectation/jvm/Ann.kt new file mode 100644 index 00000000000..4c6108ad4cd --- /dev/null +++ b/idea/testData/multiModuleQuickFix/annotationOptionalExpectation/jvm/Ann.kt @@ -0,0 +1 @@ +// Ann: to be implemented \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/annotationOptionalExpectation/jvm/Ann.kt.after b/idea/testData/multiModuleQuickFix/annotationOptionalExpectation/jvm/Ann.kt.after new file mode 100644 index 00000000000..885fca77516 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/annotationOptionalExpectation/jvm/Ann.kt.after @@ -0,0 +1,5 @@ +package kotlin + +// Ann: to be implemented +actual +annotation class Ann \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTestGenerated.java index 4759269f02d..0a79758de44 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTestGenerated.java @@ -74,6 +74,11 @@ public class QuickFixMultiModuleTestGenerated extends AbstractQuickFixMultiModul runTest("idea/testData/multiModuleQuickFix/annotation/"); } + @TestMetadata("annotationOptionalExpectation") + public void testAnnotationOptionalExpectation() throws Exception { + runTest("idea/testData/multiModuleQuickFix/annotationOptionalExpectation/"); + } + @TestMetadata("class") public void testClass() throws Exception { runTest("idea/testData/multiModuleQuickFix/class/");