diff --git a/idea/resources/META-INF/plugin-common.xml b/idea/resources/META-INF/plugin-common.xml index dd25f626673..c7fb48e9b3c 100644 --- a/idea/resources/META-INF/plugin-common.xml +++ b/idea/resources/META-INF/plugin-common.xml @@ -3459,6 +3459,15 @@ language="kotlin" /> + + diff --git a/idea/resources/inspectionDescriptions/UnusedUnaryOperator.html b/idea/resources/inspectionDescriptions/UnusedUnaryOperator.html new file mode 100644 index 00000000000..7aec0df569b --- /dev/null +++ b/idea/resources/inspectionDescriptions/UnusedUnaryOperator.html @@ -0,0 +1,5 @@ + + +This inspection reports unary operators that are not used. + + \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedUnaryOperatorInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedUnaryOperatorInspection.kt new file mode 100644 index 00000000000..6a96488478f --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedUnaryOperatorInspection.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +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 org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.util.textRangeIn +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.KtPrefixExpression +import org.jetbrains.kotlin.psi.prefixExpressionVisitor +import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode + +class UnusedUnaryOperatorInspection : AbstractKotlinInspection() { + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = prefixExpressionVisitor(fun(prefix) { + if (prefix.baseExpression == null) return + val operationToken = prefix.operationToken + if (operationToken != KtTokens.PLUS && operationToken != KtTokens.MINUS) return + + val context = prefix.analyze(BodyResolveMode.PARTIAL_WITH_CFA) + if (prefix.isUsedAsExpression(context)) return + val operatorDescriptor = prefix.operationReference.getResolvedCall(context)?.resultingDescriptor as? DeclarationDescriptor ?: return + if (!KotlinBuiltIns.isUnderKotlinPackage(operatorDescriptor)) return + + holder.registerProblem( + prefix, + "Unused unary operator", + ProblemHighlightType.LIKE_UNUSED_SYMBOL, + prefix.operationReference.textRangeIn(prefix), + RemoveUnaryOperatorFix() + ) + }) + + private class RemoveUnaryOperatorFix : LocalQuickFix { + override fun getName() = "Remove unused unary operator" + + override fun getFamilyName() = name + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val prefixExpression = descriptor.psiElement as? KtPrefixExpression ?: return + val baseExpression = prefixExpression.baseExpression ?: return + prefixExpression.replace(baseExpression) + } + } +} diff --git a/idea/testData/inspectionsLocal/unusedUnaryOperator/.inspection b/idea/testData/inspectionsLocal/unusedUnaryOperator/.inspection new file mode 100644 index 00000000000..88fec6b3d6e --- /dev/null +++ b/idea/testData/inspectionsLocal/unusedUnaryOperator/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.UnusedUnaryOperatorInspection \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unusedUnaryOperator/basic.kt b/idea/testData/inspectionsLocal/unusedUnaryOperator/basic.kt new file mode 100644 index 00000000000..c24a7c83671 --- /dev/null +++ b/idea/testData/inspectionsLocal/unusedUnaryOperator/basic.kt @@ -0,0 +1,6 @@ +// "Remove unused unary operator" "true" +fun test(foo: Int?): Int { + val a = 1 + 2 + + 3 + return a +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unusedUnaryOperator/basic.kt.after b/idea/testData/inspectionsLocal/unusedUnaryOperator/basic.kt.after new file mode 100644 index 00000000000..504989c22f5 --- /dev/null +++ b/idea/testData/inspectionsLocal/unusedUnaryOperator/basic.kt.after @@ -0,0 +1,6 @@ +// "Remove unused unary operator" "true" +fun test(foo: Int?): Int { + val a = 1 + 2 + 3 + return a +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unusedUnaryOperator/basic2.kt b/idea/testData/inspectionsLocal/unusedUnaryOperator/basic2.kt new file mode 100644 index 00000000000..7ee28701257 --- /dev/null +++ b/idea/testData/inspectionsLocal/unusedUnaryOperator/basic2.kt @@ -0,0 +1,6 @@ +// "Remove unused unary operator" "true" +fun test(foo: Int?): Int { + val a = 1 + 2 + - 3 + return a +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unusedUnaryOperator/basic2.kt.after b/idea/testData/inspectionsLocal/unusedUnaryOperator/basic2.kt.after new file mode 100644 index 00000000000..504989c22f5 --- /dev/null +++ b/idea/testData/inspectionsLocal/unusedUnaryOperator/basic2.kt.after @@ -0,0 +1,6 @@ +// "Remove unused unary operator" "true" +fun test(foo: Int?): Int { + val a = 1 + 2 + 3 + return a +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unusedUnaryOperator/usedAsExpression.kt b/idea/testData/inspectionsLocal/unusedUnaryOperator/usedAsExpression.kt new file mode 100644 index 00000000000..4f1d360050e --- /dev/null +++ b/idea/testData/inspectionsLocal/unusedUnaryOperator/usedAsExpression.kt @@ -0,0 +1,6 @@ +// PROBLEM: none +fun test(foo: Int?): Int { + val a = (1 + 2 + - 3) + return a +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unusedUnaryOperator/usedAsExpression2.kt b/idea/testData/inspectionsLocal/unusedUnaryOperator/usedAsExpression2.kt new file mode 100644 index 00000000000..c5220932196 --- /dev/null +++ b/idea/testData/inspectionsLocal/unusedUnaryOperator/usedAsExpression2.kt @@ -0,0 +1,4 @@ +// PROBLEM: none +fun test(foo: Int?): Int { + return -1 +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unusedUnaryOperator/userOperator.kt b/idea/testData/inspectionsLocal/unusedUnaryOperator/userOperator.kt new file mode 100644 index 00000000000..dc6dab67cff --- /dev/null +++ b/idea/testData/inspectionsLocal/unusedUnaryOperator/userOperator.kt @@ -0,0 +1,11 @@ +// PROBLEM: none +data class Point(val x: Int) + +operator fun Point.plus(other: Point) = Point(this.x + other.x) + +operator fun Point.unaryMinus() = Point(-x) + +fun test() { + val p = Point(1) + Point(2) + -Point(3) +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index 788e3f8ecb2..3a27dcb3a3a 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -12407,6 +12407,44 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { } } + @TestMetadata("idea/testData/inspectionsLocal/unusedUnaryOperator") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class UnusedUnaryOperator extends AbstractLocalInspectionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath); + } + + public void testAllFilesPresentInUnusedUnaryOperator() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/unusedUnaryOperator"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true); + } + + @TestMetadata("basic.kt") + public void testBasic() throws Exception { + runTest("idea/testData/inspectionsLocal/unusedUnaryOperator/basic.kt"); + } + + @TestMetadata("basic2.kt") + public void testBasic2() throws Exception { + runTest("idea/testData/inspectionsLocal/unusedUnaryOperator/basic2.kt"); + } + + @TestMetadata("usedAsExpression.kt") + public void testUsedAsExpression() throws Exception { + runTest("idea/testData/inspectionsLocal/unusedUnaryOperator/usedAsExpression.kt"); + } + + @TestMetadata("usedAsExpression2.kt") + public void testUsedAsExpression2() throws Exception { + runTest("idea/testData/inspectionsLocal/unusedUnaryOperator/usedAsExpression2.kt"); + } + + @TestMetadata("userOperator.kt") + public void testUserOperator() throws Exception { + runTest("idea/testData/inspectionsLocal/unusedUnaryOperator/userOperator.kt"); + } + } + @TestMetadata("idea/testData/inspectionsLocal/useExpressionBody") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)