diff --git a/idea/resources/inspectionDescriptions/WrapUnaryOperator.html b/idea/resources/inspectionDescriptions/WrapUnaryOperator.html new file mode 100644 index 00000000000..e7ee5a0a05b --- /dev/null +++ b/idea/resources/inspectionDescriptions/WrapUnaryOperator.html @@ -0,0 +1,5 @@ + + +This inspection reports an unary operator followed by a dot qualifier (such as -1.inc()) that is potentially wrong. + + diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index ee7032fc6de..1d32b6bf800 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -2214,6 +2214,14 @@ language="kotlin" /> + + diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/WrapUnaryOperatorInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/WrapUnaryOperatorInspection.kt new file mode 100644 index 00000000000..bfb31679a06 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/WrapUnaryOperatorInspection.kt @@ -0,0 +1,71 @@ +/* + * 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.psi.PsiElementVisitor +import com.intellij.psi.tree.IElementType +import org.jetbrains.kotlin.KtNodeTypes +import org.jetbrains.kotlin.idea.intentions.getLeftMostReceiverExpression +import org.jetbrains.kotlin.idea.intentions.replaceFirstReceiver +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.* + +class WrapUnaryOperatorInspection : AbstractKotlinInspection() { + + val numberTypes = listOf(KtNodeTypes.INTEGER_CONSTANT, KtNodeTypes.FLOAT_CONSTANT) + + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { + return object : KtVisitorVoid() { + override fun visitPrefixExpression(expression: KtPrefixExpression) { + super.visitPrefixExpression(expression) + if (expression.operationToken.isUnaryMinusOrPlus()) { + val baseExpression = expression.baseExpression + if (baseExpression is KtDotQualifiedExpression) { + val receiverExpression = baseExpression.receiverExpression + if (receiverExpression is KtConstantExpression && + receiverExpression.node.elementType in numberTypes) { + holder.registerProblem(expression, + "Wrap unary operator and value with ()", + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + WrapUnaryOperatorQuickfix()) + } + } + } + } + + private fun IElementType.isUnaryMinusOrPlus() = this == KtTokens.MINUS || this == KtTokens.PLUS + } + } + + private class WrapUnaryOperatorQuickfix : LocalQuickFix { + override fun getFamilyName() = "Wrap unary operator and value with ()" + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val expression = descriptor.psiElement as? KtPrefixExpression ?: return + val dotQualifiedExpression = expression.baseExpression as? KtDotQualifiedExpression ?: return + val factory = KtPsiFactory(project) + val newReceiver = factory.createExpressionByPattern("($0$1)", expression.operationReference.text, dotQualifiedExpression.getLeftMostReceiverExpression()) + val newExpression = dotQualifiedExpression.replaceFirstReceiver(factory, newReceiver) + expression.replace(newExpression) + } + } +} \ No newline at end of file diff --git a/idea/testData/inspections/wrapUnaryOperator/inspectionData/expected.xml b/idea/testData/inspections/wrapUnaryOperator/inspectionData/expected.xml new file mode 100644 index 00000000000..a98c36d672e --- /dev/null +++ b/idea/testData/inspections/wrapUnaryOperator/inspectionData/expected.xml @@ -0,0 +1,58 @@ + + + simple.kt + 6 + light_idea_test_case + + Ambiguous unary operator use with number constant + Wrap unary operator and value with () + + + simple.kt + 9 + light_idea_test_case + + Ambiguous unary operator use with number constant + Wrap unary operator and value with () + + + simple.kt + 10 + light_idea_test_case + + Ambiguous unary operator use with number constant + Wrap unary operator and value with () + + + simple.kt + 12 + light_idea_test_case + + Ambiguous unary operator use with number constant + Wrap unary operator and value with () + + + simple.kt + 14 + light_idea_test_case + + Ambiguous unary operator use with number constant + Wrap unary operator and value with () + + + simple.kt + 22 + light_idea_test_case + + Ambiguous unary operator use with number constant + Wrap unary operator and value with () + + + simple.kt + 23 + light_idea_test_case + + Ambiguous unary operator use with number constant + Wrap unary operator and value with () + + \ No newline at end of file diff --git a/idea/testData/inspections/wrapUnaryOperator/inspectionData/inspections.test b/idea/testData/inspections/wrapUnaryOperator/inspectionData/inspections.test new file mode 100644 index 00000000000..6deaa9e0d9c --- /dev/null +++ b/idea/testData/inspections/wrapUnaryOperator/inspectionData/inspections.test @@ -0,0 +1 @@ +// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.WrapUnaryOperatorInspection diff --git a/idea/testData/inspections/wrapUnaryOperator/simple.kt b/idea/testData/inspections/wrapUnaryOperator/simple.kt new file mode 100644 index 00000000000..53739be6fab --- /dev/null +++ b/idea/testData/inspections/wrapUnaryOperator/simple.kt @@ -0,0 +1,27 @@ +object Constants { + val ONE = 1 +} + +fun foo() { + println(-1.inc()) + val i = 100 + println(-i.inc()) + println(-1.1.dec()) + println(-1.1f.dec()) + println(1 - -1.dec().dec()) + println(1 - -1.javaClass) + println(1 - - 1.dec().dec()) + println(1 - - 1.javaClass) + println(1-1.inc()) // NG + println(1 - 1.dec().dec()) // NG + println(1 - 1.javaClass) // NG + println(-1) //NG + println(!true) // NG + println(!true.not()) // NG + println(+1) // NG + println(+1.inc()) + println(+3.14.inc()) + + println(-Constants.ONE) // NG + println(-"2".toInt()) // NG +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/wrapUnaryOperator/.inspection b/idea/testData/inspectionsLocal/wrapUnaryOperator/.inspection new file mode 100644 index 00000000000..c92bb50f7e7 --- /dev/null +++ b/idea/testData/inspectionsLocal/wrapUnaryOperator/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.WrapUnaryOperatorInspection \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/wrapUnaryOperator/simple.kt b/idea/testData/inspectionsLocal/wrapUnaryOperator/simple.kt new file mode 100644 index 00000000000..cb52d0e2527 --- /dev/null +++ b/idea/testData/inspectionsLocal/wrapUnaryOperator/simple.kt @@ -0,0 +1,3 @@ +fun Int.inc() = this + 1 + +val x = -1.inc() \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/wrapUnaryOperator/simple.kt.after b/idea/testData/inspectionsLocal/wrapUnaryOperator/simple.kt.after new file mode 100644 index 00000000000..ccc62ce789c --- /dev/null +++ b/idea/testData/inspectionsLocal/wrapUnaryOperator/simple.kt.after @@ -0,0 +1,3 @@ +fun Int.inc() = this + 1 + +val x = (-1).inc() \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java index 1b071259d52..10d31b153c6 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java @@ -400,5 +400,11 @@ public class InspectionTestGenerated extends AbstractInspectionTest { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/unusedSymbol/typeParameter/inspectionData/inspections.test"); doTest(fileName); } + + @TestMetadata("wrapUnaryOperator/inspectionData/inspections.test") + public void testWrapUnaryOperator_inspectionData_Inspections_test() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/wrapUnaryOperator/inspectionData/inspections.test"); + doTest(fileName); + } } } diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index 067700e7d90..d07c8802a28 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -320,4 +320,19 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { doTest(fileName); } } + + @TestMetadata("idea/testData/inspectionsLocal/wrapUnaryOperator") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class WrapUnaryOperator extends AbstractLocalInspectionTest { + public void testAllFilesPresentInWrapUnaryOperator() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/wrapUnaryOperator"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/wrapUnaryOperator/simple.kt"); + doTest(fileName); + } + } }