Create DeprecatedLambdaSyntaxFix for whole project

This commit is contained in:
Stanislav Erokhin
2015-03-25 21:52:02 +03:00
parent 3fd8c5e980
commit f40b503454
10 changed files with 258 additions and 64 deletions
@@ -192,6 +192,9 @@ migrate.class.object.to.companion.in.whole.project=Replace 'class' keyword with
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
migrate.lambda.syntax.in.whole.project=Migrate lambda syntax in whole project
migrate.lambda.syntax.in.whole.project.modal.title=Migrating lambda syntax in whole project
migrate.lambda.syntax.in.whole.project.family=Migrate lambda syntax in whole project
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
@@ -32,6 +32,10 @@ public fun Project.executeWriteCommand(name: String, command: () -> Unit) {
CommandProcessor.getInstance().executeCommand(this, { runWriteAction(command) }, name, null)
}
public fun Project.executeCommand(name: String, command: () -> Unit) {
CommandProcessor.getInstance().executeCommand(this, command, name, null)
}
public fun <T> Project.executeWriteCommand(name: String, command: () -> T): T {
var result: T = null as T
CommandProcessor.getInstance().executeCommand(this, { result = runWriteAction(command) }, name, null)
@@ -16,35 +16,20 @@
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.getFunctionLiteralArgumentName
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) {
@@ -52,7 +37,7 @@ public class DeprecatedLambdaSyntaxFix(element: JetFunctionLiteralExpression) :
override fun getFamilyName() = JetBundle.message("migrate.lambda.syntax.family")
override fun invoke(project: Project, editor: Editor, file: JetFile) {
LambdaWithDeprecatedSyntax(element, JetPsiFactory(project)).runFix()
DeprecatedSyntaxFix.createFix(element).runFix()
}
companion object Factory : JetSingleIntentionActionFactory() {
@@ -61,43 +46,125 @@ public class DeprecatedLambdaSyntaxFix(element: JetFunctionLiteralExpression) :
}
}
private class LambdaWithDeprecatedSyntax(val functionLiteralExpression: JetFunctionLiteralExpression, val psiFactory: JetPsiFactory, val level: Int = 0) {
val functionLiteral = functionLiteralExpression.getFunctionLiteral()
val hasNoReturnAndReceiverType = !functionLiteral.hasDeclaredReturnType() && functionLiteral.getReceiverTypeReference() == null
public class DeprecatedLambdaSyntaxInWholeProjectFix(element: JetFunctionLiteralExpression) :
JetWholeProjectModalAction<JetFunctionLiteralExpression, Collection<DeprecatedSyntaxFix>>(
element, JetBundle.message("migrate.lambda.syntax.in.whole.project.modal.title")) {
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
override fun getText() = JetBundle.message("migrate.lambda.syntax.in.whole.project")
override fun getFamilyName() = JetBundle.message("migrate.lambda.syntax.in.whole.project.family")
override fun collectDataForFile(project: Project, file: JetFile): Collection<DeprecatedSyntaxFix>? {
val lambdas = ArrayList<DeprecatedSyntaxFix>()
file.accept(LambdaCollectionVisitor(lambdas), 0)
return lambdas.sortBy { -it.level }
}
override fun applyChangesForFile(project: Project, file: JetFile, data: Collection<DeprecatedSyntaxFix>) {
data.forEach {
it.runFix()
}
}
private class LambdaCollectionVisitor(val lambdas: MutableCollection<DeprecatedSyntaxFix>) : JetTreeVisitor<Int>() {
override fun visitFunctionLiteralExpression(functionLiteralExpression: JetFunctionLiteralExpression, data: Int): Void? {
functionLiteralExpression.acceptChildren(this, data + 1)
if (JetPsiUtil.isDeprecatedLambdaSyntax(functionLiteralExpression)) {
lambdas.add(DeprecatedSyntaxFix.createFix(functionLiteralExpression, data))
}
return null
}
override fun visitJetFile(file: JetFile, data: Int?): Void? {
super.visitJetFile(file, data)
file.acceptChildren(this, data)
return null
}
}
companion object Factory : JetSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic)
= (diagnostic.getPsiElement() as? JetFunctionLiteralExpression)?.let { DeprecatedLambdaSyntaxInWholeProjectFix(it) }
}
}
private trait DeprecatedSyntaxFix {
val level: Int
// you must run it under write action
fun runFix() {
fun runFix()
internal companion object {
fun createFix(functionLiteralExpression: JetFunctionLiteralExpression, level: Int = 0): DeprecatedSyntaxFix {
val functionLiteral = functionLiteralExpression.getFunctionLiteral()
val hasNoReturnAndReceiverType = !functionLiteral.hasDeclaredReturnType() && functionLiteral.getReceiverTypeReference() == null
return if (hasNoReturnAndReceiverType) DeparenthesizeParameterList(functionLiteralExpression, level)
else LambdaToFunctionExpression(functionLiteralExpression, level)
}
}
}
private class DeparenthesizeParameterList(
val functionLiteralExpression: JetFunctionLiteralExpression,
override val level: Int = 0
): DeprecatedSyntaxFix {
override fun runFix() {
if (!JetPsiUtil.isDeprecatedLambdaSyntax(functionLiteralExpression)) return
if (hasNoReturnAndReceiverType) {
removeExternalParenthesesOnParameterList(functionLiteral, psiFactory)
val psiFactory = JetPsiFactory(functionLiteralExpression)
val functionLiteral = functionLiteralExpression.getFunctionLiteral()
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 class LambdaToFunctionExpression(
val functionLiteralExpression: JetFunctionLiteralExpression,
override val level: Int = 0
): DeprecatedSyntaxFix {
val functionLiteralArgumentName: String?
val receiverType: String?
val returnType: String?
init {
val bindingContext = functionLiteralExpression.analyze()
val functionLiteralType = bindingContext.get(BindingContext.EXPRESSION_TYPE, functionLiteralExpression)
assert(functionLiteralType != null && KotlinBuiltIns.isFunctionOrExtensionFunctionType(functionLiteralType)) {
"Broken function type for expression: ${functionLiteralExpression.getText()}, at: ${DiagnosticUtils.atLocation(functionLiteralExpression)}"
}
receiverType = KotlinBuiltIns.getReceiverType(functionLiteralType)?.let { IdeDescriptorRenderers.SOURCE_CODE.renderType(it) }
returnType = KotlinBuiltIns.getReturnTypeFromFunctionType(functionLiteralType).let {
if (KotlinBuiltIns.isUnit(it))
null
else
IdeDescriptorRenderers.SOURCE_CODE.renderType(it)
}
functionLiteralArgumentName = getFunctionLiteralArgument()?.getFunctionLiteralArgumentName(bindingContext)
}
override fun runFix() {
if (!JetPsiUtil.isDeprecatedLambdaSyntax(functionLiteralExpression)) return
val newFunctionExpression = createFunctionExpression()
val literalArgument = getFunctionLiteralArgument()
val replacedFunctionExpression = if (literalArgument == null) {
functionLiteralExpression.replace(newFunctionExpression)
}
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()
)
literalArgument.moveInsideParenthesesAndReplaceWith(newFunctionExpression, functionLiteralArgumentName).
getValueArguments().last().getArgumentExpression()
}
val functionExpression = JetPsiUtil.deparenthesize(replacedFunctionExpression as JetExpression) as JetNamedFunction
ShortenReferences.DEFAULT.process(
listOf(functionExpression.getReceiverTypeReference(), functionExpression.getTypeReference()).filterNotNull())
}
private fun getFunctionLiteralArgument(): JetFunctionLiteralArgument? {
@@ -109,15 +176,6 @@ private class LambdaWithDeprecatedSyntax(val functionLiteralExpression: JetFunct
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
@@ -135,18 +193,11 @@ private class LambdaWithDeprecatedSyntax(val functionLiteralExpression: JetFunct
return null
}
private fun convertToFunctionExpression(
functionLiteralType: JetType
): JetNamedFunction {
private fun createFunctionExpression(): JetNamedFunction {
val psiFactory = JetPsiFactory(functionLiteralExpression)
val functionLiteral = functionLiteralExpression.getFunctionLiteral()
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." } ?: "") +
@@ -0,0 +1,90 @@
/*
* 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.openapi.command.CommandProcessor
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.progress.ProcessCanceledException
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.PsiElement
import com.intellij.util.ui.UIUtil
import org.jetbrains.kotlin.idea.project.PluginJetFilesProvider
import org.jetbrains.kotlin.idea.util.application.executeCommand
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.psi.JetFile
import java.util.HashMap
public abstract class JetWholeProjectModalAction<T : PsiElement, D: Any>(element: T, val title: String) : JetIntentionAction<T>(element) {
override final fun startInWriteAction() = false
override final fun invoke(project: Project, editor: Editor, file: JetFile) =
ProgressManager.getInstance().run(
object : Task.Modal(project, title, true) {
override fun run(indicator: ProgressIndicator) {
val filesToData = HashMap<JetFile, D>()
val files = runReadAction { PluginJetFilesProvider.allFilesInProject(project) }
for ((i, currentFile) in files.withIndex()) {
indicator.setText("Checking file $i of ${files.size()}...")
indicator.setText2(currentFile.getVirtualFile().getPath())
indicator.setFraction((i + 1) / files.size().toDouble())
try {
val data = runReadAction { collectDataForFile(project, currentFile) }
if (data != null) filesToData[currentFile] = data
}
catch (e: ProcessCanceledException) {
return
}
catch (e: Throwable) {
LOG.error(e)
}
}
applyAll(project, filesToData)
}
})
private fun applyAll(project: Project, filesToData: Map<JetFile, D>) {
UIUtil.invokeLaterIfNeeded {
project.executeCommand(getText()) {
filesToData.forEach {
try {
runWriteAction { applyChangesForFile(project, it.getKey(), it.getValue()) }
}
catch (e: Throwable) {
LOG.error(e)
}
}
}
}
}
// this method will be started under read action
protected abstract fun collectDataForFile(project: Project, file: JetFile): D?
// this method will be started under write action
protected abstract fun applyChangesForFile(project: Project, file: JetFile, data: D)
private companion object {
val LOG = Logger.getInstance(javaClass<JetWholeProjectModalAction<*, *>>());
}
}
@@ -233,6 +233,7 @@ public class QuickFixRegistrar {
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(DEPRECATED_LAMBDA_SYNTAX, DeprecatedLambdaSyntaxInWholeProjectFix.Factory);
QuickFixes.factories.put(EXPECTED_PARAMETER_TYPE_MISMATCH, ChangeTypeFix.createFactoryForExpectedParameterTypeMismatch());
QuickFixes.factories.put(EXPECTED_RETURN_TYPE_MISMATCH, ChangeTypeFix.createFactoryForExpectedReturnTypeMismatch());
@@ -0,0 +1,8 @@
val h = { -> }
val l = @bar (
fun Int.bar() {
})
val s = (
fun (): Int = 5)()
@@ -0,0 +1,9 @@
// "Migrate lambda syntax in whole project" "true"
val a =
fun (): Int {
val b =
fun (): Int = 5
return b()
}
@@ -0,0 +1,8 @@
// "Migrate lambda syntax in whole project" "true"
val a = { <caret>(): Int ->
val b = { (): Int -> 5 }
b()
}
@@ -0,0 +1,5 @@
val h = { () -> }
val l = @bar { Int.() -> }
val s = {(): Int -> 5}()
@@ -857,6 +857,7 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
@TestMetadata("idea/testData/quickfix/migration")
@TestDataPath("$PROJECT_ROOT")
@InnerTestClasses({
Migration.LambdaSyntax.class,
})
@RunWith(JUnit3RunnerWithInners.class)
public static class Migration extends AbstractQuickFixMultiFileTest {
@@ -876,6 +877,20 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
doTestWithExtraFile(fileName);
}
@TestMetadata("idea/testData/quickfix/migration/lambdaSyntax")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class LambdaSyntax extends AbstractQuickFixMultiFileTest {
public void testAllFilesPresentInLambdaSyntax() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/migration/lambdaSyntax"), Pattern.compile("^(\\w+)\\.before\\.Main\\.kt$"), true);
}
@TestMetadata("lambdaSyntaxMultiple.before.Main.kt")
public void testLambdaSyntaxMultiple() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/lambdaSyntax/lambdaSyntaxMultiple.before.Main.kt");
doTestWithExtraFile(fileName);
}
}
}
@TestMetadata("idea/testData/quickfix/modifiers")