Convert to block body intention action

This commit is contained in:
Valentin Kipyatkov
2014-03-04 19:13:54 +04:00
parent af2a28c099
commit a2f6b99862
36 changed files with 358 additions and 17 deletions
@@ -73,7 +73,12 @@ public class JetPropertyAccessor extends JetDeclarationImpl
@Override
public boolean hasBlockBody() {
return findChildByType(JetTokens.EQ) == null;
return getEqualsToken() == null;
}
@Nullable
public PsiElement getEqualsToken() {
return findChildByType(JetTokens.EQ);
}
@Override
@@ -104,6 +109,6 @@ public class JetPropertyAccessor extends JetDeclarationImpl
@Nullable
@Override
public JetExpression getInitializer() {
return PsiTreeUtil.getNextSiblingOfType(findChildByType(JetTokens.EQ), JetExpression.class);
return PsiTreeUtil.getNextSiblingOfType(getEqualsToken(), JetExpression.class);
}
}
@@ -274,6 +274,10 @@ fun main(args: Array<String>) {
model("intentions/convertToExpressionBody", pattern = "^before(\\w+)\\.kt$")
}
testClass(javaClass<AbstractIntentionTest>(), "ConvertToBlockBodyTestGenerated") {
model("intentions/convertToBlockBody", pattern = "^before(\\w+)\\.kt$")
}
testClass(javaClass<AbstractJSBasicCompletionTest>()) {
model("completion/basic/common")
model("completion/basic/js")
@@ -0,0 +1,3 @@
fun foo()<spot>: String {
return "abc"
}</spot>
@@ -0,0 +1 @@
fun foo() <spot>= "abc"</spot>
@@ -0,0 +1,5 @@
<html>
<body>
This intention converts expression body (single expression after '=' sign) to block body with return statement.
</body>
</html>
+5
View File
@@ -381,6 +381,11 @@
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.jet.plugin.intentions.ConvertToBlockBodyAction</className>
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.jet.plugin.ktSignature.KotlinSignatureAnnotationIntention</className>
<category>Kotlin</category>
@@ -55,6 +55,8 @@ specify.type.explicitly.add.action.name=Specify type explicitly
specify.type.explicitly.remove.action.name=Remove explicitly specified type
convert.to.expression.body.action.family.name=Convert to Expression Body
convert.to.expression.body.action.name=Convert to expression body
convert.to.block.body.action.family.name=Convert to Block Body
convert.to.block.body.action.name=Convert to block body
rename.parameter.to.match.overridden.method=Rename parameter to match overridden method
rename.family=Rename
rename.kotlin.package.class.error="Can't rename kotlin package class"
@@ -0,0 +1,80 @@
package org.jetbrains.jet.plugin.intentions
import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction
import com.intellij.openapi.project.Project
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.jet.lang.psi.JetBlockExpression
import org.jetbrains.jet.plugin.JetBundle
import org.jetbrains.jet.lang.psi.*
import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache
import org.jetbrains.jet.lang.resolve.BindingContext
import org.jetbrains.jet.lang.types.TypeUtils
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns
import org.jetbrains.jet.lexer.JetTokens
import org.jetbrains.jet.lang.types.JetType
public class ConvertToBlockBodyAction : PsiElementBaseIntentionAction() {
override fun getFamilyName(): String = JetBundle.message("convert.to.block.body.action.family.name")
override fun isAvailable(project: Project, editor: Editor, element: PsiElement): Boolean {
setText(JetBundle.message("convert.to.block.body.action.name"))
return findDeclaration(element) != null
}
override fun invoke(project: Project, editor: Editor, element: PsiElement) {
val declaration = findDeclaration(element)!!
val body = declaration.getBodyExpression()!!
fun generateBody(returnsValue: Boolean): JetExpression {
val bodyType = expressionType(body)
val needReturn = returnsValue &&
(bodyType == null || (!KotlinBuiltIns.getInstance().isUnit(bodyType) && !KotlinBuiltIns.getInstance().isNothing(bodyType)))
val oldBodyText = body.getText()!!
val newBodyText = if (needReturn) "return ${oldBodyText}" else oldBodyText
return JetPsiFactory.createFunctionBody(project, newBodyText)
}
if (declaration is JetNamedFunction) {
val returnType = functionReturnType(declaration)!!
if (!declaration.hasDeclaredReturnType() && !KotlinBuiltIns.getInstance().isUnit(returnType)) {
specifyTypeExplicitly(declaration, returnType)
}
val newBody = generateBody(!KotlinBuiltIns.getInstance().isUnit(returnType) && !KotlinBuiltIns.getInstance().isNothing(returnType))
declaration.getEqualsToken()!!.delete()
body.replace(newBody)
}
else if (declaration is JetPropertyAccessor) {
val newBody = generateBody(declaration.isGetter())
declaration.getEqualsToken()!!.delete()
body.replace(newBody)
}
else {
throw RuntimeException("Unknown declaration type: $declaration")
}
}
private fun findDeclaration(element: PsiElement): JetDeclarationWithBody? {
val declaration = PsiTreeUtil.getParentOfType(element, javaClass<JetDeclarationWithBody>())
if (declaration == null || declaration is JetFunctionLiteral || declaration.hasBlockBody()) return null
val body = declaration.getBodyExpression()
if (body == null) return null
return when (declaration) {
is JetNamedFunction -> {
val returnType = functionReturnType(declaration)
if (returnType == null) return null
if (!declaration.hasDeclaredReturnType() && returnType.isError()) return null // do not convert when type is implicit and unknown
declaration
}
is JetPropertyAccessor -> declaration
else -> throw RuntimeException("Unknown declaration type: $declaration")
}
}
}
@@ -30,7 +30,7 @@ public class ConvertToExpressionBodyAction : PsiElementBaseIntentionAction() {
if (!declaration.hasDeclaredReturnType() && declaration is JetNamedFunction) {
val valueType = expressionType(value)
if (valueType == null || !KotlinBuiltIns.getInstance().isUnit(valueType)) {
specifyUnitTypeExplicitly(declaration)
specifyTypeExplicitly(declaration, "Unit")
}
}
@@ -75,20 +75,6 @@ public class ConvertToExpressionBodyAction : PsiElementBaseIntentionAction() {
}
}
private fun expressionType(expression: JetExpression): JetType? {
val resolveSession = AnalyzerFacadeWithCache.getLazyResolveSessionForFile(expression.getContainingFile() as JetFile)
val bindingContext = resolveSession.resolveToElement(expression)
return bindingContext.get(BindingContext.EXPRESSION_TYPE, expression)
}
private fun specifyUnitTypeExplicitly(declaration: JetNamedFunction) {
val project = declaration.getProject()
val typeReference = JetPsiFactory.createType(project, "Unit")
val anchor = declaration.getValueParameterList() ?: return/*incomplete declaration*/
declaration.addAfter(typeReference, anchor)
declaration.addAfter(JetPsiFactory.createColon(project), anchor)
}
private fun containsReturn(element: PsiElement): Boolean {
if (element is JetReturnExpression) return true
//TODO: would be better to have some interface of declaration where return can be used
@@ -0,0 +1,41 @@
package org.jetbrains.jet.plugin.intentions
import org.jetbrains.jet.lang.psi.*
import org.jetbrains.jet.lang.types.JetType
import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache
import org.jetbrains.jet.lang.resolve.BindingContext
import org.jetbrains.jet.lang.descriptors.FunctionDescriptor
import org.jetbrains.jet.renderer.DescriptorRenderer
import org.jetbrains.jet.plugin.codeInsight.ShortenReferences
fun specifyTypeExplicitly(declaration: JetNamedFunction, typeText: String) {
specifyTypeExplicitly(declaration, JetPsiFactory.createType(declaration.getProject(), typeText))
}
fun specifyTypeExplicitly(declaration: JetNamedFunction, `type`: JetType) {
if (`type`.isError()) return
val typeReference = JetPsiFactory.createType(declaration.getProject(), DescriptorRenderer.SOURCE_CODE.renderType(`type`))
specifyTypeExplicitly(declaration, typeReference)
ShortenReferences.process(declaration.getReturnTypeRef()!!)
}
fun specifyTypeExplicitly(declaration: JetNamedFunction, typeReference: JetTypeReference) {
val project = declaration.getProject()
val anchor = declaration.getValueParameterList() ?: return/*incomplete declaration*/
declaration.addAfter(typeReference, anchor)
declaration.addAfter(JetPsiFactory.createColon(project), anchor)
}
fun expressionType(expression: JetExpression): JetType? {
val resolveSession = AnalyzerFacadeWithCache.getLazyResolveSessionForFile(expression.getContainingFile() as JetFile)
val bindingContext = resolveSession.resolveToElement(expression)
return bindingContext.get(BindingContext.EXPRESSION_TYPE, expression)
}
fun functionReturnType(function: JetNamedFunction): JetType? {
val resolveSession = AnalyzerFacadeWithCache.getLazyResolveSessionForFile(function.getContainingFile() as JetFile)
val bindingContext = resolveSession.resolveToElement(function)
val descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, function)
if (descriptor == null) return null
return (descriptor as FunctionDescriptor).getReturnType()
}
@@ -0,0 +1,4 @@
// "Convert to block body" "true"
fun foo(): String {
return "abc"
}
@@ -0,0 +1,5 @@
// "Convert to block body" "true"
// ERROR: Unresolved reference: bar
fun <caret>foo(): String {
return bar()
}
@@ -0,0 +1,6 @@
// "Convert to block body" "true"
// ERROR: Unresolved reference: XXX
// ERROR: Unresolved reference: bar
fun <caret>foo(): XXX {
return bar()
}
@@ -0,0 +1,6 @@
// "Convert to block body" "true"
fun foo(): Unit {
bar()
}
fun bar() { }
@@ -0,0 +1,5 @@
// "Convert to block body" "true"
// ERROR: Unresolved reference: bar
fun <caret>foo(): Unit {
bar()
}
@@ -0,0 +1,4 @@
// "Convert to block body" "true"
fun <caret>foo(): String {
throw UnsupportedOperationException()
}
@@ -0,0 +1,5 @@
// "Convert to block body" "true"
val foo: String
get() {
return "abc"
}
@@ -0,0 +1,5 @@
// "Convert to block body" "true"
val foo: String
get() {
throw UnsupportedOperationException()
}
@@ -0,0 +1,6 @@
// "Convert to block body" "true"
import java.net.URI
fun foo(): URI {
return java.io.File("x").toURI()
}
@@ -0,0 +1,6 @@
// "Convert to block body" "true"
fun foo() {
bar()
}
fun bar() { }
@@ -0,0 +1,4 @@
// "Convert to block body" "true"
fun foo(): Nothing {
throw UnsupportedOperationException()
}
@@ -0,0 +1,8 @@
// "Convert to block body" "true"
var foo: String
get() = "abc"
set(value) {
doSet(value)
}
fun doSet(value: String){}
@@ -0,0 +1,2 @@
// "Convert to block body" "true"
fun <caret>foo(): String = "abc"
@@ -0,0 +1,3 @@
// "Convert to block body" "true"
// ERROR: Unresolved reference: bar
fun <caret>foo(): String = bar()
@@ -0,0 +1,4 @@
// "Convert to block body" "true"
// ERROR: Unresolved reference: XXX
// ERROR: Unresolved reference: bar
fun <caret>foo(): XXX = bar()
@@ -0,0 +1,4 @@
// "Convert to block body" "true"
fun <caret>foo(): Unit = bar()
fun bar() { }
@@ -0,0 +1,3 @@
// "Convert to block body" "true"
// ERROR: Unresolved reference: bar
fun <caret>foo(): Unit = bar()
@@ -0,0 +1,2 @@
// "Convert to block body" "true"
fun <caret>foo(): String = throw UnsupportedOperationException()
@@ -0,0 +1,3 @@
// "Convert to block body" "true"
val foo: String
<caret>get() = "abc"
@@ -0,0 +1,3 @@
// "Convert to block body" "true"
val foo: String
<caret>get() = throw UnsupportedOperationException()
@@ -0,0 +1,2 @@
// "Convert to block body" "true"
fun <caret>foo() = java.io.File("x").toURI()
@@ -0,0 +1,3 @@
// "Convert to block body" "false"
// ERROR: Unresolved reference: bar
fun <caret>foo() = bar()
@@ -0,0 +1,4 @@
// "Convert to block body" "true"
fun <caret>foo() = bar()
fun bar() { }
@@ -0,0 +1,2 @@
// "Convert to block body" "true"
fun <caret>foo(): Nothing = throw UnsupportedOperationException()
@@ -0,0 +1,6 @@
// "Convert to block body" "true"
var foo: String
get() = "abc"
<caret>set(value) = doSet(value)
fun doSet(value: String){}
@@ -0,0 +1,104 @@
/*
* 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 junit.framework.Assert;
import junit.framework.Test;
import junit.framework.TestSuite;
import java.io.File;
import java.util.regex.Pattern;
import org.jetbrains.jet.JetTestUtils;
import org.jetbrains.jet.test.InnerTestClasses;
import org.jetbrains.jet.test.TestMetadata;
import org.jetbrains.jet.plugin.intentions.AbstractIntentionTest;
/** This class is generated by {@link org.jetbrains.jet.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("idea/testData/intentions/convertToBlockBody")
public class ConvertToBlockBodyTestGenerated extends AbstractIntentionTest {
public void testAllFilesPresentInConvertToBlockBody() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("idea/testData/intentions/convertToBlockBody"), Pattern.compile("^before(\\w+)\\.kt$"), true);
}
@TestMetadata("beforeExplicitlyNonUnitFun.kt")
public void testExplicitlyNonUnitFun() throws Exception {
doTest("idea/testData/intentions/convertToBlockBody/beforeExplicitlyNonUnitFun.kt");
}
@TestMetadata("beforeExplicitlyTypedFunWithUnresolvedExpression.kt")
public void testExplicitlyTypedFunWithUnresolvedExpression() throws Exception {
doTest("idea/testData/intentions/convertToBlockBody/beforeExplicitlyTypedFunWithUnresolvedExpression.kt");
}
@TestMetadata("beforeExplicitlyTypedFunWithUnresolvedType.kt")
public void testExplicitlyTypedFunWithUnresolvedType() throws Exception {
doTest("idea/testData/intentions/convertToBlockBody/beforeExplicitlyTypedFunWithUnresolvedType.kt");
}
@TestMetadata("beforeExplicitlyUnitFun.kt")
public void testExplicitlyUnitFun() throws Exception {
doTest("idea/testData/intentions/convertToBlockBody/beforeExplicitlyUnitFun.kt");
}
@TestMetadata("beforeExplicitlyUnitFunWithUnresolvedExpression.kt")
public void testExplicitlyUnitFunWithUnresolvedExpression() throws Exception {
doTest("idea/testData/intentions/convertToBlockBody/beforeExplicitlyUnitFunWithUnresolvedExpression.kt");
}
@TestMetadata("beforeFunWithThrow.kt")
public void testFunWithThrow() throws Exception {
doTest("idea/testData/intentions/convertToBlockBody/beforeFunWithThrow.kt");
}
@TestMetadata("beforeGetter.kt")
public void testGetter() throws Exception {
doTest("idea/testData/intentions/convertToBlockBody/beforeGetter.kt");
}
@TestMetadata("beforeGetterWithThrow.kt")
public void testGetterWithThrow() throws Exception {
doTest("idea/testData/intentions/convertToBlockBody/beforeGetterWithThrow.kt");
}
@TestMetadata("beforeImplicitlyNonUnitFun.kt")
public void testImplicitlyNonUnitFun() throws Exception {
doTest("idea/testData/intentions/convertToBlockBody/beforeImplicitlyNonUnitFun.kt");
}
@TestMetadata("beforeImplicitlyTypedFunWithUnresolvedType.kt")
public void testImplicitlyTypedFunWithUnresolvedType() throws Exception {
doTest("idea/testData/intentions/convertToBlockBody/beforeImplicitlyTypedFunWithUnresolvedType.kt");
}
@TestMetadata("beforeImplicitlyUnitFun.kt")
public void testImplicitlyUnitFun() throws Exception {
doTest("idea/testData/intentions/convertToBlockBody/beforeImplicitlyUnitFun.kt");
}
@TestMetadata("beforeNothingFun.kt")
public void testNothingFun() throws Exception {
doTest("idea/testData/intentions/convertToBlockBody/beforeNothingFun.kt");
}
@TestMetadata("beforeSetter.kt")
public void testSetter() throws Exception {
doTest("idea/testData/intentions/convertToBlockBody/beforeSetter.kt");
}
}