"JetPsiFactory.createExpressionByPattern" and some usages of it

This commit is contained in:
Valentin Kipyatkov
2015-04-29 22:51:21 +03:00
parent 0a5951fcb9
commit b0aca040d8
12 changed files with 218 additions and 67 deletions
@@ -23,6 +23,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.JetNodeType;
import static org.jetbrains.kotlin.psi.PsiPackage.JetPsiFactory;
import static org.jetbrains.kotlin.psi.PsiPackage.createExpressionByPattern;
public abstract class JetExpressionImpl extends JetElementImpl implements JetExpression {
public JetExpressionImpl(@NotNull ASTNode node) {
@@ -48,7 +49,7 @@ public abstract class JetExpressionImpl extends JetElementImpl implements JetExp
PsiElement parent = getParent();
if (parent instanceof JetExpression && newElement instanceof JetExpression &&
JetPsiUtil.areParenthesesNecessary((JetExpression) newElement, this, (JetExpression) parent)) {
return super.replace(JetPsiFactory(this).createExpression("(" + newElement.getText() + ")"));
return super.replace(createExpressionByPattern(JetPsiFactory(this), "($0)", newElement));
}
return super.replace(newElement);
}
@@ -24,6 +24,7 @@ import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import static org.jetbrains.kotlin.psi.PsiPackage.JetPsiFactory;
import static org.jetbrains.kotlin.psi.PsiPackage.createExpressionByPattern;
public abstract class JetExpressionImplStub<T extends StubElement> extends JetElementImplStub<T> implements JetExpression {
public JetExpressionImplStub(@NotNull T stub, @NotNull IStubElementType nodeType) {
@@ -46,7 +47,7 @@ public abstract class JetExpressionImplStub<T extends StubElement> extends JetEl
PsiElement parent = getParent();
if (parent instanceof JetExpression && newElement instanceof JetExpression &&
JetPsiUtil.areParenthesesNecessary((JetExpression) newElement, this, (JetExpression) parent)) {
return super.replace(JetPsiFactory(this).createExpression("(" + newElement.getText() + ")"));
return super.replace(createExpressionByPattern(JetPsiFactory(this), "($0)", newElement));
}
return super.replace(newElement);
}
@@ -0,0 +1,176 @@
/*
* 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.psi
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement
import com.intellij.psi.SmartPointerManager
import com.intellij.psi.SmartPsiElementPointer
import com.intellij.psi.codeStyle.CodeStyleManager
import com.intellij.psi.impl.source.codeStyle.CodeEditUtil
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.util.ArrayList
import java.util.HashMap
import java.util.LinkedHashMap
public fun JetPsiFactory.createExpressionByPattern(pattern: String, vararg args: Any): JetExpression {
val (processedText, allPlaceholders) = processPattern(pattern, args)
var expression = createExpression(processedText)
val project = expression.getProject()
val start = expression.getTextRange().getStartOffset()
val pointerManager = SmartPointerManager.getInstance(project)
val pointers = HashMap<SmartPsiElementPointer<JetExpression>, Int>()
for ((n, placeholders) in allPlaceholders) {
if (args[n] is String) continue // already in the text
for ((range, text) in placeholders) {
val token = expression.findElementAt(range.getStartOffset())!!
for (element in token.parents()) {
val elementRange = element.getTextRange().shiftRight(-start)
if (elementRange == range) {
val elementToUse = element.parents(withItself = false).firstIsInstanceOrNull<JetExpression>()
?: error("Invalid pattern - no JetExpression found for $n")
val pointer = pointerManager.createSmartPsiElementPointer(elementToUse)
pointers.put(pointer, n)
break
}
else if (!range.contains(elementRange)) {
throw IllegalArgumentException("Invalid pattern - no PsiElement found for $$n")
}
}
}
}
val codeStyleManager = CodeStyleManager.getInstance(project)
val stringPlaceholderRanges = allPlaceholders
.filter { args[it.getKey()] is String }
.flatMap { it.getValue() }
.map { it.range }
.sortBy { -it.getStartOffset() }
// reformat whole text except for String arguments (as they can contain user's formatting to be preserved)
if (stringPlaceholderRanges.none()) {
expression = codeStyleManager.reformat(expression, true) as JetExpression
}
else {
var bound = expression.getTextRange().getEndOffset() - 1
for (range in stringPlaceholderRanges) {
// we extend reformatting range by 1 to the right because otherwise some of spaces are not reformatted
expression = codeStyleManager.reformatRange(expression, range.getEndOffset() + start, bound + 1, true) as JetExpression
bound = range.getStartOffset() + start
}
expression = codeStyleManager.reformatRange(expression, start, bound + 1, true) as JetExpression
// we need to adjust indent of all lines within expression
codeStyleManager.adjustLineIndent(expression.getContainingFile(), expression.getTextRange())
}
// do not reformat the whole expression in PostprocessReformattingAspect
CodeEditUtil.setNodeGeneratedRecursively(expression.getNode(), false)
for ((pointer, n) in pointers) {
val element = pointer.getElement()!!
val arg = args[n]
if (arg !is JetExpression) {
throw IllegalArgumentException("Unknown argument $arg - should be JetExpression or String")
}
element.replace(arg)
}
return expression
}
private data class Placeholder(val range: TextRange, val text: String)
private data class PatternData(val processedText: String, val placeholders: Map<Int, List<Placeholder>>)
private fun processPattern(pattern: String, args: Array<out Any>): PatternData {
val ranges = LinkedHashMap<Int, MutableList<Placeholder>>()
fun charOrNull(i: Int) = if (i < pattern.length()) pattern[i] else null
fun check(condition: Boolean, message: String) {
if (!condition) {
throw IllegalArgumentException("Invalid pattern '$pattern' - $message")
}
}
val text = StringBuilder {
var i = 0
while (i < pattern.length()) {
var c = pattern[i]
if (c == '$') {
val nextChar = charOrNull(++i)
if (nextChar == '$') {
append(nextChar)
}
else {
check(nextChar?.isDigit() ?: false, "unclosed '$'")
val lastIndex = (i..pattern.length() - 1).firstOrNull { !pattern[it].isDigit() } ?: pattern.length()
val n = pattern.substring(i, lastIndex).toInt()
check(n >= 0, "invalid placeholder number: $n")
i = lastIndex
val arg: Any? = if (n < args.size()) args[n] else null /* report wrong number of arguments later */
val placeholderText = if (charOrNull(i) != '=') {
arg as? String ?: "xyz"
}
else {
check(arg !is String, "do not specify placeholder text for $$n - String argument passed")
i++
val endIndex = pattern.indexOf('$', i)
check(endIndex >= 0, "unclosed placeholder text")
check(endIndex > i, "empty placeholder text")
val text = pattern.substring(i, endIndex)
i = endIndex + 1
text
}
append(placeholderText)
val range = TextRange(length() - placeholderText.length(), length())
ranges.getOrPut(n, { ArrayList() }).add(Placeholder(range, placeholderText))
continue
}
}
else {
append(c)
}
i++
}
}.toString()
check(!ranges.isEmpty(), "no placeholders found")
val max = ranges.keySet().max()!!
for (i in 0..max) {
check(ranges.contains(i), "no '$$i' placeholder")
}
if (args.size() != ranges.size()) {
throw IllegalArgumentException("Wrong number of arguments, expected: ${ranges.size()}, passed: ${args.size()}")
}
return PatternData(text, ranges)
}
@@ -37,8 +37,8 @@ public class ConvertForEachToForLoopIntention : JetSelfTargetingOffsetIndependen
override fun applyTo(element: JetSimpleNameExpression, editor: Editor) {
val (expressionToReplace, receiver, functionLiteral) = extractData(element)!!
val loopText = generateLoopText(functionLiteral, receiver)
expressionToReplace.replace(JetPsiFactory(element).createExpression(loopText))
val loop = generateLoop(functionLiteral, receiver)
expressionToReplace.replace(loop)
}
private data class Data(
@@ -64,10 +64,11 @@ public class ConvertForEachToForLoopIntention : JetSelfTargetingOffsetIndependen
return Data(expression, receiver.getExpression(), functionLiteral)
}
private fun generateLoopText(functionLiteral: JetFunctionLiteralExpression, receiver: JetExpression): String {
val loopRangeText = JetPsiUtil.safeDeparenthesize(receiver).getText()
val bodyText = functionLiteral.getBodyExpression()!!.getText()
val varText = functionLiteral.getValueParameters().singleOrNull()?.getText() ?: "it"
return "for ($varText in $loopRangeText) { $bodyText }"
private fun generateLoop(functionLiteral: JetFunctionLiteralExpression, receiver: JetExpression): JetExpression {
val factory = JetPsiFactory(functionLiteral)
val loopRange = JetPsiUtil.safeDeparenthesize(receiver)
val body = functionLiteral.getBodyExpression()!!
val parameter = functionLiteral.getValueParameters().singleOrNull()
return factory.createExpressionByPattern("for($0 in $1){ $2 }", parameter ?: "it", loopRange, body)
}
}
@@ -17,13 +17,11 @@
package org.jetbrains.kotlin.idea.intentions
import com.intellij.openapi.editor.Editor
import org.jetbrains.kotlin.psi.JetProperty
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.idea.refactoring.CallableRefactoring
import org.jetbrains.kotlin.psi.JetPsiFactory
import com.intellij.psi.PsiWhiteSpace
import org.jetbrains.kotlin.psi.psiUtil.siblings
import com.intellij.util.containers.MultiMap
@@ -39,24 +37,22 @@ import org.jetbrains.kotlin.idea.search.usagesSearch.search
import org.jetbrains.kotlin.idea.references.JetSimpleNameReference
import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.kotlin.resolve.calls.callUtil.getCall
import org.jetbrains.kotlin.psi.JetSimpleNameExpression
import org.jetbrains.kotlin.idea.references.JetReference
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiField
import com.intellij.psi.PsiReferenceExpression
import com.intellij.psi.PsiElementFactory
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.psi.JetCallableReferenceExpression
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import com.intellij.psi.PsiJavaReference
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
import org.jetbrains.kotlin.psi.JetExpressionImpl
import org.jetbrains.kotlin.codegen.PropertyCodegen
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
import org.jetbrains.kotlin.idea.refactoring.getContainingScope
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.core.refactoring.checkConflictsInteractively
import org.jetbrains.kotlin.idea.core.refactoring.reportDeclarationConflict
import org.jetbrains.kotlin.psi.*
public class ConvertPropertyToFunctionIntention : JetSelfTargetingIntention<JetProperty>(javaClass(), "Convert property to function") {
private inner class Converter(
@@ -172,7 +168,7 @@ public class ConvertPropertyToFunctionIntention : JetSelfTargetingIntention<JetP
is PsiMethod -> it.setName(propertyName)
}
}
kotlinRefs.forEach { it.replace(kotlinPsiFactory.createExpression(it.getText() + "()")) }
kotlinRefs.forEach { it.replace(kotlinPsiFactory.createExpressionByPattern("$0()", it)) }
foreignRefsToRename.forEach { it.handleElementRename(propertyName) }
javaRefsToReplaceWithCall.forEach { it.replace(javaPsiFactory.createExpressionFromText(it.getText() + "()", null)) }
}
@@ -17,7 +17,10 @@
package org.jetbrains.kotlin.idea.intentions
import com.intellij.openapi.editor.Editor
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.JetBlockExpression
import org.jetbrains.kotlin.psi.JetForExpression
import org.jetbrains.kotlin.psi.JetPsiFactory
import org.jetbrains.kotlin.psi.createExpressionByPattern
public class ConvertToForEachFunctionCallIntention : JetSelfTargetingIntention<JetForExpression>(javaClass(), "Replace with a forEach function call") {
override fun isApplicableTo(element: JetForExpression, caretOffset: Int): Boolean {
@@ -29,16 +32,14 @@ public class ConvertToForEachFunctionCallIntention : JetSelfTargetingIntention<J
override fun applyTo(element: JetForExpression, editor: Editor) {
val body = element.getBody()!!
val loopParameter = element.getLoopParameter()!!
val factory = JetPsiFactory(element)
val functionBodyText = when (body) {
is JetBlockExpression -> body.getStatements().map { it.getText() }.joinToString("\n")
else -> body.getText()
}
val bodyText = "${loopParameter.getText()} -> $functionBodyText"
val foreachExpression = factory.createExpression("x.forEach { $bodyText }") as JetDotQualifiedExpression
foreachExpression.getReceiverExpression().replace(element.getLoopRange()!!)
val foreachExpression = JetPsiFactory(element).createExpressionByPattern(
"$0.forEach{$1->$2}", element.getLoopRange()!!, loopParameter, functionBodyText)
element.replace(foreachExpression)
}
}
@@ -62,9 +62,7 @@ public class IfNullToElvisIntention : JetSelfTargetingOffsetIndependentIntention
declaration.add(comment)
}
val elvis = factory.createExpression("a ?: b") as JetBinaryExpression
elvis.getLeft()!!.replace(initializer)
elvis.getRight()!!.replace(ifNullExpr)
val elvis = factory.createExpressionByPattern("$0 ?: $1", initializer, ifNullExpr) as JetBinaryExpression
val newElvis = initializer.replaced(elvis)
element.delete()
@@ -17,11 +17,8 @@
package org.jetbrains.kotlin.idea.intentions
import com.intellij.openapi.editor.Editor
import org.jetbrains.kotlin.psi.JetBinaryExpression
import org.jetbrains.kotlin.psi.JetPsiFactory
import org.jetbrains.kotlin.psi.JetFunctionLiteralExpression
import org.jetbrains.kotlin.psi.JetParenthesizedExpression
import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.psi.*
public class InfixCallToOrdinaryIntention : JetSelfTargetingIntention<JetBinaryExpression>(javaClass(), "Replace infix call with ordinary call") {
override fun isApplicableTo(element: JetBinaryExpression, caretOffset: Int): Boolean {
@@ -30,21 +27,12 @@ public class InfixCallToOrdinaryIntention : JetSelfTargetingIntention<JetBinaryE
}
override fun applyTo(element: JetBinaryExpression, editor: Editor) {
val receiverText = element.getLeft()!!.getText()
val argumentText = element.getRight()!!.getText()
val functionName = element.getOperationReference().getText()
val text = StringBuilder {
append(receiverText)
append(".")
append(functionName)
append(when (element.getRight()) {
is JetFunctionLiteralExpression -> " $argumentText"
is JetParenthesizedExpression -> argumentText
else -> "($argumentText)"
})
}.toString()
element.replace(JetPsiFactory(element).createExpression(text))
val argument = JetPsiUtil.safeDeparenthesize(element.getRight()!!)
val pattern = "$0.$1" + when (argument) {
is JetFunctionLiteralExpression -> " $2={}$"
else -> "($2)"
}
val replacement = JetPsiFactory(element).createExpressionByPattern(pattern, element.getLeft()!!, element.getOperationReference().getText(), argument)
element.replace(replacement)
}
}
@@ -75,10 +75,7 @@ public class InvertIfConditionIntention : JetSelfTargetingIntention<JetIfExpress
private fun negate(expression: JetExpression): JetExpression {
val specialNegation = specialNegationText(expression)
if (specialNegation != null) return specialNegation
val negationExpr = JetPsiFactory(expression).createExpression("!a") as JetPrefixExpression
negationExpr.getBaseExpression()!!.replace(expression)
return negationExpr
return JetPsiFactory(expression).createExpressionByPattern("!$0", expression)
}
private fun specialNegationText(expression: JetExpression): JetExpression? {
@@ -98,7 +95,7 @@ public class InvertIfConditionIntention : JetSelfTargetingIntention<JetIfExpress
if (operator !in NEGATABLE_OPERATORS) return null
val left = expression.getLeft() ?: return null
val right = expression.getRight() ?: return null
return factory.createExpression(left.getText() + " " + getNegatedOperatorText(operator) + " " + right.getText())
return factory.createExpressionByPattern("$0 $1 $2", left, getNegatedOperatorText(operator), right)
}
is JetConstantExpression -> {
@@ -18,14 +18,11 @@ package org.jetbrains.kotlin.idea.intentions
import com.intellij.openapi.editor.Editor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.psi.JetBinaryExpression
import org.jetbrains.kotlin.psi.JetCallExpression
import org.jetbrains.kotlin.psi.JetDotQualifiedExpression
import org.jetbrains.kotlin.psi.JetPsiFactory
import org.jetbrains.kotlin.psi.*
public class ToInfixCallIntention : JetSelfTargetingIntention<JetCallExpression>(javaClass(), "Replace with infix function call") {
override fun isApplicableTo(element: JetCallExpression, caretOffset: Int): Boolean {
val calleeExpr = element.getCalleeExpression() ?: return false
val calleeExpr = element.getCalleeExpression() as? JetSimpleNameExpression ?: return false
if (!calleeExpr.getTextRange().containsOffset(caretOffset)) return false
val dotQualified = element.getParent() as? JetDotQualifiedExpression ?: return false
@@ -46,13 +43,10 @@ public class ToInfixCallIntention : JetSelfTargetingIntention<JetCallExpression>
override fun applyTo(element: JetCallExpression, editor: Editor) {
val dotQualified = element.getParent() as JetDotQualifiedExpression
val receiver = dotQualified.getReceiverExpression()
val argument = element.getValueArguments().single().getArgumentExpression()!!
val name = element.getCalleeExpression()!!.getText()
val operatorText = element.getCalleeExpression()!!.getText()
val newCall = JetPsiFactory(element).createExpression("${receiver.getText()} $operatorText x") as JetBinaryExpression
val argument = element.getValueArguments().single()
newCall.getRight()!!.replace(argument.getArgumentExpression()!!)
val newCall = JetPsiFactory(element).createExpressionByPattern("$0 $1 $2", receiver, name, argument)
dotQualified.replace(newCall)
}
}
@@ -1,8 +1,7 @@
fun foo() {
val list = 1..4
<caret>for (x in list) {
x
// check that original formatting of "x+1" and "1 .. 4" is preserved
<caret>for (x in 1 .. 4) {
x
x+1
}
}
@@ -1,8 +1,7 @@
fun foo() {
val list = 1..4
list.forEach { x ->
x
// check that original formatting of "x+1" and "1 .. 4" is preserved
(1 .. 4).forEach { x ->
x
x+1
}
}