Add intention for replacing explicit function literal parameter with 'it'

Kotlin's function literals have a shortcut for one-argument literals:
the single argument doesn't need to be explicitly named, but can be
referred via the 'it' contextual keyword.

For example, 'array(1, 2, 3).filter { x -> x % 2 == 0 }'
          -> 'array(1, 2, 3).filter { it % 2 == 0 }'

Add an intention action for this transformation.
This commit is contained in:
Tuomas Tynkkynen
2014-02-08 15:57:54 -08:00
committed by Nikolay Krasko
parent 1dd57b5f6e
commit c6d6a32314
21 changed files with 226 additions and 1 deletions
@@ -369,6 +369,7 @@ fun main(args: Array<String>) {
model("intentions/insertCurlyBracestsToTemplate", testMethod = "doTestInsertCurlyToTemplate")
model("intentions/moveLambdaInsideParentheses", testMethod = "doTestMoveLambdaInsideParentheses")
model("intentions/moveLambdaOutsideParentheses", testMethod = "doTestMoveLambdaOutsideParentheses")
model("intentions/replaceExplicitFunctionLiteralParamWithIt", testMethod = "doTestReplaceExplicitFunctionLiteralParamWithIt")
}
testClass(javaClass<AbstractHierarchyTest>()) {
@@ -0,0 +1 @@
array(1, 2, 3).filter <spot>{ it % 2 == 0 }</spot>
@@ -0,0 +1 @@
array(1, 2, 3).filter <spot>{ x -> x % 2 == 0 }</spot>
@@ -0,0 +1,5 @@
<html>
<body>
This intention replaces the explicit parameter of a single-parameter function literal with the implicit 'it' parameter.
</body>
</html>
+5
View File
@@ -528,6 +528,11 @@
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.jet.plugin.intentions.ReplaceExplicitFunctionLiteralParamWithItIntention</className>
<category>Kotlin</category>
</intentionAction>
<project.converterProvider implementation="org.jetbrains.jet.plugin.converters.JetRunConfigurationSettingsFormatConverterProvider"/>
</extensions>
@@ -233,6 +233,8 @@ replace.with.dot.qualified.method.call.intention=Replace with simple method call
replace.with.dot.qualified.method.call.intention.family=Replace with simple method call
replace.with.infix.function.call.intention=Replace with infix function call
replace.with.infix.function.call.intention.family=Replace with infix function call
replace.explicit.function.literal.param.with.it=Replace explicit parameter ''{0}'' with ''it''
replace.explicit.function.literal.param.with.it.family=Replace Explicit Parameter With 'it'
move.lambda.inside.parentheses=Move lambda function into parentheses
move.lambda.inside.parentheses.family=Move lambda function into parentheses
move.lambda.outside.parentheses=Move lambda expression out of parentheses
@@ -0,0 +1,115 @@
/*
* Copyright 2010-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jet.plugin.intentions
import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.refactoring.rename.RenameProcessor
import com.intellij.usageView.UsageInfo
import org.jetbrains.jet.lang.psi.*
import org.jetbrains.jet.plugin.JetBundle
import com.intellij.psi.codeStyle.CodeStyleManager
import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache
import org.jetbrains.jet.lang.descriptors.impl.AnonymousFunctionDescriptor
import org.jetbrains.jet.lang.resolve.BindingContextUtils
import org.jetbrains.jet.plugin.references.JetReference
import org.jetbrains.jet.plugin.codeInsight.firstOrNull
import org.jetbrains.jet.lang.descriptors.VariableDescriptor
public class ReplaceExplicitFunctionLiteralParamWithItIntention() : PsiElementBaseIntentionAction() {
override fun invoke(project: Project, editor: Editor, element: PsiElement) {
val funcExpr = findFunctionLiteralToActOn(element)!!
val cursorWasOverParameterList = PsiTreeUtil.getParentOfType(element, javaClass<JetParameter>()) != null
ParamRenamingProcessor(editor, funcExpr, cursorWasOverParameterList).run()
}
override fun isAvailable(project: Project, editor: Editor, element: PsiElement): Boolean {
val funcExpr = findFunctionLiteralToActOn(element)
if (funcExpr == null || funcExpr.getValueParameters().size() != 1) {
return false
}
val parameter = funcExpr.getValueParameters().first()
if (parameter.getTypeReference() != null) {
return false
}
setText(JetBundle.message("replace.explicit.function.literal.param.with.it", parameter.getName()))
return true
}
override fun getFamilyName(): String {
return JetBundle.message("replace.explicit.function.literal.param.with.it.family")
}
private fun findFunctionLiteralToActOn(element: PsiElement): JetFunctionLiteral? {
if (PsiTreeUtil.getParentOfType(element, javaClass<JetFunctionLiteralExpression>()) == null) {
return null
}
val expression = PsiTreeUtil.getParentOfType(element, javaClass<JetSimpleNameExpression>(), javaClass<JetParameter>())
if (expression == null) {
return null
}
when (expression) {
is JetParameter -> {
return PsiTreeUtil.skipParentsOfType(expression, javaClass<JetParameterList>()) as? JetFunctionLiteral
}
is JetSimpleNameExpression -> {
val reference = expression.getReference() as JetReference?
val variableDescriptor = reference?.resolveToDescriptors()?.firstOrNull() as? VariableDescriptor?
if (variableDescriptor != null) {
val containingDescriptor = variableDescriptor.getContainingDeclaration()
if (containingDescriptor is AnonymousFunctionDescriptor) {
val context = AnalyzerFacadeWithCache.getContextForElement(expression)
return BindingContextUtils.descriptorToDeclaration(context, containingDescriptor) as? JetFunctionLiteral
}
}
return null
}
else -> return null
}
}
private class ParamRenamingProcessor(
val editor: Editor,
val funcLiteral: JetFunctionLiteral,
val cursorWasOverParameterList: Boolean) : RenameProcessor(editor.getProject(),
funcLiteral.getValueParameters().first(),
"it",
false,
false
) {
public override fun performRefactoring(usages: Array<out UsageInfo>?) {
super.performRefactoring(usages)
funcLiteral.deleteChildRange(funcLiteral.getValueParameterList(), funcLiteral.getArrowNode()!!.getPsi())
if (cursorWasOverParameterList) {
editor.getCaretModel().moveToOffset(funcLiteral.getBodyExpression()!!.getTextOffset())
}
CodeStyleManager.getInstance(editor.getProject()!!)!!.reformatText(funcLiteral.getContainingFile()!!,
funcLiteral.getTextRange()!!.getStartOffset(),
funcLiteral.getTextRange()!!.getEndOffset())
}
}
}
@@ -0,0 +1,2 @@
fun foo(a: (Int) -> Int): Int = a(1)
val x = foo { p -> foo { y -> <caret>p } }
@@ -0,0 +1,2 @@
fun foo(a: (Int) -> Int): Int = a(1)
val x = foo { foo { y -> <caret>it } }
@@ -0,0 +1,2 @@
fun applyTwice<A>(f: (A) -> A, x: A) = f(f(x))
val x = applyTwice({ <caret>x -> 1 + x }, 40)
@@ -0,0 +1,2 @@
fun applyTwice<A>(f: (A) -> A, x: A) = f(f(x))
val x = applyTwice({ <caret>1 + it }, 40)
@@ -0,0 +1,2 @@
fun applyTwice<A>(f: (A) -> A, x: A) = f(f(x))
val x = applyTwice({ x -> <caret>x + 1 }, 40)
@@ -0,0 +1,2 @@
fun applyTwice<A>(f: (A) -> A, x: A) = f(f(x))
val x = applyTwice({ <caret>it + 1 }, 40)
@@ -0,0 +1,6 @@
fun foo(i: (Int) -> Unit) {}
fun test() {
foo { <caret>x ->
array(1, 2, 3).filter { x -> x % 2 == 0 }
}
}
@@ -0,0 +1,6 @@
fun foo(i: (Int) -> Unit) {}
fun test() {
foo {
<caret>array(1, 2, 3).filter { x -> x % 2 == 0 }
}
}
@@ -0,0 +1,4 @@
// IS_APPLICABLE: false
fun applyTwice<A>(f: (A) -> A, x: A) = f(f(x))
val x = applyTwice({ i<caret>t + 1 }, 40)
@@ -0,0 +1,4 @@
// IS_APPLICABLE: false
fun call2(f: (Int, Int) -> Int, x: Int, y: Int) = f(x, y)
val foo = call2({ <caret>x, y -> x + y }, 40, 2)
@@ -0,0 +1,8 @@
// IS_APPLICABLE: false
val foo = { (x: Int) ->
class Inner() {
fun temp(<caret>y: Int) : Int { return x + y }
}
Inner()
}
@@ -0,0 +1,2 @@
// IS_APPLICABLE: false
val incr = { (<caret>x: Int) -> x + 1}
@@ -142,6 +142,10 @@ public abstract class AbstractCodeTransformationTest extends LightCodeInsightTes
doTestIntention(path, new ReplaceWithInfixFunctionCallIntention());
}
public void doTestReplaceExplicitFunctionLiteralParamWithIt(@NotNull String path) throws Exception {
doTestIntention(path, new ReplaceExplicitFunctionLiteralParamWithItIntention());
}
private void doTestIntention(@NotNull String path, @NotNull IntentionAction intentionAction) throws Exception {
configureByFile(path);
@@ -30,7 +30,7 @@ import org.jetbrains.jet.plugin.intentions.AbstractCodeTransformationTest;
/** This class is generated by {@link org.jetbrains.jet.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@InnerTestClasses({CodeTransformationTestGenerated.IfToAssignment.class, CodeTransformationTestGenerated.IfToReturn.class, CodeTransformationTestGenerated.IfToReturnAsymmetrically.class, CodeTransformationTestGenerated.WhenToAssignment.class, CodeTransformationTestGenerated.WhenToReturn.class, CodeTransformationTestGenerated.AssignmentToIf.class, CodeTransformationTestGenerated.AssignmentToWhen.class, CodeTransformationTestGenerated.PropertyToIf.class, CodeTransformationTestGenerated.PropertyToWhen.class, CodeTransformationTestGenerated.ReturnToIf.class, CodeTransformationTestGenerated.ReturnToWhen.class, CodeTransformationTestGenerated.IfToWhen.class, CodeTransformationTestGenerated.WhenToIf.class, CodeTransformationTestGenerated.Flatten.class, CodeTransformationTestGenerated.Merge.class, CodeTransformationTestGenerated.IntroduceSubject.class, CodeTransformationTestGenerated.EliminateSubject.class, CodeTransformationTestGenerated.Split.class, CodeTransformationTestGenerated.Join.class, CodeTransformationTestGenerated.ConvertMemberToExtension.class, CodeTransformationTestGenerated.ReconstructedType.class, CodeTransformationTestGenerated.RemoveUnnecessaryParentheses.class, CodeTransformationTestGenerated.ReplaceWithDotQualifiedMethodCall.class, CodeTransformationTestGenerated.ReplaceWithInfixFunctionCall.class, CodeTransformationTestGenerated.RemoveCurlyBracesFromTemplate.class, CodeTransformationTestGenerated.MoveLambdaInsideParentheses.class, CodeTransformationTestGenerated.MoveLambdaOutsideParentheses.class})
@InnerTestClasses({CodeTransformationTestGenerated.IfToAssignment.class, CodeTransformationTestGenerated.IfToReturn.class, CodeTransformationTestGenerated.IfToReturnAsymmetrically.class, CodeTransformationTestGenerated.WhenToAssignment.class, CodeTransformationTestGenerated.WhenToReturn.class, CodeTransformationTestGenerated.AssignmentToIf.class, CodeTransformationTestGenerated.AssignmentToWhen.class, CodeTransformationTestGenerated.PropertyToIf.class, CodeTransformationTestGenerated.PropertyToWhen.class, CodeTransformationTestGenerated.ReturnToIf.class, CodeTransformationTestGenerated.ReturnToWhen.class, CodeTransformationTestGenerated.IfToWhen.class, CodeTransformationTestGenerated.WhenToIf.class, CodeTransformationTestGenerated.Flatten.class, CodeTransformationTestGenerated.Merge.class, CodeTransformationTestGenerated.IntroduceSubject.class, CodeTransformationTestGenerated.EliminateSubject.class, CodeTransformationTestGenerated.Split.class, CodeTransformationTestGenerated.Join.class, CodeTransformationTestGenerated.ConvertMemberToExtension.class, CodeTransformationTestGenerated.ReconstructedType.class, CodeTransformationTestGenerated.RemoveUnnecessaryParentheses.class, CodeTransformationTestGenerated.ReplaceWithDotQualifiedMethodCall.class, CodeTransformationTestGenerated.ReplaceWithInfixFunctionCall.class, CodeTransformationTestGenerated.RemoveCurlyBracesFromTemplate.class, CodeTransformationTestGenerated.MoveLambdaInsideParentheses.class, CodeTransformationTestGenerated.MoveLambdaOutsideParentheses.class, CodeTransformationTestGenerated.ReplaceExplicitFunctionLiteralParamWithIt.class})
public class CodeTransformationTestGenerated extends AbstractCodeTransformationTest {
@TestMetadata("idea/testData/intentions/branched/folding/ifToAssignment")
public static class IfToAssignment extends AbstractCodeTransformationTest {
@@ -1478,6 +1478,54 @@ public class CodeTransformationTestGenerated extends AbstractCodeTransformationT
}
@TestMetadata("idea/testData/intentions/replaceExplicitFunctionLiteralParamWithIt")
public static class ReplaceExplicitFunctionLiteralParamWithIt extends AbstractCodeTransformationTest {
public void testAllFilesPresentInReplaceExplicitFunctionLiteralParamWithIt() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("idea/testData/intentions/replaceExplicitFunctionLiteralParamWithIt"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("applicable_cursofOverParamInInnerLiteral.kt")
public void testApplicable_cursofOverParamInInnerLiteral() throws Exception {
doTestReplaceExplicitFunctionLiteralParamWithIt("idea/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/applicable_cursofOverParamInInnerLiteral.kt");
}
@TestMetadata("applicable_cursorOverParameterDeclaration.kt")
public void testApplicable_cursorOverParameterDeclaration() throws Exception {
doTestReplaceExplicitFunctionLiteralParamWithIt("idea/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/applicable_cursorOverParameterDeclaration.kt");
}
@TestMetadata("applicable_cursorOverParameterUse.kt")
public void testApplicable_cursorOverParameterUse() throws Exception {
doTestReplaceExplicitFunctionLiteralParamWithIt("idea/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/applicable_cursorOverParameterUse.kt");
}
@TestMetadata("applicable_formatsProperly.kt")
public void testApplicable_formatsProperly() throws Exception {
doTestReplaceExplicitFunctionLiteralParamWithIt("idea/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/applicable_formatsProperly.kt");
}
@TestMetadata("notApplicable_alreadyUsesImplicitIt.kt")
public void testNotApplicable_alreadyUsesImplicitIt() throws Exception {
doTestReplaceExplicitFunctionLiteralParamWithIt("idea/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/notApplicable_alreadyUsesImplicitIt.kt");
}
@TestMetadata("notApplicable_hasMultipleParameters.kt")
public void testNotApplicable_hasMultipleParameters() throws Exception {
doTestReplaceExplicitFunctionLiteralParamWithIt("idea/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/notApplicable_hasMultipleParameters.kt");
}
@TestMetadata("notApplicable_notFunctionLiteralParameter.kt")
public void testNotApplicable_notFunctionLiteralParameter() throws Exception {
doTestReplaceExplicitFunctionLiteralParamWithIt("idea/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/notApplicable_notFunctionLiteralParameter.kt");
}
@TestMetadata("notApplicable_parameterHasExplicitType.kt")
public void testNotApplicable_parameterHasExplicitType() throws Exception {
doTestReplaceExplicitFunctionLiteralParamWithIt("idea/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/notApplicable_parameterHasExplicitType.kt");
}
}
public static Test suite() {
TestSuite suite = new TestSuite("CodeTransformationTestGenerated");
suite.addTestSuite(IfToAssignment.class);
@@ -1507,6 +1555,7 @@ public class CodeTransformationTestGenerated extends AbstractCodeTransformationT
suite.addTestSuite(RemoveCurlyBracesFromTemplate.class);
suite.addTestSuite(MoveLambdaInsideParentheses.class);
suite.addTestSuite(MoveLambdaOutsideParentheses.class);
suite.addTestSuite(ReplaceExplicitFunctionLiteralParamWithIt.class);
return suite;
}
}