Create DeprecatedLambdaSyntaxFix

This commit is contained in:
Stanislav Erokhin
2015-03-18 16:35:25 +03:00
parent a5a6ecd1ec
commit 0b76dbb492
21 changed files with 377 additions and 0 deletions
@@ -184,6 +184,8 @@ migrate.class.object.to.companion=Replace 'class' keyword with 'companion' modif
migrate.class.object.to.companion.family=Replace 'class' Keyword with 'companion' Modifier
migrate.class.object.to.companion.in.whole.project=Replace 'class' keyword with 'companion' modifier in whole project
migrate.class.object.to.companion.in.whole.project.family=Replace 'class' Keyword with 'companion' Modifier in Whole Project
migrate.lambda.syntax=Migrate lambda syntax
migrate.lambda.syntax.family=Migrate lambda syntax
remove.val.var.from.parameter=Remove ''{0}'' from parameter
add.override.to.equals.hashCode.toString=Add 'override' to equals, hashCode, toString in project
add.when.else.branch.action.family.name=Add Else Branch
@@ -0,0 +1,175 @@
/*
* Copyright 2010-2015 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.quickfix
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.Task
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.JetBundle
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFully
import org.jetbrains.kotlin.idea.project.PluginJetFilesProvider
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.ShortenReferences
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.idea.util.psiModificationUtil.moveInsideParenthesesAndReplaceWith
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.types.JetType
import org.jetbrains.kotlin.types.expressions.FunctionsTypingVisitor
import org.jetbrains.kotlin.utils.addIfNotNull
import java.util.ArrayList
public class DeprecatedLambdaSyntaxFix(element: JetFunctionLiteralExpression) : JetIntentionAction<JetFunctionLiteralExpression>(element) {
override fun getText() = JetBundle.message("migrate.lambda.syntax")
override fun getFamilyName() = JetBundle.message("migrate.lambda.syntax.family")
override fun invoke(project: Project, editor: Editor, file: JetFile) {
LambdaWithDeprecatedSyntax(element, JetPsiFactory(project)).runFix()
}
companion object Factory : JetSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic)
= (diagnostic.getPsiElement() as? JetFunctionLiteralExpression)?.let { DeprecatedLambdaSyntaxFix(it) }
}
}
private class LambdaWithDeprecatedSyntax(val functionLiteralExpression: JetFunctionLiteralExpression, val psiFactory: JetPsiFactory, val level: Int = 0) {
val functionLiteral = functionLiteralExpression.getFunctionLiteral()
val hasNoReturnAndReceiverType = !functionLiteral.hasDeclaredReturnType() && functionLiteral.getReceiverTypeReference() == null
val bindingContext = if (hasNoReturnAndReceiverType) null else functionLiteralExpression.analyze()
val functionLiteralType: JetType? = if (hasNoReturnAndReceiverType) null else {
val type = bindingContext!!.get(BindingContext.EXPRESSION_TYPE, functionLiteralExpression)
assert(type != null && KotlinBuiltIns.isFunctionOrExtensionFunctionType(type)) {
"Broken function type for expression: ${functionLiteralExpression.getText()}, at: ${DiagnosticUtils.atLocation(functionLiteralExpression)}"
}
type
}
// you must run it under write action
fun runFix() {
if (!JetPsiUtil.isDeprecatedLambdaSyntax(functionLiteralExpression)) return
if (hasNoReturnAndReceiverType) {
removeExternalParenthesesOnParameterList(functionLiteral, psiFactory)
}
else {
val functionExpression = convertToFunctionExpression(functionLiteralType!!)
val literalArgument = getFunctionLiteralArgument()
val newFunctionExpression = if (literalArgument == null) {
functionLiteralExpression.replace(functionExpression) as JetNamedFunction
}
else {
literalArgument.moveInsideParenthesesAndReplaceWith(functionExpression, bindingContext!!).
getValueArguments().last().getArgumentExpression() as JetNamedFunction
}
// todo move outside
ShortenReferences.DEFAULT.process(
listOf(newFunctionExpression.getReceiverTypeReference(), newFunctionExpression.getTypeReference()).filterNotNull()
)
}
}
private fun getFunctionLiteralArgument(): JetFunctionLiteralArgument? {
val argument = functionLiteralExpression.getParentOfType<JetFunctionLiteralArgument>(strict = false)
if (argument != null && argument.getFunctionLiteral() == functionLiteralExpression) {
return argument
}
return null
}
private fun removeExternalParenthesesOnParameterList(functionLiteral: JetFunctionLiteral, psiFactory: JetPsiFactory) {
val parameterList = functionLiteral.getValueParameterList()
if (parameterList != null && parameterList.isParenthesized()) {
val oldParameterList = parameterList.getText()
val newParameterList = oldParameterList.substring(1..oldParameterList.length() - 2)
parameterList.replace(psiFactory.createFunctionLiteralParameterList(newParameterList))
}
}
private fun JetElement.replaceWithReturn(psiFactory: JetPsiFactory) {
if (this is JetReturnExpression) {
return
}
else {
replace(psiFactory.createReturn(getText()))
}
}
private fun getLambdaLabelName(): String? {
val labeledExpression = functionLiteralExpression.getParentOfType<JetLabeledExpression>(strict = false)
if (labeledExpression != null && JetPsiUtil.deparenthesize(labeledExpression.getBaseExpression()) == functionLiteralExpression) {
return labeledExpression.getLabelName()
}
return null
}
private fun convertToFunctionExpression(
functionLiteralType: JetType
): JetNamedFunction {
val functionName = getLambdaLabelName()
val parameterList = functionLiteral.getValueParameterList()?.getText()
val receiverType = KotlinBuiltIns.getReceiverType(functionLiteralType)?.let { IdeDescriptorRenderers.SOURCE_CODE.renderType(it) }
val returnType = KotlinBuiltIns.getReturnTypeFromFunctionType(functionLiteralType).let {
if (KotlinBuiltIns.isUnit(it))
null
else
IdeDescriptorRenderers.SOURCE_CODE.renderType(it)
}
val functionDeclaration = "fun " +
(receiverType?.let { "$it." } ?: "") +
(functionName ?: "") +
(parameterList ?: "()") +
(returnType?.let { ": $it" } ?: "")
val functionWithEmptyBody = psiFactory.createFunction(functionDeclaration + " {}")
val blockExpression = functionLiteral.getBodyExpression()
if (blockExpression == null) return functionWithEmptyBody
val statements = blockExpression.getStatements()
if (statements.isEmpty()) return functionWithEmptyBody
if (statements.size() == 1) {
return psiFactory.createFunction(functionDeclaration + " = " + statements.first().getText())
}
// many statements
if (returnType != null) statements.last().replaceWithReturn(psiFactory)
return psiFactory.createFunction(functionDeclaration + "{ " + blockExpression.getText() + "}")
}
}
@@ -231,6 +231,7 @@ public class QuickFixRegistrar {
QuickFixes.factories.put(NO_VALUE_FOR_PARAMETER, ChangeFunctionSignatureFix.createFactory());
QuickFixes.factories.put(UNUSED_PARAMETER, ChangeFunctionSignatureFix.createFactoryForUnusedParameter());
QuickFixes.factories.put(EXPECTED_PARAMETERS_NUMBER_MISMATCH, ChangeFunctionSignatureFix.createFactoryForParametersNumberMismatch());
QuickFixes.factories.put(DEPRECATED_LAMBDA_SYNTAX, DeprecatedLambdaSyntaxFix.Factory);
QuickFixes.factories.put(EXPECTED_PARAMETER_TYPE_MISMATCH, ChangeTypeFix.createFactoryForExpectedParameterTypeMismatch());
QuickFixes.factories.put(EXPECTED_RETURN_TYPE_MISMATCH, ChangeTypeFix.createFactoryForExpectedReturnTypeMismatch());
@@ -0,0 +1,10 @@
// "Migrate lambda syntax" "true"
class A
fun foo(a: Int.(String) -> A) {}
val a = foo(fun Int.a(a: String): A {
A()
return A()
})
@@ -0,0 +1,10 @@
// "Migrate lambda syntax" "true"
class A
fun foo(a: Int.(String) -> A) {}
val a = foo(fun Int.a(a: String): A {
A()
return A()
})
@@ -0,0 +1,10 @@
// "Migrate lambda syntax" "true"
class A
fun foo(a: Int.(String) -> A) {}
val a = foo(fun Int.a(a: String): A {
A()
return A()
})
@@ -0,0 +1,7 @@
// "Migrate lambda syntax" "true"
class A
fun foo(a: Any) {}
val a = foo(fun Int.a(a: String): A = A())
@@ -0,0 +1,10 @@
// "Migrate lambda syntax" "true"
class A
fun foo(a: (Int).(String) -> Int) {
}
val a = foo (fun Int.(a: String): Int = 4)
@@ -0,0 +1,11 @@
// "Migrate lambda syntax" "true"
class A
fun foo(a: Any) {}
val a = foo @a {
val a =
fun (): Int = 1
}
@@ -0,0 +1,4 @@
// "Migrate lambda syntax" "true"
val a = { a: Int -> }
@@ -0,0 +1,5 @@
// "Migrate lambda syntax" "true"
val a =
fun Int.(a: Int): String = ""
@@ -0,0 +1,10 @@
// "Migrate lambda syntax" "true"
class A
fun foo(a: Int.(String) -> A) {}
val a = foo @a { <caret>Int.(a: String): A ->
A()
A()
}
@@ -0,0 +1,10 @@
// "Migrate lambda syntax" "true"
class A
fun foo(a: Int.(String) -> A) {}
val a = foo @a { <caret>(a: String): A ->
A()
A()
}
@@ -0,0 +1,10 @@
// "Migrate lambda syntax" "true"
class A
fun foo(a: Int.(String) -> A) {}
val a = foo @a { <caret>Int.(a: String) ->
A()
A()
}
@@ -0,0 +1,9 @@
// "Migrate lambda syntax" "true"
class A
fun foo(a: Any) {}
val a = foo @a { <caret>Int.(a: String): A ->
A()
}
@@ -0,0 +1,12 @@
// "Migrate lambda syntax" "true"
class A
fun foo(a: (Int).(String) -> Int) {
}
val a = foo ({
<caret>(Int).(a: String) -> 4
})
@@ -0,0 +1,10 @@
// "Migrate lambda syntax" "true"
class A
fun foo(a: Any) {}
val a = foo @a {
val a = { <caret>(): Int -> 1 }
}
@@ -0,0 +1,4 @@
// "Migrate lambda syntax" "true"
val a = { <caret>(a: Int) -> }
@@ -0,0 +1,4 @@
// "Migrate lambda syntax" "true"
val a = { <caret>Int.(a: Int): String -> "" }
@@ -856,6 +856,8 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
@TestMetadata("idea/testData/quickfix/migration")
@TestDataPath("$PROJECT_ROOT")
@InnerTestClasses({
})
@RunWith(JUnit3RunnerWithInners.class)
public static class Migration extends AbstractQuickFixMultiFileTest {
public void testAllFilesPresentInMigration() throws Exception {
@@ -867,6 +869,7 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/classObjectToDefaultMultiple.before.Main.kt");
doTestWithExtraFile(fileName);
}
}
@TestMetadata("idea/testData/quickfix/modifiers")
@@ -2887,6 +2887,9 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
@TestMetadata("idea/testData/quickfix/migration")
@TestDataPath("$PROJECT_ROOT")
@InnerTestClasses({
Migration.LambdaSyntax.class,
})
@RunWith(JUnit3RunnerWithInners.class)
public static class Migration extends AbstractQuickFixTest {
@TestMetadata("beforeAddOverrideToEqualsHashCodeToString.kt")
@@ -2904,6 +2907,63 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/beforeClassObjectToDefaultSingle.kt");
doTest(fileName);
}
@TestMetadata("idea/testData/quickfix/migration/lambdaSyntax")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class LambdaSyntax extends AbstractQuickFixTest {
public void testAllFilesPresentInLambdaSyntax() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/migration/lambdaSyntax"), Pattern.compile("^before(\\w+)\\.kt$"), true);
}
@TestMetadata("beforeLabelInLiteralArgument.kt")
public void testLabelInLiteralArgument() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/lambdaSyntax/beforeLabelInLiteralArgument.kt");
doTest(fileName);
}
@TestMetadata("beforeLabelInLiteralArgumentImplicitReceiverType.kt")
public void testLabelInLiteralArgumentImplicitReceiverType() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/lambdaSyntax/beforeLabelInLiteralArgumentImplicitReceiverType.kt");
doTest(fileName);
}
@TestMetadata("beforeLabelInLiteralArgumentImplicitReturnType.kt")
public void testLabelInLiteralArgumentImplicitReturnType() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/lambdaSyntax/beforeLabelInLiteralArgumentImplicitReturnType.kt");
doTest(fileName);
}
@TestMetadata("beforeLabelInLiteralArgumentOneStatement.kt")
public void testLabelInLiteralArgumentOneStatement() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/lambdaSyntax/beforeLabelInLiteralArgumentOneStatement.kt");
doTest(fileName);
}
@TestMetadata("beforeLambdaInFunctionArgument.kt")
public void testLambdaInFunctionArgument() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/lambdaSyntax/beforeLambdaInFunctionArgument.kt");
doTest(fileName);
}
@TestMetadata("beforeLambdaInsideLambda.kt")
public void testLambdaInsideLambda() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/lambdaSyntax/beforeLambdaInsideLambda.kt");
doTest(fileName);
}
@TestMetadata("beforeParanthesizedParameters.kt")
public void testParanthesizedParameters() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/lambdaSyntax/beforeParanthesizedParameters.kt");
doTest(fileName);
}
@TestMetadata("beforeReceiverAndReturnInExpression.kt")
public void testReceiverAndReturnInExpression() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/lambdaSyntax/beforeReceiverAndReturnInExpression.kt");
doTest(fileName);
}
}
}
@TestMetadata("idea/testData/quickfix/modifiers")