Add inspection removing redundant spread operator for arrayOf call

So #KT-17920 Fixed
This commit is contained in:
shiraji
2017-06-14 04:52:22 +03:00
committed by Mikhail Glukhikh
parent ce55d9e364
commit cbccf932a7
36 changed files with 352 additions and 13 deletions
@@ -0,0 +1,5 @@
<html>
<body>
This inspection reports the redundant spread operator for arrayOf call
</body>
</html>
+8
View File
@@ -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"/>
@@ -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)
}
}
}
@@ -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")
}
@@ -0,0 +1,5 @@
fun foo(vararg x: Boolean) {}
fun bar() {
foo(*booleanArrayOf<caret>(true, true))
}
@@ -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))
}
@@ -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'))
}
@@ -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))
}
@@ -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>())
}
@@ -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))
}
@@ -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))
}
@@ -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))
}
@@ -0,0 +1,5 @@
fun foo(vararg x: Long) {}
fun bar() {
foo(1L)
}
@@ -0,0 +1,5 @@
fun foo(vararg x: String) {}
fun bar() {
foo(*arrayOf<caret>("abc", "def"))
}
@@ -0,0 +1,5 @@
fun foo(vararg x: String) {}
fun bar() {
foo("abc", "def")
}
@@ -0,0 +1,5 @@
fun foo(vararg x: String) {}
fun bar() {
foo(*arrayOf<caret>("abc", "def"), "ghi", "jkl")
}
@@ -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"))
}
@@ -0,0 +1,5 @@
fun foo(vararg x: String) {}
fun bar() {
foo(*arrayOf<caret>(elements = "abc"))
}
@@ -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>())
}
@@ -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))
}
@@ -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)