Implement Add Kotlin library quickfix for GSK

This commit is contained in:
Vyacheslav Gerasimov
2017-05-31 19:52:05 +03:00
parent 2ec1a4311a
commit 9810c10f8b
3 changed files with 247 additions and 48 deletions
@@ -24,11 +24,14 @@ import org.jetbrains.kotlin.resolve.ImportPath
import org.jetbrains.kotlin.utils.addToStdlib.cast
fun getKotlinScriptDependencySnippet(artifactName: String): String =
"compile(${getKotlinModuleDependencySnippet(artifactName)})"
fun getGSKCompileDependencySnippet(groupId: String, artifactId: String, compileScope: String = "compile", version: String? = null): String =
if (groupId == KOTLIN_GROUP_ID)
"$compileScope(${getKotlinModuleDependencySnippet(artifactId, version)})"
else
"$compileScope(\"$groupId:$artifactId:$version\")"
fun getKotlinModuleDependencySnippet(artifactName: String): String =
"kotlinModule(\"${artifactName.removePrefix("kotlin-")}\", $GSK_KOTLIN_VERSION_PROPERTY_NAME)"
fun getKotlinModuleDependencySnippet(artifactId: String, version: String? = null): String =
"kotlinModule(\"${artifactId.removePrefix("kotlin-")}\", ${version?.let { "\"$it\"" } ?: GSK_KOTLIN_VERSION_PROPERTY_NAME})"
fun KtFile.containsCompileStdLib(): Boolean =
findScriptInitializer("dependencies")?.getBlock()?.findCompileStdLib() != null
@@ -36,12 +39,30 @@ fun KtFile.containsCompileStdLib(): Boolean =
fun KtFile.containsApplyKotlinPlugin(pluginName: String): Boolean =
findScriptInitializer("apply")?.getBlock()?.findPlugin(pluginName) != null
fun KtFile.getKotlinStdlibVersion(): String? {
return findScriptInitializer("dependencies")?.getBlock()?.let {
val expression = it.findCompileStdLib()?.valueArguments?.firstOrNull()?.getArgumentExpression()
when (expression) {
is KtCallExpression -> expression.valueArguments[1].text.trim('\"')
is KtStringTemplateExpression -> expression.text?.trim('\"')?.substringAfterLast(":")?.removePrefix("$")
else -> null
}
}
}
fun KtBlockExpression.findCompileStdLib(): KtCallExpression? {
return PsiTreeUtil.getChildrenOfType(this, KtCallExpression::class.java)?.find {
it.calleeExpression?.text == "compile" && (it.valueArguments.firstOrNull()?.getArgumentExpression()?.isKotlinStdLib() ?: false)
}
}
private fun KtExpression.isKotlinStdLib(): Boolean = when (this) {
is KtCallExpression -> calleeExpression?.text == "kotlinModule" &&
valueArguments.firstOrNull()?.getArgumentExpression()?.text?.startsWith("\"stdlib") ?: false
is KtStringTemplateExpression -> text.startsWith("\"$STDLIB_ARTIFACT_PREFIX")
else -> false
}
fun KtFile.getRepositoriesBlock(): KtBlockExpression? =
findScriptInitializer("repositories")?.getBlock() ?: addTopLevelBlock("repositories")
@@ -58,13 +79,6 @@ fun KtFile.createApplyBlock(): KtBlockExpression? {
fun KtFile.getApplyBlock(): KtBlockExpression? = findScriptInitializer("apply")?.getBlock() ?: createApplyBlock()
private fun KtExpression.isKotlinStdLib(): Boolean = when (this) {
is KtCallExpression -> calleeExpression?.text == "kotlinModule" &&
valueArguments.firstOrNull()?.getArgumentExpression()?.text == "\"stdlib\""
is KtStringTemplateExpression -> text.startsWith("\"org.jetbrains.kotlin:kotlin-stdlib:")
else -> false
}
private fun KtBlockExpression.findPlugin(pluginName: String): KtCallExpression? {
return PsiTreeUtil.getChildrenOfType(this, KtCallExpression::class.java)?.find {
it.calleeExpression?.text == "plugin" && it.valueArguments.firstOrNull()?.text == "\"$pluginName\""
@@ -215,7 +229,6 @@ fun KtBlockExpression.addDeclarationIfMissing(text: String, first: Boolean = fal
private inline fun <reified T: PsiElement> KtBlockExpression.addStatementIfMissing(
text: String,
crossinline factory: (String) -> PsiElement): T {
statements.find { it.text == text }?.let {
return it as T
}
@@ -232,5 +245,7 @@ private val PsiElement.psiFactory: KtPsiFactory
private val MAVEN_CENTRAL = "mavenCentral()"
private val JCENTER = "jcenter()"
private val STDLIB_ARTIFACT_PREFIX = "org.jetbrains.kotlin:kotlin-stdlib"
val KOTLIN_GROUP_ID = "org.jetbrains.kotlin"
val GSK_KOTLIN_VERSION_PROPERTY_NAME = "kotlin_version"
@@ -175,7 +175,7 @@ abstract class KotlinWithGradleConfigurator : KotlinProjectConfigurator {
private fun KtBlockExpression.addCompileStdlibIfMissing(sdk: Sdk?, version: String): KtCallExpression? =
findCompileStdLib() ?:
addExpressionIfMissing(getKotlinScriptDependencySnippet(getStdlibArtifactName(sdk, version))) as? KtCallExpression
addExpressionIfMissing(getGSKCompileDependencySnippet(KOTLIN_GROUP_ID, getStdlibArtifactName(sdk, version))) as? KtCallExpression
protected fun addElementsToModuleGroovyFile(file: GroovyFile, version: String): Boolean {
val oldText = file.text
@@ -281,41 +281,71 @@ abstract class KotlinWithGradleConfigurator : KotlinProjectConfigurator {
fun getGroovyApplyPluginDirective(pluginName: String) = "apply plugin: '$pluginName'"
fun addKotlinLibraryToModule(module: Module, scope: DependencyScope, libraryDescriptor: ExternalLibraryDescriptor) {
val gradleFile = module.getBuildScriptPsiFile() as? GroovyFile
if (gradleFile != null && canConfigureFile(gradleFile)) {
gradleFile.project.executeWriteCommand("Add Kotlin library") {
val groovyScope = when (scope) {
DependencyScope.COMPILE -> "compile"
DependencyScope.TEST -> if (KotlinPluginUtil.isAndroidGradleModule(module)) {
// TODO we should add testCompile or androidTestCompile
"compile"
}
else {
"testCompile"
}
DependencyScope.RUNTIME -> "runtime"
DependencyScope.PROVIDED -> "compile"
else -> "compile"
}
val dependencyString = String.format(
"%s \"%s:%s:%s\"",
groovyScope, libraryDescriptor.libraryGroupId, libraryDescriptor.libraryArtifactId,
libraryDescriptor.maxVersion)
val dependenciesBlock = getDependenciesBlock(gradleFile)
addLastExpressionInBlockIfNeeded(dependencyString, dependenciesBlock)
CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(gradleFile)
}
val virtualFile = gradleFile.virtualFile
if (virtualFile != null) {
createConfigureKotlinNotificationCollector(gradleFile.project)
.addMessage(virtualFile.path + " was modified")
.showNotification()
}
val buildScript = module.getBuildScriptPsiFile() ?: return
if (!canConfigureFile(buildScript)) {
return
}
val isAndroidModule = KotlinPluginUtil.isAndroidGradleModule(module)
when (buildScript) {
is GroovyFile -> addKotlinLibraryToModuleGroovyFile(buildScript, scope, libraryDescriptor, isAndroidModule)
is KtFile -> addKotlinLibraryToModuleGSKFile(buildScript, scope, libraryDescriptor, isAndroidModule)
}
buildScript.virtualFile?.let {
createConfigureKotlinNotificationCollector(buildScript.project)
.addMessage(it.path + " was modified")
.showNotification()
}
}
fun addKotlinLibraryToModuleGroovyFile(
gradleFile: GroovyFile,
scope: DependencyScope,
libraryDescriptor: ExternalLibraryDescriptor,
isAndroidModule: Boolean) {
gradleFile.project.executeWriteCommand("Add Kotlin library") {
val dependencyString = String.format(
"%s \"%s:%s:%s\"",
scope.toGradleCompileScope(isAndroidModule),
libraryDescriptor.libraryGroupId,
libraryDescriptor.libraryArtifactId,
libraryDescriptor.maxVersion)
val dependenciesBlock = getDependenciesBlock(gradleFile)
addLastExpressionInBlockIfNeeded(dependencyString, dependenciesBlock)
CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(gradleFile)
}
}
fun addKotlinLibraryToModuleGSKFile(
gradleFile: KtFile,
scope: DependencyScope,
libraryDescriptor: ExternalLibraryDescriptor,
isAndroidModule: Boolean) {
gradleFile.project.executeWriteCommand("Add Kotlin library") {
val kotlinLibraryVersion = libraryDescriptor.maxVersion.takeIf { it != GSK_KOTLIN_VERSION_PROPERTY_NAME }
gradleFile.getDependenciesBlock()?.apply {
addExpressionIfMissing(
getGSKCompileDependencySnippet(
libraryDescriptor.libraryGroupId,
libraryDescriptor.libraryArtifactId,
scope.toGradleCompileScope(isAndroidModule),
kotlinLibraryVersion))
}
CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(gradleFile)
}
}
private fun DependencyScope.toGradleCompileScope(isAndroidModule: Boolean) = when (this) {
DependencyScope.COMPILE -> "compile"
// TODO we should add testCompile or androidTestCompile
DependencyScope.TEST -> if (isAndroidModule) "compile" else "testCompile"
DependencyScope.RUNTIME -> "runtime"
DependencyScope.PROVIDED -> "compile"
else -> "compile"
}
fun changeCoroutineConfiguration(module: Module, coroutineOption: String): PsiElement? {
@@ -419,7 +449,15 @@ abstract class KotlinWithGradleConfigurator : KotlinProjectConfigurator {
}
fun getKotlinStdlibVersion(module: Module): String? {
val gradleFile = module.getBuildScriptPsiFile() as? GroovyFile ?: return null
val gradleFile = module.getBuildScriptPsiFile() ?: return null
return when (gradleFile) {
is GroovyFile -> getKotlinStdlibVersion(gradleFile)
is KtFile -> gradleFile.getKotlinStdlibVersion()
else -> null
}
}
private fun getKotlinStdlibVersion(gradleFile: GroovyFile): String? {
val versionProperty = "\$kotlin_version"
val block = getBuildScriptBlock(gradleFile)
if (block.text.contains("ext.kotlin_version = ")) {
@@ -16,6 +16,8 @@
package org.jetbrains.kotlin.gradle
import com.intellij.openapi.roots.DependencyScope
import com.intellij.openapi.roots.ExternalLibraryDescriptor
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.configuration.KotlinWithGradleConfigurator
import org.jetbrains.kotlin.idea.configuration.changeCoroutineConfiguration
@@ -162,4 +164,148 @@ class KotlinWithGradleConfiguratorTest : LightCodeInsightFixtureTestCase() {
|}
|""".trimMargin("|"))
}
fun testAddLibrary() {
val buildGradle = myFixture.configureByText("build.gradle",
"""
|dependencies {
| compile "org.jetbrains.kotlin:stdlib-jre8:1.0.0"
|}
|""".trimMargin("|")) as GroovyFile
myFixture.project.executeWriteCommand("") {
KotlinWithGradleConfigurator.addKotlinLibraryToModuleGroovyFile(
buildGradle,
DependencyScope.COMPILE,
object: ExternalLibraryDescriptor("org.jetbrains.kotlin", "kotlin-reflect", "1.0.0", "1.0.0") {
override fun getLibraryClassesRoots() = emptyList<String>()
},
false)
}
myFixture.checkResult(
"""
|dependencies {
| compile "org.jetbrains.kotlin:stdlib-jre8:1.0.0"
| compile "org.jetbrains.kotlin:kotlin-reflect:1.0.0"
|}
|""".trimMargin("|"))
}
fun testAddLibraryGSK() {
val buildGradle = myFixture.configureByText("build.gradle.kts",
"""
|dependencies {
| testCompile("junit:junit:4.12")
| compile(kotlinModule("stdlib-jre8"))
|}
|""".trimMargin("|")) as KtFile
myFixture.project.executeWriteCommand("") {
KotlinWithGradleConfigurator.addKotlinLibraryToModuleGSKFile(
buildGradle,
DependencyScope.COMPILE,
object: ExternalLibraryDescriptor("org.jetbrains.kotlin", "kotlin-reflect", "1.0.0", "1.0.0") {
override fun getLibraryClassesRoots() = emptyList<String>()
},
false)
}
myFixture.checkResult(
"""
|dependencies {
| testCompile("junit:junit:4.12")
| compile(kotlinModule("stdlib-jre8"))
| compile(kotlinModule("reflect", "1.0.0"))
|}
|""".trimMargin("|"))
}
fun testAddTestLibraryGSK() {
val buildGradle = myFixture.configureByText("build.gradle.kts",
"""
|dependencies {
| compile(kotlinModule("stdlib-jre8"))
|}
|""".trimMargin("|")) as KtFile
myFixture.project.executeWriteCommand("") {
KotlinWithGradleConfigurator.addKotlinLibraryToModuleGSKFile(
buildGradle,
DependencyScope.TEST,
object: ExternalLibraryDescriptor("junit", "junit", "4.12", "4.12") {
override fun getLibraryClassesRoots() = emptyList<String>()
},
false)
KotlinWithGradleConfigurator.addKotlinLibraryToModuleGSKFile(
buildGradle,
DependencyScope.TEST,
object: ExternalLibraryDescriptor("org.jetbrains.kotlin", "kotlin-test", "1.1.2", "1.1.2") {
override fun getLibraryClassesRoots() = emptyList<String>()
},
false)
}
myFixture.checkResult(
"""
|dependencies {
| compile(kotlinModule("stdlib-jre8"))
| testCompile("junit:junit:4.12")
| testCompile(kotlinModule("test", "1.1.2"))
|}
|""".trimMargin("|"))
}
fun testAddLibraryGSK_WithKotlinVersion() {
val buildGradle = myFixture.configureByText("build.gradle.kts",
"""
|val kotlin_version: String by extra
|dependencies {
| testCompile("junit:junit:4.12")
| compile(kotlinModule("stdlib-jre8", kotlin_version))
|}
|""".trimMargin("|")) as KtFile
myFixture.project.executeWriteCommand("") {
val stdLibVersion = KotlinWithGradleConfigurator.getKotlinStdlibVersion(myFixture.module)
KotlinWithGradleConfigurator.addKotlinLibraryToModuleGSKFile(
buildGradle,
DependencyScope.COMPILE,
object: ExternalLibraryDescriptor("org.jetbrains.kotlin", "kotlin-reflect", stdLibVersion, stdLibVersion) {
override fun getLibraryClassesRoots() = emptyList<String>()
},
false)
}
myFixture.checkResult(
"""
|val kotlin_version: String by extra
|dependencies {
| testCompile("junit:junit:4.12")
| compile(kotlinModule("stdlib-jre8", kotlin_version))
| compile(kotlinModule("reflect", kotlin_version))
|}
|""".trimMargin("|"))
}
fun testAddNonKotlinLibraryGSK() {
val buildGradle = myFixture.configureByText("build.gradle.kts",
"""
|dependencies {
| testCompile("junit:junit:4.12")
| compile(kotlinModule("stdlib-jre8"))
|}
|""".trimMargin("|")) as KtFile
myFixture.project.executeWriteCommand("") {
KotlinWithGradleConfigurator.addKotlinLibraryToModuleGSKFile(
buildGradle,
DependencyScope.COMPILE,
object: ExternalLibraryDescriptor("org.a.b", "lib", "1.0.0", "1.0.0") {
override fun getLibraryClassesRoots() = emptyList<String>()
},
false)
}
myFixture.checkResult(
"""
|dependencies {
| testCompile("junit:junit:4.12")
| compile(kotlinModule("stdlib-jre8"))
| compile("org.a.b:lib:1.0.0")
|}
|""".trimMargin("|"))
}
}