diff --git a/idea/idea-maven/idea-maven.iml b/idea/idea-maven/idea-maven.iml index 0f939d887d9..d7f0488f552 100644 --- a/idea/idea-maven/idea-maven.iml +++ b/idea/idea-maven/idea-maven.iml @@ -24,5 +24,6 @@ + \ No newline at end of file diff --git a/idea/idea-maven/src/org/jetbrains/kotlin/idea/maven/PomFile.kt b/idea/idea-maven/src/org/jetbrains/kotlin/idea/maven/PomFile.kt index ce9b9bae9c2..86cf88d5006 100644 --- a/idea/idea-maven/src/org/jetbrains/kotlin/idea/maven/PomFile.kt +++ b/idea/idea-maven/src/org/jetbrains/kotlin/idea/maven/PomFile.kt @@ -94,6 +94,11 @@ class PomFile private constructor(val xmlFile: XmlFile, val domModel: MavenDomPr } } + fun findProperty(name: String): XmlTag? { + val propertiesNode = nodesByName["properties"] ?: return null + return propertiesNode.findFirstSubTag(name) + } + fun addDependency(artifact: MavenId, scope: MavenArtifactScope? = null, classifier: String? = null, optional: Boolean = false, systemPath: String? = null): MavenDomDependency { require(systemPath == null || scope == MavenArtifactScope.SYSTEM) { "systemPath is only applicable for system scope dependency" } require(artifact.groupId != null) { "groupId shouldn't be null" } @@ -560,4 +565,41 @@ class PomFile private constructor(val xmlFile: XmlFile, val domModel: MavenDomPr val recommendedOrderAsList = recommendedElementsOrder.toList() } -} \ No newline at end of file +} + +fun PomFile.changeLanguageVersion(languageVersion: String?, apiVersion: String?): PsiElement? { + val kotlinPlugin = findPlugin(MavenId(KotlinMavenConfigurator.GROUP_ID, + KotlinMavenConfigurator.MAVEN_PLUGIN_ID, + null)) ?: return null + val languageElement = languageVersion?.let { + changeConfigurationOrProperty(kotlinPlugin, "languageVersion", "kotlin.compiler.languageVersion", it) + } + val apiElement = apiVersion?.let { + changeConfigurationOrProperty(kotlinPlugin, "apiVersion", "kotlin.compiler.apiVersion", it) + } + return languageElement ?: apiElement +} + +private fun PomFile.changeConfigurationOrProperty(kotlinPlugin: MavenDomPlugin, + configurationTagName: String, + propertyName: String, value: String): XmlTag? { + val configuration = kotlinPlugin.configuration + if (configuration.exists()) { + val subTag = configuration.xmlTag.findFirstSubTag(configurationTagName) + if (subTag != null) { + subTag.value.text = value + return subTag + } + } + + val propertyTag = findProperty(propertyName) + if (propertyTag != null) { + val textNode = propertyTag.children.filterIsInstance().firstOrNull() + if (textNode != null) { + textNode.value = value + return propertyTag + } + } + + return null +} diff --git a/idea/idea-maven/src/org/jetbrains/kotlin/idea/maven/configuration/KotlinMavenConfigurator.kt b/idea/idea-maven/src/org/jetbrains/kotlin/idea/maven/configuration/KotlinMavenConfigurator.kt index 7cff69dc169..a947c277942 100644 --- a/idea/idea-maven/src/org/jetbrains/kotlin/idea/maven/configuration/KotlinMavenConfigurator.kt +++ b/idea/idea-maven/src/org/jetbrains/kotlin/idea/maven/configuration/KotlinMavenConfigurator.kt @@ -196,7 +196,7 @@ abstract class KotlinMavenConfigurator return !FileTypeIndex.getFiles(JavaFileType.INSTANCE, GlobalSearchScope.moduleScope(module)).isEmpty() } - private fun findModulePomFile(module: Module): PsiFile? { + fun findModulePomFile(module: Module): PsiFile? { val files = MavenProjectsManager.getInstance(module.project).projectsFiles for (file in files) { val fileModule = ModuleUtilCore.findModuleForFile(file, module.project) diff --git a/idea/idea-maven/test/org/jetbrains/kotlin/idea/maven/MavenLanguageFeatureQuickFixTest.kt b/idea/idea-maven/test/org/jetbrains/kotlin/idea/maven/MavenLanguageFeatureQuickFixTest.kt new file mode 100644 index 00000000000..9d000450977 --- /dev/null +++ b/idea/idea-maven/test/org/jetbrains/kotlin/idea/maven/MavenLanguageFeatureQuickFixTest.kt @@ -0,0 +1,88 @@ +/* + * 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.maven + +import com.intellij.openapi.fileEditor.FileDocumentManager +import com.intellij.openapi.fileEditor.impl.LoadTextUtil +import com.intellij.openapi.roots.ModuleRootManager +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.rt.execution.junit.FileComparisonFailure +import com.intellij.testFramework.fixtures.CodeInsightTestFixture +import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory +import org.jetbrains.kotlin.test.KotlinTestUtils +import org.jetbrains.kotlin.test.testFramework.runInEdtAndWait +import org.junit.Test +import java.io.File +import kotlin.reflect.KMutableProperty0 + +class MavenLanguageFeatureQuickFixTest : MavenImportingTestCase() { + private lateinit var codeInsightTestFixture: CodeInsightTestFixture + + fun getTestDataPath() = KotlinTestUtils.getHomeDirectory() + "/idea/idea-maven/testData/languageFeature/" + getTestName(true).substringBefore('_') + + override fun setUpFixtures() { + myTestFixture = IdeaTestFixtureFactory.getFixtureFactory().createFixtureBuilder(getName()).fixture + codeInsightTestFixture = IdeaTestFixtureFactory.getFixtureFactory().createCodeInsightFixture(myTestFixture) + codeInsightTestFixture.setUp() + } + + override fun tearDownFixtures() { + codeInsightTestFixture.tearDown() + (this::codeInsightTestFixture as KMutableProperty0).set(null) + myTestFixture = null + } + + @Test fun testUpdateLanguageVersion() { + doTest("Set module language version to 1.1") + } + + @Test fun testUpdateLanguageVersionProperty() { + doTest("Set module language version to 1.1") + } + + @Test fun testUpdateApiVersion() { + doTest("Set module API version to 1.1") + } + + @Test fun testUpdateLanguageAndApiVersion() { + doTest("Set module language version to 1.1") + } + + private fun doTest(intentionName: String) { + val pomVFile = createProjectSubFile("pom.xml", File(getTestDataPath(), "pom.xml").readText()) + val sourceVFile = createProjectSubFile("src/main/kotlin/src.kt", File(getTestDataPath(), "src.kt").readText()) + myProjectPom = pomVFile + myAllPoms.add(myProjectPom) + importProject() + runInEdtAndWait { + assertTrue(ModuleRootManager.getInstance(myTestFixture.module).fileIndex.isInSourceContent(sourceVFile)) + codeInsightTestFixture.configureFromExistingVirtualFile(sourceVFile) + codeInsightTestFixture.launchAction(codeInsightTestFixture.findSingleIntention(intentionName)) + FileDocumentManager.getInstance().saveAllDocuments() + checkResult(pomVFile) + } + } + + private fun checkResult(file: VirtualFile) { + val expectedPath = File(getTestDataPath(), "pom.xml.after") + val expectedContent = expectedPath.readText() + val actualContent = LoadTextUtil.loadText(file).toString() + if (actualContent != expectedContent) { + throw FileComparisonFailure("pom.xml doesn't match", expectedContent, actualContent, expectedPath.path) + } + } +} \ No newline at end of file diff --git a/idea/idea-maven/testData/languageFeature/updateApiVersion/pom.xml b/idea/idea-maven/testData/languageFeature/updateApiVersion/pom.xml new file mode 100644 index 00000000000..84f9507073c --- /dev/null +++ b/idea/idea-maven/testData/languageFeature/updateApiVersion/pom.xml @@ -0,0 +1,60 @@ + + + 4.0.0 + + maventest + maventest + 1.0-SNAPSHOT + + $VERSION$ + + + + org.jetbrains.kotlin + kotlin-stdlib-jre8 + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-test + ${kotlin.version} + test + + + + + ${project.basedir}/src/main/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + wrong-goal + compile + + + + test-compile + test-compile + + test-compile + + + + + 1.8 + 1.0 + + + + + + + \ No newline at end of file diff --git a/idea/idea-maven/testData/languageFeature/updateApiVersion/pom.xml.after b/idea/idea-maven/testData/languageFeature/updateApiVersion/pom.xml.after new file mode 100644 index 00000000000..5b293c05a4d --- /dev/null +++ b/idea/idea-maven/testData/languageFeature/updateApiVersion/pom.xml.after @@ -0,0 +1,60 @@ + + + 4.0.0 + + maventest + maventest + 1.0-SNAPSHOT + + $VERSION$ + + + + org.jetbrains.kotlin + kotlin-stdlib-jre8 + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-test + ${kotlin.version} + test + + + + + ${project.basedir}/src/main/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + wrong-goal + compile + + + + test-compile + test-compile + + test-compile + + + + + 1.8 + 1.1 + + + + + + + \ No newline at end of file diff --git a/idea/idea-maven/testData/languageFeature/updateApiVersion/src.kt b/idea/idea-maven/testData/languageFeature/updateApiVersion/src.kt new file mode 100644 index 00000000000..ccaa5fc137a --- /dev/null +++ b/idea/idea-maven/testData/languageFeature/updateApiVersion/src.kt @@ -0,0 +1 @@ +val x = "s"::length diff --git a/idea/idea-maven/testData/languageFeature/updateLanguageAndApiVersion/pom.xml b/idea/idea-maven/testData/languageFeature/updateLanguageAndApiVersion/pom.xml new file mode 100644 index 00000000000..96a5b6f591d --- /dev/null +++ b/idea/idea-maven/testData/languageFeature/updateLanguageAndApiVersion/pom.xml @@ -0,0 +1,61 @@ + + + 4.0.0 + + maventest + maventest + 1.0-SNAPSHOT + + $VERSION$ + + + + org.jetbrains.kotlin + kotlin-stdlib-jre8 + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-test + ${kotlin.version} + test + + + + + ${project.basedir}/src/main/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + wrong-goal + compile + + + + test-compile + test-compile + + test-compile + + + + + 1.8 + 1.0 + 1.0 + + + + + + + \ No newline at end of file diff --git a/idea/idea-maven/testData/languageFeature/updateLanguageAndApiVersion/pom.xml.after b/idea/idea-maven/testData/languageFeature/updateLanguageAndApiVersion/pom.xml.after new file mode 100644 index 00000000000..282d2f6b28a --- /dev/null +++ b/idea/idea-maven/testData/languageFeature/updateLanguageAndApiVersion/pom.xml.after @@ -0,0 +1,61 @@ + + + 4.0.0 + + maventest + maventest + 1.0-SNAPSHOT + + $VERSION$ + + + + org.jetbrains.kotlin + kotlin-stdlib-jre8 + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-test + ${kotlin.version} + test + + + + + ${project.basedir}/src/main/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + wrong-goal + compile + + + + test-compile + test-compile + + test-compile + + + + + 1.8 + 1.1 + 1.1 + + + + + + + \ No newline at end of file diff --git a/idea/idea-maven/testData/languageFeature/updateLanguageAndApiVersion/src.kt b/idea/idea-maven/testData/languageFeature/updateLanguageAndApiVersion/src.kt new file mode 100644 index 00000000000..ccaa5fc137a --- /dev/null +++ b/idea/idea-maven/testData/languageFeature/updateLanguageAndApiVersion/src.kt @@ -0,0 +1 @@ +val x = "s"::length diff --git a/idea/idea-maven/testData/languageFeature/updateLanguageVersion/pom.xml b/idea/idea-maven/testData/languageFeature/updateLanguageVersion/pom.xml new file mode 100644 index 00000000000..addeea2bcb8 --- /dev/null +++ b/idea/idea-maven/testData/languageFeature/updateLanguageVersion/pom.xml @@ -0,0 +1,60 @@ + + + 4.0.0 + + maventest + maventest + 1.0-SNAPSHOT + + $VERSION$ + + + + org.jetbrains.kotlin + kotlin-stdlib-jre8 + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-test + ${kotlin.version} + test + + + + + ${project.basedir}/src/main/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + wrong-goal + compile + + + + test-compile + test-compile + + test-compile + + + + + 1.8 + 1.0 + + + + + + + \ No newline at end of file diff --git a/idea/idea-maven/testData/languageFeature/updateLanguageVersion/pom.xml.after b/idea/idea-maven/testData/languageFeature/updateLanguageVersion/pom.xml.after new file mode 100644 index 00000000000..3247b52210a --- /dev/null +++ b/idea/idea-maven/testData/languageFeature/updateLanguageVersion/pom.xml.after @@ -0,0 +1,60 @@ + + + 4.0.0 + + maventest + maventest + 1.0-SNAPSHOT + + $VERSION$ + + + + org.jetbrains.kotlin + kotlin-stdlib-jre8 + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-test + ${kotlin.version} + test + + + + + ${project.basedir}/src/main/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + wrong-goal + compile + + + + test-compile + test-compile + + test-compile + + + + + 1.8 + 1.1 + + + + + + + \ No newline at end of file diff --git a/idea/idea-maven/testData/languageFeature/updateLanguageVersion/src.kt b/idea/idea-maven/testData/languageFeature/updateLanguageVersion/src.kt new file mode 100644 index 00000000000..5531476003c --- /dev/null +++ b/idea/idea-maven/testData/languageFeature/updateLanguageVersion/src.kt @@ -0,0 +1 @@ +val x get() = 1 diff --git a/idea/idea-maven/testData/languageFeature/updateLanguageVersionProperty/pom.xml b/idea/idea-maven/testData/languageFeature/updateLanguageVersionProperty/pom.xml new file mode 100644 index 00000000000..5199179b137 --- /dev/null +++ b/idea/idea-maven/testData/languageFeature/updateLanguageVersionProperty/pom.xml @@ -0,0 +1,60 @@ + + + 4.0.0 + + maventest + maventest + 1.0-SNAPSHOT + + $VERSION$ + 1.0 + + + + org.jetbrains.kotlin + kotlin-stdlib-jre8 + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-test + ${kotlin.version} + test + + + + + ${project.basedir}/src/main/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + wrong-goal + compile + + + + test-compile + test-compile + + test-compile + + + + + 1.8 + + + + + + + \ No newline at end of file diff --git a/idea/idea-maven/testData/languageFeature/updateLanguageVersionProperty/pom.xml.after b/idea/idea-maven/testData/languageFeature/updateLanguageVersionProperty/pom.xml.after new file mode 100644 index 00000000000..24314c05ac6 --- /dev/null +++ b/idea/idea-maven/testData/languageFeature/updateLanguageVersionProperty/pom.xml.after @@ -0,0 +1,60 @@ + + + 4.0.0 + + maventest + maventest + 1.0-SNAPSHOT + + $VERSION$ + 1.1 + + + + org.jetbrains.kotlin + kotlin-stdlib-jre8 + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-test + ${kotlin.version} + test + + + + + ${project.basedir}/src/main/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + wrong-goal + compile + + + + test-compile + test-compile + + test-compile + + + + + 1.8 + + + + + + + \ No newline at end of file diff --git a/idea/idea-maven/testData/languageFeature/updateLanguageVersionProperty/src.kt b/idea/idea-maven/testData/languageFeature/updateLanguageVersionProperty/src.kt new file mode 100644 index 00000000000..5531476003c --- /dev/null +++ b/idea/idea-maven/testData/languageFeature/updateLanguageVersionProperty/src.kt @@ -0,0 +1 @@ +val x get() = 1 diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/EnableUnsupportedFeatureFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/EnableUnsupportedFeatureFix.kt index 4d518ef28e1..54d10353080 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/EnableUnsupportedFeatureFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/EnableUnsupportedFeatureFix.kt @@ -28,6 +28,7 @@ import com.intellij.openapi.roots.ex.ProjectRootManagerEx import com.intellij.openapi.roots.libraries.Library import com.intellij.openapi.ui.Messages import com.intellij.psi.PsiElement +import com.intellij.psi.xml.XmlFile import org.jetbrains.kotlin.config.ApiVersion import org.jetbrains.kotlin.config.KotlinFacetSettingsProvider import org.jetbrains.kotlin.config.LanguageFeature @@ -40,6 +41,9 @@ import org.jetbrains.kotlin.idea.compiler.configuration.KotlinCommonCompilerArgu import org.jetbrains.kotlin.idea.configuration.KotlinWithGradleConfigurator import org.jetbrains.kotlin.idea.facet.KotlinFacet import org.jetbrains.kotlin.idea.facet.getRuntimeLibraryVersion +import org.jetbrains.kotlin.idea.maven.PomFile +import org.jetbrains.kotlin.idea.maven.changeLanguageVersion +import org.jetbrains.kotlin.idea.maven.configuration.KotlinMavenConfigurator import org.jetbrains.kotlin.idea.util.projectStructure.allModules import org.jetbrains.kotlin.idea.versions.findKotlinRuntimeLibrary import org.jetbrains.kotlin.idea.versions.updateLibraries @@ -74,22 +78,23 @@ sealed class EnableUnsupportedFeatureFix( null } - if (KotlinPluginUtil.isGradleModule(module)) { + if (KotlinPluginUtil.isGradleModule(module) || KotlinPluginUtil.isMavenModule(module)) { if (runtimeUpdateRequired) { Messages.showErrorDialog(project, "This language feature requires version ${feature.sinceApiVersion} or later of the Kotlin runtime library. " + "Please update the version in your build script.", - "Update Language Level") + "Update Language Version") return } - val forTests = ModuleRootManager.getInstance(module).fileIndex.isInTestSourceContent(file.virtualFile) - val element = KotlinWithGradleConfigurator.changeLanguageVersion(module, - if (apiVersionOnly) null else targetVersion.versionString, - targetApiLevel, forTests) - + val element = if (KotlinPluginUtil.isGradleModule(module)) { + updateGradleLanguageVersion(module, file, targetApiLevel) + } + else { + updateMavenLanguageVersion(module, targetApiLevel) + } element?.let { - OpenFileDescriptor(project, it.containingFile.virtualFile, it.textRange.startOffset).navigate(true) + OpenFileDescriptor(module.project, it.containingFile.virtualFile, it.textRange.startOffset).navigate(true) } return } @@ -109,6 +114,31 @@ sealed class EnableUnsupportedFeatureFix( } } } + + private fun updateGradleLanguageVersion(module: Module, file: KtFile, targetApiLevel: String?): PsiElement? { + val forTests = ModuleRootManager.getInstance(module).fileIndex.isInTestSourceContent(file.virtualFile) + return KotlinWithGradleConfigurator.changeLanguageVersion(module, + if (apiVersionOnly) null else feature.sinceVersion!!.versionString, + targetApiLevel, forTests) + } + + private fun updateMavenLanguageVersion(module: Module, targetApiLevel: String?): PsiElement? { + fun doUpdateMavenLanguageVersion(): PsiElement? { + val psi = KotlinMavenConfigurator.findModulePomFile(module) as? XmlFile ?: return null + val pom = PomFile.forFileOrNull(psi) ?: return null + return pom.changeLanguageVersion( + if (apiVersionOnly) null else feature.sinceVersion!!.versionString, + targetApiLevel) + } + + val element = doUpdateMavenLanguageVersion() + if (element == null) { + Messages.showErrorDialog(module.project, + "Failed to update.pom.xml. Please update the file manually.", + text) + } + return element + } } class InProject(element: PsiElement, feature: LanguageFeature, apiVersionOnly: Boolean) @@ -152,8 +182,7 @@ sealed class EnableUnsupportedFeatureFix( } val module = ModuleUtilCore.findModuleForPsiElement(diagnostic.psiElement) ?: return null - if (KotlinPluginUtil.isMavenModule(module)) return null - if (!KotlinPluginUtil.isGradleModule(module)) { + if (!KotlinPluginUtil.isGradleModule(module) && !KotlinPluginUtil.isMavenModule(module)) { val facetSettings = KotlinFacet.get(module)?.configuration?.settings if (facetSettings == null || facetSettings.useProjectSettings) return InProject(diagnostic.psiElement, feature, apiVersionOnly) }