[kotlin] Add "'StringBuilder.append(CharArray, offset, len)' call on the JVM" inspection

^KTIJ-22712 Fixed

closes https://github.com/JetBrains/intellij-community/pull/2152

GitOrigin-RevId: e8762a92a3f18685e91bb7aa007ccf731266f239
This commit is contained in:
Toshiaki Kameyama
2022-09-10 13:07:22 +00:00
committed by intellij-monorepo-bot
parent be952fc946
commit 266b0d7e7d
14 changed files with 189 additions and 0 deletions
@@ -2496,3 +2496,5 @@ inspection.message.sealed.object.can.be.converted.to.data.object='sealed' sub-ob
serializable.object.must.be.marked.with.data=Serializable 'object' must be marked with 'data' to properly support serialization
convert.to.data.object=Convert to 'data object' (may change semantic)
inspection.convert.object.to.data.object.display.name=Convert 'object' to 'data object'
inspection.replace.with.string.builder.append.range.display.name='StringBuilder.append(CharArray, offset, len)' call on the JVM
@@ -0,0 +1,22 @@
<html>
<body>
Reports a <code>StringBuilder.append(CharArray, offset, len)</code> function call on the JVM platform that should be replaced with a <code>StringBuilder.appendRange(CharArray, startIndex, endIndex)</code> function call.
<p>The <code>append</code> function behaves differently on the JVM, JS and Native platforms, so using the <code>appendRange</code> function is recommended.</p>
<p><b>Example:</b></p>
<pre><code>
fun f(charArray: CharArray, offset: Int, len: Int): String {
return buildString {
append(charArray, offset, len)
}
}
</code></pre>
<p>After the quick-fix is applied:</p>
<pre><code>
fun f(charArray: CharArray, offset: Int, len: Int): String {
return buildString {
appendRange(charArray, offset, offset + len)
}
}
</code></pre>
</body>
</html>
@@ -0,0 +1,77 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.inspections
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.idea.base.facet.platform.platform
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
import org.jetbrains.kotlin.idea.codeinsight.api.classic.inspections.AbstractKotlinInspection
import org.jetbrains.kotlin.idea.refactoring.fqName.fqName
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.platform.jvm.isJvm
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.types.typeUtil.isInt
class ReplaceWithStringBuilderAppendRangeInspection : AbstractKotlinInspection() {
companion object {
private const val appendFunctionName = "append"
private const val appendRangeFunctionName = "appendRange"
private val appendFunctionFqName = FqName("java.lang.StringBuilder.append")
private val charArrayFqName = FqName("kotlin.CharArray")
}
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): KtVisitorVoid =
callExpressionVisitor(fun(callExpression: KtCallExpression) {
if (!callExpression.platform.isJvm()) return
val calleeExpression = callExpression.calleeExpression ?: return
if (calleeExpression.text != appendFunctionName) return
if (callExpression.valueArguments.size != 3) return
val descriptor = callExpression.resolveToCall()?.resultingDescriptor ?: return
if (descriptor.fqNameSafe != appendFunctionFqName) return
val params = descriptor.valueParameters
if (params.size != 3 || !params[0].isCharArray() || !params[1].isInt() || !params[2].isInt()) return
holder.registerProblem(
calleeExpression,
KotlinBundle.message("replace.with.0", appendRangeFunctionName),
ReplaceFix(appendRangeFunctionName)
)
})
private fun ValueParameterDescriptor.isCharArray(): Boolean = type.fqName == charArrayFqName
private fun ValueParameterDescriptor.isInt(): Boolean = type.isInt()
private class ReplaceFix(private val functionName: String) : LocalQuickFix {
override fun getName(): String = KotlinBundle.message("replace.with.0", functionName)
override fun getFamilyName(): String = name
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val calleeExpression = descriptor.psiElement as? KtExpression ?: return
val callExpression = calleeExpression.parent as? KtCallExpression ?: return
val args = callExpression.valueArguments
val secondArg = args.getOrNull(1)?.getArgumentExpression() ?: return
val thirdArg = args.getOrNull(2)?.getArgumentExpression() ?: return
val psiFactory = KtPsiFactory(callExpression)
calleeExpression.replace(psiFactory.createCalleeExpression(functionName))
if (secondArg is KtConstantExpression && thirdArg is KtConstantExpression) {
thirdArg.replace(psiFactory.createExpression(secondArg.text.toInt().plus(thirdArg.text.toInt()).toString()))
} else {
thirdArg.replace(psiFactory.createExpressionByPattern("$0 + $1", secondArg, thirdArg))
}
}
private fun KtPsiFactory.createCalleeExpression(functionName: String): KtExpression =
(createExpression("$functionName()") as KtCallExpression).calleeExpression!!
}
}
@@ -13277,6 +13277,34 @@ public abstract class LocalInspectionTestGenerated extends AbstractLocalInspecti
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("testData/inspectionsLocal/replaceWithStringBuilderAppendRange")
public static class ReplaceWithStringBuilderAppendRange extends AbstractLocalInspectionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("constantArgument.kt")
public void testConstantArgument() throws Exception {
runTest("testData/inspectionsLocal/replaceWithStringBuilderAppendRange/constantArgument.kt");
}
@TestMetadata("hasReceiver.kt")
public void testHasReceiver() throws Exception {
runTest("testData/inspectionsLocal/replaceWithStringBuilderAppendRange/hasReceiver.kt");
}
@TestMetadata("precedence.kt")
public void testPrecedence() throws Exception {
runTest("testData/inspectionsLocal/replaceWithStringBuilderAppendRange/precedence.kt");
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
runTest("testData/inspectionsLocal/replaceWithStringBuilderAppendRange/simple.kt");
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("testData/inspectionsLocal/restrictReturnStatementTargetMigration")
public static class RestrictReturnStatementTargetMigration extends AbstractLocalInspectionTest {
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.inspections.ReplaceWithStringBuilderAppendRangeInspection
@@ -0,0 +1,6 @@
// WITH_STDLIB
fun test(charArray: CharArray): String {
return buildString {
<caret>append(charArray, 2, 4)
}
}
@@ -0,0 +1,6 @@
// WITH_STDLIB
fun test(charArray: CharArray): String {
return buildString {
appendRange(charArray, 2, 6)
}
}
@@ -0,0 +1,6 @@
// WITH_STDLIB
fun test(charArray: CharArray, offset: Int, len: Int): String {
return buildString {
this.append<caret>(charArray, offset, len)
}
}
@@ -0,0 +1,6 @@
// WITH_STDLIB
fun test(charArray: CharArray, offset: Int, len: Int): String {
return buildString {
this.appendRange(charArray, offset, offset + len)
}
}
@@ -0,0 +1,6 @@
// WITH_STDLIB
fun test(charArray: CharArray, a: Int, b: Int, c: Int, d: Int): String {
return buildString {
<caret>append(charArray, a - b, c - d)
}
}
@@ -0,0 +1,6 @@
// WITH_STDLIB
fun test(charArray: CharArray, a: Int, b: Int, c: Int, d: Int): String {
return buildString {
appendRange(charArray, a - b, a - b + (c - d))
}
}
@@ -0,0 +1,7 @@
// FIX: Replace with 'appendRange'
// WITH_STDLIB
fun test(charArray: CharArray, offset: Int, len: Int): String {
return buildString {
<caret>append(charArray, offset, len)
}
}
@@ -0,0 +1,7 @@
// FIX: Replace with 'appendRange'
// WITH_STDLIB
fun test(charArray: CharArray, offset: Int, len: Int): String {
return buildString {
appendRange(charArray, offset, offset + len)
}
}
@@ -2914,6 +2914,15 @@
level="WEAK WARNING"
language="kotlin"
key="inspection.replace.readline.with.readln.display.name" bundle="messages.KotlinBundle"/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.ReplaceWithStringBuilderAppendRangeInspection"
groupPath="Kotlin"
groupBundle="messages.KotlinBundle" groupKey="group.names.other.problems"
enabledByDefault="true"
level="WARNING"
language="kotlin"
key="inspection.replace.with.string.builder.append.range.display.name" bundle="messages.KotlinBundle"/>
</extensions>
</idea-plugin>