Introduce OptionalExpectationInspection

It works at INFORMATION level only and is intended to form quick-fixes
to add relevant actual declarations.

#KT-25533 Fixed
This commit is contained in:
Mikhail Glukhikh
2018-08-29 18:31:36 +03:00
parent ba84c6adf3
commit 4dd95e5640
16 changed files with 184 additions and 2 deletions
@@ -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<PsiSourceFile>()
?.run { VfsUtilCore.virtualToIoFile(psiFile.virtualFile) }
private fun Map<out Compatibility, Collection<MemberDescriptor>>.allStrongIncompatibilities(): Boolean =
fun Map<out Compatibility, Collection<MemberDescriptor>>.allStrongIncompatibilities(): Boolean =
this.keys.all { it is Incompatible && it.kind == Compatibility.IncompatibilityKind.STRONG }
private fun checkActualDeclarationHasExpected(
@@ -0,0 +1,5 @@
<html>
<body>
This special inspection reports optionally expected annotations without actual annotation in some platform modules.
</body>
</html>
+9
View File
@@ -3057,6 +3057,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.OptionalExpectationInspection"
displayName="Optionally expected annotation has no actual annotation"
groupPath="Kotlin"
groupName="Style issues"
enabledByDefault="true"
level="INFORMATION"
language="kotlin"
/>
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
+9
View File
@@ -3055,6 +3055,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.OptionalExpectationInspection"
displayName="Optionally expected annotation has no actual annotation"
groupPath="Kotlin"
groupName="Style issues"
enabledByDefault="true"
level="INFORMATION"
language="kotlin"
/>
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
+9
View File
@@ -3056,6 +3056,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.OptionalExpectationInspection"
displayName="Optionally expected annotation has no actual annotation"
groupPath="Kotlin"
groupName="Style issues"
enabledByDefault="true"
level="INFORMATION"
language="kotlin"
/>
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
+9
View File
@@ -3057,6 +3057,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.OptionalExpectationInspection"
displayName="Optionally expected annotation has no actual annotation"
groupPath="Kotlin"
groupName="Style issues"
enabledByDefault="true"
level="INFORMATION"
language="kotlin"
/>
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
+9
View File
@@ -3055,6 +3055,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.OptionalExpectationInspection"
displayName="Optionally expected annotation has no actual annotation"
groupPath="Kotlin"
groupName="Style issues"
enabledByDefault="true"
level="INFORMATION"
language="kotlin"
/>
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
+9
View File
@@ -3055,6 +3055,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.OptionalExpectationInspection"
displayName="Optionally expected annotation has no actual annotation"
groupPath="Kotlin"
groupName="Style issues"
enabledByDefault="true"
level="INFORMATION"
language="kotlin"
/>
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
+9
View File
@@ -3057,6 +3057,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.OptionalExpectationInspection"
displayName="Optionally expected annotation has no actual annotation"
groupPath="Kotlin"
groupName="Style issues"
enabledByDefault="true"
level="INFORMATION"
language="kotlin"
/>
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
@@ -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)
)
}
})
}
@@ -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<out D : KtNamedDeclaration>(
@@ -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,
@@ -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 <caret>Ann
@@ -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 <caret>Ann
@@ -0,0 +1 @@
// Ann: to be implemented
@@ -0,0 +1,5 @@
package kotlin
// Ann: to be implemented
actual
annotation class Ann
@@ -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/");