From 9f828b8f410a0e54b3fbbec9f4d8d33271a81251 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Wed, 3 Apr 2013 18:01:08 +0400 Subject: [PATCH] Code transformation: if statement with assignments <-> assignment with if expression --- .../after.kt.template | 11 ++ .../before.kt.template | 11 ++ .../description.html | 6 + .../after.kt.template | 11 ++ .../before.kt.template | 11 ++ .../description.html | 6 + .../after.kt.template | 8 - .../before.kt.template | 5 - idea/src/META-INF/plugin.xml | 10 ++ .../jetbrains/jet/plugin/JetBundle.properties | 4 + ...tWithIfExpressionToStatementIntention.java | 68 +++++++ .../CodeTransformationUtils.java | 170 ++++++++++++++++++ ...tWithAssignmentsToExpressionIntention.java | 64 +++++++ 13 files changed, 372 insertions(+), 13 deletions(-) create mode 100644 idea/resources/intentionDescriptions/AssignmentWithIfExpressionToStatementIntention/after.kt.template create mode 100644 idea/resources/intentionDescriptions/AssignmentWithIfExpressionToStatementIntention/before.kt.template create mode 100644 idea/resources/intentionDescriptions/AssignmentWithIfExpressionToStatementIntention/description.html create mode 100644 idea/resources/intentionDescriptions/IfStatementToExpressionIntention/after.kt.template create mode 100644 idea/resources/intentionDescriptions/IfStatementToExpressionIntention/before.kt.template create mode 100644 idea/resources/intentionDescriptions/IfStatementToExpressionIntention/description.html delete mode 100644 idea/resources/intentionDescriptions/KotlinSignatureAnnotationIntention/after.kt.template delete mode 100644 idea/resources/intentionDescriptions/KotlinSignatureAnnotationIntention/before.kt.template create mode 100644 idea/src/org/jetbrains/jet/plugin/codeInsight/codeTransformations/AssignmentWithIfExpressionToStatementIntention.java create mode 100644 idea/src/org/jetbrains/jet/plugin/codeInsight/codeTransformations/CodeTransformationUtils.java create mode 100644 idea/src/org/jetbrains/jet/plugin/codeInsight/codeTransformations/IfStatementWithAssignmentsToExpressionIntention.java diff --git a/idea/resources/intentionDescriptions/AssignmentWithIfExpressionToStatementIntention/after.kt.template b/idea/resources/intentionDescriptions/AssignmentWithIfExpressionToStatementIntention/after.kt.template new file mode 100644 index 00000000000..d0d6d42bd75 --- /dev/null +++ b/idea/resources/intentionDescriptions/AssignmentWithIfExpressionToStatementIntention/after.kt.template @@ -0,0 +1,11 @@ +public class MyClass { + public method(a: Int): String { + var res: String + + if (a == 1) res = "one"; + else if (a == 2) res = "two"; + else res = "too many"; + + return res; + } +} \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/AssignmentWithIfExpressionToStatementIntention/before.kt.template b/idea/resources/intentionDescriptions/AssignmentWithIfExpressionToStatementIntention/before.kt.template new file mode 100644 index 00000000000..f833c7a5191 --- /dev/null +++ b/idea/resources/intentionDescriptions/AssignmentWithIfExpressionToStatementIntention/before.kt.template @@ -0,0 +1,11 @@ +public class MyClass { + public method(a: Int): String { + var res: String + + res = if (a == 1) "one"; + else if (a == 2) "two"; + else "too many"; + + return res; + } +} \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/AssignmentWithIfExpressionToStatementIntention/description.html b/idea/resources/intentionDescriptions/AssignmentWithIfExpressionToStatementIntention/description.html new file mode 100644 index 00000000000..bac916bbe3e --- /dev/null +++ b/idea/resources/intentionDescriptions/AssignmentWithIfExpressionToStatementIntention/description.html @@ -0,0 +1,6 @@ + + +This intention converts assignment with 'if' expression as right-hand side +into 'if' statement where each branch is terminated with assignment to the original variable + + \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/IfStatementToExpressionIntention/after.kt.template b/idea/resources/intentionDescriptions/IfStatementToExpressionIntention/after.kt.template new file mode 100644 index 00000000000..f833c7a5191 --- /dev/null +++ b/idea/resources/intentionDescriptions/IfStatementToExpressionIntention/after.kt.template @@ -0,0 +1,11 @@ +public class MyClass { + public method(a: Int): String { + var res: String + + res = if (a == 1) "one"; + else if (a == 2) "two"; + else "too many"; + + return res; + } +} \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/IfStatementToExpressionIntention/before.kt.template b/idea/resources/intentionDescriptions/IfStatementToExpressionIntention/before.kt.template new file mode 100644 index 00000000000..d0d6d42bd75 --- /dev/null +++ b/idea/resources/intentionDescriptions/IfStatementToExpressionIntention/before.kt.template @@ -0,0 +1,11 @@ +public class MyClass { + public method(a: Int): String { + var res: String + + if (a == 1) res = "one"; + else if (a == 2) res = "two"; + else res = "too many"; + + return res; + } +} \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/IfStatementToExpressionIntention/description.html b/idea/resources/intentionDescriptions/IfStatementToExpressionIntention/description.html new file mode 100644 index 00000000000..bdb8e184981 --- /dev/null +++ b/idea/resources/intentionDescriptions/IfStatementToExpressionIntention/description.html @@ -0,0 +1,6 @@ + + +This intention converts 'if' statement where each branch is terminated with assignment to the same variable +into single assignment with 'if' expression + + \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/KotlinSignatureAnnotationIntention/after.kt.template b/idea/resources/intentionDescriptions/KotlinSignatureAnnotationIntention/after.kt.template deleted file mode 100644 index 9375f4ba83a..00000000000 --- a/idea/resources/intentionDescriptions/KotlinSignatureAnnotationIntention/after.kt.template +++ /dev/null @@ -1,8 +0,0 @@ -import jet.runtime.typeinfo.KotlinSignature; - -public class MyClass { - @KotlinSignature("fun method() : jet.String?") - public String method() { - return ""; - } -} \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/KotlinSignatureAnnotationIntention/before.kt.template b/idea/resources/intentionDescriptions/KotlinSignatureAnnotationIntention/before.kt.template deleted file mode 100644 index d8550fadf73..00000000000 --- a/idea/resources/intentionDescriptions/KotlinSignatureAnnotationIntention/before.kt.template +++ /dev/null @@ -1,5 +0,0 @@ -public class MyClass { - public String method() { - return ""; - } -} \ No newline at end of file diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 81bfccaa9f5..7e11c21975b 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -283,6 +283,16 @@ Kotlin + + org.jetbrains.jet.plugin.codeInsight.codeTransformations.IfStatementWithAssignmentsToExpressionIntention + Kotlin + + + + org.jetbrains.jet.plugin.codeInsight.codeTransformations.AssignmentWithIfExpressionToStatementIntention + Kotlin + + diff --git a/idea/src/org/jetbrains/jet/plugin/JetBundle.properties b/idea/src/org/jetbrains/jet/plugin/JetBundle.properties index 01ba6441028..f0eb6b1fa3a 100644 --- a/idea/src/org/jetbrains/jet/plugin/JetBundle.properties +++ b/idea/src/org/jetbrains/jet/plugin/JetBundle.properties @@ -139,3 +139,7 @@ surround.with.function.template={ } surround.with.cannot.perform.action=Cannot perform Surround With action to the current contextsurround.with.function.template={ } remove.variable.family.name=Remove variable remove.variable.action=Remove variable ''{0}'' + +kotlin.code.transformations=Kotlin Code Transformations +transform.if.statement.with.assignments.to.expression=Transform 'if' statement with assignments to expression +transform.assignment.with.if.expression.to.statement=Transform assignment with 'if' expression to statement diff --git a/idea/src/org/jetbrains/jet/plugin/codeInsight/codeTransformations/AssignmentWithIfExpressionToStatementIntention.java b/idea/src/org/jetbrains/jet/plugin/codeInsight/codeTransformations/AssignmentWithIfExpressionToStatementIntention.java new file mode 100644 index 00000000000..f77daf3c149 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/codeInsight/codeTransformations/AssignmentWithIfExpressionToStatementIntention.java @@ -0,0 +1,68 @@ +/* + * Copyright 2010-2013 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.jet.plugin.codeInsight.codeTransformations; + +import com.intellij.codeInsight.intention.impl.BaseIntentionAction; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jet.lang.psi.JetBinaryExpression; +import org.jetbrains.jet.plugin.JetBundle; + +public class AssignmentWithIfExpressionToStatementIntention extends BaseIntentionAction { + public AssignmentWithIfExpressionToStatementIntention() { + setText(JetBundle.message("transform.assignment.with.if.expression.to.statement")); + } + + @NotNull + @Override + public String getFamilyName() { + return JetBundle.message("kotlin.code.transformations"); + } + + @Nullable + private static JetBinaryExpression getAssignment(@NotNull Editor editor, @NotNull PsiFile file) { + int offset = editor.getCaretModel().getOffset(); + PsiElement element = file.findElementAt(offset); + + while (element != null) { + if (CodeTransformationUtils.checkAssignmentWithIfExpression(element)) return (JetBinaryExpression)element; + element = PsiTreeUtil.getParentOfType(element, JetBinaryExpression.class, false); + } + + return null; + } + + @Override + public boolean isAvailable(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) { + return getAssignment(editor, file) != null; + } + + @Override + public void invoke( + @NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file + ) throws IncorrectOperationException { + JetBinaryExpression assignment = getAssignment(editor, file); + assert assignment != null; + CodeTransformationUtils.transformAssignmentWithIfExpressionToStatement(assignment); + } +} diff --git a/idea/src/org/jetbrains/jet/plugin/codeInsight/codeTransformations/CodeTransformationUtils.java b/idea/src/org/jetbrains/jet/plugin/codeInsight/codeTransformations/CodeTransformationUtils.java new file mode 100644 index 00000000000..08306827f83 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/codeInsight/codeTransformations/CodeTransformationUtils.java @@ -0,0 +1,170 @@ +/* + * Copyright 2010-2013 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.jet.plugin.codeInsight.codeTransformations; + +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.lang.psi.*; +import org.jetbrains.jet.lexer.JetTokens; + +import java.util.ArrayList; +import java.util.List; + +public class CodeTransformationUtils { + private static List getIfExpressionOutcomes(@NotNull JetElement root) { + return root.accept( + new JetVisitor, List>() { + @Override + public List visitExpression(JetExpression expression, List data) { + data.add(expression); + return data; + } + + @Override + public List visitBlockExpression( + JetBlockExpression expression, List data) { + int n = expression.getStatements().size(); + if (n > 0) { + expression.getStatements().get(n - 1).accept(this, data); + } else { + data.add(expression); + } + + return data; + } + + @SuppressWarnings("ConstantConditions") + @Override + public List visitIfExpression( + JetIfExpression expression, List data) { + if (expression.getThen() != null) { + expression.getThen().accept(this, data); + } + if (expression.getElse() != null) { + expression.getElse().accept(this, data); + } + return data; + } + }, + new ArrayList() + ); + } + + private static Boolean checkAllIfExpressionsAreComplete(@NotNull JetElement root) { + return root.accept( + new JetVisitor() { + @Override + public Boolean visitJetElement(JetElement element, Boolean data) { + return data; + } + + @SuppressWarnings("ConstantConditions") + @Override + public Boolean visitIfExpression(JetIfExpression expression, Boolean data) { + if (data && expression.getThen() != null) { + data = expression.getThen().accept(this, data); + } else { + data = false; + } + if (data && expression.getElse() != null) { + data = expression.getElse().accept(this, data); + } else { + data = false; + } + + return data; + } + }, + true + ); + } + + public static boolean isAssignment(@NotNull PsiElement element) { + if (!(element instanceof JetBinaryExpression)) return false; + JetBinaryExpression binaryExpression = (JetBinaryExpression)element; + if (binaryExpression.getOperationReference().getReferencedNameElementType() != JetTokens.EQ) return false; + return true; + } + + private static boolean checkAllOutcomesAreCompatibleAssignments(@NotNull List outcomes) { + JetExpression lastLhs = null; + for (JetExpression outcome : outcomes) { + if (!isAssignment(outcome)) return false; + + JetExpression currLhs = ((JetBinaryExpression)outcome).getLeft(); + if (!(currLhs instanceof JetSimpleNameExpression)) return false; + + if (lastLhs == null) { + lastLhs = currLhs; + } else if (!lastLhs.getText().equals(currLhs.getText())) return false; + } + + return true; + } + + static boolean checkIfStatementWithAssignments(@NotNull JetIfExpression ifExpression) { + if (ifExpression.getParent() == null) return false; + List outcomes = getIfExpressionOutcomes(ifExpression); + + return !outcomes.isEmpty() && checkAllIfExpressionsAreComplete(ifExpression) && checkAllOutcomesAreCompatibleAssignments(outcomes); + } + + static boolean checkAssignmentWithIfExpression(@NotNull PsiElement element) { + if (!isAssignment(element)) return false; + JetBinaryExpression assignment = (JetBinaryExpression)element; + return (assignment.getLeft() instanceof JetSimpleNameExpression) && + (assignment.getRight() instanceof JetIfExpression); + } + + @SuppressWarnings("ConstantConditions") + static void transformIfStatementWithAssignmentsToExpression(@NotNull JetIfExpression ifExpression) { + Project project = ifExpression.getProject(); + List outcomes = getIfExpressionOutcomes(ifExpression); + JetExpression lhs = ((JetBinaryExpression)outcomes.get(0)).getLeft(); + + JetBinaryExpression assignment = (JetBinaryExpression)JetPsiFactory.createExpression(project, "a = b"); + + assignment = (JetBinaryExpression)assignment.getLeft().replace(lhs).getParent(); + assignment = (JetBinaryExpression)assignment.getRight().replace(ifExpression).getParent(); + assignment = (JetBinaryExpression)ifExpression.replace(assignment); + ifExpression = (JetIfExpression)assignment.getRight(); + + for (JetExpression outcome : getIfExpressionOutcomes(ifExpression)) { + outcome.replace(((JetBinaryExpression)outcome).getRight()); + } + } + + @SuppressWarnings("ConstantConditions") + static void transformAssignmentWithIfExpressionToStatement(@NotNull JetBinaryExpression assignment) { + Project project = assignment.getProject(); + String varName = assignment.getLeft().getText(); + JetIfExpression ifExpression = (JetIfExpression)assignment.getRight(); + + ifExpression = (JetIfExpression)assignment.replace(ifExpression); + + for (JetExpression outcome : getIfExpressionOutcomes(ifExpression)) { + JetBinaryExpression localAssignment = (JetBinaryExpression)JetPsiFactory.createExpression(project, "a = b"); + localAssignment = (JetBinaryExpression)localAssignment.getLeft().replace(JetPsiFactory.createExpression(project, varName)).getParent(); + localAssignment = (JetBinaryExpression)localAssignment.getRight().replace(outcome).getParent(); + outcome.replace(localAssignment); + } + } + + private CodeTransformationUtils() { + } +} diff --git a/idea/src/org/jetbrains/jet/plugin/codeInsight/codeTransformations/IfStatementWithAssignmentsToExpressionIntention.java b/idea/src/org/jetbrains/jet/plugin/codeInsight/codeTransformations/IfStatementWithAssignmentsToExpressionIntention.java new file mode 100644 index 00000000000..7332e9fd110 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/codeInsight/codeTransformations/IfStatementWithAssignmentsToExpressionIntention.java @@ -0,0 +1,64 @@ +/* + * Copyright 2010-2013 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.jet.plugin.codeInsight.codeTransformations; + +import com.intellij.codeInsight.intention.impl.BaseIntentionAction; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jet.lang.psi.JetIfExpression; +import org.jetbrains.jet.plugin.JetBundle; + +public class IfStatementWithAssignmentsToExpressionIntention extends BaseIntentionAction { + public IfStatementWithAssignmentsToExpressionIntention() { + setText(JetBundle.message("transform.if.statement.with.assignments.to.expression")); + } + + @NotNull + @Override + public String getFamilyName() { + return JetBundle.message("kotlin.code.transformations"); + } + + @Nullable + private static JetIfExpression getOriginalExpression(@NotNull Editor editor, @NotNull PsiFile file) { + int offset = editor.getCaretModel().getOffset(); + PsiElement element = file.findElementAt(offset); + return PsiTreeUtil.getParentOfType(element, JetIfExpression.class, false); + } + + @Override + public boolean isAvailable( + @NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) { + JetIfExpression ifExpression = getOriginalExpression(editor, file); + return (ifExpression != null) && CodeTransformationUtils.checkIfStatementWithAssignments(ifExpression); + } + + @Override + public void invoke( + @NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file + ) throws IncorrectOperationException { + JetIfExpression ifExpression = getOriginalExpression(editor, file); + assert ifExpression != null; + CodeTransformationUtils.transformIfStatementWithAssignmentsToExpression(ifExpression); + } +}