From 424c5f296f9b20f86f87953e5df849d31ccca02c Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Tue, 25 Mar 2014 19:08:53 +0400 Subject: [PATCH] Extract Function: Analysis and code generation --- .../jet/lang/cfg/JetControlFlowProcessor.java | 2 + .../lang/cfg/pseudocode/PseudocodeUtil.java | 2 +- .../jetbrains/jet/lang/psi/JetPsiFactory.java | 132 +++- .../jet/lang/psi/psiUtil/jetPsiUtil.kt | 10 +- .../jet/plugin/imports/ImportsUtils.kt | 6 + .../JetRefactoringBundle.properties | 12 +- .../extractFunction/ExtractionData.kt | 139 ++++ .../extractFunction/ExtractionDescriptor.kt | 123 ++++ .../extractFunction/extractFunctionUtils.kt | 626 ++++++++++++++++++ .../plugin/refactoring/jetRefactoringUtil.kt | 28 +- 10 files changed, 1066 insertions(+), 14 deletions(-) create mode 100644 idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractionData.kt create mode 100644 idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractionDescriptor.kt create mode 100644 idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractFunctionUtils.kt diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/cfg/JetControlFlowProcessor.java b/compiler/frontend/src/org/jetbrains/jet/lang/cfg/JetControlFlowProcessor.java index 17b03daf42e..9c8e778c450 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/cfg/JetControlFlowProcessor.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/cfg/JetControlFlowProcessor.java @@ -67,12 +67,14 @@ public class JetControlFlowProcessor { this.trace = trace; } + @NotNull public Pseudocode generatePseudocode(@NotNull JetElement subroutine) { Pseudocode pseudocode = generate(subroutine); ((PseudocodeImpl) pseudocode).postProcess(); return pseudocode; } + @NotNull private Pseudocode generate(@NotNull JetElement subroutine) { builder.enterSubroutine(subroutine); CFPVisitor cfpVisitor = new CFPVisitor(builder); diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/cfg/pseudocode/PseudocodeUtil.java b/compiler/frontend/src/org/jetbrains/jet/lang/cfg/pseudocode/PseudocodeUtil.java index 15005aa924b..cea24a6f0c6 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/cfg/pseudocode/PseudocodeUtil.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/cfg/pseudocode/PseudocodeUtil.java @@ -32,7 +32,7 @@ import org.jetbrains.jet.util.slicedmap.WritableSlice; import java.util.Collection; public class PseudocodeUtil { - + @NotNull public static Pseudocode generatePseudocode(@NotNull JetDeclaration declaration, @NotNull final BindingContext bindingContext) { BindingTrace mockTrace = new BindingTrace() { @NotNull diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiFactory.java b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiFactory.java index 5b24677e272..f97150e117f 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiFactory.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiFactory.java @@ -28,7 +28,6 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.lang.resolve.ImportPath; import org.jetbrains.jet.lang.resolve.name.Name; import org.jetbrains.jet.lexer.JetKeywordToken; -import org.jetbrains.jet.lexer.JetTokens; import org.jetbrains.jet.plugin.JetFileType; import java.util.Collections; @@ -132,7 +131,7 @@ public class JetPsiFactory { } @NotNull - private static PsiElement createWhiteSpace(Project project, String text) { + public static PsiElement createWhiteSpace(Project project, String text) { JetProperty property = createProperty(project, "val" + text + "x"); 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 public static JetExpression createFunctionBody(Project project, @NotNull String bodyText) { JetFunction func = createFunction(project, "fun foo() {\n" + bodyText + "\n}"); diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/psiUtil/jetPsiUtil.kt b/compiler/frontend/src/org/jetbrains/jet/lang/psi/psiUtil/jetPsiUtil.kt index 8a05268a461..6c8d36c3a76 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/psiUtil/jetPsiUtil.kt +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/psiUtil/jetPsiUtil.kt @@ -104,6 +104,9 @@ public fun JetElement.outermostLastBlockElement(predicate: (JetElement) -> Boole public fun JetBlockExpression.appendElement(element: JetElement): 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 { val block = JetPsiFactory.createEmptyBody(getProject()) as JetBlockExpression 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 PsiElement.isInsideOf(elements: Iterable): Boolean = elements.any { it.isAncestor(this) } \ No newline at end of file +public fun PsiElement.isInsideOf(elements: Iterable): 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) +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/imports/ImportsUtils.kt b/idea/src/org/jetbrains/jet/plugin/imports/ImportsUtils.kt index 89308580e46..964b518048a 100644 --- a/idea/src/org/jetbrains/jet/plugin/imports/ImportsUtils.kt +++ b/idea/src/org/jetbrains/jet/plugin/imports/ImportsUtils.kt @@ -25,6 +25,7 @@ import org.jetbrains.jet.lang.descriptors.ClassDescriptor import org.jetbrains.jet.lang.descriptors.CallableDescriptor import org.jetbrains.jet.lang.psi.JetSimpleNameExpression import com.intellij.psi.PsiElement +import org.jetbrains.jet.lang.types.JetType public val DeclarationDescriptor.importableFqName: FqName? get() { @@ -53,6 +54,11 @@ public fun DeclarationDescriptor.canBeReferencedViaImport(): Boolean { 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 { val isExpressionWithReceiver = referenceElement is JetSimpleNameExpression && referenceElement.getReceiverExpression() != null return isExpressionWithReceiver && !referencedDescriptor.isExtension diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringBundle.properties b/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringBundle.properties index 0d29629b52e..8d3e9f1c21c 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringBundle.properties +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringBundle.properties @@ -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.expression.has.unit.type=Cannot introduce expression of unit type cannot.refactor.package.expression=Cannot introduce package reference +extract.function=Extract Function cannot.extract.method=Cannot find statements to extract 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.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 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} \ No newline at end of file +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 \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractionData.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractionData.kt new file mode 100644 index 00000000000..4a6953e18ed --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractionData.kt @@ -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, + val nextSibling: PsiElement +) { + val project: Project = originalFile.getProject() + + fun getExpressions(): List = originalElements.filterIsInstance(javaClass()) + + 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() + 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() + } + + fun getBrokenReferencesInfo(body: JetBlockExpression): List { + 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() + 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] } +} diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractionDescriptor.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractionDescriptor.kt new file mode 100644 index 00000000000..cc79c53694a --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractionDescriptor.kt @@ -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 + +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 + val elementToInsertAfterCall: JetElement +} + +class ConditionalJump( + override val elementsToReplace: List, + override val elementToInsertAfterCall: JetElement +): JumpBasedControlFlow { + override val returnType: JetType get() = KotlinBuiltIns.getInstance().getBooleanType() +} + +class UnconditionalJump( + override val elementsToReplace: List, + 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, + val receiverParameter: Parameter?, + val replacementMap: Map, + val controlFlow: ControlFlow +) + +class ExtractionDescriptorWithConflicts( + val descriptor: ExtractionDescriptor, + val conflicts: MultiMap +) diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractFunctionUtils.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractFunctionUtils.kt new file mode 100644 index 00000000000..1db7e5d6f14 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractFunctionUtils.kt @@ -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.getModifiedVarDescriptors(bindingContext: BindingContext): Set { + return this + .map {if (it is WriteValueInstruction) PseudocodeUtil.extractVariableDescriptorIfAny(it, true, bindingContext) else null} + .filterNotNullTo(HashSet()) +} + +private fun List.getExitPoints(): List = + filter { localInstruction -> localInstruction.getNextInstructions().any { it !in this } } + +private fun List.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.checkEquivalence(checkPsi: Boolean): Boolean { + if (mapTo(HashSet()) { it.getTargetLabel() }.size > 1) return false + return !checkPsi || mapTo(HashSet()) { it.getElement().getText() }.size <= 1 +} + +private fun List.analyzeControlFlow( + bindingContext: BindingContext, + parameters: Set, + inferredType: JetType? +): Maybe { + val outParameters = parameters.filterTo(HashSet()) { 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() + val defaultExits = ArrayList() + val jumpExits = ArrayList() + 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(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())!! + return tmpFunction.getBodyExpression() as JetBlockExpression +} + +private fun ExtractionData.inferParametersInfo( + commonParent: PsiElement, + localInstructions: List, + bindingContext: BindingContext, + resultType: JetType?, + replacementMap: MutableMap, + parameters: MutableSet +): MaybeError? { + val varNameValidator = JetNameValidatorImpl(commonParent.getParentByType(javaClass()), originalElements.first, true) + val modifiedVarDescriptors = localInstructions.getModifiedVarDescriptors(bindingContext) + + val extractedDescriptorToParameter = HashMap() + val nonDenotableTypes = HashSet() + 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, + localInstructions: List, + bindingContext: BindingContext +): MaybeError? { + // todo: non-locally used declaration can be turned into the output value + + val declarations = ArrayList() + 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? { + val declarationsOutOfScope = HashSet() + 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 { + if (originalElements.empty) return MaybeError(JetRefactoringBundle.message("cannot.refactor.no.expresson")!!) + + val commonParent = PsiTreeUtil.findCommonParent(originalElements)!! + + val enclosingDeclaration = commonParent.getParentByType(javaClass()) + 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() + val parameters = HashSet() + 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()) { 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()) +} + +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())!! + } + else { + JetPsiFactory.createFunction(project, getFunctionText()) + } + } + } + + fun adjustFunctionBody(function: JetNamedFunction) { + val body = function.getBodyExpression() as JetBlockExpression + + val exprReplacementMap = HashMap 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()) + 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 +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt index 35ac6f51321..489022fe5c6 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt @@ -16,25 +16,17 @@ 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.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.JetUserType import org.jetbrains.jet.lang.resolve.name.isOneSegmentFQN import com.intellij.psi.PsiElement import com.intellij.openapi.util.Key import com.intellij.psi.PsiDirectory -import org.jetbrains.jet.lang.psi.JetFile import com.intellij.openapi.roots.JavaProjectRootsUtil import com.intellij.psi.PsiPackage import com.intellij.psi.PsiClass import com.intellij.psi.PsiMember -import org.jetbrains.jet.lang.psi.JetPsiUtil import org.jetbrains.jet.asJava.namedUnwrappedElement -import org.jetbrains.jet.lang.psi.JetNamedDeclaration import com.intellij.psi.util.PsiTreeUtil import com.intellij.refactoring.util.ConflictsUtil 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.PsiFile 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 @@ -120,4 +123,11 @@ public fun PsiElement.getUsageContext(): PsiElement { } public fun PsiElement.isInJavaSourceRoot(): Boolean = - !JavaProjectRootsUtil.isOutsideJavaSourceRoot(getContainingFile()) \ No newline at end of file + !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 +} +