Add kotlin.stdlib requirement to module-info on configure (KT-19207)
KotlinAddRequiredModuleFix is almost copy ofAddRequiredModuleFix from the platform but it moves actual fix method to static with less parameters and checks directive presence before add. #KT-19207 Fixed
This commit is contained in:
@@ -17,17 +17,21 @@
|
||||
package org.jetbrains.kotlin.idea.configuration
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.command.WriteCommandAction
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.module.ModuleManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.JavaSdkVersion
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.roots.*
|
||||
import com.intellij.openapi.roots.libraries.*
|
||||
import com.intellij.openapi.util.Computable
|
||||
import com.intellij.openapi.vfs.JarFileSystem
|
||||
import com.intellij.openapi.vfs.LocalFileSystem
|
||||
import com.intellij.openapi.vfs.VfsUtil
|
||||
import com.intellij.openapi.vfs.VfsUtilCore
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiJavaModule
|
||||
import org.jetbrains.annotations.Contract
|
||||
import org.jetbrains.kotlin.config.ApiVersion
|
||||
import org.jetbrains.kotlin.config.KotlinFacetSettingsProvider
|
||||
@@ -37,11 +41,16 @@ import org.jetbrains.kotlin.idea.KotlinPluginUtil
|
||||
import org.jetbrains.kotlin.idea.facet.getRuntimeLibraryVersion
|
||||
import org.jetbrains.kotlin.idea.framework.ui.CreateLibraryDialogWithModules
|
||||
import org.jetbrains.kotlin.idea.framework.ui.FileUIUtils
|
||||
import org.jetbrains.kotlin.idea.quickfix.KotlinAddRequiredModuleFix
|
||||
import org.jetbrains.kotlin.idea.quickfix.askUpdateRuntime
|
||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||
import org.jetbrains.kotlin.idea.util.findFirstPsiJavaModule
|
||||
import org.jetbrains.kotlin.idea.util.projectStructure.sdk
|
||||
import org.jetbrains.kotlin.idea.util.projectStructure.version
|
||||
import org.jetbrains.kotlin.idea.versions.LibraryJarDescriptor
|
||||
import org.jetbrains.kotlin.idea.versions.findAllUsedLibraries
|
||||
import org.jetbrains.kotlin.idea.versions.findKotlinRuntimeLibrary
|
||||
import org.jetbrains.kotlin.resolve.jvm.modules.KOTLIN_STDLIB_MODULE_NAME
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
@@ -56,7 +65,7 @@ abstract class KotlinWithLibraryConfigurator internal constructor() : KotlinProj
|
||||
|
||||
open val libraryType: LibraryType<DummyLibraryProperties>? = null
|
||||
|
||||
protected val libraryKind: PersistentLibraryKind<*>? = libraryType?.kind
|
||||
protected val libraryKind: PersistentLibraryKind<*>? = libraryType?.kind
|
||||
|
||||
override fun getStatus(moduleSourceRootGroup: ModuleSourceRootGroup): ConfigureKotlinStatus {
|
||||
val module = moduleSourceRootGroup.baseModule
|
||||
@@ -129,12 +138,12 @@ abstract class KotlinWithLibraryConfigurator internal constructor() : KotlinProj
|
||||
pathFromDialog: String?,
|
||||
collector: NotificationMessageCollector
|
||||
) {
|
||||
val classesPath = getPathToCopyFileTo(module.project, OrderRootType.CLASSES, defaultPath, pathFromDialog)
|
||||
val sourcesPath = getPathToCopyFileTo(module.project, OrderRootType.SOURCES, defaultPath, pathFromDialog)
|
||||
val classesPath = getPathToCopyFileTo(module.project, OrderRootType.CLASSES, defaultPath, pathFromDialog)
|
||||
val sourcesPath = getPathToCopyFileTo(module.project, OrderRootType.SOURCES, defaultPath, pathFromDialog)
|
||||
configureModuleWithLibrary(module, classesPath, sourcesPath, collector, useBundled = pathFromDialog == null)
|
||||
}
|
||||
|
||||
fun configureModuleWithLibrary(
|
||||
fun configureModuleWithLibrary(
|
||||
module: Module,
|
||||
classesPath: String,
|
||||
sourcesPath: String,
|
||||
@@ -149,7 +158,7 @@ abstract class KotlinWithLibraryConfigurator internal constructor() : KotlinProj
|
||||
?: getKotlinLibrary(project)
|
||||
?: createNewLibrary(project, collector)
|
||||
|
||||
val sdk = ModuleRootManager.getInstance(module).sdk
|
||||
val sdk = module.sdk
|
||||
val model = library.modifiableModel
|
||||
|
||||
for (descriptor in getLibraryJarDescriptors(sdk)) {
|
||||
@@ -167,6 +176,23 @@ abstract class KotlinWithLibraryConfigurator internal constructor() : KotlinProj
|
||||
ApplicationManager.getApplication().runWriteAction { model.commit() }
|
||||
|
||||
addLibraryToModuleIfNeeded(module, library, collector)
|
||||
|
||||
updateModuleInfo(module, collector)
|
||||
}
|
||||
|
||||
private fun updateModuleInfo(module: Module, collector: NotificationMessageCollector) {
|
||||
if (module.sdk?.version?.isAtLeast(JavaSdkVersion.JDK_1_9) != true) return
|
||||
|
||||
val project = module.project
|
||||
val javaModule: PsiJavaModule = findFirstPsiJavaModule(module) ?: return
|
||||
|
||||
val success = WriteCommandAction.runWriteCommandAction(project, Computable<Boolean> {
|
||||
KotlinAddRequiredModuleFix.addModuleRequirement(javaModule, KOTLIN_STDLIB_MODULE_NAME)
|
||||
})
|
||||
|
||||
if (success) {
|
||||
collector.addMessage("Added $KOTLIN_STDLIB_MODULE_NAME requirement to module-info in ${module.name}")
|
||||
}
|
||||
}
|
||||
|
||||
fun configureLibraryJar(
|
||||
|
||||
@@ -16,15 +16,38 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.quickfix
|
||||
|
||||
import com.intellij.codeInsight.daemon.QuickFixBundle
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.AddRequiredModuleFix
|
||||
import com.intellij.codeInsight.intention.IntentionAction
|
||||
import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.util.PsiUtil
|
||||
import com.intellij.util.containers.ContainerUtil
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
|
||||
import org.jetbrains.kotlin.idea.util.findRequireDirective
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm
|
||||
|
||||
class KotlinAddRequiredModuleFix {
|
||||
class KotlinAddRequiredModuleFix(module: PsiJavaModule, private val requiredName: String) : LocalQuickFixAndIntentionActionOnPsiElement(module) {
|
||||
override fun getFamilyName(): String = QuickFixBundle.message("module.info.add.requires.family.name")
|
||||
override fun getText(): String = QuickFixBundle.message("module.info.add.requires.name", requiredName);
|
||||
override fun startInWriteAction() = true
|
||||
|
||||
override fun isAvailable(project: Project, file: PsiFile, startElement: PsiElement, endElement: PsiElement): Boolean {
|
||||
return PsiUtil.isLanguageLevel9OrHigher(file) &&
|
||||
startElement is PsiJavaModule &&
|
||||
startElement.getManager().isInProject(startElement) &&
|
||||
getLBrace(startElement) != null;
|
||||
}
|
||||
|
||||
override fun invoke(project: Project, file: PsiFile, editor: Editor?, startElement: PsiElement, endElement: PsiElement) {
|
||||
addModuleRequirement(startElement as PsiJavaModule, requiredName)
|
||||
}
|
||||
|
||||
companion object : KotlinSingleIntentionActionFactory() {
|
||||
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
|
||||
val expression = diagnostic.psiElement as? KtExpression ?: return null
|
||||
@@ -33,7 +56,38 @@ class KotlinAddRequiredModuleFix {
|
||||
val dependDiagnostic = DiagnosticFactory.cast(diagnostic, ErrorsJvm.JAVA_MODULE_DOES_NOT_DEPEND_ON_MODULE)
|
||||
val moduleName = dependDiagnostic.a
|
||||
|
||||
return AddRequiredModuleFix(javaModule, moduleName)
|
||||
return KotlinAddRequiredModuleFix(javaModule, moduleName)
|
||||
}
|
||||
|
||||
fun addModuleRequirement(module: PsiJavaModule, requiredName: String): Boolean {
|
||||
if (!module.isValid) return false
|
||||
if (findRequireDirective(module, requiredName) != null) return false
|
||||
|
||||
val parserFacade = JavaPsiFacade.getInstance(module.project).parserFacade
|
||||
val tempModule = parserFacade.createModuleFromText("module TempModuleName { requires $requiredName; }")
|
||||
val requiresStatement = tempModule.requires.first()
|
||||
|
||||
val addingPlace = findAddingPlace(module) ?: return false
|
||||
addingPlace.parent.addAfter(requiresStatement, addingPlace)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun getLBrace(module: PsiJavaModule): PsiElement? {
|
||||
val nameElement = module.nameIdentifier
|
||||
var element: PsiElement? = nameElement.nextSibling
|
||||
while (element != null) {
|
||||
if (PsiUtil.isJavaToken(element, JavaTokenType.LBRACE)) {
|
||||
return element
|
||||
}
|
||||
element = element.nextSibling
|
||||
}
|
||||
return null // module-info is incomplete
|
||||
}
|
||||
|
||||
private fun findAddingPlace(module: PsiJavaModule): PsiElement? {
|
||||
val addingPlace = module.requires.lastOrNull()
|
||||
return addingPlace ?: getLBrace(module)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.util
|
||||
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.psi.PsiJavaFile
|
||||
import com.intellij.psi.PsiJavaModule
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.psi.PsiRequiresStatement
|
||||
import com.intellij.psi.search.FilenameIndex
|
||||
|
||||
fun findFirstPsiJavaModule(module: Module): PsiJavaModule? {
|
||||
val project = module.project
|
||||
|
||||
val moduleInfoFiles = FilenameIndex.getVirtualFilesByName(project, PsiJavaModule.MODULE_INFO_FILE, module.moduleScope)
|
||||
return moduleInfoFiles
|
||||
.asSequence()
|
||||
.map(PsiManager.getInstance(project)::findFile)
|
||||
.filterIsInstance<PsiJavaFile>()
|
||||
.map { it.moduleDeclaration }
|
||||
.firstOrNull { it != null }
|
||||
}
|
||||
|
||||
fun findRequireDirective(module: PsiJavaModule, requiredName: String): PsiRequiresStatement? =
|
||||
module.requires.find { it.moduleName == requiredName }
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/module.iml" filepath="$PROJECT_DIR$/module.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_9" default="true" project-jdk-name="9" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,3 @@
|
||||
module MAIN {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/module.iml" filepath="$PROJECT_DIR$/module.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_9" default="true" project-jdk-name="9" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
module MAIN {
|
||||
requires kotlin.stdlib;
|
||||
}
|
||||
@@ -19,11 +19,14 @@ package org.jetbrains.kotlin.idea.configuration;
|
||||
import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProviderImpl;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.module.ModuleManager;
|
||||
import com.intellij.openapi.projectRoots.Sdk;
|
||||
import com.intellij.openapi.roots.LibraryOrderEntry;
|
||||
import com.intellij.openapi.roots.ModuleRootManager;
|
||||
import com.intellij.openapi.roots.RootPolicy;
|
||||
import com.intellij.openapi.roots.libraries.Library;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.psi.PsiJavaModule;
|
||||
import com.intellij.psi.PsiRequiresStatement;
|
||||
import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments;
|
||||
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments;
|
||||
import org.jetbrains.kotlin.config.*;
|
||||
@@ -31,11 +34,14 @@ import org.jetbrains.kotlin.idea.facet.FacetUtilsKt;
|
||||
import org.jetbrains.kotlin.idea.facet.KotlinFacet;
|
||||
import org.jetbrains.kotlin.idea.framework.JsLibraryStdDetectionUtil;
|
||||
import org.jetbrains.kotlin.idea.project.PlatformKt;
|
||||
import org.jetbrains.kotlin.idea.util.Java9StructureUtilKt;
|
||||
import org.jetbrains.kotlin.idea.versions.KotlinRuntimeLibraryUtilKt;
|
||||
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleKt;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
public class ConfigureKotlinTest extends AbstractConfigureKotlinTest {
|
||||
public void testNewLibrary_copyJar() {
|
||||
@@ -250,6 +256,36 @@ public class ConfigureKotlinTest extends AbstractConfigureKotlinTest {
|
||||
assertEquals(KotlinFacet.Companion.get(module2).getConfiguration().getSettings().getImplementedModuleName(), "module1");
|
||||
}
|
||||
|
||||
public void testJava9WithModuleInfo() {
|
||||
checkAddStdlibModule();
|
||||
}
|
||||
|
||||
public void testJava9WithModuleInfoWithStdlibAlready() {
|
||||
checkAddStdlibModule();
|
||||
}
|
||||
|
||||
private void checkAddStdlibModule() {
|
||||
doTestOneJavaModule(KotlinWithLibraryConfigurator.FileState.COPY);
|
||||
|
||||
Module module = getModule();
|
||||
Sdk moduleSdk = ModuleRootManager.getInstance(getModule()).getSdk();
|
||||
assertNotNull("Module SDK is not defined", moduleSdk);
|
||||
|
||||
PsiJavaModule javaModule = Java9StructureUtilKt.findFirstPsiJavaModule(module);
|
||||
assertNotNull(javaModule);
|
||||
|
||||
PsiRequiresStatement stdlibDirective =
|
||||
Java9StructureUtilKt.findRequireDirective(javaModule, JavaModuleKt.KOTLIN_STDLIB_MODULE_NAME);
|
||||
assertNotNull("Require directive for " + JavaModuleKt.KOTLIN_STDLIB_MODULE_NAME + " is expected",
|
||||
stdlibDirective);
|
||||
|
||||
long numberOfStdlib = StreamSupport.stream(javaModule.getRequires().spliterator(), false)
|
||||
.filter((statement) -> JavaModuleKt.KOTLIN_STDLIB_MODULE_NAME.equals(statement.getModuleName()))
|
||||
.count();
|
||||
|
||||
assertTrue("Only one standard library directive is expected", numberOfStdlib == 1);
|
||||
}
|
||||
|
||||
private void configureFacetAndCheckJvm(JvmTarget jvmTarget) {
|
||||
IdeModifiableModelsProviderImpl modelsProvider = new IdeModifiableModelsProviderImpl(getProject());
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user