diff --git a/src/main/kotlin/org/move/ide/inspections/compilerV2/MvReplaceWithCompoundAssignmentInspection.kt b/src/main/kotlin/org/move/ide/inspections/compilerV2/MvReplaceWithCompoundAssignmentInspection.kt new file mode 100644 index 00000000..eedd0aa9 --- /dev/null +++ b/src/main/kotlin/org/move/ide/inspections/compilerV2/MvReplaceWithCompoundAssignmentInspection.kt @@ -0,0 +1,56 @@ +package org.move.ide.inspections.compilerV2 + +import com.intellij.codeInsight.PsiEquivalenceUtil +import com.intellij.codeInspection.ProblemHighlightType.WEAK_WARNING +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiFile +import org.move.ide.inspections.DiagnosticFix +import org.move.lang.core.MOVE_ARITHMETIC_BINARY_OPS +import org.move.lang.core.psi.MvAssignmentExpr +import org.move.lang.core.psi.MvBinaryExpr +import org.move.lang.core.psi.ext.elementType +import org.move.lang.core.psi.ext.operator +import org.move.lang.core.psi.psiFactory + +class MvReplaceWithCompoundAssignmentInspection: + Move2OnlyInspectionBase(MvAssignmentExpr::class.java) { + + override fun visitTargetElement(element: MvAssignmentExpr, holder: ProblemsHolder, isOnTheFly: Boolean) { + val lhsExpr = element.expr + val initializerExpr = element.initializer.expr ?: return + if (initializerExpr is MvBinaryExpr + && initializerExpr.operator.elementType in MOVE_ARITHMETIC_BINARY_OPS + ) { + // take lhs of binary plus expr + val argumentExpr = initializerExpr.left + if (PsiEquivalenceUtil.areElementsEquivalent(lhsExpr, argumentExpr)) { + val op = initializerExpr.operator.text + holder.registerProblem( + element, + "Can be replaced with compound assignment", + WEAK_WARNING, + ReplaceWithCompoundAssignmentFix(element, op) + ) + } + } + } + + class ReplaceWithCompoundAssignmentFix(assignmentExpr: MvAssignmentExpr, val op: String): + DiagnosticFix(assignmentExpr) { + + override fun getText(): String = "Replace with compound assignment expr" + + override fun invoke(project: Project, file: PsiFile, element: MvAssignmentExpr) { + val lhsExpr = element.expr + val rhsExpr = (element.initializer.expr as? MvBinaryExpr)?.right ?: return + + val psiFactory = project.psiFactory + val assignBinExpr = psiFactory.expr("x $op= 1") + assignBinExpr.left.replace(lhsExpr) + assignBinExpr.right?.replace(rhsExpr) + + element.replace(assignBinExpr) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/move/lang/core/MvTokenType.kt b/src/main/kotlin/org/move/lang/core/MvTokenType.kt index 6a9bc58a..5295ac51 100644 --- a/src/main/kotlin/org/move/lang/core/MvTokenType.kt +++ b/src/main/kotlin/org/move/lang/core/MvTokenType.kt @@ -28,6 +28,11 @@ val TYPES = tokenSetOf(PATH_TYPE, REF_TYPE, TUPLE_TYPE) val MOVE_COMMENTS = tokenSetOf(BLOCK_COMMENT, EOL_COMMENT, EOL_DOC_COMMENT) +val MOVE_ARITHMETIC_BINARY_OPS = tokenSetOf( + PLUS, MINUS, MUL, DIV, MODULO, + AND, OR, XOR, + LT_LT, GT_GT, +) val MOVE_BINARY_OPS = tokenSetOf( OR_OR, AND_AND, EQ_EQ_GT, LT_EQ_EQ_GT, diff --git a/src/main/kotlin/org/move/lang/core/psi/ext/MvBinaryExpr.kt b/src/main/kotlin/org/move/lang/core/psi/ext/MvBinaryExpr.kt index d5883e06..cd7c718d 100644 --- a/src/main/kotlin/org/move/lang/core/psi/ext/MvBinaryExpr.kt +++ b/src/main/kotlin/org/move/lang/core/psi/ext/MvBinaryExpr.kt @@ -1,10 +1,13 @@ package org.move.lang.core.psi.ext import com.intellij.lang.ASTNode +import com.intellij.psi.PsiElement import org.move.lang.MvElementTypes.BINARY_OP import org.move.lang.core.psi.MvBinaryExpr import org.move.lang.core.psi.MvElementImpl +val MvBinaryExpr.operator: PsiElement get() = binaryOp.operator + abstract class MvBinaryExprMixin(node: ASTNode): MvElementImpl(node), MvBinaryExpr { override fun toString(): String { diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 0165711a..1e985a27 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -295,6 +295,10 @@ displayName="Convert to index expr" enabledByDefault="true" level="WEAK WARNING" implementationClass="org.move.ide.inspections.compilerV2.MvReplaceWithIndexExprInspection" /> + + +Replaces `x = x + 1` with `x += 1` + + \ No newline at end of file diff --git a/src/test/kotlin/org/move/ide/inspections/compilerV2/MvReplaceWithCompoundAssignmentInspectionTest.kt b/src/test/kotlin/org/move/ide/inspections/compilerV2/MvReplaceWithCompoundAssignmentInspectionTest.kt new file mode 100644 index 00000000..87f3f835 --- /dev/null +++ b/src/test/kotlin/org/move/ide/inspections/compilerV2/MvReplaceWithCompoundAssignmentInspectionTest.kt @@ -0,0 +1,72 @@ +package org.move.ide.inspections.compilerV2 + +import org.intellij.lang.annotations.Language +import org.move.utils.tests.MoveV2 +import org.move.utils.tests.annotation.InspectionTestBase + +@MoveV2 +class MvReplaceWithCompoundAssignmentInspectionTest: + InspectionTestBase(MvReplaceWithCompoundAssignmentInspection::class) { + + fun `test replace variable assignment with plus`() = doFixTest( + """ + module 0x1::m { + fun main() { + let x = 1; + /*caret*/x = x + 1; + } + } + """, """ + module 0x1::m { + fun main() { + let x = 1; + x += 1; + } + } + """ + ) + + fun `test replace variable assignment with left shift`() = doFixTest( + """ + module 0x1::m { + fun main() { + let x = 1; + /*caret*/x = x << 1; + } + } + """, """ + module 0x1::m { + fun main() { + let x = 1; + x <<= 1; + } + } + """ + ) + + fun `test replace deref assignment with plus`() = doFixTest( + """ + module 0x1::m { + fun main(p: &u8) { + /*caret*/*p = *p + 1; + } + } + """, """ + module 0x1::m { + fun main(p: &u8) { + *p += 1; + } + } + """ + ) + + private fun doTest(@Language("Move") text: String) = + checkByText(text, checkWarn = false, checkWeakWarn = true) + + private fun doFixTest( + @Language("Move") before: String, + @Language("Move") after: String, + ) = + checkFixByText("Replace with compound assignment expr", before, after, + checkWarn = false, checkWeakWarn = true) +} \ No newline at end of file