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 package org.jetbrains.kotlin.idea.configuration
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.module.Module import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleManager import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.JavaSdkVersion
import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.* import com.intellij.openapi.roots.*
import com.intellij.openapi.roots.libraries.* import com.intellij.openapi.roots.libraries.*
import com.intellij.openapi.util.Computable
import com.intellij.openapi.vfs.JarFileSystem import com.intellij.openapi.vfs.JarFileSystem
import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VfsUtil import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VfsUtilCore import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiJavaModule
import org.jetbrains.annotations.Contract import org.jetbrains.annotations.Contract
import org.jetbrains.kotlin.config.ApiVersion import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.config.KotlinFacetSettingsProvider 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.facet.getRuntimeLibraryVersion
import org.jetbrains.kotlin.idea.framework.ui.CreateLibraryDialogWithModules import org.jetbrains.kotlin.idea.framework.ui.CreateLibraryDialogWithModules
import org.jetbrains.kotlin.idea.framework.ui.FileUIUtils 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.quickfix.askUpdateRuntime
import org.jetbrains.kotlin.idea.util.application.runWriteAction 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.LibraryJarDescriptor
import org.jetbrains.kotlin.idea.versions.findAllUsedLibraries import org.jetbrains.kotlin.idea.versions.findAllUsedLibraries
import org.jetbrains.kotlin.idea.versions.findKotlinRuntimeLibrary import org.jetbrains.kotlin.idea.versions.findKotlinRuntimeLibrary
import org.jetbrains.kotlin.resolve.jvm.modules.KOTLIN_STDLIB_MODULE_NAME
import java.io.File import java.io.File
import java.util.* import java.util.*
@@ -56,7 +65,7 @@ abstract class KotlinWithLibraryConfigurator internal constructor() : KotlinProj
open val libraryType: LibraryType<DummyLibraryProperties>? = null open val libraryType: LibraryType<DummyLibraryProperties>? = null
protected val libraryKind: PersistentLibraryKind<*>? = libraryType?.kind protected val libraryKind: PersistentLibraryKind<*>? = libraryType?.kind
override fun getStatus(moduleSourceRootGroup: ModuleSourceRootGroup): ConfigureKotlinStatus { override fun getStatus(moduleSourceRootGroup: ModuleSourceRootGroup): ConfigureKotlinStatus {
val module = moduleSourceRootGroup.baseModule val module = moduleSourceRootGroup.baseModule
@@ -129,12 +138,12 @@ abstract class KotlinWithLibraryConfigurator internal constructor() : KotlinProj
pathFromDialog: String?, pathFromDialog: String?,
collector: NotificationMessageCollector collector: NotificationMessageCollector
) { ) {
val classesPath = getPathToCopyFileTo(module.project, OrderRootType.CLASSES, defaultPath, pathFromDialog) val classesPath = getPathToCopyFileTo(module.project, OrderRootType.CLASSES, defaultPath, pathFromDialog)
val sourcesPath = getPathToCopyFileTo(module.project, OrderRootType.SOURCES, defaultPath, pathFromDialog) val sourcesPath = getPathToCopyFileTo(module.project, OrderRootType.SOURCES, defaultPath, pathFromDialog)
configureModuleWithLibrary(module, classesPath, sourcesPath, collector, useBundled = pathFromDialog == null) configureModuleWithLibrary(module, classesPath, sourcesPath, collector, useBundled = pathFromDialog == null)
} }
fun configureModuleWithLibrary( fun configureModuleWithLibrary(
module: Module, module: Module,
classesPath: String, classesPath: String,
sourcesPath: String, sourcesPath: String,
@@ -149,7 +158,7 @@ abstract class KotlinWithLibraryConfigurator internal constructor() : KotlinProj
?: getKotlinLibrary(project) ?: getKotlinLibrary(project)
?: createNewLibrary(project, collector) ?: createNewLibrary(project, collector)
val sdk = ModuleRootManager.getInstance(module).sdk val sdk = module.sdk
val model = library.modifiableModel val model = library.modifiableModel
for (descriptor in getLibraryJarDescriptors(sdk)) { for (descriptor in getLibraryJarDescriptors(sdk)) {
@@ -167,6 +176,23 @@ abstract class KotlinWithLibraryConfigurator internal constructor() : KotlinProj
ApplicationManager.getApplication().runWriteAction { model.commit() } ApplicationManager.getApplication().runWriteAction { model.commit() }
addLibraryToModuleIfNeeded(module, library, collector) 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( fun configureLibraryJar(
@@ -16,15 +16,38 @@
package org.jetbrains.kotlin.idea.quickfix 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.analysis.JavaModuleGraphUtil
import com.intellij.codeInsight.daemon.impl.quickfix.AddRequiredModuleFix import com.intellij.codeInsight.daemon.impl.quickfix.AddRequiredModuleFix
import com.intellij.codeInsight.intention.IntentionAction 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.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
import org.jetbrains.kotlin.idea.util.findRequireDirective
import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm 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() { companion object : KotlinSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): IntentionAction? { override fun createAction(diagnostic: Diagnostic): IntentionAction? {
val expression = diagnostic.psiElement as? KtExpression ?: return null 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 dependDiagnostic = DiagnosticFactory.cast(diagnostic, ErrorsJvm.JAVA_MODULE_DOES_NOT_DEPEND_ON_MODULE)
val moduleName = dependDiagnostic.a 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.externalSystem.service.project.IdeModifiableModelsProviderImpl;
import com.intellij.openapi.module.Module; import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.LibraryOrderEntry; import com.intellij.openapi.roots.LibraryOrderEntry;
import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.RootPolicy; import com.intellij.openapi.roots.RootPolicy;
import com.intellij.openapi.roots.libraries.Library; import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.util.io.FileUtil; 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.K2JSCompilerArguments;
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments; import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments;
import org.jetbrains.kotlin.config.*; 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.facet.KotlinFacet;
import org.jetbrains.kotlin.idea.framework.JsLibraryStdDetectionUtil; import org.jetbrains.kotlin.idea.framework.JsLibraryStdDetectionUtil;
import org.jetbrains.kotlin.idea.project.PlatformKt; 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.idea.versions.KotlinRuntimeLibraryUtilKt;
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleKt;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.stream.StreamSupport;
public class ConfigureKotlinTest extends AbstractConfigureKotlinTest { public class ConfigureKotlinTest extends AbstractConfigureKotlinTest {
public void testNewLibrary_copyJar() { public void testNewLibrary_copyJar() {
@@ -250,6 +256,36 @@ public class ConfigureKotlinTest extends AbstractConfigureKotlinTest {
assertEquals(KotlinFacet.Companion.get(module2).getConfiguration().getSettings().getImplementedModuleName(), "module1"); 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) { private void configureFacetAndCheckJvm(JvmTarget jvmTarget) {
IdeModifiableModelsProviderImpl modelsProvider = new IdeModifiableModelsProviderImpl(getProject()); IdeModifiableModelsProviderImpl modelsProvider = new IdeModifiableModelsProviderImpl(getProject());
try { try {