Add inspection removing redundant spread operator for arrayOf call
So #KT-17920 Fixed
This commit is contained in:
committed by
Mikhail Glukhikh
parent
ce55d9e364
commit
cbccf932a7
@@ -0,0 +1,5 @@
|
||||
<html>
|
||||
<body>
|
||||
This inspection reports the redundant spread operator for arrayOf call
|
||||
</body>
|
||||
</html>
|
||||
@@ -2230,6 +2230,14 @@
|
||||
language="kotlin"
|
||||
/>
|
||||
|
||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.RemoveRedundantSpreadOperatorInspection"
|
||||
displayName="Redundant spread operator"
|
||||
groupName="Kotlin"
|
||||
enabledByDefault="true"
|
||||
level="WARNING"
|
||||
language="kotlin"
|
||||
/>
|
||||
|
||||
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
|
||||
|
||||
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
|
||||
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.inspections
|
||||
|
||||
import com.intellij.codeInspection.LocalQuickFix
|
||||
import com.intellij.codeInspection.ProblemDescriptor
|
||||
import com.intellij.codeInspection.ProblemHighlightType
|
||||
import com.intellij.codeInspection.ProblemsHolder
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiElementVisitor
|
||||
import org.jetbrains.kotlin.idea.intentions.isArrayOfMethod
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
|
||||
class RemoveRedundantSpreadOperatorInspection : AbstractKotlinInspection() {
|
||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
|
||||
return object : KtVisitorVoid() {
|
||||
override fun visitArgument(argument: KtValueArgument) {
|
||||
super.visitArgument(argument)
|
||||
val spreadElement = argument.getSpreadElement() ?: return
|
||||
if (argument.isNamed()) return
|
||||
val argumentExpression = argument.getArgumentExpression() as? KtCallExpression ?: return
|
||||
if (argumentExpression.isArrayOfMethod()) {
|
||||
val argumentOffset = argument.startOffset
|
||||
val problemDescriptor = holder.manager.createProblemDescriptor(
|
||||
argument,
|
||||
TextRange(spreadElement.startOffset - argumentOffset,
|
||||
argumentExpression.calleeExpression!!.endOffset - argumentOffset),
|
||||
"Remove redundant spread operator",
|
||||
ProblemHighlightType.LIKE_UNUSED_SYMBOL,
|
||||
isOnTheFly,
|
||||
RemoveRedundantSpreadOperatorQuickfix()
|
||||
)
|
||||
holder.registerProblem(problemDescriptor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RemoveRedundantSpreadOperatorQuickfix : LocalQuickFix {
|
||||
override fun getFamilyName() = "Remove redundant spread operator"
|
||||
|
||||
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
||||
val arrayOfValueArgument = descriptor.psiElement as? KtValueArgument ?: return
|
||||
val arrayOfArgumentExpression = arrayOfValueArgument.getArgumentExpression() as? KtCallExpression ?: return
|
||||
val arrayOfArgumentList = arrayOfArgumentExpression.valueArgumentList ?: return
|
||||
val factory = KtPsiFactory(project)
|
||||
arrayOfValueArgument.getStrictParentOfType<KtValueArgumentList>()?.let { outerArgumentList ->
|
||||
arrayOfArgumentList.arguments.reversed().forEach { argument ->
|
||||
val newValueArgument = factory.createArgument(argument.getArgumentExpression())
|
||||
outerArgumentList.addArgumentAfter(newValueArgument, arrayOfValueArgument)
|
||||
}
|
||||
outerArgumentList.removeArgument(arrayOfValueArgument)
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-13
@@ -23,21 +23,13 @@ import com.intellij.codeInspection.ProblemsHolder
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiElementVisitor
|
||||
import org.jetbrains.kotlin.builtins.BuiltInsPackageFragment
|
||||
import org.jetbrains.kotlin.config.LanguageFeature.ArrayLiteralsInAnnotations
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.intentions.isArrayOfMethod
|
||||
import org.jetbrains.kotlin.idea.project.languageVersionSettings
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.CollectionLiteralResolver
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
|
||||
class ReplaceArrayOfWithLiteralInspection : AbstractKotlinInspection() {
|
||||
|
||||
private val acceptableNames = setOf(CollectionLiteralResolver.ARRAY_OF_FUNCTION) +
|
||||
CollectionLiteralResolver.PRIMITIVE_TYPE_TO_ARRAY.values.toSet() +
|
||||
Name.identifier("emptyArray")
|
||||
|
||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
|
||||
return object : KtVisitorVoid() {
|
||||
override fun visitCallExpression(expression: KtCallExpression) {
|
||||
@@ -47,10 +39,7 @@ class ReplaceArrayOfWithLiteralInspection : AbstractKotlinInspection() {
|
||||
!ApplicationManager.getApplication().isUnitTestMode) return
|
||||
|
||||
val calleeExpression = expression.calleeExpression as? KtNameReferenceExpression ?: return
|
||||
val resolvedCall = expression.getResolvedCall(expression.analyze()) ?: return
|
||||
val descriptor = resolvedCall.candidateDescriptor
|
||||
if (descriptor.containingDeclaration !is BuiltInsPackageFragment) return
|
||||
if (descriptor.name !in acceptableNames) return
|
||||
if (!expression.isArrayOfMethod()) return
|
||||
|
||||
val parent = expression.parent
|
||||
when (parent) {
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.intentions
|
||||
|
||||
import com.intellij.psi.tree.IElementType
|
||||
import org.jetbrains.kotlin.KtNodeTypes
|
||||
import org.jetbrains.kotlin.builtins.BuiltInsPackageFragment
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
|
||||
@@ -26,10 +27,14 @@ import org.jetbrains.kotlin.idea.core.replaced
|
||||
import org.jetbrains.kotlin.idea.core.setType
|
||||
import org.jetbrains.kotlin.idea.references.mainReference
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.CollectionLiteralResolver
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
@@ -272,3 +277,13 @@ fun KtDotQualifiedExpression.deleteFirstReceiver(): KtExpression {
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
private val ARRAY_OF_METHODS = setOf(CollectionLiteralResolver.ARRAY_OF_FUNCTION) +
|
||||
CollectionLiteralResolver.PRIMITIVE_TYPE_TO_ARRAY.values.toSet() +
|
||||
Name.identifier("emptyArray")
|
||||
|
||||
fun KtCallExpression.isArrayOfMethod(): Boolean {
|
||||
val resolvedCall = getResolvedCall(analyze()) ?: return false
|
||||
val descriptor = resolvedCall.candidateDescriptor
|
||||
return descriptor.containingDeclaration is BuiltInsPackageFragment && ARRAY_OF_METHODS.contains(descriptor.name)
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.idea.inspections.RemoveRedundantSpreadOperatorInspection
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo(vararg x: String) {}
|
||||
|
||||
fun bar() {
|
||||
foo(*arrayOf<caret>("abc"))
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo(vararg x: String) {}
|
||||
|
||||
fun bar() {
|
||||
foo("abc")
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
fun foo(vararg x: Boolean) {}
|
||||
|
||||
fun bar() {
|
||||
foo(*booleanArrayOf<caret>(true, true))
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
fun foo(vararg x: Boolean) {}
|
||||
|
||||
fun bar() {
|
||||
foo(true, true)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo(vararg x: Byte) {}
|
||||
|
||||
fun bar() {
|
||||
foo(*byteArrayOf<caret>(1))
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
fun foo(vararg x: Byte) {}
|
||||
|
||||
fun bar() {
|
||||
foo(1)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo(vararg x: Char) {}
|
||||
|
||||
fun bar() {
|
||||
foo(*charArrayOf<caret>('a', 'b'))
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
fun foo(vararg x: Char) {}
|
||||
|
||||
fun bar() {
|
||||
foo('a', 'b')
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo(vararg x: Double) {}
|
||||
|
||||
fun bar() {
|
||||
foo(*doubleArrayOf<caret>(1.0))
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
fun foo(vararg x: Double) {}
|
||||
|
||||
fun bar() {
|
||||
foo(1.0)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo(vararg x: String) {}
|
||||
|
||||
fun bar() {
|
||||
foo(*emptyArray<caret><String>())
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
fun foo(vararg x: String) {}
|
||||
|
||||
fun bar() {
|
||||
foo()
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo(vararg x: Float) {}
|
||||
|
||||
fun bar() {
|
||||
foo(*floatArrayOf<caret>(1.0f))
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
fun foo(vararg x: Float) {}
|
||||
|
||||
fun bar() {
|
||||
foo(1.0f)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo(vararg x: Int) {}
|
||||
|
||||
fun bar() {
|
||||
foo(*intArrayOf<caret>(1))
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
fun foo(vararg x: Int) {}
|
||||
|
||||
fun bar() {
|
||||
foo(1)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo(vararg x: Long) {}
|
||||
|
||||
fun bar() {
|
||||
foo(*longArrayOf<caret>(1L))
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
fun foo(vararg x: Long) {}
|
||||
|
||||
fun bar() {
|
||||
foo(1L)
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
fun foo(vararg x: String) {}
|
||||
|
||||
fun bar() {
|
||||
foo(*arrayOf<caret>("abc", "def"))
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
fun foo(vararg x: String) {}
|
||||
|
||||
fun bar() {
|
||||
foo("abc", "def")
|
||||
}
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
fun foo(vararg x: String) {}
|
||||
|
||||
fun bar() {
|
||||
foo(*arrayOf<caret>("abc", "def"), "ghi", "jkl")
|
||||
}
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
fun foo(vararg x: String) {}
|
||||
|
||||
fun bar() {
|
||||
foo("abc", "def", "ghi", "jkl")
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// PROBLEM: none
|
||||
|
||||
fun foo(vararg x: String) {}
|
||||
|
||||
fun bar() {
|
||||
// We do not want to apply inspection here, because named argument is lost
|
||||
foo(x = *arrayOf<caret>("abc"))
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
fun foo(vararg x: String) {}
|
||||
|
||||
fun bar() {
|
||||
foo(*arrayOf<caret>(elements = "abc"))
|
||||
}
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
fun foo(vararg x: String) {}
|
||||
|
||||
fun bar() {
|
||||
foo("abc")
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo(vararg x: String) {}
|
||||
|
||||
fun bar() {
|
||||
foo(*arrayOf<caret>())
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
fun foo(vararg x: String) {}
|
||||
|
||||
fun bar() {
|
||||
foo()
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo(vararg x: Short) {}
|
||||
|
||||
fun bar() {
|
||||
foo(*shortArrayOf<caret>(1))
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
fun foo(vararg x: Short) {}
|
||||
|
||||
fun bar() {
|
||||
foo(1)
|
||||
}
|
||||
@@ -216,6 +216,105 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/inspectionsLocal/removeRedundantSpreadOperator")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class RemoveRedundantSpreadOperator extends AbstractLocalInspectionTest {
|
||||
public void testAllFilesPresentInRemoveRedundantSpreadOperator() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/removeRedundantSpreadOperator"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("basic.kt")
|
||||
public void testBasic() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/removeRedundantSpreadOperator/basic.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("booleanArrayOf.kt")
|
||||
public void testBooleanArrayOf() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/removeRedundantSpreadOperator/booleanArrayOf.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("byteArrayOf.kt")
|
||||
public void testByteArrayOf() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/removeRedundantSpreadOperator/byteArrayOf.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("charArrayOf.kt")
|
||||
public void testCharArrayOf() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/removeRedundantSpreadOperator/charArrayOf.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("doubleArrayOf.kt")
|
||||
public void testDoubleArrayOf() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/removeRedundantSpreadOperator/doubleArrayOf.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("emptyArray.kt")
|
||||
public void testEmptyArray() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/removeRedundantSpreadOperator/emptyArray.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("floatArrayOf.kt")
|
||||
public void testFloatArrayOf() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/removeRedundantSpreadOperator/floatArrayOf.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("intArrayOf.kt")
|
||||
public void testIntArrayOf() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/removeRedundantSpreadOperator/intArrayOf.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("longArrayOf.kt")
|
||||
public void testLongArrayOf() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/removeRedundantSpreadOperator/longArrayOf.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("multipleValues.kt")
|
||||
public void testMultipleValues() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/removeRedundantSpreadOperator/multipleValues.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("multipleValuesWithOtherValues.kt")
|
||||
public void testMultipleValuesWithOtherValues() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/removeRedundantSpreadOperator/multipleValuesWithOtherValues.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("namedArgument.kt")
|
||||
public void testNamedArgument() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/removeRedundantSpreadOperator/namedArgument.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("namedArgumentForArray.kt")
|
||||
public void testNamedArgumentForArray() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/removeRedundantSpreadOperator/namedArgumentForArray.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("noParams.kt")
|
||||
public void testNoParams() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/removeRedundantSpreadOperator/noParams.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("shortArrayOf.kt")
|
||||
public void testShortArrayOf() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/removeRedundantSpreadOperator/shortArrayOf.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/inspectionsLocal/removeToStringInStringTemplate")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user