[kotlin] Support summing of freeCompilerArgs

Allow conversion from
```
kotlinOptions {
    freeCompilerArgs = freeCompilerArgs + "-Xopt-in=kotlin.RequiresOptIn"
}
``

to
```
compilerOptions {
    freeCompilerArgs.add("-Xopt-in=kotlin.RequiresOptIn")
}
```

And more complex cases with multiple params in sum.

KTIJ-28468 kotlinOptions to compilerOptions inspection

GitOrigin-RevId: 26b1419e702a22e57f2bf430599ea775f47c5324
This commit is contained in:
Victoria.Petrakovich
2024-09-26 12:48:22 +00:00
committed by intellij-monorepo-bot
parent f972eb7c3b
commit 78c4d75a36
17 changed files with 518 additions and 37 deletions
@@ -8324,6 +8324,11 @@ public abstract class K2LocalInspectionTestGenerated extends AbstractK2LocalInsp
runTest("testData/inspectionsLocal/kotlinOptionsToCompilerOptions/allprojects.gradle.kts");
}
@TestMetadata("assignmentOperation2params.gradle.kts")
public void testAssignmentOperation2params_gradle() throws Exception {
runTest("testData/inspectionsLocal/kotlinOptionsToCompilerOptions/assignmentOperation2params.gradle.kts");
}
@TestMetadata("assignmentOperation.gradle.kts")
public void testAssignmentOperation_gradle() throws Exception {
runTest("testData/inspectionsLocal/kotlinOptionsToCompilerOptions/assignmentOperation.gradle.kts");
@@ -8359,6 +8364,36 @@ public abstract class K2LocalInspectionTestGenerated extends AbstractK2LocalInsp
runTest("testData/inspectionsLocal/kotlinOptionsToCompilerOptions/freeCompilerArgsAddAllFromList.gradle.kts");
}
@TestMetadata("freeCompilerArgsMinusFreeCompilerArgs.gradle.kts")
public void testFreeCompilerArgsMinusFreeCompilerArgs_gradle() throws Exception {
runTest("testData/inspectionsLocal/kotlinOptionsToCompilerOptions/freeCompilerArgsMinusFreeCompilerArgs.gradle.kts");
}
@TestMetadata("freeCompilerArgsMultipleAddition1.gradle.kts")
public void testFreeCompilerArgsMultipleAddition1_gradle() throws Exception {
runTest("testData/inspectionsLocal/kotlinOptionsToCompilerOptions/freeCompilerArgsMultipleAddition1.gradle.kts");
}
@TestMetadata("freeCompilerArgsMultipleAddition2.gradle.kts")
public void testFreeCompilerArgsMultipleAddition2_gradle() throws Exception {
runTest("testData/inspectionsLocal/kotlinOptionsToCompilerOptions/freeCompilerArgsMultipleAddition2.gradle.kts");
}
@TestMetadata("freeCompilerArgsMultipleAddition3.gradle.kts")
public void testFreeCompilerArgsMultipleAddition3_gradle() throws Exception {
runTest("testData/inspectionsLocal/kotlinOptionsToCompilerOptions/freeCompilerArgsMultipleAddition3.gradle.kts");
}
@TestMetadata("freeCompilerArgsMultipleAddition4.gradle.kts")
public void testFreeCompilerArgsMultipleAddition4_gradle() throws Exception {
runTest("testData/inspectionsLocal/kotlinOptionsToCompilerOptions/freeCompilerArgsMultipleAddition4.gradle.kts");
}
@TestMetadata("freeCompilerArgsMultipleAddition5.gradle.kts")
public void testFreeCompilerArgsMultipleAddition5_gradle() throws Exception {
runTest("testData/inspectionsLocal/kotlinOptionsToCompilerOptions/freeCompilerArgsMultipleAddition5.gradle.kts");
}
@TestMetadata("freeCompilerArgsPlusFreeCompilerArgs.gradle.kts")
public void testFreeCompilerArgsPlusFreeCompilerArgs_gradle() throws Exception {
runTest("testData/inspectionsLocal/kotlinOptionsToCompilerOptions/freeCompilerArgsPlusFreeCompilerArgs.gradle.kts");
@@ -0,0 +1,27 @@
// PROBLEM: Use of deprecated 'kotlinOptions' DSL
// FIX: Replace 'kotlinOptions' with 'compilerOptions'
plugins {
kotlin("jvm") version "2.0.0"
}
group = "org.example"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
dependencies {
testImplementation(kotlin("test"))
}
tasks.test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(8)
}
tasks.named("compileKotlin", org.jetbrains.kotlin.gradle.tasks.KotlinCompile::class.java) {
<caret>kotlinOptions.freeCompilerArgs += "-Xexport-kdoc" + "-Xopt-in=kotlin.RequiresOptIn"
}
@@ -0,0 +1,27 @@
// PROBLEM: Use of deprecated 'kotlinOptions' DSL
// FIX: Replace 'kotlinOptions' with 'compilerOptions'
plugins {
kotlin("jvm") version "2.0.0"
}
group = "org.example"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
dependencies {
testImplementation(kotlin("test"))
}
tasks.test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(8)
}
tasks.named("compileKotlin", org.jetbrains.kotlin.gradle.tasks.KotlinCompile::class.java) {
compilerOptions.freeCompilerArgs.addAll("-Xexport-kdoc", "-Xopt-in=kotlin.RequiresOptIn")
}
@@ -0,0 +1,23 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "2.0.0"
}
repositories {
mavenCentral()
}
subprojects {
apply(plugin = "org.jetbrains.kotlin.jvm")
dependencies {
implementation(kotlin("stdlib-jdk8"))
}
tasks.withType<KotlinCompile>().all {
<caret>kotlinOptions {
freeCompilerArgs = freeCompilerArgs - "-Xopt-in=kotlin.RequiresOptIn"
}
}
}
@@ -0,0 +1,23 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "2.0.0"
}
repositories {
mavenCentral()
}
subprojects {
apply(plugin = "org.jetbrains.kotlin.jvm")
dependencies {
implementation(kotlin("stdlib-jdk8"))
}
tasks.withType<KotlinCompile>().all {
compilerOptions {
freeCompilerArgs = freeCompilerArgs - "-Xopt-in=kotlin.RequiresOptIn"
}
}
}
@@ -0,0 +1,24 @@
// FIX: Replace 'kotlinOptions' with 'compilerOptions'
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "2.0.0"
}
repositories {
mavenCentral()
}
subprojects {
apply(plugin = "org.jetbrains.kotlin.jvm")
dependencies {
implementation(kotlin("stdlib-jdk8"))
}
tasks.withType<KotlinCompile>().all {
<caret>kotlinOptions {
freeCompilerArgs = freeCompilerArgs + "-Xopt-in=kotlin.RequiresOptIn" + "-Xjvm-default=all"
}
}
}
@@ -0,0 +1,24 @@
// FIX: Replace 'kotlinOptions' with 'compilerOptions'
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "2.0.0"
}
repositories {
mavenCentral()
}
subprojects {
apply(plugin = "org.jetbrains.kotlin.jvm")
dependencies {
implementation(kotlin("stdlib-jdk8"))
}
tasks.withType<KotlinCompile>().all {
compilerOptions {
freeCompilerArgs.addAll("-Xopt-in=kotlin.RequiresOptIn", "-Xjvm-default=all")
}
}
}
@@ -0,0 +1,24 @@
// FIX: Replace 'kotlinOptions' with 'compilerOptions'
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "2.0.0"
}
repositories {
mavenCentral()
}
subprojects {
apply(plugin = "org.jetbrains.kotlin.jvm")
dependencies {
implementation(kotlin("stdlib-jdk8"))
}
tasks.withType<KotlinCompile>().all {
<caret>kotlinOptions {
freeCompilerArgs = project.benchmark.compilerOpts + project.compilerArgs
}
}
}
@@ -0,0 +1,24 @@
// FIX: Replace 'kotlinOptions' with 'compilerOptions'
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "2.0.0"
}
repositories {
mavenCentral()
}
subprojects {
apply(plugin = "org.jetbrains.kotlin.jvm")
dependencies {
implementation(kotlin("stdlib-jdk8"))
}
tasks.withType<KotlinCompile>().all {
compilerOptions {
freeCompilerArgs.addAll(project.benchmark.compilerOpts, project.compilerArgs)
}
}
}
@@ -0,0 +1,28 @@
// FIX: Replace 'kotlinOptions' with 'compilerOptions'
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "2.0.0"
}
repositories {
mavenCentral()
}
subprojects {
apply(plugin = "org.jetbrains.kotlin.jvm")
dependencies {
implementation(kotlin("stdlib-jdk8"))
}
tasks.withType<KotlinCompile>().all {
<caret>kotlinOptions {
freeCompilerArgs = freeCompilerArgs +
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi" +
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi" +
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api" +
"-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi"
}
}
}
@@ -0,0 +1,29 @@
// FIX: Replace 'kotlinOptions' with 'compilerOptions'
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "2.0.0"
}
repositories {
mavenCentral()
}
subprojects {
apply(plugin = "org.jetbrains.kotlin.jvm")
dependencies {
implementation(kotlin("stdlib-jdk8"))
}
tasks.withType<KotlinCompile>().all {
compilerOptions {
freeCompilerArgs.addAll(
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
"-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi"
)
}
}
}
@@ -0,0 +1,24 @@
// FIX: Replace 'kotlinOptions' with 'compilerOptions'
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "2.0.0"
}
repositories {
mavenCentral()
}
subprojects {
apply(plugin = "org.jetbrains.kotlin.jvm")
dependencies {
implementation(kotlin("stdlib-jdk8"))
}
tasks.withType<KotlinCompile>().all {
<caret>kotlinOptions {
freeCompilerArgs += allLibraries.map { listOf("-include-binary", it) }.flatten() + linkerFlags
}
}
}
@@ -0,0 +1,24 @@
// FIX: Replace 'kotlinOptions' with 'compilerOptions'
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "2.0.0"
}
repositories {
mavenCentral()
}
subprojects {
apply(plugin = "org.jetbrains.kotlin.jvm")
dependencies {
implementation(kotlin("stdlib-jdk8"))
}
tasks.withType<KotlinCompile>().all {
compilerOptions {
freeCompilerArgs.addAll(allLibraries.map { listOf("-include-binary", it) }.flatten(), linkerFlags)
}
}
}
@@ -0,0 +1,34 @@
// FIX: Replace 'kotlinOptions' with 'compilerOptions'
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "2.0.0"
}
repositories {
mavenCentral()
}
subprojects {
apply(plugin = "org.jetbrains.kotlin.jvm")
dependencies {
implementation(kotlin("stdlib-jdk8"))
}
tasks.withType<KotlinCompile>().all {
<caret>kotlinOptions {
freeCompilerArgs = freeCompilerArgs + listOf(
"-Xopt-in=kotlin.RequiresOptIn",
"-Xopt-in=kotlin.ExperimentalStdlibApi",
"-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-Xopt-in=kotlinx.coroutines.FlowPreview",
"-Xopt-in=kotlin.time.ExperimentalTime",
"-Xopt-in=kotlin.RequiresOptIn",
"-Xjvm-default=all",
"-XXLanguage:+DataObjects",
"-Xcontext-receivers"
)
}
}
}
@@ -0,0 +1,36 @@
// FIX: Replace 'kotlinOptions' with 'compilerOptions'
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "2.0.0"
}
repositories {
mavenCentral()
}
subprojects {
apply(plugin = "org.jetbrains.kotlin.jvm")
dependencies {
implementation(kotlin("stdlib-jdk8"))
}
tasks.withType<KotlinCompile>().all {
compilerOptions {
freeCompilerArgs.addAll(
listOf(
"-Xopt-in=kotlin.RequiresOptIn",
"-Xopt-in=kotlin.ExperimentalStdlibApi",
"-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-Xopt-in=kotlinx.coroutines.FlowPreview",
"-Xopt-in=kotlin.time.ExperimentalTime",
"-Xopt-in=kotlin.RequiresOptIn",
"-Xjvm-default=all",
"-XXLanguage:+DataObjects",
"-Xcontext-receivers"
)
)
}
}
}
@@ -25,6 +25,6 @@ kotlin {
tasks.withType<KotlinCompile<*>> {
compilerOptions {
freeCompilerArgs = freeCompilerArgs + "-Xopt-in=kotlin.RequiresOptIn"
freeCompilerArgs.addAll("-Xopt-in=kotlin.RequiresOptIn")
}
}
@@ -1,6 +1,7 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.gradleJava.configuration.utils
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiFile
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.kotlin.idea.base.util.module
@@ -14,6 +15,7 @@ import org.jetbrains.kotlin.psi.KtReferenceExpression
import org.jetbrains.kotlin.resolve.calls.util.getCalleeExpressionIfAny
import org.jetbrains.kotlin.tooling.core.KotlinToolingVersion
import java.util.function.Function
import kotlin.String
data class CompilerOption(val expression: String, val classToImport: FqName? = null, val compilerOptionValue: String? = null)
@@ -29,44 +31,29 @@ fun expressionContainsOperationForbiddenToReplace(binaryExpression: KtBinaryExpr
fun getReplacementForOldKotlinOptionIfNeeded(binaryExpression: KtBinaryExpression): Replacement? {
val rightPartOfBinaryExpression = binaryExpression.right ?: return null
val textOfRightPartOfBinaryExpression = rightPartOfBinaryExpression.text
val leftPartOfBinaryExpression = binaryExpression.left ?: return null
val textOfLeftPartOfBinaryExpression = leftPartOfBinaryExpression.text
// We don't touch strings like `freeCompilerArgs = freeCompilerArgs + "-Xopt-in=kotlin.RequiresOptIn"`
if (rightPartOfBinaryExpression is KtBinaryExpression) {
return getReplacementOnlyOfKotlinOptionsIfNeeded(binaryExpression, textOfLeftPartOfBinaryExpression)
val (optionName, replacementOfKotlinOptionsIfNeeded) = getOptionName(leftPartOfBinaryExpression)
?: return null
if (rightPartOfBinaryExpression is KtBinaryExpression && !optionName.contains("freeCompilerArgs")) {
return getReplacementOnlyOfKotlinOptionsIfNeeded(rightPartOfBinaryExpression, textOfLeftPartOfBinaryExpression)
}
val replacement = StringBuilder()
val optionName = when (leftPartOfBinaryExpression) {
is KtDotQualifiedExpression -> {
val partBeforeDot = leftPartOfBinaryExpression.receiverExpression.text
if (!partBeforeDot.contains("kotlinOptions")) {
if (partBeforeDot != "options") {
return null
}
} else {
replacement.append("compilerOptions.")
}
leftPartOfBinaryExpression.getCalleeExpressionIfAny()?.text ?: return null
}
is KtReferenceExpression -> {
textOfLeftPartOfBinaryExpression
}
else -> {
return null
}
}
val (optionValue, valueContainsMultipleValues) = getOptionValue(rightPartOfBinaryExpression, optionName) ?: return null
val operationReference = binaryExpression.operationReference.text
val expressionForCompilerOption =
getReplacementForOldKotlinOptionIfNeeded(replacement, optionName, textOfRightPartOfBinaryExpression, operationReference)
getReplacementForOldKotlinOptionIfNeeded(
replacementOfKotlinOptionsIfNeeded,
optionName,
optionValue,
operationReference,
valueContainsMultipleValues
)
if (expressionForCompilerOption != null) {
return Replacement(binaryExpression, expressionForCompilerOption.expression, expressionForCompilerOption.classToImport)
}
@@ -74,19 +61,97 @@ fun getReplacementForOldKotlinOptionIfNeeded(binaryExpression: KtBinaryExpressio
return getReplacementOnlyOfKotlinOptionsIfNeeded(binaryExpression, textOfLeftPartOfBinaryExpression)
}
private fun getOptionValue(expression: KtExpression, optionName: String): Pair<String, Boolean>? {
val optionValue: String
val valueContainsMultipleValues: Boolean
if (expression is KtBinaryExpression) {
if (expression.operationReference.text != "+") {
return null
}
if (optionName == "freeCompilerArgs") {
val leftPart = expression.left ?: return null
val rightPart = expression.right ?: return null
val optionValues = getOptionsFromFreeCompilerArgsExpression(leftPart, mutableSetOf(rightPart.text)) ?: return null
optionValue = StringUtil.join(optionValues.reversed(), ", ")
valueContainsMultipleValues = true
} else {
return null
}
} else {
optionValue = expression.text
valueContainsMultipleValues = false
}
return Pair(optionValue, valueContainsMultipleValues)
}
/**
* Returns collection of option values or null if something goes wrong.
*/
private fun getOptionsFromFreeCompilerArgsExpression(expression: KtExpression, optionValues: MutableSet<String>): Set<String>? {
if (expression is KtBinaryExpression) {
val operationReference = expression.operationReference.text
if (operationReference != "+") {
return null
} else {
optionValues.add(expression.right?.text ?: return null)
getOptionsFromFreeCompilerArgsExpression(expression.left ?: return null, optionValues)
}
} else {
if (expression.text != "freeCompilerArgs") {
optionValues.add(expression.text)
}
}
return optionValues
}
private fun getOptionName(expression: KtExpression): Pair<String, StringBuilder>? {
val replacementOfKotlinOptionsIfNeeded = StringBuilder()
val optionName = when (expression) {
is KtDotQualifiedExpression -> {
val partBeforeDot = expression.receiverExpression.text
if (!partBeforeDot.contains("kotlinOptions")) {
if (partBeforeDot != "options") {
return null
}
} else {
replacementOfKotlinOptionsIfNeeded.append("compilerOptions.")
}
expression.getCalleeExpressionIfAny()?.text ?: return null
}
is KtReferenceExpression -> {
expression.text
}
else -> {
return null
}
}
return Pair(optionName, replacementOfKotlinOptionsIfNeeded)
}
fun kotlinVersionIsEqualOrHigher(major: Int, minor: Int, patch: Int, file: PsiFile): Boolean {
val version = file.module?.kotlinGradlePluginVersion ?: return false
return version >= KotlinToolingVersion(major, minor, patch, classifier = null)
}
private fun getOperationReplacer(operationReference: String, optionValue: String): String? {
private fun getOperationReplacer(
operationReference: String,
optionValue: String,
valueContainsMultipleValues: Boolean = false
): String? {
return when (operationReference) {
"=" -> {
"set"
if (valueContainsMultipleValues) {
"addAll"
} else {
"set"
}
}
"+=" -> {
if (!collectionsNamesRegex.find(optionValue)?.value.isNullOrEmpty()) {
if (!collectionsNamesRegex.find(optionValue)?.value.isNullOrEmpty() || valueContainsMultipleValues) {
"addAll"
} else {
"add"
@@ -104,13 +169,20 @@ private fun getReplacementForOldKotlinOptionIfNeeded(
optionName: String,
optionValue: String,
operationReference: String,
valueContainsMultipleValues: Boolean = false,
): CompilerOption? {
val operationReplacer = getOperationReplacer(operationReference, optionValue) ?: return null
val operationReplacer =
getOperationReplacer(operationReference, optionValue, valueContainsMultipleValues) ?: return null
// jvmTarget, apiVersion and languageVersion
val versionOptionData = optionsWithValuesMigratedFromNumericStringsToEnums[optionName]
if (versionOptionData != null) {
return getCompilerOptionForVersionValue(versionOptionData, optionValue, replacement, optionName, operationReplacer)
return getCompilerOptionForVersionValue(
versionOptionData,
optionValue,
replacement,
optionName,
operationReplacer
)
} else if (jsOptions.contains(optionName)) { // JS options
val processedOptionValue = optionValue.removeSurrounding("\"", "\"")
val jsOptionsValuesStringToEnumCorrespondence = jsOptions[optionName] ?: return null
@@ -126,8 +198,11 @@ private fun getReplacementForOldKotlinOptionIfNeeded(
}
private fun getCompilerOptionForVersionValue(
versionOptionData: VersionOption, optionValue: String, replacement: StringBuilder,
optionName: String, operationReplacer: String,
versionOptionData: VersionOption,
optionValue: String,
replacement: StringBuilder,
optionName: String,
operationReplacer: String,
): CompilerOption? {
val processedOptionValue = optionValue.removeSurrounding("\"", "\"")
val convertedValue = versionOptionData.mappingRule.apply(processedOptionValue)