diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiFactory.kt b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiFactory.kt index 71792e4d996..01e6a84197f 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiFactory.kt +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiFactory.kt @@ -25,6 +25,7 @@ import com.intellij.util.LocalTimeCounter import org.jetbrains.jet.lang.resolve.ImportPath import org.jetbrains.jet.lexer.JetKeywordToken import org.jetbrains.jet.plugin.JetFileType +import org.jetbrains.jet.lang.psi.JetPsiFactory.CallableBuilder.Target public fun JetPsiFactory(project: Project?): JetPsiFactory = JetPsiFactory(project!!) public fun JetPsiFactory(contextElement: JetElement): JetPsiFactory = JetPsiFactory(contextElement.getProject()) @@ -424,7 +425,12 @@ public class JetPsiFactory(private val project: Project) { return WhenBuilder(subject?.getText()) } - public class FunctionBuilder() { + public class CallableBuilder(private val target: Target) { + public enum class Target { + FUNCTION + READ_ONLY_PROPERTY + } + enum class State { MODIFIERS NAME @@ -440,25 +446,35 @@ public class JetPsiFactory(private val project: Project) { private var state = State.MODIFIERS private fun closeParams() { - assert(state == State.FIRST_PARAM || state == State.REST_PARAMS) - - sb.append(")") + if (target == Target.FUNCTION) { + assert(state == State.FIRST_PARAM || state == State.REST_PARAMS) + sb.append(")") + } state = State.TYPE_CONSTRAINTS } - private fun placeFun() { + private fun placeKeyword() { assert(state == State.MODIFIERS) if (sb.length() != 0) { sb.append(" ") } - sb.append("fun ") + val keyword = when (target) { + Target.FUNCTION -> "fun" + Target.READ_ONLY_PROPERTY -> "val" + } + sb.append("$keyword ") state = State.RECEIVER } - public fun modifier(modifier: String): FunctionBuilder { + private fun blockPrefix() = when (target) { + Target.FUNCTION -> "" + Target.READ_ONLY_PROPERTY -> "\nget()" + } + + public fun modifier(modifier: String): CallableBuilder { assert(state == State.MODIFIERS) sb.append(modifier) @@ -466,8 +482,8 @@ public class JetPsiFactory(private val project: Project) { return this } - public fun typeParams(values: Collection): FunctionBuilder { - placeFun() + public fun typeParams(values: Collection): CallableBuilder { + placeKeyword() if (!values.isEmpty()) { sb.append(values.joinToString(", ", "<", "> ", -1, "")) } @@ -475,7 +491,7 @@ public class JetPsiFactory(private val project: Project) { return this } - public fun receiver(receiverType: String): FunctionBuilder { + public fun receiver(receiverType: String): CallableBuilder { assert(state == State.RECEIVER) sb.append(receiverType).append(".") @@ -484,16 +500,24 @@ public class JetPsiFactory(private val project: Project) { return this } - public fun name(name: String): FunctionBuilder { + public fun name(name: String): CallableBuilder { assert(state == State.NAME || state == State.RECEIVER) - sb.append(name).append("(") - state = State.FIRST_PARAM + sb.append(name) + when (target) { + Target.FUNCTION -> { + sb.append("(") + state = State.FIRST_PARAM + } + else -> + state = State.TYPE_CONSTRAINTS + } return this } - public fun param(name: String, `type`: String): FunctionBuilder { + public fun param(name: String, `type`: String): CallableBuilder { + assert(target == Target.FUNCTION) assert(state == State.FIRST_PARAM || state == State.REST_PARAMS) if (state == State.REST_PARAMS) { @@ -507,20 +531,20 @@ public class JetPsiFactory(private val project: Project) { return this } - public fun returnType(`type`: String): FunctionBuilder { + public fun returnType(`type`: String): CallableBuilder { closeParams() sb.append(": ").append(`type`) return this } - public fun noReturnType(): FunctionBuilder { + public fun noReturnType(): CallableBuilder { closeParams() return this } - public fun typeConstraints(values: Collection): FunctionBuilder { + public fun typeConstraints(values: Collection): CallableBuilder { assert(state == State.TYPE_CONSTRAINTS) if (!values.isEmpty()) { @@ -531,25 +555,25 @@ public class JetPsiFactory(private val project: Project) { return this } - public fun simpleBody(body: String): FunctionBuilder { + public fun simpleBody(body: String): CallableBuilder { assert(state == State.BODY || state == State.TYPE_CONSTRAINTS) - sb.append(" = ").append(body) + sb.append(blockPrefix()).append(" = ").append(body) state = State.DONE return this } - public fun blockBody(body: String): FunctionBuilder { + public fun blockBody(body: String): CallableBuilder { assert(state == State.BODY || state == State.TYPE_CONSTRAINTS) - sb.append(" {\n").append(body).append("\n}") + sb.append(blockPrefix()).append(" {\n").append(body).append("\n}") state = State.DONE return this } - public fun toFunctionText(): String { + public fun asString(): String { if (state != State.DONE) { state = State.DONE } diff --git a/idea/src/org/jetbrains/jet/plugin/debugger/evaluate/extractFunctionForDebuggerUtil.kt b/idea/src/org/jetbrains/jet/plugin/debugger/evaluate/extractFunctionForDebuggerUtil.kt index 76d7c855806..919222bb9da 100644 --- a/idea/src/org/jetbrains/jet/plugin/debugger/evaluate/extractFunctionForDebuggerUtil.kt +++ b/idea/src/org/jetbrains/jet/plugin/debugger/evaluate/extractFunctionForDebuggerUtil.kt @@ -30,7 +30,9 @@ import org.jetbrains.jet.plugin.refactoring.extractFunction.performAnalysis import org.jetbrains.jet.plugin.refactoring.extractFunction.AnalysisResult.Status import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil import org.jetbrains.jet.plugin.refactoring.extractFunction.validate -import org.jetbrains.jet.plugin.refactoring.extractFunction.generateFunction +import org.jetbrains.jet.lang.psi.JetImportList +import org.jetbrains.jet.lang.psi.JetPsiFactory +import org.jetbrains.jet.lang.psi.JetExpression import org.jetbrains.jet.plugin.refactoring.extractFunction.ExtractionOptions import org.jetbrains.jet.plugin.refactoring.runReadAction import com.intellij.psi.PsiManager @@ -39,6 +41,7 @@ import org.jetbrains.jet.lang.psi.* import org.jetbrains.jet.plugin.intentions.InsertExplicitTypeArguments import com.intellij.psi.util.PsiTreeUtil import org.jetbrains.jet.plugin.refactoring.extractFunction.ExtractionGeneratorOptions +import org.jetbrains.jet.plugin.refactoring.extractFunction.generateDeclaration fun getFunctionForExtractedFragment( codeFragment: JetCodeFragment, @@ -101,8 +104,8 @@ fun getFunctionForExtractedFragment( } return validationResult.descriptor - .generateFunction(ExtractionGeneratorOptions(inTempFile = true)) - .function + .generateDeclaration(ExtractionGeneratorOptions(inTempFile = true)) + .declaration as JetNamedFunction } return runReadAction { generateFunction() } diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringSupportProvider.java b/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringSupportProvider.java index 4031afc1a7a..19e70ad1b9b 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringSupportProvider.java +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringSupportProvider.java @@ -25,6 +25,7 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.plugin.refactoring.changeSignature.JetChangeSignatureHandler; import org.jetbrains.jet.plugin.refactoring.extractFunction.ExtractKotlinFunctionHandler; +import org.jetbrains.jet.plugin.refactoring.extractFunction.ExtractKotlinFunctionHandlerHelper; import org.jetbrains.jet.plugin.refactoring.introduce.introduceVariable.KotlinIntroduceVariableHandler; import org.jetbrains.jet.plugin.refactoring.safeDelete.SafeDeletePackage; @@ -46,7 +47,7 @@ public class JetRefactoringSupportProvider extends RefactoringSupportProvider { @NotNull public RefactoringActionHandler getExtractFunctionToScopeHandler() { - return new ExtractKotlinFunctionHandler(true); + return new ExtractKotlinFunctionHandler(true, ExtractKotlinFunctionHandlerHelper.DEFAULT); } @Override diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractKotlinFunctionHandler.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractKotlinFunctionHandler.kt index 736b9accac7..81b796a068d 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractKotlinFunctionHandler.kt +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractKotlinFunctionHandler.kt @@ -67,13 +67,23 @@ import org.jetbrains.jet.renderer.DescriptorRenderer import org.jetbrains.jet.lang.psi.JetPropertyAccessor import org.jetbrains.jet.lang.psi.JetClassOrObject -public class ExtractKotlinFunctionHandler(public val allContainersEnabled: Boolean = false) : RefactoringActionHandler { +public open class ExtractKotlinFunctionHandlerHelper { + open fun adjustGeneratorOptions(options: ExtractionGeneratorOptions): ExtractionGeneratorOptions = options + open fun adjustDescriptor(descriptor: ExtractableCodeDescriptor): ExtractableCodeDescriptor = descriptor + + class object { + public val DEFAULT: ExtractKotlinFunctionHandlerHelper = ExtractKotlinFunctionHandlerHelper() + } +} + +public class ExtractKotlinFunctionHandler( + public val allContainersEnabled: Boolean = false, + private val helper: ExtractKotlinFunctionHandlerHelper = ExtractKotlinFunctionHandlerHelper.DEFAULT) : RefactoringActionHandler { fun doInvoke( editor: Editor, file: JetFile, elements: List, - targetSibling: PsiElement, - preprocessor: ((ExtractableCodeDescriptor) -> Unit)? = null + targetSibling: PsiElement ) { val project = file.getProject() @@ -84,8 +94,9 @@ public class ExtractKotlinFunctionHandler(public val allContainersEnabled: Boole } fun doRefactor(descriptor: ExtractableCodeDescriptor, generatorOptions: ExtractionGeneratorOptions) { - preprocessor?.invoke(descriptor) - project.executeWriteCommand(EXTRACT_FUNCTION) { descriptor.generateFunction(generatorOptions) } + val adjustedDescriptor = helper.adjustDescriptor(descriptor) + val adjustedGeneratorOptions = helper.adjustGeneratorOptions(generatorOptions) + project.executeWriteCommand(EXTRACT_FUNCTION) { adjustedDescriptor.generateDeclaration(adjustedGeneratorOptions) } } fun validateAndRefactor() { diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractableCodeDescriptor.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractableCodeDescriptor.kt index b40eeb31307..ce45fe3842a 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractableCodeDescriptor.kt +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractableCodeDescriptor.kt @@ -38,7 +38,9 @@ import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor import org.jetbrains.jet.lang.psi.JetProperty import org.jetbrains.jet.lang.psi.JetDeclaration import com.intellij.openapi.util.text.StringUtil -import org.jetbrains.jet.lang.psi.JetNamedFunction +import org.jetbrains.jet.lang.psi.JetClassBody +import org.jetbrains.jet.lang.psi.JetFile +import org.jetbrains.jet.lang.psi.JetNamedDeclaration trait Parameter { val argumentText: String @@ -164,8 +166,9 @@ data class ExtractableCodeDescriptor( val controlFlow: ControlFlow ) -class ExtractionGeneratorOptions( - val inTempFile: Boolean = false +data class ExtractionGeneratorOptions( + val inTempFile: Boolean = false, + val extractAsProperty: Boolean = false ) { class object { val DEFAULT = ExtractionGeneratorOptions() @@ -173,7 +176,7 @@ class ExtractionGeneratorOptions( } data class ExtractionResult( - val function: JetNamedFunction, + val declaration: JetNamedDeclaration, val nameByOffset: Map ) @@ -232,3 +235,10 @@ class ExtractableCodeDescriptorWithConflicts( val descriptor: ExtractableCodeDescriptor, val conflicts: MultiMap ) + +fun ExtractableCodeDescriptor.canGenerateProperty(): Boolean { + if (!parameters.empty) return false + + val parent = extractionData.targetSibling.getParent() + return parent is JetFile || parent is JetClassBody +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractableAnalysisUtil.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractableAnalysisUtil.kt index 2a27ec76130..d48bcac5a2a 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractableAnalysisUtil.kt +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractableAnalysisUtil.kt @@ -274,7 +274,7 @@ private fun ExtractionData.analyzeControlFlow( return Pair(defaultControlFlow, null) } -fun ExtractionData.createTemporaryFunction(functionText: String): JetNamedFunction { +fun ExtractionData.createTemporaryDeclaration(functionText: String): JetNamedDeclaration { val textRange = targetSibling.getTextRange()!! val insertText: String @@ -294,11 +294,11 @@ fun ExtractionData.createTemporaryFunction(functionText: String): JetNamedFuncti val tmpFile = originalFile.createTempCopy { text -> StringBuilder(text).insert(insertPosition, insertText).toString() } - return tmpFile.findElementAt(lookupPosition)?.getParentByType(javaClass())!! + return tmpFile.findElementAt(lookupPosition)?.getParentByType(javaClass())!! } private fun ExtractionData.createTemporaryCodeBlock(): JetBlockExpression = - createTemporaryFunction("fun() {\n${getCodeFragmentText()}\n}\n").getBodyExpression() as JetBlockExpression + (createTemporaryDeclaration("fun() {\n${getCodeFragmentText()}\n}\n") as JetNamedFunction).getBodyExpression() as JetBlockExpression private fun JetType.collectReferencedTypes(processTypeArguments: Boolean): List { if (!processTypeArguments) return Collections.singletonList(this) @@ -683,12 +683,19 @@ fun ExtractionData.performAnalysis(): AnalysisResult { ) } +private fun JetNamedDeclaration.getGeneratedBlockBody() = + when (this) { + is JetNamedFunction -> getBodyExpression() + else -> (this as JetProperty).getGetter()!!.getBodyExpression() + } as JetBlockExpression + fun ExtractableCodeDescriptor.validate(): ExtractableCodeDescriptorWithConflicts { val conflicts = MultiMap() - val result = generateFunction(ExtractionGeneratorOptions(inTempFile = true)) + val result = generateDeclaration(ExtractionGeneratorOptions(inTempFile = true)) - val bindingContext = AnalyzerFacadeWithCache.getContextForElement(result.function.getBodyExpression()!!) + val valueParameterList = (result.declaration as? JetNamedFunction)?.getValueParameterList() + val bindingContext = AnalyzerFacadeWithCache.getContextForElement(result.declaration.getGeneratedBlockBody()) for ((originalOffset, resolveResult) in extractionData.refOffsetToDeclaration) { if (resolveResult.declaration.isInsideOf(extractionData.originalElements)) continue @@ -703,7 +710,7 @@ fun ExtractableCodeDescriptor.validate(): ExtractableCodeDescriptorWithConflicts val currentDescriptor = bindingContext[BindingContext.REFERENCE_TARGET, currentRefExpr] val currentTarget = currentDescriptor?.let { DescriptorToDeclarationUtil.getDeclaration(extractionData.project, it) } as? PsiNamedElement - if (currentTarget is JetParameter && currentTarget.getParent() == result.function.getValueParameterList()) continue + if (currentTarget is JetParameter && currentTarget.getParent() == valueParameterList) continue if (currentDescriptor is LocalVariableDescriptor && parameters.any { it.mirrorVarName == currentDescriptor.getName().asString() }) continue diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractorUtil.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractorUtil.kt index 642f60c82f3..41218680cb6 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractorUtil.kt +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractorUtil.kt @@ -17,39 +17,44 @@ package org.jetbrains.jet.plugin.refactoring.extractFunction import org.jetbrains.jet.renderer.DescriptorRenderer -import org.jetbrains.jet.lang.psi.JetPsiFactory.FunctionBuilder import org.jetbrains.jet.lang.psi.JetElement import org.jetbrains.jet.lang.psi.JetSimpleNameExpression import java.util.HashMap import org.jetbrains.jet.lang.psi.JetTreeVisitorVoid import org.jetbrains.jet.lang.psi.psiUtil.getParentByType import org.jetbrains.jet.lang.psi.JetNamedFunction -import org.jetbrains.jet.lang.psi.JetPsiFactory -import org.jetbrains.jet.lang.psi.JetBlockExpression -import java.util.LinkedHashMap import org.jetbrains.jet.lang.psi.JetExpression -import java.util.Collections -import org.jetbrains.jet.lang.psi.psiUtil.prependElement -import org.jetbrains.jet.lang.psi.psiUtil.appendElement -import org.jetbrains.jet.lang.psi.psiUtil.replaced import com.intellij.psi.PsiElement -import org.jetbrains.jet.lang.psi.JetCallExpression -import com.intellij.psi.PsiWhiteSpace import org.jetbrains.jet.lang.psi.JetDeclaration import org.jetbrains.jet.lang.psi.JetProperty -import org.jetbrains.jet.plugin.intentions.declarations.DeclarationUtils +import org.jetbrains.jet.lang.psi.JetPsiFactory.CallableBuilder +import org.jetbrains.jet.lang.psi.JetPsiFactory.CallableBuilder.Target +import org.jetbrains.jet.lang.psi.JetNamedDeclaration +import org.jetbrains.jet.lang.psi.JetPsiFactory +import java.util.LinkedHashMap +import java.util.Collections import org.jetbrains.jet.plugin.codeInsight.ShortenReferences import org.jetbrains.jet.lang.psi.psiUtil.isFunctionLiteralOutsideParentheses import com.intellij.psi.util.PsiTreeUtil import org.jetbrains.jet.lang.psi.JetFunctionLiteralArgument import org.jetbrains.jet.lang.resolve.BindingContext import org.jetbrains.jet.plugin.util.psiModificationUtil.moveInsideParenthesesAndReplaceWith +import org.jetbrains.jet.lang.psi.psiUtil.prependElement +import org.jetbrains.jet.lang.psi.psiUtil.appendElement +import org.jetbrains.jet.lang.psi.psiUtil.replaced +import org.jetbrains.jet.plugin.intentions.declarations.DeclarationUtils -fun ExtractableCodeDescriptor.getFunctionText( +fun ExtractableCodeDescriptor.getDeclarationText( + options: ExtractionGeneratorOptions = ExtractionGeneratorOptions.DEFAULT, withBody: Boolean = true, descriptorRenderer: DescriptorRenderer = DescriptorRenderer.FQ_NAMES_IN_TYPES ): String { - return FunctionBuilder().let { builder -> + if (!canGenerateProperty() && options.extractAsProperty) { + throw IllegalArgumentException("Can't generate property: ${extractionData.getCodeFragmentText()}") + } + + val builderTarget = if (options.extractAsProperty) Target.READ_ONLY_PROPERTY else Target.FUNCTION + return CallableBuilder(builderTarget).let { builder -> builder.modifier(visibility) builder.typeParams(typeParameters.map { it.originalDeclaration.getText()!! }) @@ -72,7 +77,7 @@ fun ExtractableCodeDescriptor.getFunctionText( builder.blockBody(extractionData.getCodeFragmentText()) } - builder.toFunctionText() + builder.asString() } } @@ -95,23 +100,23 @@ fun createNameCounterpartMap(from: JetElement, to: JetElement): Map() - fun createFunction(): JetNamedFunction { + fun createDeclaration(): JetNamedDeclaration { return with(extractionData) { if (options.inTempFile) { - createTemporaryFunction("${getFunctionText()}\n") + createTemporaryDeclaration("${getDeclarationText()}\n") } else { - psiFactory.createFunction(getFunctionText()) + psiFactory.createDeclaration(getDeclarationText(options)) } } } - fun adjustFunctionBody(function: JetNamedFunction) { - val body = function.getBodyExpression() as JetBlockExpression + fun adjustDeclarationBody(declaration: JetNamedDeclaration) { + val body = declaration.getGeneratedBlockBody() val exprReplacementMap = HashMap JetElement>() val originalOffsetByExpr = LinkedHashMap() @@ -183,28 +188,32 @@ fun ExtractableCodeDescriptor.generateFunction(options: ExtractionGeneratorOptio is ExpressionEvaluation -> body.getStatements().last?.let { - val newExpr = it.replaced(psiFactory.createReturn(it.getText() ?: throw AssertionError("Return expression shouldn't be empty: code fragment = ${body.getText()}"))).getReturnedExpression()!! + val newExpr = it.replaced( + psiFactory.createReturn( + it.getText() ?: throw AssertionError("Return expression shouldn't be empty: code fragment = ${body.getText()}") + ) + ).getReturnedExpression()!! val counterpartMap = createNameCounterpartMap(it, newExpr) nameByOffset.entrySet().forEach { e -> counterpartMap[e.getValue()]?.let { e.setValue(it) } } } } } - fun insertFunction(function: JetNamedFunction): JetNamedFunction { + fun insertDeclaration(declaration: JetNamedDeclaration): JetNamedDeclaration { return with(extractionData) { val targetContainer = targetSibling.getParent()!! val emptyLines = psiFactory.createWhiteSpace("\n\n") if (insertBefore) { - val functionInFile = targetContainer.addBefore(function, targetSibling) as JetNamedFunction + val declarationInFile = targetContainer.addBefore(declaration, targetSibling) as JetNamedDeclaration targetContainer.addBefore(emptyLines, targetSibling) - functionInFile + declarationInFile } else { - val functionInFile = targetContainer.addAfter(function, targetSibling) as JetNamedFunction + val declarationInFile = targetContainer.addAfter(declaration, targetSibling) as JetNamedDeclaration targetContainer.addAfter(emptyLines, targetSibling) - functionInFile + declarationInFile } } } @@ -225,9 +234,9 @@ fun ExtractableCodeDescriptor.generateFunction(options: ExtractionGeneratorOptio anchor.replace(wrappedCall) } - fun makeCall(function: JetNamedFunction): JetNamedFunction { + fun makeCall(declaration: JetNamedDeclaration): JetNamedDeclaration { val anchor = extractionData.originalElements.first - if (anchor == null) return function + if (anchor == null) return declaration val anchorParent = anchor.getParent()!! @@ -238,9 +247,13 @@ fun ExtractableCodeDescriptor.generateFunction(options: ExtractionGeneratorOptio } } - val callText = parameters - .map { it.argumentText } - .joinToString(separator = ", ", prefix = "${name}(", postfix = ")") + val callText = when (declaration) { + is JetNamedFunction -> + parameters + .map { it.argumentText } + .joinToString(separator = ", ", prefix = "${name}(", postfix = ")") + else -> name + } val copiedDeclarations = HashMap() for (decl in controlFlow.declarationsToCopy) { @@ -280,17 +293,15 @@ fun ExtractableCodeDescriptor.generateFunction(options: ExtractionGeneratorOptio } insertCall(anchor, wrappedCall) - return function + return declaration } - val function = createFunction() - adjustFunctionBody(function) - - if (options.inTempFile) return ExtractionResult(function, nameByOffset) - - val functionInPlace = makeCall(insertFunction(function)) - ShortenReferences.process(functionInPlace) - return ExtractionResult(functionInPlace, nameByOffset) -} + val declaration = createDeclaration() + adjustDeclarationBody(declaration) + if (options.inTempFile) return ExtractionResult(declaration, nameByOffset) + val declarationInPlace = makeCall(insertDeclaration(declaration)) + ShortenReferences.process(declarationInPlace) + return ExtractionResult(declaration, nameByOffset) +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ui/KotlinExtractFunctionDialog.form b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ui/KotlinExtractFunctionDialog.form index 8e732c56f97..b3728f4b2d1 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ui/KotlinExtractFunctionDialog.form +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ui/KotlinExtractFunctionDialog.form @@ -1,9 +1,9 @@
- + - + @@ -11,7 +11,7 @@ - + @@ -38,7 +38,7 @@ - + @@ -127,9 +127,32 @@ - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ui/KotlinExtractFunctionDialog.java b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ui/KotlinExtractFunctionDialog.java index b13952c7108..7e77b7214af 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ui/KotlinExtractFunctionDialog.java +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ui/KotlinExtractFunctionDialog.java @@ -36,6 +36,8 @@ import org.jetbrains.jet.plugin.refactoring.extractFunction.*; import org.jetbrains.jet.renderer.DescriptorRenderer; import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.util.List; @@ -48,6 +50,7 @@ public class KotlinExtractFunctionDialog extends DialogWrapper { private KotlinFunctionSignatureComponent signaturePreviewField; private EditorTextField functionNameField; private JLabel functionNameLabel; + private JCheckBox propertyCheckBox; private KotlinParameterTablePanel parameterTablePanel; private final Project project; @@ -106,7 +109,8 @@ public class KotlinExtractFunctionDialog extends DialogWrapper { setOKActionEnabled(checkNames()); signaturePreviewField.setText( - ExtractFunctionPackage.getFunctionText(currentDescriptor, false, DescriptorRenderer.SOURCE_CODE_SHORT_NAMES_IN_TYPES) + ExtractFunctionPackage.getDeclarationText(currentDescriptor, getGeneratorOptions(), false, + DescriptorRenderer.SOURCE_CODE_SHORT_NAMES_IN_TYPES) ); } @@ -140,6 +144,18 @@ public class KotlinExtractFunctionDialog extends DialogWrapper { } ); + propertyCheckBox.setEnabled(ExtractFunctionPackage.canGenerateProperty(originalDescriptor.getDescriptor())); + if (propertyCheckBox.isEnabled()) { + propertyCheckBox.addActionListener( + new ActionListener() { + @Override + public void actionPerformed(@NotNull ActionEvent e) { + update(); + } + } + ); + } + parameterTablePanel = new KotlinParameterTablePanel() { @Override protected void updateSignature() { @@ -256,6 +272,6 @@ public class KotlinExtractFunctionDialog extends DialogWrapper { @NotNull public ExtractionGeneratorOptions getGeneratorOptions() { - return ExtractionGeneratorOptions.DEFAULT; + return new ExtractionGeneratorOptions(false, propertyCheckBox.isSelected()); } } diff --git a/idea/testData/refactoring/extractFunction/asProperty/extractToClass.kt b/idea/testData/refactoring/extractFunction/asProperty/extractToClass.kt new file mode 100644 index 00000000000..4c81fabc025 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/asProperty/extractToClass.kt @@ -0,0 +1,10 @@ +// EXTRACT_AS_PROPERTY + +class A(val n: Int = 1) { + val m: Int = 2 + // SIBLING: + fun foo(): Int { + return m + n + 1 + } +} + diff --git a/idea/testData/refactoring/extractFunction/asProperty/extractToClass.kt.after b/idea/testData/refactoring/extractFunction/asProperty/extractToClass.kt.after new file mode 100644 index 00000000000..b7d63d5dcd4 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/asProperty/extractToClass.kt.after @@ -0,0 +1,15 @@ +// EXTRACT_AS_PROPERTY + +class A(val n: Int = 1) { + val m: Int = 2 + // SIBLING: + fun foo(): Int { + return i + } + + private val i: Int + get() { + return m + n + 1 + } +} + diff --git a/idea/testData/refactoring/extractFunction/asProperty/extractToFile.kt b/idea/testData/refactoring/extractFunction/asProperty/extractToFile.kt new file mode 100644 index 00000000000..d5a7f6acb83 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/asProperty/extractToFile.kt @@ -0,0 +1,8 @@ +// EXTRACT_AS_PROPERTY + +val n: Int = 1 + +// SIBLING: +fun foo(): Int { + return n + 1 +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/asProperty/extractToFile.kt.after b/idea/testData/refactoring/extractFunction/asProperty/extractToFile.kt.after new file mode 100644 index 00000000000..66b10017b71 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/asProperty/extractToFile.kt.after @@ -0,0 +1,13 @@ +// EXTRACT_AS_PROPERTY + +val n: Int = 1 + +// SIBLING: +fun foo(): Int { + return i +} + +private val i: Int + get() { + return n + 1 + } \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/asProperty/extractToFunction.kt b/idea/testData/refactoring/extractFunction/asProperty/extractToFunction.kt new file mode 100644 index 00000000000..f64f62f9580 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/asProperty/extractToFunction.kt @@ -0,0 +1,6 @@ +// EXTRACT_AS_PROPERTY + +fun foo(n: Int): Int { + // SIBLING: + return {n + 1}() +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/asProperty/extractToFunction.kt.conflicts b/idea/testData/refactoring/extractFunction/asProperty/extractToFunction.kt.conflicts new file mode 100644 index 00000000000..218ce997768 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/asProperty/extractToFunction.kt.conflicts @@ -0,0 +1 @@ +Can't generate property: n + 1 \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/asProperty/extractWithParams.kt b/idea/testData/refactoring/extractFunction/asProperty/extractWithParams.kt new file mode 100644 index 00000000000..05d245f3c07 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/asProperty/extractWithParams.kt @@ -0,0 +1,11 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: val m: kotlin.Int defined in foo +// EXTRACT_AS_PROPERTY + +val n: Int = 1 + +// SIBLING: +fun foo(): Int { + val m = 1 + return n + m + 1 +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/asProperty/extractWithParams.kt.conflicts b/idea/testData/refactoring/extractFunction/asProperty/extractWithParams.kt.conflicts new file mode 100644 index 00000000000..af481ebcb1f --- /dev/null +++ b/idea/testData/refactoring/extractFunction/asProperty/extractWithParams.kt.conflicts @@ -0,0 +1 @@ +Can't generate property: n + m + 1 \ No newline at end of file diff --git a/idea/tests/org/jetbrains/jet/plugin/refactoring/introduce/introduceVariable/AbstractJetExtractionTest.kt b/idea/tests/org/jetbrains/jet/plugin/refactoring/introduce/introduceVariable/AbstractJetExtractionTest.kt index 0b87b58b5a3..0f0bbdf9400 100644 --- a/idea/tests/org/jetbrains/jet/plugin/refactoring/introduce/introduceVariable/AbstractJetExtractionTest.kt +++ b/idea/tests/org/jetbrains/jet/plugin/refactoring/introduce/introduceVariable/AbstractJetExtractionTest.kt @@ -38,6 +38,9 @@ import com.intellij.util.containers.ContainerUtil import kotlin.test.assertEquals import org.jetbrains.jet.plugin.JetLightCodeInsightFixtureTestCase import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase +import org.jetbrains.jet.plugin.refactoring.extractFunction.ExtractKotlinFunctionHandlerHelper +import org.jetbrains.jet.plugin.refactoring.extractFunction.ExtractionGeneratorOptions +import org.jetbrains.jet.plugin.refactoring.extractFunction.ExtractableCodeDescriptor public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTestCase() { override fun getProjectDescriptor() = LightCodeInsightFixtureTestCase.JAVA_LATEST @@ -78,21 +81,32 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, "// PARAM_DESCRIPTOR: ").joinToString() val expectedTypes = InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, "// PARAM_TYPES: ").map { "[$it]" }.joinToString() + val extractAsProperty = InTextDirectivesUtils.isDirectiveDefined(fileText, "// EXTRACT_AS_PROPERTY") val renderer = DescriptorRenderer.DEBUG_TEXT val editor = fixture.getEditor() - selectElements(editor, file) { (elements, previousSibling) -> - ExtractKotlinFunctionHandler().doInvoke(editor, file, elements, explicitPreviousSibling ?: previousSibling) { - val allParameters = ContainerUtil.createMaybeSingletonList(it.receiverParameter) + it.parameters - val actualDescriptors = allParameters.map { renderer.render(it.originalDescriptor) }.joinToString() - val actualTypes = allParameters.map { - it.parameterTypeCandidates.map { renderer.renderType(it) }.joinToString(", ", "[", "]") - }.joinToString() + selectElements(editor, file) {(elements, previousSibling) -> + ExtractKotlinFunctionHandler( + helper = object : ExtractKotlinFunctionHandlerHelper() { + override fun adjustGeneratorOptions(options: ExtractionGeneratorOptions): ExtractionGeneratorOptions { + return options.copy(extractAsProperty = extractAsProperty) + } - assertEquals(expectedDescriptors, actualDescriptors, "Expected descriptors mismatch.") - assertEquals(expectedTypes, actualTypes, "Expected types mismatch.") - } + override fun adjustDescriptor(descriptor: ExtractableCodeDescriptor): ExtractableCodeDescriptor { + val allParameters = ContainerUtil.createMaybeSingletonList(descriptor.receiverParameter) + descriptor.parameters + val actualDescriptors = allParameters.map { renderer.render(it.originalDescriptor) }.joinToString() + val actualTypes = allParameters.map { + it.parameterTypeCandidates.map { renderer.renderType(it) }.joinToString(", ", "[", "]") + }.joinToString() + + assertEquals(expectedDescriptors, actualDescriptors, "Expected descriptors mismatch.") + assertEquals(expectedTypes, actualTypes, "Expected types mismatch.") + + return descriptor + } + } + ).doInvoke(editor, file, elements, explicitPreviousSibling ?: previousSibling) } } } diff --git a/idea/tests/org/jetbrains/jet/plugin/refactoring/introduce/introduceVariable/JetExtractionTestGenerated.java b/idea/tests/org/jetbrains/jet/plugin/refactoring/introduce/introduceVariable/JetExtractionTestGenerated.java index a1fb47bd9cd..00da07c8fea 100644 --- a/idea/tests/org/jetbrains/jet/plugin/refactoring/introduce/introduceVariable/JetExtractionTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/plugin/refactoring/introduce/introduceVariable/JetExtractionTestGenerated.java @@ -211,12 +211,40 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest { } @TestMetadata("idea/testData/refactoring/extractFunction") - @InnerTestClasses({ExtractFunction.Basic.class, ExtractFunction.ControlFlow.class, ExtractFunction.DefaultContainer.class, ExtractFunction.Delegation.class, ExtractFunction.Initializers.class, ExtractFunction.Parameters.class, ExtractFunction.TypeParameters.class}) + @InnerTestClasses({ExtractFunction.AsProperty.class, ExtractFunction.Basic.class, ExtractFunction.ControlFlow.class, ExtractFunction.DefaultContainer.class, ExtractFunction.Delegation.class, ExtractFunction.Initializers.class, ExtractFunction.Parameters.class, ExtractFunction.TypeParameters.class}) public static class ExtractFunction extends AbstractJetExtractionTest { public void testAllFilesPresentInExtractFunction() throws Exception { JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("idea/testData/refactoring/extractFunction"), Pattern.compile("^(.+)\\.kt$"), true); } + @TestMetadata("idea/testData/refactoring/extractFunction/asProperty") + public static class AsProperty extends AbstractJetExtractionTest { + public void testAllFilesPresentInAsProperty() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("idea/testData/refactoring/extractFunction/asProperty"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("extractToClass.kt") + public void testExtractToClass() throws Exception { + doExtractFunctionTest("idea/testData/refactoring/extractFunction/asProperty/extractToClass.kt"); + } + + @TestMetadata("extractToFile.kt") + public void testExtractToFile() throws Exception { + doExtractFunctionTest("idea/testData/refactoring/extractFunction/asProperty/extractToFile.kt"); + } + + @TestMetadata("extractToFunction.kt") + public void testExtractToFunction() throws Exception { + doExtractFunctionTest("idea/testData/refactoring/extractFunction/asProperty/extractToFunction.kt"); + } + + @TestMetadata("extractWithParams.kt") + public void testExtractWithParams() throws Exception { + doExtractFunctionTest("idea/testData/refactoring/extractFunction/asProperty/extractWithParams.kt"); + } + + } + @TestMetadata("idea/testData/refactoring/extractFunction/basic") public static class Basic extends AbstractJetExtractionTest { public void testAllFilesPresentInBasic() throws Exception { @@ -1311,6 +1339,7 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest { public static Test innerSuite() { TestSuite suite = new TestSuite("ExtractFunction"); suite.addTestSuite(ExtractFunction.class); + suite.addTestSuite(AsProperty.class); suite.addTestSuite(Basic.class); suite.addTest(ControlFlow.innerSuite()); suite.addTestSuite(DefaultContainer.class);