mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[kotlin] K2: Implement 'CreateActualForExpectLocalQuickFix' to fix missing actuals
See: ^FL-28916 GitOrigin-RevId: 9c14adc27d853261d505ac2126cbc8a40a48d544
This commit is contained in:
committed by
intellij-monorepo-bot
parent
a8b94d70bd
commit
fcf649cc75
@@ -1069,6 +1069,8 @@ add.0.constructor.to.1=Add {0} constructor to ''{1}''
|
||||
text.secondary=secondary
|
||||
text.primary=primary
|
||||
add.method.0.to.1=Add method ''{0}'' to ''{1}''
|
||||
create.actual=Create actual
|
||||
create.actual.in.0=Create actual in ''{0}''
|
||||
create.actual.0.for.module.1.2=Create actual {0} for module {1} ({2})
|
||||
create.expected.0.in.common.module.1=Create expected {0} in common module {1}
|
||||
repair.actual.members=Repair actual members
|
||||
|
||||
+10
@@ -650,6 +650,11 @@ public abstract class HighLevelQuickFixMultiModuleTestGenerated extends Abstract
|
||||
runTest("../../../idea/tests/testData/multiModuleQuickFix/createActual/function/");
|
||||
}
|
||||
|
||||
@TestMetadata("functionK2")
|
||||
public void testFunctionK2() throws Exception {
|
||||
runTest("../../../idea/tests/testData/multiModuleQuickFix/createActual/functionK2/");
|
||||
}
|
||||
|
||||
@TestMetadata("functionSameFile")
|
||||
public void testFunctionSameFile() throws Exception {
|
||||
runTest("../../../idea/tests/testData/multiModuleQuickFix/createActual/functionSameFile/");
|
||||
@@ -700,6 +705,11 @@ public abstract class HighLevelQuickFixMultiModuleTestGenerated extends Abstract
|
||||
runTest("../../../idea/tests/testData/multiModuleQuickFix/createActual/property/");
|
||||
}
|
||||
|
||||
@TestMetadata("propertyK2")
|
||||
public void testPropertyK2() throws Exception {
|
||||
runTest("../../../idea/tests/testData/multiModuleQuickFix/createActual/propertyK2/");
|
||||
}
|
||||
|
||||
@TestMetadata("sealed")
|
||||
public void testSealed() throws Exception {
|
||||
runTest("../../../idea/tests/testData/multiModuleQuickFix/createActual/sealed/");
|
||||
|
||||
@@ -62,5 +62,10 @@
|
||||
<orderEntry type="module" module-name="intellij.java" />
|
||||
<orderEntry type="module" module-name="kotlin.code-insight.k2" />
|
||||
<orderEntry type="module" module-name="kotlin.base.plugin" />
|
||||
<orderEntry type="module" module-name="kotlin.base.external-build-system" />
|
||||
<orderEntry type="module" module-name="kotlin.code-insight.override-implement.k2" />
|
||||
<orderEntry type="module" module-name="kotlin.code-insight.override-implement.shared" />
|
||||
<orderEntry type="library" name="kotlin-tooling-core" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.externalSystem" />
|
||||
</component>
|
||||
</module>
|
||||
+217
-10
@@ -1,28 +1,51 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.kotlin.idea.k2.codeinsight.inspections
|
||||
|
||||
import com.intellij.codeInspection.LocalInspectionToolSession
|
||||
import com.intellij.codeInspection.ProblemsHolder
|
||||
import com.intellij.codeInspection.*
|
||||
import com.intellij.modcommand.*
|
||||
import com.intellij.openapi.application.*
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.module.ModuleManager
|
||||
import com.intellij.openapi.project.DumbService
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.rootManager
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.util.getOrCreateUserData
|
||||
import com.intellij.psi.PsiElementVisitor
|
||||
import com.intellij.openapi.vfs.VfsUtil
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.vfs.findOrCreateDirectory
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.PsiElementVisitor.EMPTY_VISITOR
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.util.childrenOfType
|
||||
import com.intellij.psi.util.findParentOfType
|
||||
import com.intellij.util.concurrency.annotations.RequiresWriteLock
|
||||
import org.jetbrains.kotlin.analysis.api.KaExperimentalApi
|
||||
import org.jetbrains.kotlin.analysis.api.analyze
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KaCallableSymbol
|
||||
import org.jetbrains.kotlin.idea.base.codeInsight.ShortenReferencesFacility
|
||||
import org.jetbrains.kotlin.idea.base.externalSystem.KotlinBuildSystemFacade
|
||||
import org.jetbrains.kotlin.idea.base.externalSystem.KotlinBuildSystemSourceSet
|
||||
import org.jetbrains.kotlin.idea.base.facet.implementedModules
|
||||
import org.jetbrains.kotlin.idea.base.facet.implementingModules
|
||||
import org.jetbrains.kotlin.idea.base.facet.isMultiPlatformModule
|
||||
import org.jetbrains.kotlin.idea.base.facet.isTestModule
|
||||
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
|
||||
import org.jetbrains.kotlin.idea.base.util.isAndroidModule
|
||||
import org.jetbrains.kotlin.idea.base.util.module
|
||||
import org.jetbrains.kotlin.idea.base.util.reformatted
|
||||
import org.jetbrains.kotlin.idea.codeinsight.api.classic.inspections.AbstractKotlinInspection
|
||||
import org.jetbrains.kotlin.idea.core.overrideImplement.MemberGenerateMode
|
||||
import org.jetbrains.kotlin.idea.core.overrideImplement.generateMember
|
||||
import org.jetbrains.kotlin.idea.k2.refactoring.move.processor.createKotlinFile
|
||||
import org.jetbrains.kotlin.idea.searching.kmp.findAllActualForExpect
|
||||
import org.jetbrains.kotlin.idea.util.sourceRoot
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.name.StandardClassIds
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtModifierList
|
||||
import org.jetbrains.kotlin.psi.KtVisitorVoid
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.tooling.core.withClosure
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.absolutePathString
|
||||
|
||||
class KotlinNoActualForExpectInspection : AbstractKotlinInspection() {
|
||||
|
||||
@@ -59,6 +82,7 @@ class KotlinNoActualForExpectInspection : AbstractKotlinInspection() {
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(KaExperimentalApi::class)
|
||||
override fun buildVisitor(
|
||||
holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession
|
||||
): PsiElementVisitor {
|
||||
@@ -67,13 +91,13 @@ class KotlinNoActualForExpectInspection : AbstractKotlinInspection() {
|
||||
|
||||
return object : KtVisitorVoid() {
|
||||
override fun visitModifierList(list: KtModifierList) {
|
||||
val module = list.module ?: return
|
||||
val expectModule = list.module ?: return
|
||||
val expectModifier = list.getModifier(KtTokens.EXPECT_KEYWORD) ?: return
|
||||
val parentDeclaration = list.findParentOfType<KtDeclaration>() ?: return
|
||||
if (parentDeclaration.hasOptionalExpectationAnnotation()) return
|
||||
|
||||
val leaves = session.getLeaves(module)
|
||||
val foundActuals = parentDeclaration.findAllActualForExpect(session.getActualsSearchScope(module))
|
||||
val leaves = session.getLeaves(expectModule)
|
||||
val foundActuals = parentDeclaration.findAllActualForExpect(session.getActualsSearchScope(expectModule))
|
||||
.mapNotNullTo(mutableSetOf()) { it.element?.module }.toSet()
|
||||
|
||||
val missingActuals = leaves.filter { module ->
|
||||
@@ -81,12 +105,195 @@ class KotlinNoActualForExpectInspection : AbstractKotlinInspection() {
|
||||
}
|
||||
if (missingActuals.isEmpty()) return
|
||||
|
||||
/*
|
||||
Will return all modules that could potentially fix the issue by providing the actual.
|
||||
In the example above, it will return `nativeMain`, `appleMain`, `iosMain`, `iosX64Main` iosArm64Main`
|
||||
*/
|
||||
val allModulesCapableOfProvidingActuals = missingActuals
|
||||
.withClosure<Module> { leafModule -> leafModule.implementedModules - expectModule }
|
||||
.reversed() /* reversing the list to ensure that modules "closest to the 'expect'" come first. */
|
||||
|
||||
val actualDeclaration = analyze(parentDeclaration) {
|
||||
generateMember(
|
||||
project = expectModule.project,
|
||||
ktClassMember = null,
|
||||
symbol = parentDeclaration.symbol as? KaCallableSymbol ?: return@analyze null,
|
||||
targetClass = null,
|
||||
copyDoc = false,
|
||||
mode = MemberGenerateMode.ACTUAL
|
||||
)
|
||||
}
|
||||
|
||||
val fixes = allModulesCapableOfProvidingActuals.mapIndexedNotNull map@{ index, actualModule ->
|
||||
CreateActualForExpectLocalQuickFix(
|
||||
priority = index, moduleName = actualModule.name,
|
||||
sourceSet = KotlinBuildSystemFacade.getInstance().findSourceSet(actualModule) ?: return@map null,
|
||||
actualDeclaration = actualDeclaration?.createSmartPointer() ?: return@map null,
|
||||
)
|
||||
}
|
||||
|
||||
// We only care about the name of the target, not the common submodule of the MPP module
|
||||
val missingModulesWithActuals = missingActuals.joinToString { it.name.substringAfterLast('.') }
|
||||
|
||||
holder.registerProblem(
|
||||
expectModifier, KotlinBundle.message("no.actual.for.expect.declaration", missingModulesWithActuals)
|
||||
expectModifier, KotlinBundle.message("no.actual.for.expect.declaration", missingModulesWithActuals),
|
||||
*fixes.toTypedArray()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CreateActualForExpectLocalQuickFix(
|
||||
private val priority: Int,
|
||||
private val moduleName: String,
|
||||
private val sourceSet: KotlinBuildSystemSourceSet,
|
||||
private val actualDeclaration: SmartPsiElementPointer<KtDeclaration>,
|
||||
) : LocalQuickFix {
|
||||
|
||||
override fun startInWriteAction(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getName(): String {
|
||||
/**
|
||||
* We're using a 'zero width space' character to influence the sorting of the QuickFix as they
|
||||
* are traditionally just sorted by their name.
|
||||
*/
|
||||
val sortingWhiteSpace = buildString(priority) { repeat(priority) { append('\u200B') } }
|
||||
return KotlinBundle.message("create.actual.in.0", "$sortingWhiteSpace${sourceSet.name}")
|
||||
}
|
||||
|
||||
override fun getFamilyName(): String {
|
||||
return KotlinBundle.message("create.actual")
|
||||
}
|
||||
|
||||
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
||||
val module = ModuleManager.getInstance(project).findModuleByName(moduleName) ?: return
|
||||
val expectDeclaration = descriptor.psiElement.findParentOfType<KtDeclaration>() ?: return
|
||||
val expectPackageFqn = expectDeclaration.containingKtFile.packageFqName
|
||||
|
||||
/**
|
||||
* Where is the 'expect' declaration located in regard to its source root?
|
||||
* For example, src/commonMain/kotlin/my/pkg/foo/Foo.kt
|
||||
* <my/pkg/foo>
|
||||
* This relative path can then be used to put the actual under the same relative path.
|
||||
* Note: The package name is used as convention.
|
||||
*/
|
||||
val expectRelativePackagePath: String = run {
|
||||
val expectSourceFileDirectory = expectDeclaration.containingKtFile.containingDirectory?.virtualFile ?: return@run null
|
||||
val expectSourceRootDirectory = expectDeclaration.containingKtFile.sourceRoot ?: return@run null
|
||||
VfsUtil.getRelativePath(expectSourceFileDirectory, expectSourceRootDirectory)
|
||||
} /* Fallback: Construct a path from packageFqName */ ?: expectPackageFqn.pathSegments().joinToString(VfsUtil.VFS_SEPARATOR)
|
||||
|
||||
|
||||
/**
|
||||
* Let's get a directory in the [sourceSet] that would allow us to find or create
|
||||
* the corresponding KtFile. Create the source root in the IJ model if necessary.
|
||||
*/
|
||||
val actualDirectory: PsiDirectory = run {
|
||||
val actualSourceRoot = sourceSet.findOrCreateMostSuitableKotlinSourceRoot() ?: return
|
||||
val modifiableModel = module.rootManager.modifiableModel
|
||||
val contentEntry = modifiableModel.contentEntries.find { contentEntry ->
|
||||
contentEntry.file?.let { contentEntryFile -> VfsUtil.isAncestor(contentEntryFile, actualSourceRoot, false) } ?: false
|
||||
} ?: return
|
||||
|
||||
if (contentEntry.sourceFolders.none { it.file == actualSourceRoot }) {
|
||||
contentEntry.addSourceFolder(actualSourceRoot, module.isTestModule)
|
||||
}
|
||||
modifiableModel.commit()
|
||||
val actualDirectory = expectRelativePackagePath.split(VfsUtil.VFS_SEPARATOR)
|
||||
.fold(actualSourceRoot) { acc, segment -> acc.findOrCreateDirectory(segment) }
|
||||
PsiManager.getInstance(project).findDirectory(actualDirectory) ?: return
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an existing KtFile that also matches the package name or create a new file:
|
||||
* Note: We've located the [actualDirectory], and we know what the target file name shall be,
|
||||
* but this file might already exist, but might have a different package name (which would not match).
|
||||
* In this case we try to find another file which does not yet exist or has a matching package name.
|
||||
*/
|
||||
val actualKtFile: KtFile = run {
|
||||
val fileName = getActualTargetFileName(module, sourceSet, expectDeclaration.containingKtFile)
|
||||
/*
|
||||
We attempt to find or create the file. If there exists af file with the current fileName, which
|
||||
cannot be re-used (e.g., because of a non-fitting package), then we try to find or create
|
||||
another file (by counting up foo1.kt, foo2.kt, foo3.kt ...)
|
||||
*/
|
||||
val fileNameCandidates = listOf("$fileName.kt") + (1..16).map { "${fileName}$it.kt" }
|
||||
fileNameCandidates.firstNotNullOfOrNull findOrCreate@{ fileNameCandidate ->
|
||||
val existing = actualDirectory.findFile(fileNameCandidate)
|
||||
/* No file with this fileName exists: We can try to create it! */
|
||||
?: return@findOrCreate createKotlinFile(
|
||||
fileNameCandidate, actualDirectory, expectPackageFqn.asString()
|
||||
)
|
||||
|
||||
/* Check: The existing file could still have a different package FQN: In this case, we cannot re-use it! */
|
||||
(existing as? KtFile)?.takeIf { file -> file.packageFqName == expectPackageFqn }
|
||||
}
|
||||
} ?: return
|
||||
|
||||
/**
|
||||
* The above call to [findOrCreateKtFileForActualDeclaration] might have created a new source root and therefore triggered
|
||||
* dumb mode. We execute the action right away because we require smart mode for creating the 'actual' actual (pun intended)
|
||||
*/
|
||||
DumbService.getInstance(project).completeJustSubmittedTasks()
|
||||
|
||||
val added = actualKtFile.add(actualDeclaration.element ?: return).reformatted() as? KtNamedDeclaration ?: return
|
||||
val shortened = ShortenReferencesFacility.getInstance().shorten(added) as? KtDeclaration
|
||||
|
||||
/**
|
||||
* Find the navigation target:
|
||||
*
|
||||
* For example in:
|
||||
* ```
|
||||
* actual fun bar() {
|
||||
* T0D0("Not yet implemented")
|
||||
* }
|
||||
*
|
||||
* ```
|
||||
*
|
||||
* it would be convenient if we navigate to the 'T0D0' and select the expression, so that
|
||||
* writing the implementation can be started right away!
|
||||
*/
|
||||
val navigationTarget = run {
|
||||
val target = shortened ?: added
|
||||
val blockExpression = target.childrenOfType<KtBlockExpression>().firstOrNull() ?: return@run target
|
||||
blockExpression.children.first() as? KtElement ?: return@run target
|
||||
}
|
||||
|
||||
navigationTarget.navigate(true)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresWriteLock
|
||||
private fun KotlinBuildSystemSourceSet.findOrCreateMostSuitableKotlinSourceRoot(): VirtualFile? {
|
||||
val sourceDirectoryPath = sourceDirectories
|
||||
/*
|
||||
Prioritize source directories which contain the name of the source set:
|
||||
for example, a 'jvmMain' source set should prioritize '.../src/jvmMain/kotlin'
|
||||
*/
|
||||
.find { path -> path.contains(Path(name)) }
|
||||
?: sourceDirectories.firstOrNull()
|
||||
?: return null
|
||||
|
||||
return VfsUtil.createDirectoryIfMissing(sourceDirectoryPath.absolutePathString())
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Having a Source File with the same name in the same package in several Source Sets might be a problem.
|
||||
* e.g. `Foo.kt` in commonMain and `Foo.kt` in jvmMain could produce a broken .jar file.
|
||||
* Therefore, the file gets mangled by adding a classifier from the module as in `Foo.jvm.kt`
|
||||
*/
|
||||
private fun getActualTargetFileName(
|
||||
module: Module,
|
||||
sourceSet: KotlinBuildSystemSourceSet,
|
||||
expect: PsiFile
|
||||
): String {
|
||||
/* File name matches: Let's mangle by adding a module string like '.jvm', '.android', '.ios' */
|
||||
val moduleClassifier = if (module.isAndroidModule()) "android"
|
||||
else sourceSet.name.removeSuffix("Main").removeSuffix("Test")
|
||||
return "${expect.virtualFile.nameWithoutExtension}.$moduleClassifier"
|
||||
}
|
||||
+3
-2
@@ -99,13 +99,14 @@ context(KaSession)
|
||||
@ApiStatus.Internal
|
||||
fun generateMember(
|
||||
project: Project,
|
||||
ktClassMember: KtClassMember,
|
||||
ktClassMember: KtClassMember?,
|
||||
symbol: KaCallableSymbol,
|
||||
targetClass: KtClassOrObject?,
|
||||
copyDoc: Boolean,
|
||||
mode: MemberGenerateMode = MemberGenerateMode.OVERRIDE
|
||||
): KtCallableDeclaration = with(ktClassMember) {
|
||||
val bodyType = when {
|
||||
this == null -> BodyType.FromTemplate
|
||||
targetClass?.hasExpectModifier() == true -> BodyType.NoBody
|
||||
symbol.isExtension && mode == MemberGenerateMode.OVERRIDE -> BodyType.FromTemplate
|
||||
else -> bodyType
|
||||
@@ -194,7 +195,7 @@ fun generateMember(
|
||||
}
|
||||
}
|
||||
|
||||
if (preferConstructorParameter && ktClassMember.memberInfo.isProperty) {
|
||||
if (this != null && preferConstructorParameter && memberInfo.isProperty) {
|
||||
return generateConstructorParameter(project, symbol, renderer)
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -56,5 +56,6 @@
|
||||
<orderEntry type="module" module-name="kotlin.base.fe10.analysis" scope="TEST" />
|
||||
<orderEntry type="module" module-name="kotlin.project-wizard.core" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.platform.testFramework.junit5" scope="TEST" />
|
||||
<orderEntry type="module" module-name="kotlin.base.external-build-system" scope="TEST" />
|
||||
</component>
|
||||
</module>
|
||||
+10
@@ -650,6 +650,11 @@ public abstract class QuickFixMultiModuleTestGenerated extends AbstractQuickFixM
|
||||
runTest("testData/multiModuleQuickFix/createActual/function/");
|
||||
}
|
||||
|
||||
@TestMetadata("functionK2")
|
||||
public void testFunctionK2() throws Exception {
|
||||
runTest("testData/multiModuleQuickFix/createActual/functionK2/");
|
||||
}
|
||||
|
||||
@TestMetadata("functionSameFile")
|
||||
public void testFunctionSameFile() throws Exception {
|
||||
runTest("testData/multiModuleQuickFix/createActual/functionSameFile/");
|
||||
@@ -700,6 +705,11 @@ public abstract class QuickFixMultiModuleTestGenerated extends AbstractQuickFixM
|
||||
runTest("testData/multiModuleQuickFix/createActual/property/");
|
||||
}
|
||||
|
||||
@TestMetadata("propertyK2")
|
||||
public void testPropertyK2() throws Exception {
|
||||
runTest("testData/multiModuleQuickFix/createActual/propertyK2/");
|
||||
}
|
||||
|
||||
@TestMetadata("sealed")
|
||||
public void testSealed() throws Exception {
|
||||
runTest("testData/multiModuleQuickFix/createActual/sealed/");
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
// "Create actual in 'testModule_JVM'" "true"
|
||||
// IGNORE_K1
|
||||
// TOOL: org.jetbrains.kotlin.idea.k2.codeinsight.inspections.KotlinNoActualForExpectInspection
|
||||
|
||||
expect fun <caret>foo(arg: Int): String
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
// "Create actual in 'testModule_JVM'" "true"
|
||||
// IGNORE_K1
|
||||
// TOOL: org.jetbrains.kotlin.idea.k2.codeinsight.inspections.KotlinNoActualForExpectInspection
|
||||
|
||||
expect fun <selection><caret></selection>foo(arg: Int): String
|
||||
+1
@@ -0,0 +1 @@
|
||||
// foo: to be implemented
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
// foo: to be implemented
|
||||
actual fun foo(arg: Int): String {
|
||||
<selection><caret></selection>TODO("Not yet implemented")
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
// "Create actual in 'testModule_JVM'" "true"
|
||||
// IGNORE_K1
|
||||
// TOOL: org.jetbrains.kotlin.idea.k2.codeinsight.inspections.KotlinNoActualForExpectInspection
|
||||
|
||||
expect var <caret>x: Int
|
||||
+1
@@ -0,0 +1 @@
|
||||
// x: to be implemented
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
// x: to be implemented
|
||||
actual var <selection><caret></selection>x: Int
|
||||
get() = TODO("Not yet implemented")
|
||||
set(value) {}
|
||||
@@ -42,5 +42,6 @@
|
||||
<orderEntry type="module" module-name="kotlin.gradle.gradle" scope="TEST" />
|
||||
<orderEntry type="library" scope="TEST" name="gson" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.projectModel" scope="TEST" />
|
||||
<orderEntry type="module" module-name="kotlin.base.external-build-system" scope="TEST" />
|
||||
</component>
|
||||
</module>
|
||||
+25
-1
@@ -5,10 +5,13 @@ package org.jetbrains.kotlin.idea.multiplatform
|
||||
import com.intellij.openapi.application.runReadAction
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.module.StdModuleTypes
|
||||
import com.intellij.openapi.observable.util.whenDisposed
|
||||
import com.intellij.openapi.projectRoots.ProjectJdkTable
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.vfs.LocalFileSystem
|
||||
import org.jetbrains.kotlin.checkers.utils.clearFileFromDiagnosticMarkup
|
||||
import org.jetbrains.kotlin.idea.base.externalSystem.KotlinBuildSystemFacade
|
||||
import org.jetbrains.kotlin.idea.base.externalSystem.KotlinBuildSystemSourceSet
|
||||
import org.jetbrains.kotlin.idea.base.platforms.KotlinCommonLibraryKind
|
||||
import org.jetbrains.kotlin.idea.base.platforms.KotlinJavaScriptLibraryKind
|
||||
import org.jetbrains.kotlin.idea.base.platforms.KotlinWasmJsLibraryKind
|
||||
@@ -51,6 +54,7 @@ fun AbstractMultiModuleTest.setupMppProjectFromDirStructure(testRoot: File) {
|
||||
val dirs = testRoot.listFiles().filter { it.isDirectory }
|
||||
val rootInfos = dirs.map { parseDirName(it) }
|
||||
doSetupProject(rootInfos)
|
||||
setupKotlinBuildSystemFacade()
|
||||
}
|
||||
|
||||
fun AbstractMultiModuleTest.setupMppProjectFromTextFile(testRoot: File) {
|
||||
@@ -175,15 +179,21 @@ private fun AbstractMultiModuleTest.doSetupProject(rootInfos: List<RootInfo>) {
|
||||
platform.isJvm() -> module.addLibrary(TestKotlinArtifacts.kotlinStdlib)
|
||||
platform.isJs() -> module.addLibrary(TestKotlinArtifacts.kotlinStdlibJs, kind = KotlinJavaScriptLibraryKind)
|
||||
platform.isWasmJs() -> module.addLibrary(TestKotlinArtifacts.kotlinStdlibWasmJs, kind = KotlinWasmJsLibraryKind)
|
||||
platform.isWasmWasi() -> module.addLibrary(TestKotlinArtifacts.kotlinStdlibWasmWasi, kind = KotlinWasmWasiLibraryKind)
|
||||
platform.isWasmWasi() -> module.addLibrary(
|
||||
TestKotlinArtifacts.kotlinStdlibWasmWasi,
|
||||
kind = KotlinWasmWasiLibraryKind
|
||||
)
|
||||
|
||||
else -> error("Unknown platform $this")
|
||||
}
|
||||
}
|
||||
|
||||
is FullJdkDependency -> {
|
||||
ConfigLibraryUtil.configureSdk(module, PluginTestCaseBase.addJdk(testRootDisposable) {
|
||||
PluginTestCaseBase.jdk(TestJdkKind.FULL_JDK)
|
||||
})
|
||||
}
|
||||
|
||||
is CoroutinesDependency -> module.enableCoroutines()
|
||||
is KotlinTestDependency -> when {
|
||||
platform.isJvm() -> module.addLibrary(TestKotlinArtifacts.kotlinTestJunit)
|
||||
@@ -233,6 +243,20 @@ private fun AbstractMultiModuleTest.doSetupProject(rootInfos: List<RootInfo>) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See [KotlinBuildSystemFacade]:
|
||||
* - Will use the Module's name as Source Set Name
|
||||
* - Will use the module source roots as source directories
|
||||
*/
|
||||
private fun AbstractMultiModuleTest.setupKotlinBuildSystemFacade() {
|
||||
KotlinBuildSystemFacade.EP_NAME.point.registerExtension(object : KotlinBuildSystemFacade {
|
||||
override fun findSourceSet(module: Module) = KotlinBuildSystemSourceSet(
|
||||
name = module.name,
|
||||
sourceDirectories = module.sourceRoots.map { it.toNioPath() }
|
||||
)
|
||||
}, testRootDisposable)
|
||||
}
|
||||
|
||||
private fun AbstractMultiModuleTest.createModuleWithRoots(
|
||||
moduleId: ModuleId,
|
||||
infos: List<RootInfo>
|
||||
|
||||
Reference in New Issue
Block a user