Extract Function: Analysis and code generation

This commit is contained in:
Alexey Sedunov
2014-03-25 19:08:53 +04:00
parent cceaa3b66f
commit 424c5f296f
10 changed files with 1066 additions and 14 deletions
@@ -67,12 +67,14 @@ public class JetControlFlowProcessor {
this.trace = trace; this.trace = trace;
} }
@NotNull
public Pseudocode generatePseudocode(@NotNull JetElement subroutine) { public Pseudocode generatePseudocode(@NotNull JetElement subroutine) {
Pseudocode pseudocode = generate(subroutine); Pseudocode pseudocode = generate(subroutine);
((PseudocodeImpl) pseudocode).postProcess(); ((PseudocodeImpl) pseudocode).postProcess();
return pseudocode; return pseudocode;
} }
@NotNull
private Pseudocode generate(@NotNull JetElement subroutine) { private Pseudocode generate(@NotNull JetElement subroutine) {
builder.enterSubroutine(subroutine); builder.enterSubroutine(subroutine);
CFPVisitor cfpVisitor = new CFPVisitor(builder); CFPVisitor cfpVisitor = new CFPVisitor(builder);
@@ -32,7 +32,7 @@ import org.jetbrains.jet.util.slicedmap.WritableSlice;
import java.util.Collection; import java.util.Collection;
public class PseudocodeUtil { public class PseudocodeUtil {
@NotNull
public static Pseudocode generatePseudocode(@NotNull JetDeclaration declaration, @NotNull final BindingContext bindingContext) { public static Pseudocode generatePseudocode(@NotNull JetDeclaration declaration, @NotNull final BindingContext bindingContext) {
BindingTrace mockTrace = new BindingTrace() { BindingTrace mockTrace = new BindingTrace() {
@NotNull @NotNull
@@ -28,7 +28,6 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.resolve.ImportPath; import org.jetbrains.jet.lang.resolve.ImportPath;
import org.jetbrains.jet.lang.resolve.name.Name; import org.jetbrains.jet.lang.resolve.name.Name;
import org.jetbrains.jet.lexer.JetKeywordToken; import org.jetbrains.jet.lexer.JetKeywordToken;
import org.jetbrains.jet.lexer.JetTokens;
import org.jetbrains.jet.plugin.JetFileType; import org.jetbrains.jet.plugin.JetFileType;
import java.util.Collections; import java.util.Collections;
@@ -132,7 +131,7 @@ public class JetPsiFactory {
} }
@NotNull @NotNull
private static PsiElement createWhiteSpace(Project project, String text) { public static PsiElement createWhiteSpace(Project project, String text) {
JetProperty property = createProperty(project, "val" + text + "x"); JetProperty property = createProperty(project, "val" + text + "x");
return property.findElementAt(3); return property.findElementAt(3);
} }
@@ -523,6 +522,135 @@ public class JetPsiFactory {
} }
} }
public static class FunctionBuilder {
static enum State {
MODIFIERS,
NAME,
FIRST_PARAM,
REST_PARAMS,
BODY,
DONE
}
private final StringBuilder sb = new StringBuilder();
private State state = State.MODIFIERS;
public FunctionBuilder() {
}
private void closeParams() {
assert state == State.FIRST_PARAM || state == State.REST_PARAMS;
sb.append(")");
state = State.BODY;
}
private void placeFun() {
assert state == State.MODIFIERS;
if (sb.length() != 0) {
sb.append(" ");
}
sb.append("fun ");
state = State.NAME;
}
@NotNull
public FunctionBuilder modifier(@NotNull String modifier) {
assert state == State.MODIFIERS;
sb.append(modifier);
return this;
}
@NotNull
public FunctionBuilder receiver(@NotNull String receiverType) {
placeFun();
sb.append(receiverType).append(".");
return this;
}
@NotNull
public FunctionBuilder noReceiver() {
placeFun();
return this;
}
@NotNull
public FunctionBuilder name(@NotNull String name) {
assert state == State.NAME;
sb.append(name).append("(");
state = State.FIRST_PARAM;
return this;
}
@NotNull
public FunctionBuilder param(@NotNull String name, @NotNull String type) {
assert state == State.FIRST_PARAM || state == State.REST_PARAMS;
if (state == State.REST_PARAMS) {
sb.append(", ");
}
sb.append(name).append(": ").append(type);
if (state == State.FIRST_PARAM) {
state = State.REST_PARAMS;
}
return this;
}
@NotNull
public FunctionBuilder returnType(@NotNull String type) {
closeParams();
sb.append(": ").append(type);
return this;
}
@NotNull
public FunctionBuilder noReturnType() {
closeParams();
return this;
}
@NotNull
public FunctionBuilder simpleBody(@NotNull String body) {
assert state == State.BODY;
sb.append(" = ").append(body);
state = State.DONE;
return this;
}
@NotNull
public FunctionBuilder blockBody(@NotNull String body) {
assert state == State.BODY;
sb.append(" {\n").append(body).append("\n}");
state = State.DONE;
return this;
}
@NotNull
public String toFunctionText() {
if (state != State.DONE) {
state = State.DONE;
}
return sb.toString();
}
}
@NotNull @NotNull
public static JetExpression createFunctionBody(Project project, @NotNull String bodyText) { public static JetExpression createFunctionBody(Project project, @NotNull String bodyText) {
JetFunction func = createFunction(project, "fun foo() {\n" + bodyText + "\n}"); JetFunction func = createFunction(project, "fun foo() {\n" + bodyText + "\n}");
@@ -104,6 +104,9 @@ public fun JetElement.outermostLastBlockElement(predicate: (JetElement) -> Boole
public fun JetBlockExpression.appendElement(element: JetElement): JetElement = public fun JetBlockExpression.appendElement(element: JetElement): JetElement =
addAfter(element, getRBrace()!!.getPrevSibling()!!)!! as JetElement addAfter(element, getRBrace()!!.getPrevSibling()!!)!! as JetElement
public fun JetBlockExpression.prependElement(element: JetElement): JetElement =
addBefore(element, getLBrace()!!.getNextSibling()!!)!! as JetElement
public fun JetElement.wrapInBlock(): JetBlockExpression { public fun JetElement.wrapInBlock(): JetBlockExpression {
val block = JetPsiFactory.createEmptyBody(getProject()) as JetBlockExpression val block = JetPsiFactory.createEmptyBody(getProject()) as JetBlockExpression
block.appendElement(this) block.appendElement(this)
@@ -256,4 +259,9 @@ public fun PsiDirectory.getPackage(): PsiPackage? = JavaDirectoryService.getInst
public fun JetModifierListOwner.isPrivate(): Boolean = hasModifier(JetTokens.PRIVATE_KEYWORD) public fun JetModifierListOwner.isPrivate(): Boolean = hasModifier(JetTokens.PRIVATE_KEYWORD)
public fun PsiElement.isInsideOf(elements: Iterable<PsiElement>): Boolean = elements.any { it.isAncestor(this) } public fun PsiElement.isInsideOf(elements: Iterable<PsiElement>): Boolean = elements.any { it.isAncestor(this) }
public tailRecursive fun PsiElement.getOutermostParentContainedIn(container: PsiElement): PsiElement? {
val parent = getParent()
return if (parent == container) this else parent?.getOutermostParentContainedIn(container)
}
@@ -25,6 +25,7 @@ import org.jetbrains.jet.lang.descriptors.ClassDescriptor
import org.jetbrains.jet.lang.descriptors.CallableDescriptor import org.jetbrains.jet.lang.descriptors.CallableDescriptor
import org.jetbrains.jet.lang.psi.JetSimpleNameExpression import org.jetbrains.jet.lang.psi.JetSimpleNameExpression
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import org.jetbrains.jet.lang.types.JetType
public val DeclarationDescriptor.importableFqName: FqName? public val DeclarationDescriptor.importableFqName: FqName?
get() { get() {
@@ -53,6 +54,11 @@ public fun DeclarationDescriptor.canBeReferencedViaImport(): Boolean {
return this is ClassDescriptor || this is ConstructorDescriptor return this is ClassDescriptor || this is ConstructorDescriptor
} }
public fun JetType.canBeReferencedViaImport(): Boolean {
val descriptor = getConstructor().getDeclarationDescriptor()
return descriptor != null && descriptor.canBeReferencedViaImport()
}
public fun isInReceiverScope(referenceElement: PsiElement, referencedDescriptor: DeclarationDescriptor): Boolean { public fun isInReceiverScope(referenceElement: PsiElement, referencedDescriptor: DeclarationDescriptor): Boolean {
val isExpressionWithReceiver = referenceElement is JetSimpleNameExpression && referenceElement.getReceiverExpression() != null val isExpressionWithReceiver = referenceElement is JetSimpleNameExpression && referenceElement.getReceiverExpression() != null
return isExpressionWithReceiver && !referencedDescriptor.isExtension return isExpressionWithReceiver && !referencedDescriptor.isExtension
@@ -6,8 +6,17 @@ cannot.refactor.no.container=Cannot refactor in this place
cannot.refactor.no.expression=Cannot perform refactoring without an expression cannot.refactor.no.expression=Cannot perform refactoring without an expression
cannot.refactor.expression.has.unit.type=Cannot introduce expression of unit type cannot.refactor.expression.has.unit.type=Cannot introduce expression of unit type
cannot.refactor.package.expression=Cannot introduce package reference cannot.refactor.package.expression=Cannot introduce package reference
extract.function=Extract Function
cannot.extract.method=Cannot find statements to extract cannot.extract.method=Cannot find statements to extract
cannot.find.class.to.extract=Cannot find class to extract method cannot.find.class.to.extract=Cannot find class to extract method
selected.code.fragment.has.multiple.output.values=Selected code fragment has multiple output values:
variables.are.used.outside.of.selected.code.fragment=Following variables are used outside of selected code fragment:
selected.code.fragment.has.multiple.exit.points=Selected code fragment has multiple exit points
selected.code.fragment.has.output.values.and.exit.points=Selected code fragment has output values as well as alternative exit points
parameter.types.are.not.denotable=Cannot extract method since following types are not denotable in the target scope:
declarations.will.move.out.of.scope=Following declarations won't be available outside of extracted function body:
cannot.extract.super.call=Cannot extract super-call
cannot.extract.non.local.declaration.ref=Cannot extract reference to non-local declaration
cannot.refactor.expression.should.have.inferred.type=Expression should have inferred type cannot.refactor.expression.should.have.inferred.type=Expression should have inferred type
cannot.refactor.synthesized.function=Cannot refactor synthesized function ''{0}'' cannot.refactor.synthesized.function=Cannot refactor synthesized function ''{0}''
@@ -26,4 +35,5 @@ refactoring.move.top.level.declaration.file.title=Choose Destination File
refactoring.move.non.kotlin.file=Target must be a Kotlin file refactoring.move.non.kotlin.file=Target must be a Kotlin file
package.private.0.will.no.longer.be.accessible.from.1=Package-private {0} will no longer be accessible from {1} package.private.0.will.no.longer.be.accessible.from.1=Package-private {0} will no longer be accessible from {1}
0.uses.package.private.1={0} uses package-private {1} 0.uses.package.private.1={0} uses package-private {1}
0.will.no.longer.be.accessible.after.extraction={0} will no longer be accessible after extraction
@@ -0,0 +1,139 @@
/*
* 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.refactoring.extractFunction
import com.intellij.psi.PsiElement
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor
import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall
import org.jetbrains.jet.lang.psi.JetFile
import com.intellij.openapi.project.Project
import org.jetbrains.jet.lang.psi.JetExpression
import com.intellij.openapi.util.TextRange
import kotlin.properties.Delegates
import java.util.HashMap
import org.jetbrains.jet.plugin.codeInsight.JetFileReferencesResolver
import org.jetbrains.jet.lang.psi.JetSimpleNameExpression
import org.jetbrains.jet.lang.psi.JetThisExpression
import org.jetbrains.jet.lang.resolve.BindingContext
import org.jetbrains.jet.plugin.codeInsight.DescriptorToDeclarationUtil
import java.util.Collections
import org.jetbrains.jet.lang.psi.JetBlockExpression
import org.jetbrains.jet.renderer.DescriptorRenderer
import org.jetbrains.jet.lang.psi.psiUtil.getParentByTypeAndBranch
import org.jetbrains.jet.lang.psi.JetQualifiedExpression
import org.jetbrains.jet.lang.psi.psiUtil.isInsideOf
import java.util.ArrayList
import com.intellij.psi.PsiNamedElement
import org.jetbrains.jet.lang.psi.JetSuperExpression
import org.jetbrains.jet.lang.types.JetType
import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache
data class ResolveResult(
val originalRefExpr: JetSimpleNameExpression,
val declaration: PsiNamedElement,
val descriptor: DeclarationDescriptor,
val resolvedCall: ResolvedCall<*>?
)
data class ResolvedReferenceInfo(
val refExpr: JetSimpleNameExpression,
val offsetInBody: Int,
val resolveResult: ResolveResult
)
class ExtractionData(
val originalFile: JetFile,
val originalElements: List<PsiElement>,
val nextSibling: PsiElement
) {
val project: Project = originalFile.getProject()
fun getExpressions(): List<JetExpression> = originalElements.filterIsInstance(javaClass<JetExpression>())
fun getCodeFragmentTextRange(): TextRange? {
val originalElements = originalElements
return when (originalElements.size) {
0 -> null
1 -> originalElements.first!!.getTextRange()
else -> {
val from = originalElements.first!!.getTextRange()!!.getStartOffset()
val to = originalElements.last!!.getTextRange()!!.getEndOffset()
TextRange(from, to)
}
}
}
fun getCodeFragmentText(): String =
getCodeFragmentTextRange()?.let { originalFile.getText()!!.substring(it.getStartOffset(), it.getEndOffset()) } ?: ""
val originalStartOffset = originalElements.first?.let { e -> e.getTextRange()!!.getStartOffset() }
val refOffsetToDeclaration by Delegates.lazy {
if (originalStartOffset != null) {
val resultMap = HashMap<Int, ResolveResult>()
for ((ref, context) in JetFileReferencesResolver.resolve(originalFile, getExpressions())) {
if (ref !is JetSimpleNameExpression) continue
val resolvedCallKey = (ref.getParent() as? JetThisExpression) ?: ref
val resolvedCall = context[BindingContext.RESOLVED_CALL, resolvedCallKey]
val descriptor = context[BindingContext.REFERENCE_TARGET, ref]
if (descriptor == null) continue
val declaration = DescriptorToDeclarationUtil.getDeclaration(project, descriptor, context) as? PsiNamedElement
if (declaration == null) continue
val offset = ref.getTextRange()!!.getStartOffset() - originalStartOffset
resultMap[offset] = ResolveResult(ref, declaration, descriptor, resolvedCall)
}
resultMap
}
else Collections.emptyMap<Int, ResolveResult>()
}
fun getBrokenReferencesInfo(body: JetBlockExpression): List<ResolvedReferenceInfo> {
fun compareDescriptors(d1: DeclarationDescriptor?, d2: DeclarationDescriptor?): Boolean {
return d1 == d2 || (d1 != null && d2 != null && DescriptorRenderer.TEXT.render(d1) == DescriptorRenderer.TEXT.render(d2))
}
val startOffset = body.getStatements().first!!.getTextRange()!!.getStartOffset()
val referencesInfo = ArrayList<ResolvedReferenceInfo>()
for ((ref, context) in JetFileReferencesResolver.resolve(body)) {
if (ref !is JetSimpleNameExpression) continue
val parent = ref.getParent()
if (parent is JetQualifiedExpression
&& parent.getSelectorExpression() == ref
&& parent.getReceiverExpression() !is JetSuperExpression) continue
val offset = ref.getTextRange()!!.getStartOffset() - startOffset
refOffsetToDeclaration[offset]?.let { originalResolveResult ->
val descriptor = context[BindingContext.REFERENCE_TARGET, ref]
if (!compareDescriptors(originalResolveResult.descriptor, descriptor)
&& !originalResolveResult.declaration.isInsideOf(originalElements)) {
referencesInfo.add(ResolvedReferenceInfo(ref, offset, originalResolveResult))
}
}
}
return referencesInfo
}
fun getInferredResultType(): JetType? =
getExpressions().last?.let { AnalyzerFacadeWithCache.getContextForElement(it)[BindingContext.EXPRESSION_TYPE, it] }
}
@@ -0,0 +1,123 @@
/*
* 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.refactoring.extractFunction
import org.jetbrains.jet.lang.types.JetType
import org.jetbrains.jet.lang.psi.JetPsiFactory
import org.jetbrains.jet.lang.psi.JetCallExpression
import org.jetbrains.jet.lang.psi.JetElement
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns
import com.intellij.util.containers.MultiMap
import com.intellij.psi.PsiElement
import org.jetbrains.jet.lang.psi.JetThisExpression
import org.jetbrains.jet.plugin.references.JetSimpleNameReference
import org.jetbrains.jet.lang.resolve.name.FqName
import org.jetbrains.jet.plugin.references.JetSimpleNameReference.ShorteningMode
data class Parameter(
val argumentText: String,
val name: String,
var mirrorVarName: String?,
val parameterType: JetType,
val receiverCandidate: Boolean
) {
val nameForRef: String get() = mirrorVarName ?: name
}
trait Replacement: Function1<JetElement, Unit>
trait ParameterReplacement : Replacement {
val parameter: Parameter
fun copy(parameter: Parameter): ParameterReplacement
}
class RenameReplacement(override val parameter: Parameter): ParameterReplacement {
override fun copy(parameter: Parameter) = RenameReplacement(parameter)
[suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")]
override fun invoke(e: JetElement) {
val thisExpr = e.getParent() as? JetThisExpression
(thisExpr ?: e).replace(JetPsiFactory.createSimpleName(e.getProject(), parameter.nameForRef))
}
}
class AddPrefixReplacement(override val parameter: Parameter): ParameterReplacement {
override fun copy(parameter: Parameter) = AddPrefixReplacement(parameter)
[suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")]
override fun invoke(e: JetElement) {
val selector = (e.getParent() as? JetCallExpression) ?: e
selector.replace(JetPsiFactory.createExpression(e.getProject(), "${parameter.nameForRef}.${selector.getText()}"))
}
}
class FqNameReplacement(val fqName: FqName): Replacement {
[suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")]
override fun invoke(e: JetElement) {
(e.getReference() as? JetSimpleNameReference)?.bindToFqName(fqName, ShorteningMode.NO_SHORTENING)
}
}
trait ControlFlow {
val returnType: JetType
}
object DefaultControlFlow: ControlFlow {
override val returnType: JetType get() = DEFAULT_RETURN_TYPE
}
trait JumpBasedControlFlow : ControlFlow {
val elementsToReplace: List<JetElement>
val elementToInsertAfterCall: JetElement
}
class ConditionalJump(
override val elementsToReplace: List<JetElement>,
override val elementToInsertAfterCall: JetElement
): JumpBasedControlFlow {
override val returnType: JetType get() = KotlinBuiltIns.getInstance().getBooleanType()
}
class UnconditionalJump(
override val elementsToReplace: List<JetElement>,
override val elementToInsertAfterCall: JetElement
): JumpBasedControlFlow {
override val returnType: JetType get() = KotlinBuiltIns.getInstance().getUnitType()
}
class ExpressionEvaluation(override val returnType: JetType): ControlFlow
class ExpressionEvaluationWithCallSiteReturn(override val returnType: JetType): ControlFlow
class ParameterUpdate(val parameter: Parameter): ControlFlow {
override val returnType: JetType get() = parameter.parameterType
}
data class ExtractionDescriptor(
val extractionData: ExtractionData,
val name: String,
val visibility: String,
val parameters: List<Parameter>,
val receiverParameter: Parameter?,
val replacementMap: Map<Int, Replacement>,
val controlFlow: ControlFlow
)
class ExtractionDescriptorWithConflicts(
val descriptor: ExtractionDescriptor,
val conflicts: MultiMap<PsiElement, String>
)
@@ -0,0 +1,626 @@
/*
* 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.refactoring.extractFunction
import com.intellij.psi.PsiElement
import org.jetbrains.jet.lang.types.JetType
import org.jetbrains.jet.lang.psi.JetNamedDeclaration
import org.jetbrains.jet.lang.psi.JetNamedFunction
import org.jetbrains.jet.renderer.DescriptorRenderer
import org.jetbrains.jet.lang.psi.JetSimpleNameExpression
import org.jetbrains.jet.lang.psi.JetPsiFactory
import java.util.HashMap
import org.jetbrains.jet.lang.psi.JetParameter
import com.intellij.util.containers.MultiMap
import org.jetbrains.jet.lang.psi.JetBlockExpression
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor
import org.jetbrains.jet.lang.descriptors.CallableDescriptor
import org.jetbrains.jet.plugin.refactoring.JetRefactoringBundle
import org.jetbrains.jet.lang.cfg.pseudocode.PseudocodeUtil
import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache
import org.jetbrains.jet.plugin.util.MaybeValue
import org.jetbrains.jet.plugin.util.Maybe
import org.jetbrains.jet.lang.psi.psiUtil.isInsideOf
import java.util.HashSet
import org.jetbrains.jet.lang.cfg.pseudocode.WriteValueInstruction
import org.jetbrains.jet.lang.descriptors.VariableDescriptor
import org.jetbrains.jet.plugin.util.MaybeError
import org.jetbrains.jet.lang.psi.psiUtil.appendElement
import org.jetbrains.jet.plugin.refactoring.createTempCopy
import org.jetbrains.jet.lang.psi.psiUtil.getParentByType
import org.jetbrains.jet.lang.psi.JetElement
import org.jetbrains.jet.plugin.refactoring.JetNameSuggester
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns
import org.jetbrains.jet.lang.psi.JetProperty
import org.jetbrains.jet.lang.psi.psiUtil.prependElement
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.jet.lang.cfg.pseudocode.JetElementInstruction
import org.jetbrains.jet.lang.cfg.pseudocode.AbstractJumpInstruction
import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverValue
import org.jetbrains.jet.lang.resolve.scopes.receivers.ThisReceiver
import org.jetbrains.jet.lang.descriptors.ClassDescriptor
import org.jetbrains.jet.lang.psi.JetThisExpression
import org.jetbrains.jet.lang.psi.JetDeclarationWithBody
import org.jetbrains.jet.lang.cfg.pseudocode.Instruction
import java.util.ArrayList
import org.jetbrains.jet.lang.cfg.pseudocode.ReadValueInstruction
import org.jetbrains.jet.lang.cfg.pseudocode.CallInstruction
import org.jetbrains.jet.lang.types.CommonSupertypes
import org.jetbrains.jet.lang.resolve.BindingContext
import org.jetbrains.jet.lang.cfg.pseudocode.ReturnValueInstruction
import org.jetbrains.jet.lang.cfg.Label
import org.jetbrains.jet.lang.psi.JetReturnExpression
import org.jetbrains.jet.lang.psi.JetBreakExpression
import org.jetbrains.jet.lang.psi.JetContinueExpression
import org.jetbrains.jet.lang.psi.JetPsiFactory.FunctionBuilder
import org.jetbrains.jet.plugin.refactoring.JetNameValidatorImpl
import org.jetbrains.jet.lang.psi.JetExpression
import org.jetbrains.jet.plugin.codeInsight.DescriptorToDeclarationUtil
import org.jetbrains.jet.lang.psi.JetThrowExpression
import org.jetbrains.jet.plugin.imports.canBeReferencedViaImport
import org.jetbrains.jet.plugin.codeInsight.ShortenReferences
import org.jetbrains.jet.lang.cfg.pseudocode.LocalFunctionDeclarationInstruction
import org.jetbrains.jet.lang.descriptors.ClassKind
import org.jetbrains.jet.lang.psi.JetSuperExpression
import org.jetbrains.jet.lang.psi.JetQualifiedExpression
import org.jetbrains.jet.lang.psi.JetCallExpression
import org.jetbrains.jet.lang.psi.JetTreeVisitorVoid
import org.jetbrains.jet.lang.types.checker.JetTypeChecker
import org.jetbrains.jet.lang.resolve.name.FqName
import org.jetbrains.jet.lang.resolve.DescriptorUtils
private val DEFAULT_FUNCTION_NAME = "myFun"
private val DEFAULT_RETURN_TYPE = KotlinBuiltIns.getInstance().getUnitType()
private val DEFAULT_PARAMETER_TYPE = KotlinBuiltIns.getInstance().getAnyType()
private fun JetType.isDefault(): Boolean = KotlinBuiltIns.getInstance().isUnit(this)
private fun List<Instruction>.getModifiedVarDescriptors(bindingContext: BindingContext): Set<VariableDescriptor> {
return this
.map {if (it is WriteValueInstruction) PseudocodeUtil.extractVariableDescriptorIfAny(it, true, bindingContext) else null}
.filterNotNullTo(HashSet<VariableDescriptor>())
}
private fun List<Instruction>.getExitPoints(): List<Instruction> =
filter { localInstruction -> localInstruction.getNextInstructions().any { it !in this } }
private fun List<Instruction>.getResultType(bindingContext: BindingContext): JetType {
fun instructionToType(instruction: Instruction): JetType? {
return when (instruction) {
is ReadValueInstruction -> {
PseudocodeUtil.extractVariableDescriptorIfAny(instruction, true, bindingContext)?.getType()
}
is CallInstruction -> {
instruction.resolvedCall.getResultingDescriptor()?.getReturnType()
}
is ReturnValueInstruction -> {
val expression = (instruction.getElement() as JetReturnExpression).getReturnedExpression()
bindingContext[BindingContext.EXPRESSION_TYPE, expression]
}
else -> null
}
}
val resultTypes = map(::instructionToType).filterNotNull()
return if (resultTypes.isNotEmpty()) CommonSupertypes.commonSupertype(resultTypes) else DEFAULT_RETURN_TYPE
}
private fun List<AbstractJumpInstruction>.checkEquivalence(checkPsi: Boolean): Boolean {
if (mapTo(HashSet<Label?>()) { it.getTargetLabel() }.size > 1) return false
return !checkPsi || mapTo(HashSet<String?>()) { it.getElement().getText() }.size <= 1
}
private fun List<Instruction>.analyzeControlFlow(
bindingContext: BindingContext,
parameters: Set<Parameter>,
inferredType: JetType?
): Maybe<ControlFlow, String> {
val outParameters = parameters.filterTo(HashSet<Parameter>()) { it.mirrorVarName != null }
if (outParameters.size > 1) {
val outValuesStr = outParameters.map { it.argumentText }.sort().makeString(separator = "\n")
return MaybeError("${JetRefactoringBundle.message("selected.code.fragment.has.multiple.output.values")!!}\n$outValuesStr")
}
val exitPoints = getExitPoints()
val valuedReturnExits = ArrayList<ReturnValueInstruction>()
val defaultExits = ArrayList<Instruction>()
val jumpExits = ArrayList<AbstractJumpInstruction>()
exitPoints.forEach {
when (it) {
is ReturnValueInstruction -> valuedReturnExits.add(it)
is AbstractJumpInstruction -> {
val element = it.getElement()
if (element is JetReturnExpression
|| element is JetBreakExpression
|| element is JetContinueExpression
|| element is JetThrowExpression) {
jumpExits.add(it)
}
else {
defaultExits.add(it)
}
}
else -> if (it !is LocalFunctionDeclarationInstruction) {
defaultExits.add(it)
}
}
}
val builtins = KotlinBuiltIns.getInstance()
val hasMeaningfulType = inferredType != null && !builtins.isUnit(inferredType) && !builtins.isNothing(inferredType)
if (outParameters.isNotEmpty()) {
if (hasMeaningfulType || valuedReturnExits.isNotEmpty() || jumpExits.isNotEmpty()) {
return MaybeError(JetRefactoringBundle.message("selected.code.fragment.has.output.values.and.exit.points")!!)
}
return MaybeValue(ParameterUpdate(outParameters.first()))
}
val multipleExitsError =
MaybeError<ControlFlow, String>(JetRefactoringBundle.message("selected.code.fragment.has.multiple.exit.points")!!)
if (hasMeaningfulType) {
if (valuedReturnExits.isNotEmpty() || jumpExits.isNotEmpty()) return multipleExitsError
return MaybeValue(ExpressionEvaluation(inferredType!!))
}
if (valuedReturnExits.isNotEmpty()) {
if (jumpExits.isNotEmpty()) return multipleExitsError
if (defaultExits.isNotEmpty()) {
if (valuedReturnExits.size != 1) return multipleExitsError
val element = valuedReturnExits.first!!.getElement()
return MaybeValue(ConditionalJump(listOf(element), element))
}
if (!valuedReturnExits.checkEquivalence(false)) return multipleExitsError
return MaybeValue(ExpressionEvaluationWithCallSiteReturn(valuedReturnExits.getResultType(bindingContext)))
}
if (jumpExits.isNotEmpty()) {
if (!jumpExits.checkEquivalence(true)) return multipleExitsError
val elements = jumpExits.map { it.getElement() }
if (defaultExits.isNotEmpty()) return MaybeValue(ConditionalJump(elements, elements.first!!))
return MaybeValue(UnconditionalJump(elements, elements.first!!))
}
return MaybeValue(DefaultControlFlow)
}
private fun ExtractionData.createTemporaryCodeBlock(): JetBlockExpression {
val position = nextSibling.getTextRange()!!.getStartOffset()
val tmpFile = originalFile.createTempCopy { text ->
StringBuilder(text).insert(position, "fun() {\n${getCodeFragmentText()}\n}\n").toString()
}
val tmpFunction = tmpFile.findElementAt(position)?.getParentByType(javaClass<JetNamedFunction>())!!
return tmpFunction.getBodyExpression() as JetBlockExpression
}
private fun ExtractionData.inferParametersInfo(
commonParent: PsiElement,
localInstructions: List<Instruction>,
bindingContext: BindingContext,
resultType: JetType?,
replacementMap: MutableMap<Int, Replacement>,
parameters: MutableSet<Parameter>
): MaybeError<ExtractionDescriptor, String>? {
val varNameValidator = JetNameValidatorImpl(commonParent.getParentByType(javaClass<JetExpression>()), originalElements.first, true)
val modifiedVarDescriptors = localInstructions.getModifiedVarDescriptors(bindingContext)
val extractedDescriptorToParameter = HashMap<DeclarationDescriptor, Parameter>()
val nonDenotableTypes = HashSet<JetType>()
for (refInfo in getBrokenReferencesInfo(createTemporaryCodeBlock())) {
val (originalRef, originalDeclaration, originalDescriptor, resolvedCall) = refInfo.resolveResult
val ref = refInfo.refExpr
val selector = (ref.getParent() as? JetCallExpression) ?: ref
val superExpr = (selector.getParent() as? JetQualifiedExpression)?.getReceiverExpression() as? JetSuperExpression
if (superExpr != null) {
return MaybeError(JetRefactoringBundle.message("cannot.extract.super.call")!!)
}
val receiverArgument = resolvedCall?.getReceiverArgument()
val receiver = when(receiverArgument) {
ReceiverValue.NO_RECEIVER -> resolvedCall?.getThisObject()
else -> receiverArgument
} ?: ReceiverValue.NO_RECEIVER
val thisDescriptor = (receiver as? ThisReceiver)?.getDeclarationDescriptor()
val hasThisReceiver = thisDescriptor != null
val thisExpr = ref.getParent() as? JetThisExpression
val hasClassObjectReceiver = (thisDescriptor as? ClassDescriptor)?.getKind() == ClassKind.CLASS_OBJECT
val classObjectClassDescriptor =
if (hasClassObjectReceiver) thisDescriptor!!.getContainingDeclaration() as? ClassDescriptor else null
if (classObjectClassDescriptor != null) {
if (!classObjectClassDescriptor.canBeReferencedViaImport()) {
return MaybeError(JetRefactoringBundle.message("cannot.extract.non.local.declaration.ref")!!)
}
replacementMap[refInfo.offsetInBody] = FqNameReplacement(DescriptorUtils.getFqNameSafe(originalDescriptor))
}
else {
val extractThis = hasThisReceiver || thisExpr != null
val extractLocalVar =
(originalDeclaration is JetProperty && originalDeclaration.isLocal()) || originalDeclaration is JetParameter
val descriptorToExtract = (if (extractThis) thisDescriptor else null) ?: originalDescriptor
val extractParameter = extractThis || extractLocalVar
if (extractParameter) {
val parameterType =
if (hasThisReceiver) {
when (descriptorToExtract) {
is ClassDescriptor -> descriptorToExtract.getDefaultType()
is CallableDescriptor -> descriptorToExtract.getReceiverParameter()?.getType()
else -> null
} ?: DEFAULT_PARAMETER_TYPE
}
else bindingContext[BindingContext.EXPRESSION_TYPE, originalRef] ?: DEFAULT_PARAMETER_TYPE
if (!parameterType.canBeReferencedViaImport()) {
nonDenotableTypes.add(parameterType)
continue
}
val existingParameter = extractedDescriptorToParameter[descriptorToExtract]
val parameter: Parameter =
if (existingParameter != null) {
if (!JetTypeChecker.INSTANCE.equalTypes(existingParameter.parameterType, parameterType)) {
val newParameter = existingParameter.copy(
parameterType = CommonSupertypes.commonSupertype(listOf(existingParameter.parameterType, parameterType))
)
extractedDescriptorToParameter[descriptorToExtract] = newParameter
for ((offset, replacement) in replacementMap) {
if (replacement is ParameterReplacement && replacement.parameter == existingParameter) {
replacementMap[offset] = replacement.copy(newParameter)
}
}
newParameter
}
else existingParameter
}
else {
val parameterName: String =
if (extractThis) {
JetNameSuggester.suggestNames(parameterType, varNameValidator, null).first()
}
else originalDeclaration.getName()!!
val mirrorVarName: String?
if (descriptorToExtract in modifiedVarDescriptors) {
mirrorVarName = varNameValidator.validateName(parameterName)
}
else {
mirrorVarName = null
}
val argumentText =
if (hasThisReceiver && extractThis)
"this@${parameterType.getConstructor().getDeclarationDescriptor()!!.getName().asString()}"
else
(thisExpr ?: ref).getText()!!
val parameter = Parameter(argumentText, parameterName, mirrorVarName, parameterType, extractThis)
extractedDescriptorToParameter[descriptorToExtract] = parameter
parameter
}
replacementMap[refInfo.offsetInBody] =
if (hasThisReceiver && extractThis) AddPrefixReplacement(parameter) else RenameReplacement(parameter)
}
else if (originalDeclaration is JetProperty) {
return MaybeError(JetRefactoringBundle.message("cannot.extract.non.local.declaration.ref")!!)
}
}
}
if (resultType != null && !resultType.canBeReferencedViaImport()) {
nonDenotableTypes.add(resultType)
}
if (nonDenotableTypes.isNotEmpty()) {
val typeStr = nonDenotableTypes.map {
DescriptorRenderer.HTML.renderType(it)
}.sort().makeString(separator = "\n")
return MaybeError("${JetRefactoringBundle.message("parameter.types.are.not.denotable")!!}\n$typeStr")
}
parameters.addAll(extractedDescriptorToParameter.values())
return null
}
private fun ExtractionData.checkLocalDeclarationsWithNonLocalUsages(
allInstructions: List<Instruction>,
localInstructions: List<Instruction>,
bindingContext: BindingContext
): MaybeError<ExtractionDescriptor, String>? {
// todo: non-locally used declaration can be turned into the output value
val declarations = ArrayList<JetNamedDeclaration>()
for (instruction in allInstructions) {
if (instruction in localInstructions) continue
PseudocodeUtil.extractVariableDescriptorIfAny(instruction, true, bindingContext)?.let { descriptor ->
val declaration = DescriptorToDeclarationUtil.getDeclaration(project, descriptor, bindingContext)
if (declaration is JetNamedDeclaration && declaration.isInsideOf(originalElements)) {
declarations.add(declaration)
}
}
}
if (declarations.isNotEmpty()) {
val localVarStr = declarations.map { it.getName()!! }.sort().makeString(separator = "\n")
return MaybeError("${JetRefactoringBundle.message("variables.are.used.outside.of.selected.code.fragment")!!}\n$localVarStr")
}
return null
}
private fun ExtractionData.checkDeclarationsMovingOutOfScope(controlFlow: ControlFlow): MaybeError<ExtractionDescriptor, String>? {
val declarationsOutOfScope = HashSet<JetNamedDeclaration>()
if (controlFlow is JumpBasedControlFlow) {
controlFlow.elementToInsertAfterCall.accept(
object: JetTreeVisitorVoid() {
override fun visitSimpleNameExpression(expression: JetSimpleNameExpression) {
val target = expression.getReference()?.resolve()
if (target is JetNamedDeclaration && target.isInsideOf(originalElements)) {
declarationsOutOfScope.add(target)
}
}
}
)
}
if (declarationsOutOfScope.isNotEmpty()) {
val declStr = declarationsOutOfScope.map { it.getName()!! }.sort().makeString(separator = "\n")
return MaybeError("${JetRefactoringBundle.message("declarations.will.move.out.of.scope")!!}\n$declStr")
}
return null
}
fun ExtractionData.performAnalysis(): Maybe<ExtractionDescriptor, String> {
if (originalElements.empty) return MaybeError(JetRefactoringBundle.message("cannot.refactor.no.expresson")!!)
val commonParent = PsiTreeUtil.findCommonParent(originalElements)!!
val enclosingDeclaration = commonParent.getParentByType(javaClass<JetDeclarationWithBody>())
if (enclosingDeclaration == null) return MaybeError(JetRefactoringBundle.message("cannot.refactor.no.container")!!)
val resolveSession = AnalyzerFacadeWithCache.getLazyResolveSessionForFile(originalFile)
val bindingContext = resolveSession.resolveToElement(enclosingDeclaration.getBodyExpression())
val pseudocode = PseudocodeUtil.generatePseudocode(enclosingDeclaration, bindingContext)
val localInstructions = pseudocode.getInstructions().filter {
it is JetElementInstruction && it.getElement().isInsideOf(originalElements)
}
val inferredResultType = getInferredResultType()
val replacementMap = HashMap<Int, Replacement>()
val parameters = HashSet<Parameter>()
val parameterError =
inferParametersInfo(commonParent, localInstructions, bindingContext, inferredResultType, replacementMap, parameters)
if (parameterError != null) return parameterError
val controlFlowInfo = localInstructions.analyzeControlFlow(bindingContext, parameters, inferredResultType)
if (controlFlowInfo is MaybeError) return MaybeError(controlFlowInfo.error)
val controlFlow = (controlFlowInfo as MaybeValue).value
val localDeclError = checkLocalDeclarationsWithNonLocalUsages(pseudocode.getInstructions(), localInstructions, bindingContext)
if (localDeclError != null) return localDeclError
val outOfScopeError = checkDeclarationsMovingOutOfScope(controlFlow)
if (outOfScopeError != null) return outOfScopeError
val functionNameValidator = JetNameValidatorImpl(nextSibling.getParent(), nextSibling, false)
val functionName = JetNameSuggester.suggestNames(controlFlow.returnType, functionNameValidator, DEFAULT_FUNCTION_NAME).first()
val receiverCandidates = parameters.filterTo(HashSet<Parameter>()) { it.receiverCandidate }
val receiverParameter = if (receiverCandidates.size == 1) receiverCandidates.first() else null
receiverParameter?.let { parameters.remove(it) }
return MaybeValue(
ExtractionDescriptor(this, functionName, "", parameters.sortBy { it.name }, receiverParameter, replacementMap, controlFlow)
)
}
fun ExtractionDescriptor.validate(): ExtractionDescriptorWithConflicts {
// todo: add name conflict checks (parameters, function, etc.)
return ExtractionDescriptorWithConflicts(this, MultiMap<PsiElement, String>())
}
fun ExtractionDescriptor.getFunctionText(
withBody: Boolean = true,
descriptorRenderer: DescriptorRenderer = DescriptorRenderer.TEXT
): String {
return FunctionBuilder().let { builder ->
builder.modifier(visibility)
receiverParameter?.let { builder.receiver(descriptorRenderer.renderType(it.parameterType)) } ?: builder.noReceiver()
builder.name(name)
parameters.forEach { parameter ->
builder.param(parameter.name, descriptorRenderer.renderType(parameter.parameterType))
}
with(controlFlow.returnType) {
if (isDefault()) builder.noReturnType() else builder.returnType(descriptorRenderer.renderType(this))
}
if (withBody) {
builder.blockBody(extractionData.getCodeFragmentText())
}
builder.toFunctionText()
}
}
fun ExtractionDescriptor.generateFunction(inTempFile: Boolean = false): JetNamedFunction {
val project = extractionData.project
fun createFunction(): JetNamedFunction {
return with(extractionData) {
if (inTempFile) {
val position = nextSibling.getTextRange()!!.getStartOffset()
val tmpFile = originalFile.createTempCopy { text ->
StringBuilder(text).insert(position, getFunctionText() + "\n").toString()
}
tmpFile.findElementAt(position)?.getParentByType(javaClass<JetNamedFunction>())!!
}
else {
JetPsiFactory.createFunction(project, getFunctionText())
}
}
}
fun adjustFunctionBody(function: JetNamedFunction) {
val body = function.getBodyExpression() as JetBlockExpression
val exprReplacementMap = HashMap<JetElement, (JetElement) -> Unit>()
val range = body.getStatements().first?.getTextRange()
if (range == null) return
val bodyOffset = range.getStartOffset()
val file = body.getContainingFile()!!
for ((offsetInBody, replacement) in replacementMap) {
if (replacement is ParameterReplacement && replacement.parameter == receiverParameter) continue
val expr = file.findElementAt(bodyOffset + offsetInBody)?.getParentByType(javaClass<JetSimpleNameExpression>())
assert(expr != null, "Couldn't find expression at $offsetInBody in '${body.getText()}'")
exprReplacementMap[expr!!] = replacement
}
if (controlFlow is JumpBasedControlFlow) {
val replacementExpr = JetPsiFactory.createExpression(
project,
if (controlFlow is ConditionalJump) "return true" else "return"
)
for (jumpElement in controlFlow.elementsToReplace) {
val offsetInBody = jumpElement.getTextRange()!!.getStartOffset() - extractionData.originalStartOffset!!
val expr = file.findElementAt(bodyOffset + offsetInBody)?.getParentByType(jumpElement.javaClass)
assert(expr != null, "Couldn't find expression at $offsetInBody in '${body.getText()}'")
exprReplacementMap[expr!!] = { it.replace(replacementExpr) }
}
}
for ((expr, replacement) in exprReplacementMap) {
replacement(expr)
}
for (param in parameters) {
param.mirrorVarName?.let { varName ->
body.prependElement(JetPsiFactory.createProperty(project, varName, null, true, param.name))
}
}
when (controlFlow) {
is ParameterUpdate ->
body.appendElement(JetPsiFactory.createReturn(project, controlFlow.parameter.nameForRef))
is ConditionalJump ->
body.appendElement(JetPsiFactory.createReturn(project, "false"))
is ExpressionEvaluation ->
body.getStatements().last?.let { it.replace(JetPsiFactory.createReturn(project, it.getText()!!)) }
}
}
fun insertFunction(function: JetNamedFunction): JetNamedFunction {
return with(extractionData) {
val targetContainer = nextSibling.getParent()!!
val functionInFile = targetContainer.addBefore(function, nextSibling) as JetNamedFunction
targetContainer.addBefore(JetPsiFactory.createWhiteSpace(project, "\n\n"), nextSibling)
functionInFile
}
}
fun makeCall(function: JetNamedFunction): JetNamedFunction {
val anchor = extractionData.originalElements.first
if (anchor == null) return function
anchor.getNextSibling()?.let { from ->
val to = extractionData.originalElements.last
if (to != anchor) {
anchor.getParent()!!.deleteChildRange(from, to);
}
}
val callText = parameters
.map { it.argumentText }
.makeString(separator = ", ", prefix = "$name(", postfix = ")")
val wrappedCall = when (controlFlow) {
is ExpressionEvaluationWithCallSiteReturn ->
JetPsiFactory.createReturn(project, callText)
is ParameterUpdate ->
JetPsiFactory.createExpression(project, "${controlFlow.parameter.argumentText} = $callText")
is ConditionalJump ->
JetPsiFactory.createExpression(project, "if ($callText) ${controlFlow.elementToInsertAfterCall.getText()}")
is UnconditionalJump -> {
val anchorParent = anchor.getParent()!!
anchorParent.addAfter(
JetPsiFactory.createExpression(project, controlFlow.elementToInsertAfterCall.getText()),
anchor
)
anchorParent.addAfter(JetPsiFactory.createNewLine(project), anchor)
JetPsiFactory.createExpression(project, callText)
}
else ->
JetPsiFactory.createExpression(project, callText)
}
anchor.replace(wrappedCall)
return function
}
val function = createFunction()
adjustFunctionBody(function)
if (inTempFile) return function
val functionInPlace = makeCall(insertFunction(function))
ShortenReferences.process(functionInPlace)
return functionInPlace
}
@@ -16,25 +16,17 @@
package org.jetbrains.jet.plugin.refactoring package org.jetbrains.jet.plugin.refactoring
import org.jetbrains.jet.lang.psi.JetSimpleNameExpression
import org.jetbrains.jet.lang.resolve.name.FqName import org.jetbrains.jet.lang.resolve.name.FqName
import org.jetbrains.jet.lang.psi.JetElement
import org.jetbrains.jet.lang.psi.JetCallExpression
import org.jetbrains.jet.lang.psi.JetPsiFactory
import org.jetbrains.jet.lang.psi.psiUtil.getQualifiedElement import org.jetbrains.jet.lang.psi.psiUtil.getQualifiedElement
import org.jetbrains.jet.lang.psi.JetUserType
import org.jetbrains.jet.lang.resolve.name.isOneSegmentFQN import org.jetbrains.jet.lang.resolve.name.isOneSegmentFQN
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
import com.intellij.psi.PsiDirectory import com.intellij.psi.PsiDirectory
import org.jetbrains.jet.lang.psi.JetFile
import com.intellij.openapi.roots.JavaProjectRootsUtil import com.intellij.openapi.roots.JavaProjectRootsUtil
import com.intellij.psi.PsiPackage import com.intellij.psi.PsiPackage
import com.intellij.psi.PsiClass import com.intellij.psi.PsiClass
import com.intellij.psi.PsiMember import com.intellij.psi.PsiMember
import org.jetbrains.jet.lang.psi.JetPsiUtil
import org.jetbrains.jet.asJava.namedUnwrappedElement import org.jetbrains.jet.asJava.namedUnwrappedElement
import org.jetbrains.jet.lang.psi.JetNamedDeclaration
import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiTreeUtil
import com.intellij.refactoring.util.ConflictsUtil import com.intellij.refactoring.util.ConflictsUtil
import org.jetbrains.jet.lang.psi.psiUtil.getPackage import org.jetbrains.jet.lang.psi.psiUtil.getPackage
@@ -46,6 +38,17 @@ import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.psi.PsiManager import com.intellij.psi.PsiManager
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.jet.lang.psi.*
import java.util.ArrayList
import com.intellij.openapi.application.ApplicationManager
import com.intellij.refactoring.BaseRefactoringProcessor.ConflictsInTestsException
import com.intellij.refactoring.ui.ConflictsDialog
import com.intellij.util.containers.MultiMap
import com.intellij.openapi.command.CommandProcessor
import org.jetbrains.jet.lexer.JetTokens
import org.jetbrains.jet.lang.psi.psiUtil.getParentByType
import org.jetbrains.jet.plugin.codeInsight.TipsManager
import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache
/** /**
* Replace [[JetSimpleNameExpression]] (and its enclosing qualifier) with qualified element given by FqName * Replace [[JetSimpleNameExpression]] (and its enclosing qualifier) with qualified element given by FqName
@@ -120,4 +123,11 @@ public fun PsiElement.getUsageContext(): PsiElement {
} }
public fun PsiElement.isInJavaSourceRoot(): Boolean = public fun PsiElement.isInJavaSourceRoot(): Boolean =
!JavaProjectRootsUtil.isOutsideJavaSourceRoot(getContainingFile()) !JavaProjectRootsUtil.isOutsideJavaSourceRoot(getContainingFile())
public inline fun JetFile.createTempCopy(textTransform: (String) -> String): JetFile {
val tmpFile = JetPsiFactory.createFile(getProject(), getName(), textTransform(getText() ?: ""))
tmpFile.setOriginalFile(this)
return tmpFile
}