From 891e033d5ba3cfe73a51da81588a3e48f3c80e29 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Mon, 26 Oct 2015 23:23:09 +0300 Subject: [PATCH] KT-9664 Add quickfix to convert a piece of code to pretty look SAM-style #KT-9664 Fixed --- .../jetbrains/kotlin/psi/createByPattern.kt | 6 + .../kotlin/idea/util/CommentSaver.kt | 76 +++---- .../kotlin/idea/util/ShortenReferences.kt | 2 +- .../ObjectLiteralToLambda.html | 5 + .../after.kt.template | 1 + .../before.kt.template | 5 + .../description.html | 5 + idea/src/META-INF/plugin.xml | 12 ++ .../RedundantSamConstructorInspection.kt | 52 +++-- .../ObjectLiteralToLambdaIntention.kt | 200 ++++++++++++++++++ .../kotlin/idea/j2k/J2kPostProcessings.kt | 1 + .../inspectionData/expected.xml | 50 +++-- .../redundantSamConstructor.kt.after | 3 + .../redundantSamConstructor.kt | 3 + .../objectLiteralToLambda/.intention | 1 + .../objectLiteralToLambda/EmptyBody.kt | 10 + .../objectLiteralToLambda/EmptyBody.kt.after | 7 + .../objectLiteralToLambda/ExpressionBody.kt | 11 + .../ExpressionBody.kt.after | 9 + .../objectLiteralToLambda/HasData.kt | 14 ++ .../objectLiteralToLambda/MultipleBases.kt | 13 ++ .../MultipleParameters.kt | 11 + .../MultipleParameters.kt.after | 9 + .../NoSamAdapterNeeded.kt | 11 + .../NoSamAdapterNeeded.kt.after | 7 + .../objectLiteralToLambda/NotInRange1.kt | 11 + .../objectLiteralToLambda/NotInRange2.kt | 11 + .../objectLiteralToLambda/NotJavaSAM.kt | 14 ++ .../ParametersNotUsed.kt | 11 + .../ParametersNotUsed.kt.after | 9 + .../objectLiteralToLambda/QualifiedReturn.kt | 15 ++ .../QualifiedReturn.kt.after | 13 ++ .../objectLiteralToLambda/ReturnNoValue.kt | 13 ++ .../ReturnNoValue.kt.after | 11 + .../objectLiteralToLambda/ReturnNotLast.kt | 14 ++ .../ReturnNotLast.kt.after | 12 ++ .../objectLiteralToLambda/ReturnsInIf.kt | 24 +++ .../ReturnsInIf.kt.after | 22 ++ .../objectLiteralToLambda/ReturnsInWhen.kt | 23 ++ .../ReturnsInWhen.kt.after | 21 ++ .../objectLiteralToLambda/Simple.kt | 11 + .../objectLiteralToLambda/Simple.kt.after | 7 + .../objectLiteralToLambda/SingleReturn.kt | 13 ++ .../SingleReturn.kt.after | 9 + .../StatementAndReturn.kt | 14 ++ .../StatementAndReturn.kt.after | 12 ++ .../objectLiteralToLambda/TwoFunctions.kt | 14 ++ .../inspectionData/expected.xml | 136 ++++++++++++ .../inspectionData/inspections.test | 1 + .../JetInspectionTestGenerated.java | 6 + .../intentions/IntentionTestGenerated.java | 124 +++++++++++ .../newClassExpression/newAnonymousClass.kt | 6 +- .../postProcessing/AnonymousObject.java | 12 ++ .../postProcessing/AnonymousObject.kt | 7 + ...otlinConverterForWebDemoTestGenerated.java | 6 + ...otlinConverterSingleFileTestGenerated.java | 6 + 56 files changed, 1050 insertions(+), 82 deletions(-) create mode 100644 idea/resources/inspectionDescriptions/ObjectLiteralToLambda.html create mode 100644 idea/resources/intentionDescriptions/ObjectLiteralToLambdaIntention/after.kt.template create mode 100644 idea/resources/intentionDescriptions/ObjectLiteralToLambdaIntention/before.kt.template create mode 100644 idea/resources/intentionDescriptions/ObjectLiteralToLambdaIntention/description.html create mode 100644 idea/src/org/jetbrains/kotlin/idea/intentions/ObjectLiteralToLambdaIntention.kt create mode 100644 idea/testData/intentions/objectLiteralToLambda/.intention create mode 100644 idea/testData/intentions/objectLiteralToLambda/EmptyBody.kt create mode 100644 idea/testData/intentions/objectLiteralToLambda/EmptyBody.kt.after create mode 100644 idea/testData/intentions/objectLiteralToLambda/ExpressionBody.kt create mode 100644 idea/testData/intentions/objectLiteralToLambda/ExpressionBody.kt.after create mode 100644 idea/testData/intentions/objectLiteralToLambda/HasData.kt create mode 100644 idea/testData/intentions/objectLiteralToLambda/MultipleBases.kt create mode 100644 idea/testData/intentions/objectLiteralToLambda/MultipleParameters.kt create mode 100644 idea/testData/intentions/objectLiteralToLambda/MultipleParameters.kt.after create mode 100644 idea/testData/intentions/objectLiteralToLambda/NoSamAdapterNeeded.kt create mode 100644 idea/testData/intentions/objectLiteralToLambda/NoSamAdapterNeeded.kt.after create mode 100644 idea/testData/intentions/objectLiteralToLambda/NotInRange1.kt create mode 100644 idea/testData/intentions/objectLiteralToLambda/NotInRange2.kt create mode 100644 idea/testData/intentions/objectLiteralToLambda/NotJavaSAM.kt create mode 100644 idea/testData/intentions/objectLiteralToLambda/ParametersNotUsed.kt create mode 100644 idea/testData/intentions/objectLiteralToLambda/ParametersNotUsed.kt.after create mode 100644 idea/testData/intentions/objectLiteralToLambda/QualifiedReturn.kt create mode 100644 idea/testData/intentions/objectLiteralToLambda/QualifiedReturn.kt.after create mode 100644 idea/testData/intentions/objectLiteralToLambda/ReturnNoValue.kt create mode 100644 idea/testData/intentions/objectLiteralToLambda/ReturnNoValue.kt.after create mode 100644 idea/testData/intentions/objectLiteralToLambda/ReturnNotLast.kt create mode 100644 idea/testData/intentions/objectLiteralToLambda/ReturnNotLast.kt.after create mode 100644 idea/testData/intentions/objectLiteralToLambda/ReturnsInIf.kt create mode 100644 idea/testData/intentions/objectLiteralToLambda/ReturnsInIf.kt.after create mode 100644 idea/testData/intentions/objectLiteralToLambda/ReturnsInWhen.kt create mode 100644 idea/testData/intentions/objectLiteralToLambda/ReturnsInWhen.kt.after create mode 100644 idea/testData/intentions/objectLiteralToLambda/Simple.kt create mode 100644 idea/testData/intentions/objectLiteralToLambda/Simple.kt.after create mode 100644 idea/testData/intentions/objectLiteralToLambda/SingleReturn.kt create mode 100644 idea/testData/intentions/objectLiteralToLambda/SingleReturn.kt.after create mode 100644 idea/testData/intentions/objectLiteralToLambda/StatementAndReturn.kt create mode 100644 idea/testData/intentions/objectLiteralToLambda/StatementAndReturn.kt.after create mode 100644 idea/testData/intentions/objectLiteralToLambda/TwoFunctions.kt create mode 100644 idea/testData/intentions/objectLiteralToLambda/inspectionData/expected.xml create mode 100644 idea/testData/intentions/objectLiteralToLambda/inspectionData/inspections.test create mode 100644 j2k/testData/fileOrElement/postProcessing/AnonymousObject.java create mode 100644 j2k/testData/fileOrElement/postProcessing/AnonymousObject.kt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/createByPattern.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/createByPattern.kt index 741b179173c..d9e3cdce383 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/createByPattern.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/createByPattern.kt @@ -296,6 +296,12 @@ public class BuilderByPattern { return this } + public fun appendChildRange(range: PsiChildRange): BuilderByPattern { + patternBuilder.append("$" + arguments.size()) + arguments.add(range) + return this + } + public fun create(factory: (String, Array) -> TElement): TElement { return factory(patternBuilder.toString(), arguments.toArray()) } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/CommentSaver.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/CommentSaver.kt index 1ea44fe041d..9fef366fbd2 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/CommentSaver.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/CommentSaver.kt @@ -230,51 +230,55 @@ public class CommentSaver(originalElements: PsiChildRange, private val saveLineB putNewElementIntoMap(newPsiElement, treeElement) } - public fun restore(resultElement: PsiElement) { - restore(PsiChildRange.singleElement(resultElement)) + public fun restore(resultElement: PsiElement, forceAdjustIndent: Boolean = false) { + restore(PsiChildRange.singleElement(resultElement), forceAdjustIndent) } - public fun restore(resultElements: PsiChildRange) { + public fun restore(resultElements: PsiChildRange, forceAdjustIndent: Boolean = false) { assert(!resultElements.isEmpty) - if (commentsToRestore.isEmpty() && lineBreaksToRestore.isEmpty()) return + if (commentsToRestore.isNotEmpty() || lineBreaksToRestore.isNotEmpty()) { + // remove comments that present inside resultElements from commentsToRestore + resultElements.forEach { deleteCommentsInside(it) } - // remove comments that present inside resultElements from commentsToRestore - resultElements.forEach { deleteCommentsInside(it) } - if (commentsToRestore.isEmpty() && lineBreaksToRestore.isEmpty()) return - - toNewPsiElementMap = HashMap>() - for (element in resultElements) { - element.accept(object : PsiRecursiveElementVisitor() { - override fun visitElement(element: PsiElement) { - val treeElement = element.savedTreeElement - if (treeElement != null) { - putNewElementIntoMap(element, treeElement) - } - super.visitElement(element) + if (commentsToRestore.isNotEmpty() || lineBreaksToRestore.isNotEmpty()) { + toNewPsiElementMap = HashMap>() + for (element in resultElements) { + element.accept(object : PsiRecursiveElementVisitor() { + override fun visitElement(element: PsiElement) { + val treeElement = element.savedTreeElement + if (treeElement != null) { + putNewElementIntoMap(element, treeElement) + } + super.visitElement(element) + } + }) } - }) + + restoreComments(resultElements) + + restoreLineBreaks() + + // clear user data + resultElements.forEach { + it.accept(object : PsiRecursiveElementVisitor() { + override fun visitElement(element: PsiElement) { + element.savedTreeElement = null + super.visitElement(element) + } + }) + } + } } - restoreComments(resultElements) - - restoreLineBreaks() - - // clear user data - resultElements.forEach { - it.accept(object : PsiRecursiveElementVisitor() { - override fun visitElement(element: PsiElement) { - element.savedTreeElement = null - super.visitElement(element) - } - }) - } - - if (needAdjustIndentAfterRestore) { - val file = resultElements.first().getContainingFile() - val project = file.getProject() + if (needAdjustIndentAfterRestore || forceAdjustIndent) { + val file = resultElements.first().containingFile + val project = file.project val psiDocumentManager = PsiDocumentManager.getInstance(project) - psiDocumentManager.doPostponedOperationsAndUnblockDocument(psiDocumentManager.getDocument(file)!!) + val document = psiDocumentManager.getDocument(file) + if (document != null) { + psiDocumentManager.doPostponedOperationsAndUnblockDocument(document) + } CodeStyleManager.getInstance(project).adjustLineIndent(file, resultElements.textRange) } } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ShortenReferences.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ShortenReferences.kt index 9934b70e9bc..23498446041 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ShortenReferences.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ShortenReferences.kt @@ -75,7 +75,7 @@ public class ShortenReferences(val options: (KtElement) -> Options = { Options.D public fun process(file: KtFile, startOffset: Int, endOffset: Int) { val documentManager = PsiDocumentManager.getInstance(file.getProject()) - val document = documentManager.getDocument(file)!! + val document = file.viewProvider.document!! if (!documentManager.isCommitted(document)) { throw IllegalStateException("Document should be committed to shorten references in range") } diff --git a/idea/resources/inspectionDescriptions/ObjectLiteralToLambda.html b/idea/resources/inspectionDescriptions/ObjectLiteralToLambda.html new file mode 100644 index 00000000000..8265ebfa2f3 --- /dev/null +++ b/idea/resources/inspectionDescriptions/ObjectLiteralToLambda.html @@ -0,0 +1,5 @@ + + +This inspection reports an anonymous object literal implementing a Java interface with single abstract method that can be converted into call with lambda expression. + + diff --git a/idea/resources/intentionDescriptions/ObjectLiteralToLambdaIntention/after.kt.template b/idea/resources/intentionDescriptions/ObjectLiteralToLambdaIntention/after.kt.template new file mode 100644 index 00000000000..4ccb7623c3c --- /dev/null +++ b/idea/resources/intentionDescriptions/ObjectLiteralToLambdaIntention/after.kt.template @@ -0,0 +1 @@ +SwingUtilities { println("a") } diff --git a/idea/resources/intentionDescriptions/ObjectLiteralToLambdaIntention/before.kt.template b/idea/resources/intentionDescriptions/ObjectLiteralToLambdaIntention/before.kt.template new file mode 100644 index 00000000000..99cd76b78be --- /dev/null +++ b/idea/resources/intentionDescriptions/ObjectLiteralToLambdaIntention/before.kt.template @@ -0,0 +1,5 @@ +SwingUtilities.invokeLater(object : Runnable { + override fun run() { + println("a") + } +}) \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/ObjectLiteralToLambdaIntention/description.html b/idea/resources/intentionDescriptions/ObjectLiteralToLambdaIntention/description.html new file mode 100644 index 00000000000..3dfba07d1c5 --- /dev/null +++ b/idea/resources/intentionDescriptions/ObjectLiteralToLambdaIntention/description.html @@ -0,0 +1,5 @@ + + +Converts an anonymous object literal implementing a Java interface with single abstract method into call with lambda expression. + + diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 44a517aa6a0..730044435f4 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -1079,6 +1079,18 @@ Kotlin + + org.jetbrains.kotlin.idea.intentions.ObjectLiteralToLambdaIntention + Kotlin + + + + ): Boolean { + private fun canBeReplaced(parentCall: KtCallExpression, samConstructorArguments: List): Boolean { val context = parentCall.analyze(BodyResolveMode.PARTIAL) val calleeExpression = parentCall.calleeExpression ?: return false @@ -120,7 +120,7 @@ public class RedundantSamConstructorInspection : AbstractKotlinInspection() { val dataFlow = context.getDataFlowInfo(parentCall) val callResolver = parentCall.getResolutionFacade().frontendService() - val newCall = CallWithNewArguments(originalCall.call, callExpressions) + val newCall = CallWithConvertedArguments(originalCall.call, samConstructorArguments) val qualifiedExpression = parentCall.getQualifiedExpressionForSelectorOrThis() val expectedType = context[BindingContext.EXPECTED_EXPRESSION_TYPE, qualifiedExpression] ?: TypeUtils.NO_EXPECTED_TYPE @@ -133,16 +133,15 @@ public class RedundantSamConstructorInspection : AbstractKotlinInspection() { return samAdapterOriginalDescriptor.original == originalCall.resultingDescriptor.original } - private class CallWithNewArguments(original: Call, val callExpressions: List): DelegatingCall(original) { + private class CallWithConvertedArguments(original: Call, val argumentsToConvert: Collection): DelegatingCall(original) { private val newArguments: List init { val factory = KtPsiFactory(callElement) newArguments = original.valueArguments.map { argument -> - argument.getArgumentExpression() - ?.check { callExpressions.contains(it) } - ?.let { factory.createArgument(replaceSamConstructorCall(it.copy() as KtCallExpression), argument.getArgumentName()?.asName) } - ?: argument + if (argument !in argumentsToConvert) return@map argument + val newExpression = argument.toCallExpression()!!.samConstructorValueArgument()!!.getArgumentExpression()!! + factory.createArgument(newExpression, argument.getArgumentName()?.asName) } } @@ -150,6 +149,10 @@ public class RedundantSamConstructorInspection : AbstractKotlinInspection() { } public fun samConstructorCallsToBeConverted(functionCall: KtCallExpression): List { + return samConstructorArgumentsToBeConverted(functionCall).map { it.toCallExpression()!! } + } + + private fun samConstructorArgumentsToBeConverted(functionCall: KtCallExpression): List { if (functionCall.valueArguments.all { !canBeSamConstructorCall(it) }) { return emptyList() } @@ -158,12 +161,11 @@ public class RedundantSamConstructorInspection : AbstractKotlinInspection() { val functionResolvedCall = functionCall.getResolvedCall(bindingContext) ?: return emptyList() if (!functionResolvedCall.isReallySuccess()) return emptyList() - val samConstructorCalls = functionCall.valueArguments.map { - (it.getArgumentExpression() as? KtCallExpression) - ?.check { it.getResolvedCall(bindingContext)?.resultingDescriptor?.original is SamConstructorDescriptor } - }.filterNotNull() + val samConstructorCallArguments = functionCall.valueArguments.filter { + it.toCallExpression()?.getResolvedCall(bindingContext)?.resultingDescriptor?.original is SamConstructorDescriptor + } - if (samConstructorCalls.isEmpty()) return emptyList() + if (samConstructorCallArguments.isEmpty()) return emptyList() val originalFunctionDescriptor = functionResolvedCall.resultingDescriptor.original as? FunctionDescriptor ?: return emptyList() val containingClass = originalFunctionDescriptor.containingDeclaration as? ClassDescriptor ?: return emptyList() @@ -171,8 +173,8 @@ public class RedundantSamConstructorInspection : AbstractKotlinInspection() { // SAM adapters for static functions for (staticFunWithSameName in containingClass.staticScope.getFunctions(functionResolvedCall.resultingDescriptor.name, NoLookupLocation.FROM_IDE)) { if (staticFunWithSameName is SamAdapterDescriptor<*>) { - if (isSamAdapterSuitableForCall(staticFunWithSameName, originalFunctionDescriptor, samConstructorCalls.size())) { - return samConstructorCalls.check { canBeReplaced(functionCall, it) } ?: emptyList() + if (isSamAdapterSuitableForCall(staticFunWithSameName, originalFunctionDescriptor, samConstructorCallArguments.size())) { + return samConstructorCallArguments.check { canBeReplaced(functionCall, it) } ?: emptyList() } } } @@ -187,25 +189,29 @@ public class RedundantSamConstructorInspection : AbstractKotlinInspection() { } for (syntheticExtension in syntheticExtensions) { val samAdapter = syntheticExtension as? SamAdapterExtensionFunctionDescriptor ?: continue - if (isSamAdapterSuitableForCall(samAdapter, originalFunctionDescriptor, samConstructorCalls.size())) { - return samConstructorCalls.check { canBeReplaced(functionCall, it) } ?: emptyList() + if (isSamAdapterSuitableForCall(samAdapter, originalFunctionDescriptor, samConstructorCallArguments.size())) { + return samConstructorCallArguments.check { canBeReplaced(functionCall, it) } ?: emptyList() } } return emptyList() } - private fun canBeSamConstructorCall(argument: KtValueArgument): Boolean { - val argumentExpression = argument.getArgumentExpression() - if (argumentExpression !is KtCallExpression) return false - - return argumentExpression.samConstructorValueArgument() != null - } + private fun canBeSamConstructorCall(argument: KtValueArgument) + = argument.toCallExpression()?.samConstructorValueArgument() != null private fun KtCallExpression.samConstructorValueArgument(): KtValueArgument? { return valueArguments.singleOrNull()?.check { it.getArgumentExpression() is KtFunctionLiteralExpression } } + private fun ValueArgument.toCallExpression(): KtCallExpression? { + val argumentExpression = getArgumentExpression() + return (if (argumentExpression is KtDotQualifiedExpression) + argumentExpression.selectorExpression + else + argumentExpression) as? KtCallExpression + } + private fun isSamAdapterSuitableForCall( samAdapter: FunctionDescriptor, originalFunction: FunctionDescriptor, diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ObjectLiteralToLambdaIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ObjectLiteralToLambdaIntention.kt new file mode 100644 index 00000000000..4ff402d29f1 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ObjectLiteralToLambdaIntention.kt @@ -0,0 +1,200 @@ +/* + * 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.intentions + +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.util.Key +import com.intellij.openapi.util.TextRange +import com.intellij.psi.search.LocalSearchScope +import com.intellij.psi.search.searches.ReferencesSearch +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor +import org.jetbrains.kotlin.idea.core.replaced +import org.jetbrains.kotlin.idea.inspections.IntentionBasedInspection +import org.jetbrains.kotlin.idea.inspections.RedundantSamConstructorInspection +import org.jetbrains.kotlin.idea.util.CommentSaver +import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers +import org.jetbrains.kotlin.idea.util.ShortenReferences +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.load.java.sam.SingleAbstractMethodUtils +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.* +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.bindingContextUtil.getTargetFunction +import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.types.KotlinType + +class ObjectLiteralToLambdaInspection : IntentionBasedInspection(ObjectLiteralToLambdaIntention()) + +class ObjectLiteralToLambdaIntention : JetSelfTargetingRangeIntention( + KtObjectLiteralExpression::class.java, + "Convert to lambda", + "Convert object literal to lambda" +) { + override fun applicabilityRange(element: KtObjectLiteralExpression): TextRange? { + val (baseTypeRef, baseType, singleFunction) = extractData(element) ?: return null + + if (!SingleAbstractMethodUtils.isSamType(baseType)) return null + + val functionDescriptor = singleFunction.resolveToDescriptor() as? FunctionDescriptor ?: return null + val overridden = functionDescriptor.overriddenDescriptors.singleOrNull() ?: return null + if (overridden.modality != Modality.ABSTRACT) return null + + if (!singleFunction.hasBody()) return null + if (singleFunction.valueParameters.any { it.name == null }) return null + + return TextRange(element.objectDeclaration.getObjectKeyword().startOffset, baseTypeRef.endOffset) + } + + override fun applyTo(element: KtObjectLiteralExpression, editor: Editor) { + applyTo(element) + } + + fun applyTo(element: KtObjectLiteralExpression) { + val commentSaver = CommentSaver(element) + + val (@Suppress("UNUSED_VARIABLE") baseTypeRef, baseType, singleFunction) = extractData(element)!! + + val RETURN_KEY = Key("RETURN_KEY") + + val body = singleFunction.bodyExpression!! + body.forEachDescendantOfType { + if (it.getTargetFunction(it.analyze(BodyResolveMode.PARTIAL)) == singleFunction) { + it.putCopyableUserData(RETURN_KEY, Unit) + } + } + + val factory = KtPsiFactory(element) + val newExpression = factory.buildExpression { + appendFixedText(IdeDescriptorRenderers.SOURCE_CODE.renderType(baseType)) + + appendFixedText("{") + + val parameters = singleFunction.valueParameters + + val needParameters = parameters.any { parameter -> ReferencesSearch.search(parameter, LocalSearchScope(body)).any() } + if (needParameters) { + parameters.forEachIndexed { index, parameter -> + if (index > 0) { + appendFixedText(",") + } + appendName(parameter.nameAsSafeName) + } + + appendFixedText("->") + } + + if (singleFunction.hasBlockBody()) { + appendChildRange((body as KtBlockExpression).contentRange()) + } + else { + appendExpression(body) + } + + appendFixedText("}") + } + + body.forEachDescendantOfType { it.putCopyableUserData(RETURN_KEY, null) } + + val replaced = element.replaced(newExpression) + commentSaver.restore(replaced, forceAdjustIndent = true/* by some reason lambda body is sometimes not properly indented */) + + val callee = replaced.getCalleeExpressionIfAny()!! as KtNameReferenceExpression + val callExpression = callee.parent as KtCallExpression + val functionLiteral = callExpression.functionLiteralArguments.single().getFunctionLiteral() + + val lambdaBody = functionLiteral.bodyExpression!! + + val returnToReplace = functionLiteral.collectDescendantsOfType() { it.getCopyableUserData(RETURN_KEY) != null } + + val returnLabel = callee.getReferencedNameAsName() + for (returnExpression in returnToReplace) { + val value = returnExpression.returnedExpression + val replaceWith = if (value != null && returnExpression.isValueOfBlock(lambdaBody)) { + value + } + else if (value != null) { + factory.createExpressionByPattern("return@$0 $1", returnLabel, value) + } + else { + factory.createExpressionByPattern("return@$0", returnLabel) + } + + returnExpression.replace(replaceWith) + + } + + val parentCall = ((replaced.parent as? KtValueArgument) + ?.parent as? KtValueArgumentList) + ?.parent as? KtCallExpression + if (parentCall != null && RedundantSamConstructorInspection.samConstructorCallsToBeConverted(parentCall).singleOrNull() == callExpression) { + RedundantSamConstructorInspection.replaceSamConstructorCall(callExpression) + } + else { + ShortenReferences.DEFAULT.process(replaced.getContainingJetFile(), replaced.startOffset, callee.endOffset) + } + } + + private fun KtExpression.isValueOfBlock(inBlock: KtBlockExpression): Boolean { + val parent = parent + when (parent) { + inBlock -> { + return this == inBlock.statements.last() + } + + is KtBlockExpression -> { + return isValueOfBlock(parent) && parent.isValueOfBlock(inBlock) + } + + is KtContainerNode -> { + val owner = parent.parent + if (owner is KtIfExpression) { + return (this == owner.then || this == owner.`else`) && owner.isValueOfBlock(inBlock) + } + } + + is KtWhenEntry -> { + return this == parent.expression && (parent.parent as KtWhenExpression).isValueOfBlock(inBlock) + } + } + + return false + } + + private data class Data( + val baseTypeRef: KtTypeReference, + val baseType: KotlinType, + val singleFunction: KtNamedFunction + ) + + private fun extractData(element: KtObjectLiteralExpression): Data? { + val objectDeclaration = element.objectDeclaration + + val singleFunction = objectDeclaration.declarations.singleOrNull() as? KtNamedFunction ?: return null + if (!singleFunction.hasModifier(KtTokens.OVERRIDE_KEYWORD)) return null + + val delegationSpecifier = objectDeclaration.getDelegationSpecifiers().singleOrNull() ?: return null + val typeRef = delegationSpecifier.typeReference ?: return null + val bindingContext = typeRef.analyze(BodyResolveMode.PARTIAL) + val baseType = bindingContext[BindingContext.TYPE, typeRef] ?: return null + + return Data(typeRef, baseType, singleFunction) + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/j2k/J2kPostProcessings.kt b/idea/src/org/jetbrains/kotlin/idea/j2k/J2kPostProcessings.kt index 1d8131fa214..82ea0f6477a 100644 --- a/idea/src/org/jetbrains/kotlin/idea/j2k/J2kPostProcessings.kt +++ b/idea/src/org/jetbrains/kotlin/idea/j2k/J2kPostProcessings.kt @@ -62,6 +62,7 @@ object J2KPostProcessingRegistrar { registerIntentionBasedProcessing(SimplifyNegatedBinaryExpressionIntention()) { applyTo(it) } registerIntentionBasedProcessing(ReplaceGetOrSetIntention(), additionalChecker = ReplaceGetOrSetInspection.additionalChecker) { applyTo(it) } registerIntentionBasedProcessing(AddOperatorModifierIntention()) { applyTo(it) } + registerIntentionBasedProcessing(ObjectLiteralToLambdaIntention()) { applyTo(it) } registerDiagnosticBasedProcessing(Errors.USELESS_CAST) { element, diagnostic -> val expression = RemoveRightPartOfBinaryExpressionFix(element, "").invoke() diff --git a/idea/testData/inspections/redundantSamConstructor/inspectionData/expected.xml b/idea/testData/inspections/redundantSamConstructor/inspectionData/expected.xml index addc212bdfe..ba983de5c4b 100644 --- a/idea/testData/inspections/redundantSamConstructor/inspectionData/expected.xml +++ b/idea/testData/inspections/redundantSamConstructor/inspectionData/expected.xml @@ -3,7 +3,7 @@ redundantSamConstructor.kt 9 light_idea_test_case - + Redundant SAM-constructor Redundant SAM-constructor @@ -11,7 +11,7 @@ redundantSamConstructor.kt 11 light_idea_test_case - + Redundant SAM-constructors Redundant SAM-constructor @@ -19,7 +19,7 @@ redundantSamConstructor.kt 22 light_idea_test_case - + Redundant SAM-constructor Redundant SAM-constructor @@ -27,7 +27,7 @@ redundantSamConstructor.kt 25 light_idea_test_case - + Redundant SAM-constructor Redundant SAM-constructor @@ -35,7 +35,7 @@ redundantSamConstructor.kt 27 light_idea_test_case - + Redundant SAM-constructor Redundant SAM-constructor @@ -43,7 +43,7 @@ redundantSamConstructor.kt 38 light_idea_test_case - + Redundant SAM-constructor Redundant SAM-constructor @@ -51,7 +51,7 @@ redundantSamConstructorWithGenericsInReturnType.kt 9 light_idea_test_case - + Redundant SAM-constructor Redundant SAM-constructor @@ -59,7 +59,7 @@ redundantSamConstructorWithGenericsInReturnType.kt 11 light_idea_test_case - + Redundant SAM-constructors Redundant SAM-constructor @@ -67,7 +67,7 @@ redundantSamConstructorWithGenericsInReturnType.kt 15 light_idea_test_case - + Redundant SAM-constructor Redundant SAM-constructor @@ -75,7 +75,7 @@ redundantSamConstructorWithGenericsInReturnType.kt 18 light_idea_test_case - + Redundant SAM-constructor Redundant SAM-constructor @@ -83,7 +83,7 @@ redundantSamConstructorWithGenericsInReturnType.kt 20 light_idea_test_case - + Redundant SAM-constructor Redundant SAM-constructor @@ -91,7 +91,7 @@ redundantSamConstructorWithGenericsInReturnType.kt 24 light_idea_test_case - + Redundant SAM-constructor Redundant SAM-constructor @@ -99,7 +99,7 @@ otherGenericsInParams.kt 6 light_idea_test_case - + Redundant SAM-constructor Redundant SAM-constructor @@ -107,7 +107,7 @@ otherGenericsInParams.kt 8 light_idea_test_case - + Redundant SAM-constructor Redundant SAM-constructor @@ -115,7 +115,7 @@ otherGenericsInParams.kt 11 light_idea_test_case - + Redundant SAM-constructor Redundant SAM-constructor @@ -123,7 +123,7 @@ expectedType.kt 7 light_idea_test_case - + Redundant SAM constructor Redundant SAM-constructor @@ -131,7 +131,23 @@ expectedType.kt 8 light_idea_test_case - + + Redundant SAM constructor + Redundant SAM-constructor + + + redundantSamConstructor.kt + 41 + light_idea_test_case + + Redundant SAM constructor + Redundant SAM-constructor + + + redundantSamConstructor.kt + 42 + light_idea_test_case + Redundant SAM constructor Redundant SAM-constructor diff --git a/idea/testData/inspections/redundantSamConstructor/inspectionData/redundantSamConstructor.kt.after b/idea/testData/inspections/redundantSamConstructor/inspectionData/redundantSamConstructor.kt.after index b01cf1d2b93..027fdb5833e 100644 --- a/idea/testData/inspections/redundantSamConstructor/inspectionData/redundantSamConstructor.kt.after +++ b/idea/testData/inspections/redundantSamConstructor/inspectionData/redundantSamConstructor.kt.after @@ -37,4 +37,7 @@ fun test() { klass.memberFunWithOtherParam(1, { }) klass.memberFunWithOtherParam(1, runnable) + + MyJavaClass.staticFunWithOtherParam(1, { }) + klass.memberFun1({ }) } \ No newline at end of file diff --git a/idea/testData/inspections/redundantSamConstructor/redundantSamConstructor.kt b/idea/testData/inspections/redundantSamConstructor/redundantSamConstructor.kt index e5be4404942..76021c22302 100644 --- a/idea/testData/inspections/redundantSamConstructor/redundantSamConstructor.kt +++ b/idea/testData/inspections/redundantSamConstructor/redundantSamConstructor.kt @@ -37,4 +37,7 @@ fun test() { klass.memberFunWithOtherParam(1, Runnable { }) klass.memberFunWithOtherParam(1, runnable) + + MyJavaClass.staticFunWithOtherParam(1, java.lang.Runnable { }) + klass.memberFun1(java.lang.Runnable { }) } \ No newline at end of file diff --git a/idea/testData/intentions/objectLiteralToLambda/.intention b/idea/testData/intentions/objectLiteralToLambda/.intention new file mode 100644 index 00000000000..12d5ce0f4d0 --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/.intention @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.intentions.ObjectLiteralToLambdaIntention diff --git a/idea/testData/intentions/objectLiteralToLambda/EmptyBody.kt b/idea/testData/intentions/objectLiteralToLambda/EmptyBody.kt new file mode 100644 index 00000000000..04ec6dfa08c --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/EmptyBody.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME + +fun foo(runnable: Runnable) {} + +fun bar() { + foo(object : Runnable { + override fun run() { + } + }) +} \ No newline at end of file diff --git a/idea/testData/intentions/objectLiteralToLambda/EmptyBody.kt.after b/idea/testData/intentions/objectLiteralToLambda/EmptyBody.kt.after new file mode 100644 index 00000000000..54f2f7824cb --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/EmptyBody.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +fun foo(runnable: Runnable) {} + +fun bar() { + foo(Runnable { }) +} \ No newline at end of file diff --git a/idea/testData/intentions/objectLiteralToLambda/ExpressionBody.kt b/idea/testData/intentions/objectLiteralToLambda/ExpressionBody.kt new file mode 100644 index 00000000000..a6f54eaad96 --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/ExpressionBody.kt @@ -0,0 +1,11 @@ +// WITH_RUNTIME +import java.io.File +import java.io.FileFilter + +fun foo(filter: FileFilter) {} + +fun bar() { + foo(object: FileFilter { + override fun accept(file: File) = file.name.startsWith("a") + }) +} diff --git a/idea/testData/intentions/objectLiteralToLambda/ExpressionBody.kt.after b/idea/testData/intentions/objectLiteralToLambda/ExpressionBody.kt.after new file mode 100644 index 00000000000..300c792cf39 --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/ExpressionBody.kt.after @@ -0,0 +1,9 @@ +// WITH_RUNTIME +import java.io.File +import java.io.FileFilter + +fun foo(filter: FileFilter) {} + +fun bar() { + foo(FileFilter { file -> file.name.startsWith("a") }) +} diff --git a/idea/testData/intentions/objectLiteralToLambda/HasData.kt b/idea/testData/intentions/objectLiteralToLambda/HasData.kt new file mode 100644 index 00000000000..67cd0cfeb33 --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/HasData.kt @@ -0,0 +1,14 @@ +// IS_APPLICABLE: false +// WITH_RUNTIME + +fun foo(runnable: Runnable) {} + +fun bar() { + foo(object : Runnable { + val v = "a".hashCode() + + override fun run() { + print(v) + } + }) +} \ No newline at end of file diff --git a/idea/testData/intentions/objectLiteralToLambda/MultipleBases.kt b/idea/testData/intentions/objectLiteralToLambda/MultipleBases.kt new file mode 100644 index 00000000000..297adeef966 --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/MultipleBases.kt @@ -0,0 +1,13 @@ +// IS_APPLICABLE: false +// WITH_RUNTIME + +interface I + +fun foo(runnable: Runnable) {} + +fun bar() { + foo(object : Runnable, I { + override fun run() { + } + }) +} \ No newline at end of file diff --git a/idea/testData/intentions/objectLiteralToLambda/MultipleParameters.kt b/idea/testData/intentions/objectLiteralToLambda/MultipleParameters.kt new file mode 100644 index 00000000000..1958dae445c --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/MultipleParameters.kt @@ -0,0 +1,11 @@ +// WITH_RUNTIME +import java.io.File +import java.io.FilenameFilter + +fun foo(filter: FilenameFilter) {} + +fun bar() { + foo(object: FilenameFilter { + override fun accept(file: File, name: String) = name == "x" + }) +} diff --git a/idea/testData/intentions/objectLiteralToLambda/MultipleParameters.kt.after b/idea/testData/intentions/objectLiteralToLambda/MultipleParameters.kt.after new file mode 100644 index 00000000000..30d2ad498e3 --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/MultipleParameters.kt.after @@ -0,0 +1,9 @@ +// WITH_RUNTIME +import java.io.File +import java.io.FilenameFilter + +fun foo(filter: FilenameFilter) {} + +fun bar() { + foo(FilenameFilter { file, name -> name == "x" }) +} diff --git a/idea/testData/intentions/objectLiteralToLambda/NoSamAdapterNeeded.kt b/idea/testData/intentions/objectLiteralToLambda/NoSamAdapterNeeded.kt new file mode 100644 index 00000000000..aa7659abd85 --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/NoSamAdapterNeeded.kt @@ -0,0 +1,11 @@ +// WITH_RUNTIME + +import javax.swing.SwingUtilities + +fun bar() { + SwingUtilities.invokeLater(object: Runnable { + override fun run() { + throw UnsupportedOperationException() + } + }) +} \ No newline at end of file diff --git a/idea/testData/intentions/objectLiteralToLambda/NoSamAdapterNeeded.kt.after b/idea/testData/intentions/objectLiteralToLambda/NoSamAdapterNeeded.kt.after new file mode 100644 index 00000000000..74622132ada --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/NoSamAdapterNeeded.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +import javax.swing.SwingUtilities + +fun bar() { + SwingUtilities.invokeLater({ throw UnsupportedOperationException() }) +} \ No newline at end of file diff --git a/idea/testData/intentions/objectLiteralToLambda/NotInRange1.kt b/idea/testData/intentions/objectLiteralToLambda/NotInRange1.kt new file mode 100644 index 00000000000..1986e95554e --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/NotInRange1.kt @@ -0,0 +1,11 @@ +// IS_APPLICABLE: false +// WITH_RUNTIME + +fun foo(runnable: Runnable) {} + +fun bar() { + foo(object : Runnable { + override fun run() { + } + }) +} \ No newline at end of file diff --git a/idea/testData/intentions/objectLiteralToLambda/NotInRange2.kt b/idea/testData/intentions/objectLiteralToLambda/NotInRange2.kt new file mode 100644 index 00000000000..8f439ea9dbc --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/NotInRange2.kt @@ -0,0 +1,11 @@ +// IS_APPLICABLE: false +// WITH_RUNTIME + +fun foo(runnable: Runnable) {} + +fun bar() { + foo(object : Runnable { + override fun run() { + } + }) +} \ No newline at end of file diff --git a/idea/testData/intentions/objectLiteralToLambda/NotJavaSAM.kt b/idea/testData/intentions/objectLiteralToLambda/NotJavaSAM.kt new file mode 100644 index 00000000000..0911aa7ea63 --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/NotJavaSAM.kt @@ -0,0 +1,14 @@ +// IS_APPLICABLE: false + +interface MyRunnable { + fun run() +} + +fun foo(runnable: MyRunnable) {} + +fun bar() { + foo(object : MyRunnable { + override fun run() { + } + }) +} \ No newline at end of file diff --git a/idea/testData/intentions/objectLiteralToLambda/ParametersNotUsed.kt b/idea/testData/intentions/objectLiteralToLambda/ParametersNotUsed.kt new file mode 100644 index 00000000000..e26359cc3b2 --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/ParametersNotUsed.kt @@ -0,0 +1,11 @@ +// WITH_RUNTIME +import java.io.File +import java.io.FilenameFilter + +fun foo(filter: FilenameFilter) {} + +fun bar() { + foo(object: FilenameFilter { + override fun accept(file: File, name: String) = true + }) +} diff --git a/idea/testData/intentions/objectLiteralToLambda/ParametersNotUsed.kt.after b/idea/testData/intentions/objectLiteralToLambda/ParametersNotUsed.kt.after new file mode 100644 index 00000000000..8d8f5c2e8e4 --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/ParametersNotUsed.kt.after @@ -0,0 +1,9 @@ +// WITH_RUNTIME +import java.io.File +import java.io.FilenameFilter + +fun foo(filter: FilenameFilter) {} + +fun bar() { + foo(FilenameFilter { true }) +} diff --git a/idea/testData/intentions/objectLiteralToLambda/QualifiedReturn.kt b/idea/testData/intentions/objectLiteralToLambda/QualifiedReturn.kt new file mode 100644 index 00000000000..c8ecd6417b9 --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/QualifiedReturn.kt @@ -0,0 +1,15 @@ +// WITH_RUNTIME + +fun foo(runnable: Runnable) {} + +fun bar(list: List) { + foo(object : Runnable { + override fun run() { + list.filter(fun (element: String): Boolean { + if (element == "a") return false + if (element == "b") return@run + return true + }) + } + }) +} diff --git a/idea/testData/intentions/objectLiteralToLambda/QualifiedReturn.kt.after b/idea/testData/intentions/objectLiteralToLambda/QualifiedReturn.kt.after new file mode 100644 index 00000000000..6e1da0c3d8a --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/QualifiedReturn.kt.after @@ -0,0 +1,13 @@ +// WITH_RUNTIME + +fun foo(runnable: Runnable) {} + +fun bar(list: List) { + foo(Runnable { + list.filter(fun (element: String): Boolean { + if (element == "a") return false + if (element == "b") return@Runnable + return true + }) + }) +} diff --git a/idea/testData/intentions/objectLiteralToLambda/ReturnNoValue.kt b/idea/testData/intentions/objectLiteralToLambda/ReturnNoValue.kt new file mode 100644 index 00000000000..80cfb58a70f --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/ReturnNoValue.kt @@ -0,0 +1,13 @@ +// WITH_RUNTIME + +fun foo(runnable: Runnable) {} + +fun bar(p: Int) { + foo(object : Runnable { + override fun run() { + if (p < 0) return + println("a") + println("b") + } + }) +} \ No newline at end of file diff --git a/idea/testData/intentions/objectLiteralToLambda/ReturnNoValue.kt.after b/idea/testData/intentions/objectLiteralToLambda/ReturnNoValue.kt.after new file mode 100644 index 00000000000..8fca6b5e8fa --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/ReturnNoValue.kt.after @@ -0,0 +1,11 @@ +// WITH_RUNTIME + +fun foo(runnable: Runnable) {} + +fun bar(p: Int) { + foo(Runnable { + if (p < 0) return@Runnable + println("a") + println("b") + }) +} \ No newline at end of file diff --git a/idea/testData/intentions/objectLiteralToLambda/ReturnNotLast.kt b/idea/testData/intentions/objectLiteralToLambda/ReturnNotLast.kt new file mode 100644 index 00000000000..bb141018206 --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/ReturnNotLast.kt @@ -0,0 +1,14 @@ +// WITH_RUNTIME +import java.io.File +import java.io.FilenameFilter + +fun foo(filter: FilenameFilter) {} + +fun bar() { + foo(object: FilenameFilter { + override fun accept(file: File, name: String): Boolean { + if (file.isDirectory) return true + return name == "x" + } + }) +} diff --git a/idea/testData/intentions/objectLiteralToLambda/ReturnNotLast.kt.after b/idea/testData/intentions/objectLiteralToLambda/ReturnNotLast.kt.after new file mode 100644 index 00000000000..cdf08d56a55 --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/ReturnNotLast.kt.after @@ -0,0 +1,12 @@ +// WITH_RUNTIME +import java.io.File +import java.io.FilenameFilter + +fun foo(filter: FilenameFilter) {} + +fun bar() { + foo(FilenameFilter { file, name -> + if (file.isDirectory) return@FilenameFilter true + name == "x" + }) +} diff --git a/idea/testData/intentions/objectLiteralToLambda/ReturnsInIf.kt b/idea/testData/intentions/objectLiteralToLambda/ReturnsInIf.kt new file mode 100644 index 00000000000..077990016d1 --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/ReturnsInIf.kt @@ -0,0 +1,24 @@ +// WITH_RUNTIME +import java.io.File +import java.io.FileFilter + +fun foo(filter: FileFilter) {} + +fun bar() { + foo(object: FileFilter { + override fun accept(file: File): Boolean { + val name = file.name + if (name.startsWith("a")) { + return false + } + else { + if (name.endsWith("b")) + return true + else { + val l = name.length + return l > 10 + } + } + } + }) +} diff --git a/idea/testData/intentions/objectLiteralToLambda/ReturnsInIf.kt.after b/idea/testData/intentions/objectLiteralToLambda/ReturnsInIf.kt.after new file mode 100644 index 00000000000..d7670e97b87 --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/ReturnsInIf.kt.after @@ -0,0 +1,22 @@ +// WITH_RUNTIME +import java.io.File +import java.io.FileFilter + +fun foo(filter: FileFilter) {} + +fun bar() { + foo(FileFilter { file -> + val name = file.name + if (name.startsWith("a")) { + false + } + else { + if (name.endsWith("b")) + true + else { + val l = name.length + l > 10 + } + } + }) +} diff --git a/idea/testData/intentions/objectLiteralToLambda/ReturnsInWhen.kt b/idea/testData/intentions/objectLiteralToLambda/ReturnsInWhen.kt new file mode 100644 index 00000000000..dafabd040fd --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/ReturnsInWhen.kt @@ -0,0 +1,23 @@ +// WITH_RUNTIME +import java.io.File +import java.io.FileFilter + +fun foo(filter: FileFilter) {} + +fun bar() { + foo(object: FileFilter { + override fun accept(file: File): Boolean { + val name = file.name + when (name) { + "foo" -> return true + + "bar" -> return false + + else -> { + if (name.startsWith("a")) return true + return false + } + } + } + }) +} diff --git a/idea/testData/intentions/objectLiteralToLambda/ReturnsInWhen.kt.after b/idea/testData/intentions/objectLiteralToLambda/ReturnsInWhen.kt.after new file mode 100644 index 00000000000..6557197a43e --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/ReturnsInWhen.kt.after @@ -0,0 +1,21 @@ +// WITH_RUNTIME +import java.io.File +import java.io.FileFilter + +fun foo(filter: FileFilter) {} + +fun bar() { + foo(FileFilter { file -> + val name = file.name + when (name) { + "foo" -> true + + "bar" -> false + + else -> { + if (name.startsWith("a")) return@FileFilter true + false + } + } + }) +} diff --git a/idea/testData/intentions/objectLiteralToLambda/Simple.kt b/idea/testData/intentions/objectLiteralToLambda/Simple.kt new file mode 100644 index 00000000000..04029be63bb --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/Simple.kt @@ -0,0 +1,11 @@ +// WITH_RUNTIME + +fun foo(runnable: Runnable) {} + +fun bar() { + foo(object : Runnable { + override fun run() { + throw UnsupportedOperationException() + } + }) +} \ No newline at end of file diff --git a/idea/testData/intentions/objectLiteralToLambda/Simple.kt.after b/idea/testData/intentions/objectLiteralToLambda/Simple.kt.after new file mode 100644 index 00000000000..f0e4768b4d7 --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/Simple.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +fun foo(runnable: Runnable) {} + +fun bar() { + foo(Runnable { throw UnsupportedOperationException() }) +} \ No newline at end of file diff --git a/idea/testData/intentions/objectLiteralToLambda/SingleReturn.kt b/idea/testData/intentions/objectLiteralToLambda/SingleReturn.kt new file mode 100644 index 00000000000..b4bc270dd8d --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/SingleReturn.kt @@ -0,0 +1,13 @@ +// WITH_RUNTIME +import java.io.File +import java.io.FileFilter + +fun foo(filter: FileFilter) {} + +fun bar() { + foo(object: FileFilter { + override fun accept(file: File): Boolean { + return file.name.startsWith("a") + } + }) +} diff --git a/idea/testData/intentions/objectLiteralToLambda/SingleReturn.kt.after b/idea/testData/intentions/objectLiteralToLambda/SingleReturn.kt.after new file mode 100644 index 00000000000..300c792cf39 --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/SingleReturn.kt.after @@ -0,0 +1,9 @@ +// WITH_RUNTIME +import java.io.File +import java.io.FileFilter + +fun foo(filter: FileFilter) {} + +fun bar() { + foo(FileFilter { file -> file.name.startsWith("a") }) +} diff --git a/idea/testData/intentions/objectLiteralToLambda/StatementAndReturn.kt b/idea/testData/intentions/objectLiteralToLambda/StatementAndReturn.kt new file mode 100644 index 00000000000..cd958ff2de3 --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/StatementAndReturn.kt @@ -0,0 +1,14 @@ +// WITH_RUNTIME +import java.io.File +import java.io.FileFilter + +fun foo(filter: FileFilter) {} + +fun bar() { + foo(object: FileFilter { + override fun accept(file: File): Boolean { + val name = file.name + return name.startsWith("a") + } + }) +} diff --git a/idea/testData/intentions/objectLiteralToLambda/StatementAndReturn.kt.after b/idea/testData/intentions/objectLiteralToLambda/StatementAndReturn.kt.after new file mode 100644 index 00000000000..612cdb4b03d --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/StatementAndReturn.kt.after @@ -0,0 +1,12 @@ +// WITH_RUNTIME +import java.io.File +import java.io.FileFilter + +fun foo(filter: FileFilter) {} + +fun bar() { + foo(FileFilter { file -> + val name = file.name + name.startsWith("a") + }) +} diff --git a/idea/testData/intentions/objectLiteralToLambda/TwoFunctions.kt b/idea/testData/intentions/objectLiteralToLambda/TwoFunctions.kt new file mode 100644 index 00000000000..709ee699182 --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/TwoFunctions.kt @@ -0,0 +1,14 @@ +// IS_APPLICABLE: false +// WITH_RUNTIME + +fun foo(runnable: Runnable) {} + +fun bar() { + foo(object : Runnable { + override fun run() { + f() + } + + fun f() {} + }) +} \ No newline at end of file diff --git a/idea/testData/intentions/objectLiteralToLambda/inspectionData/expected.xml b/idea/testData/intentions/objectLiteralToLambda/inspectionData/expected.xml new file mode 100644 index 00000000000..1085a009e29 --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/inspectionData/expected.xml @@ -0,0 +1,136 @@ + + + StatementAndReturn.kt + 8 + light_idea_test_case + + Convert object literal to lambda + Convert to lambda + + + + SingleReturn.kt + 8 + light_idea_test_case + + Convert object literal to lambda + Convert to lambda + + + + Simple.kt + 6 + light_idea_test_case + + Convert object literal to lambda + Convert to lambda + + + + ReturnNoValue.kt + 6 + light_idea_test_case + + Convert object literal to lambda + Convert to lambda + + + + ReturnNotLast.kt + 8 + light_idea_test_case + + Convert object literal to lambda + Convert to lambda + + + + ParametersNotUsed.kt + 8 + light_idea_test_case + + Convert object literal to lambda + Convert to lambda + + + + NoSamAdapterNeeded.kt + 6 + light_idea_test_case + + Convert object literal to lambda + Convert to lambda + + + + MultipleParameters.kt + 8 + light_idea_test_case + + Convert object literal to lambda + Convert to lambda + + + + ExpressionBody.kt + 8 + light_idea_test_case + + Convert object literal to lambda + Convert to lambda + + + + EmptyBody.kt + 6 + light_idea_test_case + + Convert object literal to lambda + Convert to lambda + + + + ReturnsInWhen.kt + 8 + light_idea_test_case + + Convert object literal to lambda + Convert to lambda + + + + ReturnsInIf.kt + 8 + light_idea_test_case + + Convert object literal to lambda + Convert to lambda + + + + NotInRange2.kt + 7 + light_idea_test_case + + Convert object literal to lambda + Convert to lambda + + + + NotInRange1.kt + 7 + light_idea_test_case + + Convert object literal to lambda + Convert to lambda + + + + QualifiedReturn.kt + 6 + light_idea_test_case + + Convert object literal to lambda + Convert to lambda + + \ No newline at end of file diff --git a/idea/testData/intentions/objectLiteralToLambda/inspectionData/inspections.test b/idea/testData/intentions/objectLiteralToLambda/inspectionData/inspections.test new file mode 100644 index 00000000000..ec887ae08c4 --- /dev/null +++ b/idea/testData/intentions/objectLiteralToLambda/inspectionData/inspections.test @@ -0,0 +1 @@ +// INSPECTION_CLASS: org.jetbrains.kotlin.idea.intentions.ObjectLiteralToLambdaInspection diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/JetInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/JetInspectionTestGenerated.java index 8dcf2321bf4..bad3702faaa 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/JetInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/JetInspectionTestGenerated.java @@ -67,6 +67,12 @@ public class JetInspectionTestGenerated extends AbstractJetInspectionTest { doTest(fileName); } + @TestMetadata("objectLiteralToLambda/inspectionData/inspections.test") + public void testObjectLiteralToLambda_inspectionData_Inspections_test() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/objectLiteralToLambda/inspectionData/inspections.test"); + doTest(fileName); + } + @TestMetadata("removeExplicitSuperQualifier/inspectionData/inspections.test") public void testRemoveExplicitSuperQualifier_inspectionData_Inspections_test() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/removeExplicitSuperQualifier/inspectionData/inspections.test"); diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index 71a5a19fdc8..6552bc3c10e 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -5833,6 +5833,130 @@ public class IntentionTestGenerated extends AbstractIntentionTest { } } + @TestMetadata("idea/testData/intentions/objectLiteralToLambda") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ObjectLiteralToLambda extends AbstractIntentionTest { + public void testAllFilesPresentInObjectLiteralToLambda() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/objectLiteralToLambda"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); + } + + @TestMetadata("EmptyBody.kt") + public void testEmptyBody() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/objectLiteralToLambda/EmptyBody.kt"); + doTest(fileName); + } + + @TestMetadata("ExpressionBody.kt") + public void testExpressionBody() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/objectLiteralToLambda/ExpressionBody.kt"); + doTest(fileName); + } + + @TestMetadata("HasData.kt") + public void testHasData() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/objectLiteralToLambda/HasData.kt"); + doTest(fileName); + } + + @TestMetadata("MultipleBases.kt") + public void testMultipleBases() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/objectLiteralToLambda/MultipleBases.kt"); + doTest(fileName); + } + + @TestMetadata("MultipleParameters.kt") + public void testMultipleParameters() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/objectLiteralToLambda/MultipleParameters.kt"); + doTest(fileName); + } + + @TestMetadata("NoSamAdapterNeeded.kt") + public void testNoSamAdapterNeeded() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/objectLiteralToLambda/NoSamAdapterNeeded.kt"); + doTest(fileName); + } + + @TestMetadata("NotInRange1.kt") + public void testNotInRange1() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/objectLiteralToLambda/NotInRange1.kt"); + doTest(fileName); + } + + @TestMetadata("NotInRange2.kt") + public void testNotInRange2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/objectLiteralToLambda/NotInRange2.kt"); + doTest(fileName); + } + + @TestMetadata("NotJavaSAM.kt") + public void testNotJavaSAM() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/objectLiteralToLambda/NotJavaSAM.kt"); + doTest(fileName); + } + + @TestMetadata("ParametersNotUsed.kt") + public void testParametersNotUsed() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/objectLiteralToLambda/ParametersNotUsed.kt"); + doTest(fileName); + } + + @TestMetadata("QualifiedReturn.kt") + public void testQualifiedReturn() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/objectLiteralToLambda/QualifiedReturn.kt"); + doTest(fileName); + } + + @TestMetadata("ReturnNoValue.kt") + public void testReturnNoValue() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/objectLiteralToLambda/ReturnNoValue.kt"); + doTest(fileName); + } + + @TestMetadata("ReturnNotLast.kt") + public void testReturnNotLast() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/objectLiteralToLambda/ReturnNotLast.kt"); + doTest(fileName); + } + + @TestMetadata("ReturnsInIf.kt") + public void testReturnsInIf() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/objectLiteralToLambda/ReturnsInIf.kt"); + doTest(fileName); + } + + @TestMetadata("ReturnsInWhen.kt") + public void testReturnsInWhen() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/objectLiteralToLambda/ReturnsInWhen.kt"); + doTest(fileName); + } + + @TestMetadata("Simple.kt") + public void testSimple() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/objectLiteralToLambda/Simple.kt"); + doTest(fileName); + } + + @TestMetadata("SingleReturn.kt") + public void testSingleReturn() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/objectLiteralToLambda/SingleReturn.kt"); + doTest(fileName); + } + + @TestMetadata("StatementAndReturn.kt") + public void testStatementAndReturn() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/objectLiteralToLambda/StatementAndReturn.kt"); + doTest(fileName); + } + + @TestMetadata("TwoFunctions.kt") + public void testTwoFunctions() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/objectLiteralToLambda/TwoFunctions.kt"); + doTest(fileName); + } + + } + @TestMetadata("idea/testData/intentions/operatorToFunction") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/j2k/testData/fileOrElement/newClassExpression/newAnonymousClass.kt b/j2k/testData/fileOrElement/newClassExpression/newAnonymousClass.kt index d748a1e570b..cc97236579b 100644 --- a/j2k/testData/fileOrElement/newClassExpression/newAnonymousClass.kt +++ b/j2k/testData/fileOrElement/newClassExpression/newAnonymousClass.kt @@ -1,5 +1 @@ -object : Runnable { - override fun run() { - println("Run") - } -} \ No newline at end of file +Runnable { println("Run") } \ No newline at end of file diff --git a/j2k/testData/fileOrElement/postProcessing/AnonymousObject.java b/j2k/testData/fileOrElement/postProcessing/AnonymousObject.java new file mode 100644 index 00000000000..b30c0f2ba79 --- /dev/null +++ b/j2k/testData/fileOrElement/postProcessing/AnonymousObject.java @@ -0,0 +1,12 @@ +import javax.swing.*; + +public class A { + void foo() { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + System.out.println("a"); + } + }); + } +} diff --git a/j2k/testData/fileOrElement/postProcessing/AnonymousObject.kt b/j2k/testData/fileOrElement/postProcessing/AnonymousObject.kt new file mode 100644 index 00000000000..c455a27e4e7 --- /dev/null +++ b/j2k/testData/fileOrElement/postProcessing/AnonymousObject.kt @@ -0,0 +1,7 @@ +import javax.swing.* + +class A { + internal fun foo() { + SwingUtilities.invokeLater { println("a") } + } +} diff --git a/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterForWebDemoTestGenerated.java b/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterForWebDemoTestGenerated.java index c1571c3a83a..651a6e68272 100644 --- a/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterForWebDemoTestGenerated.java +++ b/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterForWebDemoTestGenerated.java @@ -3655,6 +3655,12 @@ public class JavaToKotlinConverterForWebDemoTestGenerated extends AbstractJavaTo JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("j2k/testData/fileOrElement/postProcessing"), Pattern.compile("^(.+)\\.java$"), true); } + @TestMetadata("AnonymousObject.java") + public void testAnonymousObject() throws Exception { + String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/postProcessing/AnonymousObject.java"); + doTest(fileName); + } + @TestMetadata("GetOperator.java") public void testGetOperator() throws Exception { String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/postProcessing/GetOperator.java"); diff --git a/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterSingleFileTestGenerated.java b/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterSingleFileTestGenerated.java index a101e4e6af0..0ad2f3f9de6 100644 --- a/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterSingleFileTestGenerated.java +++ b/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterSingleFileTestGenerated.java @@ -3655,6 +3655,12 @@ public class JavaToKotlinConverterSingleFileTestGenerated extends AbstractJavaTo JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("j2k/testData/fileOrElement/postProcessing"), Pattern.compile("^(.+)\\.java$"), true); } + @TestMetadata("AnonymousObject.java") + public void testAnonymousObject() throws Exception { + String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/postProcessing/AnonymousObject.java"); + doTest(fileName); + } + @TestMetadata("GetOperator.java") public void testGetOperator() throws Exception { String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/postProcessing/GetOperator.java");