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:
Nikolay Krasko
2017-09-29 18:24:15 +03:00
parent 2634ed15fa
commit 0bffd04ef1
10 changed files with 212 additions and 7 deletions
@@ -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>
@@ -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 {
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 {