From 7649232f86317720acfc58ab084a2d8cfd11e522 Mon Sep 17 00:00:00 2001 From: Mikhail Glukhikh Date: Mon, 5 Dec 2016 15:58:56 +0300 Subject: [PATCH] Platform header annotator: implementation + simple multi-platform highlighting tests #KT-14905 Fixed --- .../checkers/HeaderImplDeclarationChecker.kt | 9 +-- .../highlighter/PlatformHeaderAnnotator.kt | 72 +++++++++++++++++++ idea/src/META-INF/plugin.xml | 1 + .../platform1/header/header.kt | 3 + .../platform1/jvm/jvm.kt | 15 ++++ .../platform2/header/header.kt | 3 + .../platform2/jvm/jvm.kt | 3 + .../platform3/header/header.kt | 3 + .../platform4/header/header.kt | 23 ++++++ .../platform4/jvm/jvm.kt | 20 ++++++ .../AbstractMultiModuleHighlightingTest.kt | 41 ++++++++++- .../resolve/MultiModuleHighlightingTest.kt | 54 ++++++++++++++ 12 files changed, 241 insertions(+), 6 deletions(-) create mode 100644 idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/PlatformHeaderAnnotator.kt create mode 100644 idea/testData/multiModuleHighlighting/platform1/header/header.kt create mode 100644 idea/testData/multiModuleHighlighting/platform1/jvm/jvm.kt create mode 100644 idea/testData/multiModuleHighlighting/platform2/header/header.kt create mode 100644 idea/testData/multiModuleHighlighting/platform2/jvm/jvm.kt create mode 100644 idea/testData/multiModuleHighlighting/platform3/header/header.kt create mode 100644 idea/testData/multiModuleHighlighting/platform4/header/header.kt create mode 100644 idea/testData/multiModuleHighlighting/platform4/jvm/jvm.kt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/HeaderImplDeclarationChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/HeaderImplDeclarationChecker.kt index 91f504a276e..2b7ad14f192 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/HeaderImplDeclarationChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/HeaderImplDeclarationChecker.kt @@ -43,7 +43,7 @@ import org.jetbrains.kotlin.types.typeUtil.asTypeProjection import org.jetbrains.kotlin.utils.SmartList import org.jetbrains.kotlin.utils.keysToMap -class HeaderImplDeclarationChecker : DeclarationChecker { +class HeaderImplDeclarationChecker(val moduleToCheck: ModuleDescriptor? = null) : DeclarationChecker { override fun check( declaration: KtDeclaration, descriptor: DeclarationDescriptor, @@ -64,7 +64,7 @@ class HeaderImplDeclarationChecker : DeclarationChecker { } } - private fun checkHeaderDeclarationHasImplementation( + fun checkHeaderDeclarationHasImplementation( reportOn: KtDeclaration, descriptor: MemberDescriptor, diagnosticHolder: DiagnosticSink, checkImpl: Boolean ) { val compatibility = when (descriptor) { @@ -132,7 +132,7 @@ class HeaderImplDeclarationChecker : DeclarationChecker { private fun CallableMemberDescriptor.findNamesakesFromTheSameModule(): Collection { val packageFqName = (containingDeclaration as? PackageFragmentDescriptor)?.fqName ?: return emptyList() - val myModule = this.module + val myModule = moduleToCheck ?: module val scope = myModule.getPackage(packageFqName).memberScope return when (this) { @@ -145,8 +145,9 @@ class HeaderImplDeclarationChecker : DeclarationChecker { } private fun ClassifierDescriptor.findClassifiersFromTheSameModule(): Collection { + val myModule = moduleToCheck ?: module // TODO: support nested classes - return module.getPackage(fqNameSafe.parent()).memberScope + return myModule.getPackage(fqNameSafe.parent()).memberScope .getDescriptorsFiltered(DescriptorKindFilter.CLASSIFIERS) { it == name } .filterIsInstance() } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/PlatformHeaderAnnotator.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/PlatformHeaderAnnotator.kt new file mode 100644 index 00000000000..8289819863e --- /dev/null +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/PlatformHeaderAnnotator.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.highlighter + +import com.intellij.lang.annotation.AnnotationHolder +import com.intellij.lang.annotation.Annotator +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.descriptors.MemberDescriptor +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.descriptors.PlatformKind +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.diagnostics.DiagnosticSink +import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor +import org.jetbrains.kotlin.idea.core.toDescriptor +import org.jetbrains.kotlin.idea.project.TargetPlatformDetector +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.resolve.checkers.HeaderImplDeclarationChecker +import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics +import org.jetbrains.kotlin.resolve.diagnostics.SimpleDiagnostics + +class PlatformHeaderAnnotator : Annotator { + + override fun annotate(element: PsiElement, holder: AnnotationHolder) { + val declaration = element as? KtDeclaration ?: return + if (!declaration.hasModifier(KtTokens.HEADER_KEYWORD)) return + + val platform = TargetPlatformDetector.getPlatform(declaration.containingKtFile) + if (platform.kind != PlatformKind.DEFAULT) return + + val defaultModuleDescriptor = declaration.findModuleDescriptor() + val dependentDescriptors = defaultModuleDescriptor.allImplementingModules + if (dependentDescriptors.isEmpty()) return + + val diagnostics = validate(declaration, dependentDescriptors) + KotlinPsiChecker().annotateElement(declaration, holder, diagnostics) + } + + fun validate(declaration: KtDeclaration, modulesToCheck: Collection): Diagnostics { + val descriptor = declaration.toDescriptor() as? MemberDescriptor ?: return Diagnostics.EMPTY + if (!descriptor.isHeader) return Diagnostics.EMPTY + + val checkers = modulesToCheck.map(::HeaderImplDeclarationChecker) + val diagnosticList = mutableListOf() + val diagnosticSink = object : DiagnosticSink { + override fun report(diagnostic: Diagnostic) { + diagnosticList += diagnostic + } + + override fun wantsDiagnostics() = true + } + for (checker in checkers) { + checker.checkHeaderDeclarationHasImplementation(declaration, descriptor, diagnosticSink, checkImpl = false) + } + + return if (diagnosticList.isNotEmpty()) SimpleDiagnostics(diagnosticList) else Diagnostics.EMPTY + } +} \ No newline at end of file diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 500df16c7bd..b12f05eced6 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -505,6 +505,7 @@ + diff --git a/idea/testData/multiModuleHighlighting/platform1/header/header.kt b/idea/testData/multiModuleHighlighting/platform1/header/header.kt new file mode 100644 index 00000000000..050ddeeabb3 --- /dev/null +++ b/idea/testData/multiModuleHighlighting/platform1/header/header.kt @@ -0,0 +1,3 @@ +header class My { + +} \ No newline at end of file diff --git a/idea/testData/multiModuleHighlighting/platform1/jvm/jvm.kt b/idea/testData/multiModuleHighlighting/platform1/jvm/jvm.kt new file mode 100644 index 00000000000..f4a151f659f --- /dev/null +++ b/idea/testData/multiModuleHighlighting/platform1/jvm/jvm.kt @@ -0,0 +1,15 @@ +impl class Your { + +} + +header class His { + +} + +header class Their { + +} + +impl class Their { + +} \ No newline at end of file diff --git a/idea/testData/multiModuleHighlighting/platform2/header/header.kt b/idea/testData/multiModuleHighlighting/platform2/header/header.kt new file mode 100644 index 00000000000..9a0bb2aa597 --- /dev/null +++ b/idea/testData/multiModuleHighlighting/platform2/header/header.kt @@ -0,0 +1,3 @@ +header class My { + +} \ No newline at end of file diff --git a/idea/testData/multiModuleHighlighting/platform2/jvm/jvm.kt b/idea/testData/multiModuleHighlighting/platform2/jvm/jvm.kt new file mode 100644 index 00000000000..35e530f7c43 --- /dev/null +++ b/idea/testData/multiModuleHighlighting/platform2/jvm/jvm.kt @@ -0,0 +1,3 @@ +impl class My { + +} \ No newline at end of file diff --git a/idea/testData/multiModuleHighlighting/platform3/header/header.kt b/idea/testData/multiModuleHighlighting/platform3/header/header.kt new file mode 100644 index 00000000000..6a9b7624046 --- /dev/null +++ b/idea/testData/multiModuleHighlighting/platform3/header/header.kt @@ -0,0 +1,3 @@ +header class My { + +} \ No newline at end of file diff --git a/idea/testData/multiModuleHighlighting/platform4/header/header.kt b/idea/testData/multiModuleHighlighting/platform4/header/header.kt new file mode 100644 index 00000000000..1400a7b589f --- /dev/null +++ b/idea/testData/multiModuleHighlighting/platform4/header/header.kt @@ -0,0 +1,23 @@ +header class My { + + fun foo(): Int + + fun bar(arg: Int): Boolean + +} + +header class Your { + + fun foo(): Int + + fun bar(arg: Int): Boolean + +} + +header class His { + + fun foo(): Int + + fun bar(arg: Int): Boolean + +} \ No newline at end of file diff --git a/idea/testData/multiModuleHighlighting/platform4/jvm/jvm.kt b/idea/testData/multiModuleHighlighting/platform4/jvm/jvm.kt new file mode 100644 index 00000000000..210507efebf --- /dev/null +++ b/idea/testData/multiModuleHighlighting/platform4/jvm/jvm.kt @@ -0,0 +1,20 @@ +impl class My { + + impl fun foo() = 42 +} + +impl class Your { + + impl fun foo() = 13 + + impl fun bar(arg: Int) = arg + +} + +impl class His { + + impl fun foo() = 7 + + impl fun bar(arg: Int) = arg == foo() + +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/AbstractMultiModuleHighlightingTest.kt b/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/AbstractMultiModuleHighlightingTest.kt index abd70f4bcae..0d17919848b 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/AbstractMultiModuleHighlightingTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/AbstractMultiModuleHighlightingTest.kt @@ -21,14 +21,19 @@ import com.intellij.openapi.module.StdModuleTypes import com.intellij.openapi.roots.ModuleRootModificationUtil import org.jetbrains.kotlin.idea.project.PluginJetFilesProvider import com.intellij.codeInsight.daemon.DaemonAnalyzerTestCase +import com.intellij.openapi.application.WriteAction import com.intellij.openapi.module.Module import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.command.WriteCommandAction +import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProviderImpl import com.intellij.testFramework.PsiTestUtil import java.io.File import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.roots.DependencyScope -import com.intellij.testFramework.IdeaTestUtil +import com.sampullara.cli.Argument +import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments +import org.jetbrains.kotlin.config.* +import org.jetbrains.kotlin.idea.facet.getOrCreateFacet import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil import org.junit.Assert @@ -48,7 +53,7 @@ abstract class AbstractMultiModuleHighlightingTest : DaemonAnalyzerTestCase() { protected fun module(name: String, hasTestRoot: Boolean = false, useFullJdk: Boolean = false): Module { val srcDir = TEST_DATA_PATH + "${getTestName(true)}/$name" - val moduleWithSrcRootSet = createModuleFromTestData(srcDir, "$name", StdModuleTypes.JAVA, true)!! + val moduleWithSrcRootSet = createModuleFromTestData(srcDir, name, StdModuleTypes.JAVA, true)!! if (hasTestRoot) { setTestRoot(moduleWithSrcRootSet, name) } @@ -78,4 +83,36 @@ abstract class AbstractMultiModuleHighlightingTest : DaemonAnalyzerTestCase() { dependencyScope: DependencyScope = DependencyScope.COMPILE, exported: Boolean = false ) = ModuleRootModificationUtil.addDependency(this, other, dependencyScope, exported) + + private fun Module.createFacet() { + val accessToken = WriteAction.start() + try { + val modelsProvider = IdeModifiableModelsProviderImpl(project) + getOrCreateFacet(modelsProvider) + modelsProvider.commit() + } + finally { + accessToken.finish() + } + } + + protected fun Module.setPlatformKind(platformKind: TargetPlatformKind<*>) { + createFacet() + val facetSettings = KotlinFacetSettingsProvider.getInstance(project).getSettings(this) + val versionInfo = facetSettings.versionInfo + versionInfo.targetPlatformKind = platformKind + } + + protected fun Module.enableMultiPlatform() { + createFacet() + val facetSettings = KotlinFacetSettingsProvider.getInstance(project).getSettings(this) + val compilerInfo = facetSettings.compilerInfo + val compilerSettings = CompilerSettings() + compilerSettings.additionalArguments += " -$multiPlatformArg" + compilerInfo.compilerSettings = compilerSettings + } + + companion object { + private val multiPlatformArg = CommonCompilerArguments::multiPlatform.annotations.filterIsInstance().single().value + } } diff --git a/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/MultiModuleHighlightingTest.kt b/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/MultiModuleHighlightingTest.kt index 0a67d176e46..ac2c8d2c177 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/MultiModuleHighlightingTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/MultiModuleHighlightingTest.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.idea.caches.resolve import com.intellij.openapi.roots.DependencyScope +import org.jetbrains.kotlin.config.TargetPlatformKind class MultiModuleHighlightingTest : AbstractMultiModuleHighlightingTest() { fun testVisibility() { @@ -58,4 +59,57 @@ class MultiModuleHighlightingTest : AbstractMultiModuleHighlightingTest() { checkHighlightingInAllFiles() } + + fun testPlatform1() { + val header = module("header") + header.setPlatformKind(TargetPlatformKind.Default) + + val jvm = module("jvm") + jvm.setPlatformKind(TargetPlatformKind.Jvm.JVM_1_6) + jvm.enableMultiPlatform() + jvm.addDependency(header) + + checkHighlightingInAllFiles() + } + + fun testPlatform2() { + val header = module("header") + header.setPlatformKind(TargetPlatformKind.Default) + + val jvm = module("jvm") + jvm.setPlatformKind(TargetPlatformKind.Jvm.JVM_1_6) + jvm.enableMultiPlatform() + jvm.addDependency(header) + + checkHighlightingInAllFiles() + } + + fun testPlatform3() { + val header = module("header") + header.setPlatformKind(TargetPlatformKind.Default) + + val jvm = module("jvm") + jvm.setPlatformKind(TargetPlatformKind.Jvm.JVM_1_6) + jvm.enableMultiPlatform() + jvm.addDependency(header) + + val js = module("js") + js.setPlatformKind(TargetPlatformKind.JavaScript) + js.enableMultiPlatform() + js.addDependency(header) + + checkHighlightingInAllFiles() + } + + fun testPlatform4() { + val header = module("header") + header.setPlatformKind(TargetPlatformKind.Default) + + val jvm = module("jvm") + jvm.setPlatformKind(TargetPlatformKind.Jvm.JVM_1_6) + jvm.enableMultiPlatform() + jvm.addDependency(header) + + checkHighlightingInAllFiles() + } }