Introduce Refactorings: Implement trimming renderer for expression

chooser. Do not suggest parenthesized expressions
 #KT-9028 Fixed
This commit is contained in:
Alexey Sedunov
2015-12-08 21:16:01 +03:00
parent ae2fc19fe5
commit f87d2d1fcc
11 changed files with 458 additions and 7 deletions
@@ -48,7 +48,6 @@ import org.jetbrains.kotlin.idea.caches.resolve.ResolutionUtils;
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde;
import org.jetbrains.kotlin.idea.refactoring.introduce.IntroduceUtilKt;
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers;
import org.jetbrains.kotlin.idea.util.string.StringUtilKt;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.psi.psiUtil.KtPsiUtilKt;
import org.jetbrains.kotlin.renderer.DescriptorRenderer;
@@ -378,7 +377,10 @@ public class KotlinRefactoringUtil {
if (element instanceof KtExpression && !(element instanceof KtStatementExpression)) {
boolean addExpression = true;
if (KtPsiUtil.isLabelIdentifierExpression(element)) {
if (element instanceof KtParenthesizedExpression) {
addExpression = false;
}
else if (KtPsiUtil.isLabelIdentifierExpression(element)) {
addExpression = false;
}
else if (element.getParent() instanceof KtQualifiedExpression) {
@@ -481,8 +483,12 @@ public class KotlinRefactoringUtil {
}
public static String getExpressionShortText(@NotNull KtElement element) { //todo: write appropriate implementation
return StringUtilKt.collapseSpaces(StringUtil.shortenTextWithEllipsis(element.getText(), 53, 0));
public static String getExpressionShortText(@NotNull KtElement element) {
String text = ElementRenderingUtilsKt.renderTrimmed(element);
int firstNewLinePos = text.indexOf('\n');
String trimmedText = text.substring(0, firstNewLinePos != -1 ? firstNewLinePos : Math.min(100, text.length()));
if (trimmedText.length() != text.length()) trimmedText += " ...";
return trimmedText;
}
@Nullable
@@ -0,0 +1,325 @@
/*
* 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.refactoring
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.utils.ifEmpty
fun KtElement.renderTrimmed(): String {
class Renderer : KtTreeVisitorVoid() {
val builder = StringBuilder()
fun render(element: KtElement): String {
element.accept(this)
return builder.toString()
}
private fun <T: PsiElement> Iterable<T>.join(separator: CharSequence = ", ", prefix: CharSequence = "", postfix: CharSequence = "") {
builder.append(prefix)
var count = 0
for (element in this) {
if (++count > 1) builder.append(separator)
element.accept(this@Renderer)
}
builder.append(postfix)
}
// Whitespace and comments
override fun visitWhiteSpace(space: PsiWhiteSpace) {
val text = space.text
val newLine = text.indexOf('\n')
if (newLine != 0) {
builder.append(' ')
}
if (newLine >= 0) {
builder.append(text.substring(newLine))
}
}
override fun visitComment(comment: PsiComment) {
}
// Basic expressions
override fun visitParenthesizedExpression(expression: KtParenthesizedExpression) {
builder.append('(')
expression.expression?.accept(this)
builder.append(')')
}
override fun visitLabeledExpression(expression: KtLabeledExpression) {
expression.baseExpression?.accept(this)
}
override fun visitAnnotatedExpression(expression: KtAnnotatedExpression) {
expression.baseExpression?.accept(this)
}
override fun visitPrefixExpression(expression: KtPrefixExpression) {
builder.append("${expression.operationReference.getReferencedName()}")
expression.baseExpression?.accept(this)
}
override fun visitPostfixExpression(expression: KtPostfixExpression) {
expression.baseExpression?.accept(this)
builder.append("${expression.operationReference.getReferencedName()}")
}
override fun visitBinaryExpression(expression: KtBinaryExpression) {
expression.left?.accept(this)
builder.append(" ${expression.operationReference.getReferencedName()} ")
expression.right?.accept(this)
}
override fun visitBinaryWithTypeRHSExpression(expression: KtBinaryExpressionWithTypeRHS) {
expression.left.accept(this)
builder.append(" ${expression.operationReference.getReferencedName()} ")
expression.right?.accept(this)
}
override fun visitIsExpression(expression: KtIsExpression) {
expression.leftHandSide.accept(this)
builder.append(" is ")
expression.typeReference?.accept(this)
}
override fun visitArrayAccessExpression(expression: KtArrayAccessExpression) {
expression.arrayExpression?.accept(this)
expression.indexExpressions.join(builder, prefix = "[", postfix = "]")
}
override fun visitCallExpression(expression: KtCallExpression) {
expression.calleeExpression?.accept(this)
expression.valueArgumentList?.accept(this)
expression.functionLiteralArguments.forEach { builder.append("{...}") }
}
override fun visitValueArgumentList(list: KtValueArgumentList) {
builder.append(if (list.arguments.isEmpty()) "()" else "(...)")
}
override fun visitQualifiedExpression(expression: KtQualifiedExpression) {
expression.receiverExpression.accept(this)
builder.append(expression.operationTokenNode.text)
expression.selectorExpression?.accept(this)
}
override fun visitClassLiteralExpression(expression: KtClassLiteralExpression) {
expression.typeReference?.accept(this)
builder.append("::class")
}
override fun visitCallableReferenceExpression(expression: KtCallableReferenceExpression) {
expression.typeReference?.accept(this)
builder.append("::")
builder.append(expression.callableReference.getReferencedName())
}
override fun visitThisExpression(expression: KtThisExpression) {
builder.append("this")
expression.getLabelName()?.let { builder.append("@$it") }
}
override fun visitSuperExpression(expression: KtSuperExpression) {
builder.append("super")
expression.superTypeQualifier?.let {
builder.append("<")
it.accept(this)
builder.append(">")
}
expression.getLabelName()?.let { builder.append("@$it") }
}
// Control structures
override fun visitBreakExpression(expression: KtBreakExpression) {
builder.append("break")
expression.getLabelName()?.let { builder.append("@$it") }
}
override fun visitContinueExpression(expression: KtContinueExpression) {
builder.append("continue")
expression.getLabelName()?.let { builder.append("@$it") }
}
override fun visitThrowExpression(expression: KtThrowExpression) {
builder.append("throw ")
expression.thrownExpression?.accept(this)
}
override fun visitReturnExpression(expression: KtReturnExpression) {
builder.append("return")
expression.getLabelName()?.let { builder.append("@$it") }
builder.append(' ')
expression.returnedExpression?.accept(this)
}
override fun visitBlockExpression(expression: KtBlockExpression) {
if (expression.parent is KtFunctionLiteral) {
super.visitBlockExpression(expression)
}
else {
builder.append("{...}")
}
}
override fun visitIfExpression(expression: KtIfExpression) {
builder.append("if (")
expression.condition?.accept(this)
builder.append(")")
expression.then?.let {
builder.append(' ')
it.accept(this)
}
expression.`else`?.let {
builder.append(" else ")
it.accept(this)
}
}
override fun visitWhenExpression(expression: KtWhenExpression) {
builder.append("when")
expression.subjectExpression?.let {
builder.append('(')
it.accept(this)
builder.append(')')
}
builder.append(" {...}")
}
override fun visitForExpression(expression: KtForExpression) {
builder.append("for (")
(expression.loopParameter ?: expression.multiParameter)?.accept(this)
builder.append(" in ")
expression.loopRange?.accept(this)
builder.append(")")
expression.body?.let {
builder.append(' ')
it.accept(this)
}
}
override fun visitWhileExpression(expression: KtWhileExpression) {
builder.append("while (")
expression.condition?.accept(this)
builder.append(")")
expression.body?.let {
builder.append(' ')
it.accept(this)
}
}
override fun visitDoWhileExpression(expression: KtDoWhileExpression) {
builder.append("do")
expression.body?.let {
builder.append(' ')
it.accept(this)
}
builder.append(" while (")
expression.condition?.accept(this)
builder.append(")")
}
override fun visitTryExpression(expression: KtTryExpression) {
builder.append("try {...}")
}
// Declarations
override fun visitNamedFunction(function: KtNamedFunction) {
builder.append("fun")
function.receiverTypeReference?.let {
builder.append('.')
it.accept(this)
}
function.name?.let { builder.append(" $it") }
function.valueParameters.mapNotNull { it.typeReference }.joinTo(builder, prefix = "(", postfix = ")")
function.equalsToken?.let { builder.append(" = ") }
function.bodyExpression?.accept(this)
}
override fun visitPropertyAccessor(accessor: KtPropertyAccessor) {
builder.append(if(accessor.isGetter) "get" else "set")
builder.append("()")
accessor.equalsToken?.let { builder.append(" = ") }
accessor.bodyExpression?.accept(this)
}
override fun visitPrimaryConstructor(constructor: KtPrimaryConstructor) {
constructor.valueParameters.mapNotNull { it.typeReference }.joinTo(builder, prefix = "(", postfix = ")")
}
override fun visitSecondaryConstructor(constructor: KtSecondaryConstructor) {
builder.append("constructor")
constructor.valueParameters.mapNotNull { it.typeReference }.joinTo(builder, prefix = "(", postfix = ")")
constructor.bodyExpression?.accept(this)
}
override fun visitClassOrObject(classOrObject: KtClassOrObject) {
val keyword = when (classOrObject) {
is KtClass -> classOrObject.getClassOrInterfaceKeyword()
is KtObjectDeclaration -> classOrObject.getObjectKeyword()
else -> return
}
keyword?.accept(this)
classOrObject.name?.let { builder.append(" $it") }
classOrObject.getDelegationSpecifierList()?.accept(this)
classOrObject.getBody()?.let { builder.append(" {...}") }
}
override fun visitDelegationSpecifierList(list: KtDelegationSpecifierList) {
list.delegationSpecifiers.ifEmpty { return }.join(builder, prefix = " : ")
}
override fun visitDelegationByExpressionSpecifier(specifier: KtDelegatorByExpressionSpecifier) {
specifier.typeReference?.accept(this)
specifier.delegateExpression?.let {
builder.append(" by ")
it.accept(this)
}
}
override fun visitDelegationToSuperCallSpecifier(specifier: KtDelegatorToSuperCall) {
specifier.typeReference?.accept(this)
specifier.valueArgumentList?.accept(this)
}
override fun visitDelegationToSuperClassSpecifier(specifier: KtDelegatorToSuperClass) {
specifier.typeReference?.accept(this)
}
// Default
override fun visitElement(element: PsiElement) {
if (element is LeafPsiElement) {
builder.append(element.text)
}
else {
super.visitElement(element)
}
}
}
return Renderer().render(this)
}
+10
View File
@@ -0,0 +1,10 @@
fun foo() {
(<caret>1 + // abc
2 /*def*/) * 3
}
/*
1
1 + 2
(1 + 2) * 3
*/
+17
View File
@@ -0,0 +1,17 @@
interface T {
fun foo(a: () -> Any): T = this
}
fun foo(t: T) {
t.foo { <caret>1 }
.foo {2}
.foo({ 3 })
}
/*
1
{ 1 }
t.foo{...}
t.foo{...}.foo{...}
t.foo{...}.foo{...}.foo(...)
*/
+18
View File
@@ -0,0 +1,18 @@
interface T {
fun foo(vararg a: Any): T = this
}
fun foo(t: T) {
t.foo(
1 + 2,
<caret>2, 3
).foo()
.foo(4)
}
/*
2
t.foo(...)
t.foo(...).foo()
t.foo(...).foo().foo(...)
*/
+13
View File
@@ -0,0 +1,13 @@
fun foo() {
(<caret>1
+ 2 -
3)*(-
4)
}
/*
1
1 + 2
1 + 2 - 3
(1 + 2 - 3) * (-4)
*/
+16
View File
@@ -0,0 +1,16 @@
interface T {
fun foo(t: T) = t
}
fun foo() {
object : T {
val x = 1
}.foo(<caret>object : T {
val x = 2
})
}
/*
object : T {...}
object : T {...}.foo(...)
*/
+10
View File
@@ -0,0 +1,10 @@
fun foo() {
val t = ((<caret>1 + 2) + 3) * 4
}
/*
1
1 + 2
(1 + 2) + 3
((1 + 2) + 3) * 4
*/
+2 -2
View File
@@ -4,6 +4,6 @@ fun main(args: Array<String>) {
/*
1.0
1.0 + 1
Math.pow(1.0 + 1, Math.abs(-42.0))
print(Math.pow(1.0 + 1, Math.abs(-42.0)))
Math.pow(...)
print(...)
*/
@@ -38,7 +38,7 @@ public abstract class AbstractSmartSelectionTest extends LightCodeInsightTestCas
List<String> textualExpressions = new ArrayList<String>();
for (KtExpression expression : expressions) {
textualExpressions.add(expression.getText());
textualExpressions.add(KotlinRefactoringUtil.getExpressionShortText(expression));
}
assertEquals(expectedResultText, StringUtil.join(textualExpressions, "\n"));
}
@@ -35,6 +35,12 @@ public class SmartSelectionTestGenerated extends AbstractSmartSelectionTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/smartSelection"), Pattern.compile("^([^\\.]+)\\.kt$"), true);
}
@TestMetadata("commentsAndExtraSpaces.kt")
public void testCommentsAndExtraSpaces() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/smartSelection/commentsAndExtraSpaces.kt");
doTestSmartSelection(fileName);
}
@TestMetadata("labelledStatement.kt")
public void testLabelledStatement() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/smartSelection/labelledStatement.kt");
@@ -47,6 +53,36 @@ public class SmartSelectionTestGenerated extends AbstractSmartSelectionTest {
doTestSmartSelection(fileName);
}
@TestMetadata("lambdaCalls.kt")
public void testLambdaCalls() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/smartSelection/lambdaCalls.kt");
doTestSmartSelection(fileName);
}
@TestMetadata("multilineCalls.kt")
public void testMultilineCalls() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/smartSelection/multilineCalls.kt");
doTestSmartSelection(fileName);
}
@TestMetadata("multilineOperations.kt")
public void testMultilineOperations() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/smartSelection/multilineOperations.kt");
doTestSmartSelection(fileName);
}
@TestMetadata("objectLiteral.kt")
public void testObjectLiteral() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/smartSelection/objectLiteral.kt");
doTestSmartSelection(fileName);
}
@TestMetadata("parenthesized.kt")
public void testParenthesized() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/smartSelection/parenthesized.kt");
doTestSmartSelection(fileName);
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/smartSelection/simple.kt");