diff --git a/plugins/kotlin/base/resources/resources-en/messages/KotlinBundle.properties b/plugins/kotlin/base/resources/resources-en/messages/KotlinBundle.properties index cf25fb161d79..d89a19c92cea 100644 --- a/plugins/kotlin/base/resources/resources-en/messages/KotlinBundle.properties +++ b/plugins/kotlin/base/resources/resources-en/messages/KotlinBundle.properties @@ -2649,4 +2649,5 @@ progress.title.searching.for.expected.actual=Searching for expect/actual\u2026 button.rename.current=Rename Current button.rename.base=Rename Base -progress.title.calculate.occurrences=Calculate occurrences\u2026 \ No newline at end of file +progress.title.calculate.occurrences=Calculate occurrences\u2026 +progress.title.check.for.conflicts=Check for conflicts\u2026 \ No newline at end of file diff --git a/plugins/kotlin/base/util/src/org/jetbrains/kotlin/idea/base/util/names/FqNames.kt b/plugins/kotlin/base/util/src/org/jetbrains/kotlin/idea/base/util/names/FqNames.kt index bfeaf285ab42..bf0904cca9ec 100644 --- a/plugins/kotlin/base/util/src/org/jetbrains/kotlin/idea/base/util/names/FqNames.kt +++ b/plugins/kotlin/base/util/src/org/jetbrains/kotlin/idea/base/util/names/FqNames.kt @@ -11,5 +11,8 @@ object FqNames { val OLD_USE_EXPERIMENTAL_FQ_NAME = FqName("kotlin.UseExperimental") val OPT_IN_FQ_NAMES = setOf(OLD_USE_EXPERIMENTAL_FQ_NAME, OptInNames.OPT_IN_FQ_NAME) + + fun isRequiresOptInFqName(fqName: FqName?) : Boolean = + fqName == OptInNames.REQUIRES_OPT_IN_FQ_NAME || fqName == OLD_EXPERIMENTAL_FQ_NAME } } \ No newline at end of file diff --git a/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java b/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java index 5a8e92cfef79..2897e597e78a 100644 --- a/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java +++ b/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java @@ -2657,6 +2657,11 @@ public abstract class K2LocalInspectionTestGenerated extends AbstractK2LocalInsp runTest("../../../idea/tests/testData/inspectionsLocal/useExpressionBody/convertToExpressionBody/assignment.kt"); } + @TestMetadata("block.kt") + public void testBlock() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/useExpressionBody/convertToExpressionBody/block.kt"); + } + @TestMetadata("constructorWithEmptyBody.kt") public void testConstructorWithEmptyBody() throws Exception { runTest("../../../idea/tests/testData/inspectionsLocal/useExpressionBody/convertToExpressionBody/constructorWithEmptyBody.kt"); diff --git a/plugins/kotlin/code-insight/inspections-shared/kotlin.code-insight.inspections.shared.iml b/plugins/kotlin/code-insight/inspections-shared/kotlin.code-insight.inspections.shared.iml index 1ac95efc78f9..733b54f37c1e 100644 --- a/plugins/kotlin/code-insight/inspections-shared/kotlin.code-insight.inspections.shared.iml +++ b/plugins/kotlin/code-insight/inspections-shared/kotlin.code-insight.inspections.shared.iml @@ -47,5 +47,6 @@ + \ No newline at end of file diff --git a/plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/InfixCallToOrdinaryInspection.kt b/plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/InfixCallToOrdinaryInspection.kt index b621afb886f2..f9b64a97e6a0 100644 --- a/plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/InfixCallToOrdinaryInspection.kt +++ b/plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/InfixCallToOrdinaryInspection.kt @@ -9,6 +9,7 @@ import org.jetbrains.kotlin.analysis.api.KtAnalysisSession import org.jetbrains.kotlin.idea.base.resources.KotlinBundle import org.jetbrains.kotlin.idea.codeinsight.api.applicable.inspections.KotlinApplicableInspectionBase import org.jetbrains.kotlin.idea.codeinsight.api.applicable.inspections.KotlinModCommandQuickFix +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.convertInfixCallToOrdinary import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* @@ -53,21 +54,4 @@ internal class InfixCallToOrdinaryInspection : KotlinApplicableInspectionBase.Si context(KtAnalysisSession) override fun prepareContext(element: KtBinaryExpression) { } -} - -fun convertInfixCallToOrdinary(element: KtBinaryExpression): KtExpression { - val argument = KtPsiUtil.safeDeparenthesize(element.right!!) - val pattern = "$0.$1" + when (argument) { - is KtLambdaExpression -> " $2:'{}'" - else -> "($2)" - } - - val replacement = KtPsiFactory(element.project).createExpressionByPattern( - pattern, - element.left!!, - element.operationReference, - argument - ) - - return element.replace(replacement) as KtExpression } \ No newline at end of file diff --git a/plugins/kotlin/code-insight/override-implement-k2/src/org/jetbrains/kotlin/idea/core/overrideImplement/KtClassMember.kt b/plugins/kotlin/code-insight/override-implement-k2/src/org/jetbrains/kotlin/idea/core/overrideImplement/KtClassMember.kt index 6072c4e6b827..e7ce1faf1cb1 100644 --- a/plugins/kotlin/code-insight/override-implement-k2/src/org/jetbrains/kotlin/idea/core/overrideImplement/KtClassMember.kt +++ b/plugins/kotlin/code-insight/override-implement-k2/src/org/jetbrains/kotlin/idea/core/overrideImplement/KtClassMember.kt @@ -24,7 +24,7 @@ import org.jetbrains.kotlin.analysis.api.symbols.* import org.jetbrains.kotlin.analysis.api.symbols.markers.KtNamedSymbol import org.jetbrains.kotlin.analysis.api.symbols.markers.KtPossibleMultiplatformSymbol import org.jetbrains.kotlin.analysis.api.symbols.pointers.KtSymbolPointer -import org.jetbrains.kotlin.idea.base.util.names.FqNames +import org.jetbrains.kotlin.idea.base.util.names.FqNames.OptInFqNames.isRequiresOptInFqName import org.jetbrains.kotlin.idea.core.TemplateKind import org.jetbrains.kotlin.idea.core.getFunctionBodyTextFromTemplate import org.jetbrains.kotlin.idea.j2k.IdeaDocCommentConverter @@ -35,7 +35,6 @@ import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.findDocComment.findDocComment import org.jetbrains.kotlin.psi.psiUtil.hasExpectModifier import org.jetbrains.kotlin.renderer.render -import org.jetbrains.kotlin.resolve.checkers.OptInNames import org.jetbrains.kotlin.types.Variance import javax.swing.Icon @@ -249,8 +248,7 @@ private fun keepAnnotation(annotation: KtAnnotationApplication, file: KtFile?): context(KtAnalysisSession) private fun KtClassOrObjectSymbol.hasRequiresOptInAnnotation(): Boolean = annotations.any { annotation -> - val fqName = annotation.classId?.asSingleFqName() - fqName == OptInNames.REQUIRES_OPT_IN_FQ_NAME || fqName == FqNames.OptInFqNames.OLD_EXPERIMENTAL_FQ_NAME + isRequiresOptInFqName(annotation.classId?.asSingleFqName()) } context(KtAnalysisSession) diff --git a/plugins/kotlin/code-insight/utils/src/org/jetbrains/kotlin/idea/codeinsight/utils/ConvertToExpressionBodyUtils.kt b/plugins/kotlin/code-insight/utils/src/org/jetbrains/kotlin/idea/codeinsight/utils/ConvertToExpressionBodyUtils.kt index e4623806e819..670fd9cb5c34 100644 --- a/plugins/kotlin/code-insight/utils/src/org/jetbrains/kotlin/idea/codeinsight/utils/ConvertToExpressionBodyUtils.kt +++ b/plugins/kotlin/code-insight/utils/src/org/jetbrains/kotlin/idea/codeinsight/utils/ConvertToExpressionBodyUtils.kt @@ -20,8 +20,11 @@ fun KtDeclarationWithBody.isConvertableToExpressionBody(): Boolean { // Check if either property accessor or named function if (this !is KtNamedFunction && this !is KtPropertyAccessor) return false - // Check if function has block with single non-empty KtReturnExpression - val returnedExpression = bodyBlockExpression?.statements?.singleOrNull() ?: return true + // Check if function has block with single non-empty KtReturnExpression or the block is empty + val statements = bodyBlockExpression?.statements ?: return true + if (statements.isEmpty()) return true + + val returnedExpression = statements.singleOrNull() ?: return false // Check if the returnedExpression actually always returns (early return is possible) // TODO: take into consideration other cases (???) diff --git a/plugins/kotlin/core/src/org/jetbrains/kotlin/idea/core/psiModificationUtils.kt b/plugins/kotlin/core/src/org/jetbrains/kotlin/idea/core/psiModificationUtils.kt index 96797fd77ad5..e7970982c9c6 100644 --- a/plugins/kotlin/core/src/org/jetbrains/kotlin/idea/core/psiModificationUtils.kt +++ b/plugins/kotlin/core/src/org/jetbrains/kotlin/idea/core/psiModificationUtils.kt @@ -208,22 +208,6 @@ private fun FunctionDescriptor.allowsMoveOfLastParameterOutsideParentheses( return movableParametersOfCandidateCount == lambdaAndCallableReferencesInOriginalCallCount } -fun KtBlockExpression.appendElement(element: KtElement, addNewLine: Boolean = false): KtElement { - val rBrace = rBrace - val newLine = KtPsiFactory(project).createNewLine() - val anchor = if (rBrace == null) { - val lastChild = lastChild - lastChild as? PsiWhiteSpace ?: addAfter(newLine, lastChild)!! - } else { - rBrace.prevSibling!! - } - val addedElement = addAfter(element, anchor)!! as KtElement - if (addNewLine) { - addAfter(newLine, addedElement) - } - return addedElement -} - //TODO: git rid of this method fun PsiElement.deleteElementAndCleanParent() { val parent = parent diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/actions/generate/KotlinGenerateSecondaryConstructorAction.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/actions/generate/KotlinGenerateSecondaryConstructorAction.kt index 735d552b7571..722896aebc60 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/actions/generate/KotlinGenerateSecondaryConstructorAction.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/actions/generate/KotlinGenerateSecondaryConstructorAction.kt @@ -20,6 +20,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithContent import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde import org.jetbrains.kotlin.idea.core.* +import org.jetbrains.kotlin.idea.refactoring.appendElement import org.jetbrains.kotlin.idea.resolve.languageVersionSettings import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.idea.util.application.isUnitTestMode diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/InitializePropertyQuickFixFactory.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/InitializePropertyQuickFixFactory.kt index a77b471ad963..3d80c94e9c54 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/InitializePropertyQuickFixFactory.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/InitializePropertyQuickFixFactory.kt @@ -23,9 +23,9 @@ import org.jetbrains.kotlin.idea.core.CollectingNameValidator import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny import org.jetbrains.kotlin.idea.codeInsight.shorten.runRefactoringAndKeepDelayedRequests import org.jetbrains.kotlin.idea.codeinsight.api.classic.quickfixes.KotlinQuickFixAction -import org.jetbrains.kotlin.idea.core.appendElement import org.jetbrains.kotlin.idea.core.getOrCreateBody import org.jetbrains.kotlin.idea.refactoring.CompositeRefactoringRunner +import org.jetbrains.kotlin.idea.refactoring.appendElement import org.jetbrains.kotlin.idea.refactoring.changeSignature.* import org.jetbrains.kotlin.idea.util.getDefaultInitializer import org.jetbrains.kotlin.psi.* diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ExtractKotlinFunctionHandler.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ExtractKotlinFunctionHandler.kt index 5ff4a3d66a14..b478e284d49d 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ExtractKotlinFunctionHandler.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ExtractKotlinFunctionHandler.kt @@ -2,40 +2,25 @@ package org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction -import com.intellij.codeInsight.template.impl.TemplateManagerImpl -import com.intellij.injected.editor.EditorWindow import com.intellij.java.refactoring.JavaRefactoringBundle -import com.intellij.openapi.command.WriteCommandAction -import com.intellij.openapi.command.impl.FinishMarkAction -import com.intellij.openapi.command.impl.StartMarkAction import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.ex.EditorSettingsExternalizable -import com.intellij.openapi.keymap.KeymapUtil import com.intellij.openapi.project.Project -import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.registry.Registry import com.intellij.psi.PsiElement -import com.intellij.psi.PsiFile -import com.intellij.psi.SmartPointerManager import com.intellij.psi.util.PsiTreeUtil -import com.intellij.refactoring.RefactoringBundle -import com.intellij.refactoring.extractMethod.newImpl.inplace.EditorState -import com.intellij.refactoring.extractMethod.newImpl.inplace.ExtractMethodTemplateBuilder -import com.intellij.refactoring.extractMethod.newImpl.inplace.InplaceExtractUtils -import com.intellij.refactoring.extractMethod.newImpl.inplace.TemplateField -import org.jetbrains.annotations.Nls +import org.jetbrains.kotlin.idea.base.psi.unifier.KotlinPsiRange import org.jetbrains.kotlin.idea.base.psi.unifier.toRange import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall import org.jetbrains.kotlin.idea.refactoring.KotlinNamesValidator import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ui.KotlinExtractFunctionDialog -import org.jetbrains.kotlin.idea.refactoring.introduce.extractableSubstringInfo import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.* import org.jetbrains.kotlin.idea.util.nonBlocking import org.jetbrains.kotlin.psi.KtBlockExpression import org.jetbrains.kotlin.psi.KtCallExpression -import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.utils.addToStdlib.safeAs class ExtractKotlinFunctionHandler( @@ -62,7 +47,7 @@ class ExtractKotlinFunctionHandler( descriptorWithConflicts: ExtractableCodeDescriptorWithConflicts, onFinish: (ExtractionResult) -> Unit ) { - fun afterFinish(extraction: ExtractionResult){ + fun afterFinish(extraction: ExtractionResult) { processDuplicates(extraction.duplicateReplacers, project, editor) onFinish(extraction) } @@ -72,102 +57,40 @@ class ExtractKotlinFunctionHandler( } } - open class InplaceExtractionHelper(private val allContainersEnabled: Boolean) : ExtractionEngineHelper(EXTRACT_FUNCTION) { + open class InplaceExtractionHelper( + val allContainersEnabled: Boolean + ) : ExtractionEngineHelper(EXTRACT_FUNCTION), + AbstractInplaceExtractionHelper { override fun configureAndRun( project: Project, editor: Editor, descriptorWithConflicts: ExtractableCodeDescriptorWithConflicts, onFinish: (ExtractionResult) -> Unit ) { - val activeTemplateState = TemplateManagerImpl.getTemplateState(editor) - if (activeTemplateState != null) { - activeTemplateState.gotoEnd(true) - ExtractKotlinFunctionHandler(allContainersEnabled, InteractiveExtractionHelper) - .invoke(project, editor, descriptorWithConflicts.descriptor.extractionData.originalFile, null) - } - val suggestedNames = descriptorWithConflicts.descriptor.suggestedNames.takeIf { it.isNotEmpty() } ?: listOf("extracted") - val descriptor = descriptorWithConflicts.descriptor.copy(suggestedNames = suggestedNames) - val elements = descriptor.extractionData.originalElements - val file = descriptor.extractionData.originalFile - val callTextRange = TextRange(rangeOf(elements.first()).startOffset, rangeOf(elements.last()).endOffset) - - val commonParent = descriptor.extractionData.commonParent - val container = commonParent.takeIf { commonParent != elements.firstOrNull() } ?: commonParent.parent - val callRangeProvider: () -> TextRange? = createSmartRangeProvider(container, callTextRange) - val editorState = EditorState(project, editor) - val disposable = Disposer.newDisposable() - WriteCommandAction.writeCommandAction(project).run { - val startMarkAction = StartMarkAction.start(editor, project, EXTRACT_FUNCTION) - Disposer.register(disposable) { FinishMarkAction.finish(project, editor, startMarkAction) } - } - fun afterFinish(extraction: ExtractionResult) { - // Templates do not work well in injected editors, see InlayModelWindow - if (editor is EditorWindow) { - Disposer.dispose(disposable) - return - } - val callRange: TextRange = callRangeProvider.invoke() ?: throw IllegalStateException() - val callIdentifier = findSingleCallExpression(file, callRange)?.calleeExpression ?: throw IllegalStateException() - val methodIdentifier = extraction.declaration.nameIdentifier ?: throw IllegalStateException() - val methodRange = extraction.declaration.textRange - val methodOffset = extraction.declaration.navigationElement.textRange.endOffset - val callOffset = callIdentifier.textRange.endOffset - val preview = InplaceExtractUtils.createPreview(editor, methodRange, methodOffset, callRange, callOffset) - Disposer.register(disposable, preview) - val templateField = TemplateField(callIdentifier.textRange, listOf(methodIdentifier.textRange)) - .withCompletionNames(descriptor.suggestedNames) - .withCompletionHint(getDialogAdvertisement()) - .withValidation { variableRange -> - val error = getIdentifierError(file, variableRange) - if (error != null) { - InplaceExtractUtils.showErrorHint(editor, variableRange.endOffset, error) - } - error == null - } - ExtractMethodTemplateBuilder(editor, EXTRACT_FUNCTION) - .enableRestartForHandler(ExtractKotlinFunctionHandler::class.java) - .onBroken { - editorState.revert() - } - .onSuccess { - processDuplicates(extraction.duplicateReplacers, file.project, editor) - } - .disposeWithTemplate(disposable) - .createTemplate(file, listOf(templateField)) - onFinish(extraction) - } - try { - val configuration = ExtractionGeneratorConfiguration(descriptor, ExtractionGeneratorOptions.DEFAULT) - doRefactor(configuration, ::afterFinish) - } catch (e: Throwable) { - Disposer.dispose(disposable) - throw e - } + super.configureAndRun(project, editor, descriptorWithConflicts, onFinish) } - @Nls - private fun getDialogAdvertisement(): String { - val shortcut = KeymapUtil.getPrimaryShortcut("ExtractFunction") ?: throw IllegalStateException("Action is not found") - return RefactoringBundle.message("inplace.refactoring.advertisement.text", KeymapUtil.getShortcutText(shortcut)) + override fun createRestartHandler(): AbstractExtractKotlinFunctionHandler = ExtractKotlinFunctionHandler( + allContainersEnabled, InteractiveExtractionHelper + ) + + override fun extractDuplicates( + duplicateReplacers: Map Unit>, + project: Project, + editor: Editor + ) { + processDuplicates(duplicateReplacers, project, editor) } - protected fun rangeOf(element: PsiElement): TextRange { - return (element as? KtExpression)?.extractableSubstringInfo?.contentRange ?: element.textRange + override fun doRefactor(descriptor: IExtractableCodeDescriptor, onFinish: (ExtractionResult) -> Unit) { + val configuration = ExtractionGeneratorConfiguration(descriptor as ExtractableCodeDescriptor, ExtractionGeneratorOptions.DEFAULT) + doRefactor(configuration, onFinish) } - protected fun createSmartRangeProvider(container: PsiElement, range: TextRange): () -> TextRange? { - val offsetFromStart = range.startOffset - container.textRange.startOffset - val offsetFromEnd = container.textRange.endOffset - range.endOffset - val pointer = SmartPointerManager.createPointer(container) - fun findRange(): TextRange? { - val containerRange = pointer.range ?: return null - return TextRange(containerRange.startOffset + offsetFromStart, containerRange.endOffset - offsetFromEnd) - } - return ::findRange - } - - @Nls - private fun getIdentifierError(file: PsiFile, variableRange: TextRange): String? { + override fun getIdentifierError( + file: KtFile, + variableRange: TextRange + ): String? { val call = PsiTreeUtil.findElementOfClassAtOffset(file, variableRange.startOffset, KtCallExpression::class.java, false) val name = file.viewProvider.document.getText(variableRange) return if (! KotlinNamesValidator().isIdentifier(name, file.project)) { @@ -178,13 +101,6 @@ class ExtractKotlinFunctionHandler( null } } - - protected fun findSingleCallExpression(file: KtFile, range: TextRange?): KtCallExpression? { - if (range == null) return null - val container = PsiTreeUtil.findCommonParent(file.findElementAt(range.startOffset), file.findElementAt(range.endOffset)) - val callExpressions = PsiTreeUtil.findChildrenOfType(container, KtCallExpression::class.java) - return callExpressions.singleOrNull { it.textRange in range } - } } override fun doInvoke( diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ui/ExtractFunctionParameterTablePanel.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ui/ExtractFunctionParameterTablePanel.kt index 782f5a19c5c7..b79ace9bbb29 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ui/ExtractFunctionParameterTablePanel.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ui/ExtractFunctionParameterTablePanel.kt @@ -30,7 +30,10 @@ open class ExtractFunctionParameterTablePanel : AbstractParameterTablePanel conflicts = ((ExtractableCodeDescriptorWithConflicts) result).getConflicts(); conflicts.values().removeAll(originalDescriptor.getConflicts().values()); - KotlinRefactoringUtilKt.checkConflictsInteractively( + checkConflictsInteractively( project, conflicts, new Function0<>() { diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt index e66a39e3cf46..e6b397745bb0 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt @@ -2,319 +2,33 @@ package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine -import com.intellij.openapi.Disposable -import com.intellij.openapi.util.text.StringUtil import com.intellij.psi.PsiElement -import com.intellij.util.containers.ContainerUtil import com.intellij.util.containers.MultiMap import org.jetbrains.annotations.ApiStatus -import org.jetbrains.annotations.Nls -import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor -import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor -import org.jetbrains.kotlin.idea.base.resources.KotlinBundle import org.jetbrains.kotlin.idea.base.psi.unifier.KotlinPsiRange -import org.jetbrains.kotlin.idea.base.psi.replaced import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.* -import org.jetbrains.kotlin.idea.references.KtSimpleNameReference.ShorteningMode -import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.idea.util.approximateFlexibleTypes -import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.lexer.KtKeywordToken import org.jetbrains.kotlin.lexer.KtModifierKeywordToken import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.resolve.BindingContext -import org.jetbrains.kotlin.resolve.DescriptorUtils -import org.jetbrains.kotlin.resolve.descriptorUtil.resolveTopLevelClass import org.jetbrains.kotlin.types.* import org.jetbrains.kotlin.types.typeUtil.TypeNullability -import org.jetbrains.kotlin.types.typeUtil.builtIns import org.jetbrains.kotlin.types.typeUtil.isUnit import org.jetbrains.kotlin.types.typeUtil.nullability import org.jetbrains.kotlin.utils.IDEAPlatforms import org.jetbrains.kotlin.utils.IDEAPluginsCompatibilityAPI -import java.util.* -interface Parameter { - val argumentText: String +interface Parameter: IParameter { val originalDescriptor: DeclarationDescriptor - val name: String - val mirrorVarName: String? - val receiverCandidate: Boolean - - val parameterType: KotlinType - - fun getParameterTypeCandidates(): List - - fun copy(name: String, parameterType: KotlinType): Parameter } -val Parameter.nameForRef: String get() = mirrorVarName ?: name - -data class TypeParameter( - val originalDeclaration: KtTypeParameter, - val originalConstraints: List -) - -interface Replacement : Function2 - -interface ParameterReplacement : Replacement { - val parameter: Parameter - fun copy(parameter: Parameter): ParameterReplacement -} - -class RenameReplacement(override val parameter: Parameter) : ParameterReplacement { - override fun copy(parameter: Parameter) = RenameReplacement(parameter) - - override fun invoke(descriptor: ExtractableCodeDescriptor, e: KtElement): KtElement { - val expressionToReplace = (e.parent as? KtThisExpression ?: e).let { it.getQualifiedExpressionForSelector() ?: it } - val parameterName = KtPsiUtil.unquoteIdentifier(parameter.nameForRef) - val replacingName = - if (e.text.startsWith('`') || !parameterName.isIdentifier()) "`$parameterName`" else parameterName - val psiFactory = KtPsiFactory(e.project) - val replacement = when { - parameter == descriptor.receiverParameter -> psiFactory.createExpression("this") - expressionToReplace is KtOperationReferenceExpression -> psiFactory.createOperationName(replacingName) - else -> psiFactory.createSimpleName(replacingName) - } - return expressionToReplace.replaced(replacement) - } -} - -abstract class WrapInWithReplacement : Replacement { - abstract val argumentText: String - - override fun invoke(descriptor: ExtractableCodeDescriptor, e: KtElement): KtElement { - val call = (e as? KtSimpleNameExpression)?.getQualifiedElement() ?: return e - val replacingExpression = KtPsiFactory(e.project).createExpressionByPattern("with($0) { $1 }", argumentText, call) - val replace = call.replace(replacingExpression) - return (replace as KtCallExpression).lambdaArguments.first().getLambdaExpression()!!.bodyExpression!!.statements.first() - } -} - -class WrapParameterInWithReplacement(override val parameter: Parameter) : WrapInWithReplacement(), ParameterReplacement { - override val argumentText: String - get() = parameter.name - - override fun copy(parameter: Parameter) = WrapParameterInWithReplacement(parameter) -} - -class WrapObjectInWithReplacement(val descriptor: ClassDescriptor) : WrapInWithReplacement() { - override val argumentText: String - get() = IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(descriptor) -} - -class AddPrefixReplacement(override val parameter: Parameter) : ParameterReplacement { - override fun copy(parameter: Parameter) = AddPrefixReplacement(parameter) - - override fun invoke(descriptor: ExtractableCodeDescriptor, e: KtElement): KtElement { - if (descriptor.receiverParameter == parameter) return e - - val selector = (e.parent as? KtCallExpression) ?: e - val replacingExpression = KtPsiFactory(e.project).createExpressionByPattern("${parameter.nameForRef}.$0", selector) - val newExpr = (selector.replace(replacingExpression) as KtQualifiedExpression).selectorExpression!! - return (newExpr as? KtCallExpression)?.calleeExpression ?: newExpr - } -} - -class FqNameReplacement(val fqName: FqName) : Replacement { - override fun invoke(descriptor: ExtractableCodeDescriptor, e: KtElement): KtElement { - val thisExpr = e.parent as? KtThisExpression - if (thisExpr != null) { - return thisExpr.replaced(KtPsiFactory(e.project).createExpression(fqName.asString())).getQualifiedElementSelector()!! - } - - val newExpr = (e as? KtSimpleNameExpression)?.mainReference?.bindToFqName(fqName, ShorteningMode.NO_SHORTENING) as KtElement - return if (newExpr is KtQualifiedExpression) newExpr.selectorExpression!! else newExpr - } -} - -interface OutputValue { - val originalExpressions: List - val valueType: KotlinType - - class ExpressionValue( - val callSiteReturn: Boolean, - override val originalExpressions: List, - override val valueType: KotlinType - ) : OutputValue - - class Jump( - val elementsToReplace: List, - val elementToInsertAfterCall: KtElement?, - val conditional: Boolean, - builtIns: KotlinBuiltIns - ) : OutputValue { - override val originalExpressions: List get() = elementsToReplace - override val valueType: KotlinType = with(builtIns) { if (conditional) booleanType else unitType } - } - - class ParameterUpdate( - val parameter: Parameter, - override val originalExpressions: List - ) : OutputValue { - override val valueType: KotlinType get() = parameter.parameterType - } - - class Initializer( - val initializedDeclaration: KtProperty, - override val valueType: KotlinType - ) : OutputValue { - override val originalExpressions: List get() = Collections.singletonList(initializedDeclaration) - } -} - -abstract class OutputValueBoxer(val outputValues: List) { - val outputValueTypes: List get() = outputValues.map { it.valueType } - - abstract val returnType: KotlinType - - protected abstract fun getBoxingExpressionPattern(arguments: List): String? - - abstract val boxingRequired: Boolean - - fun getReturnExpression(arguments: List, psiFactory: KtPsiFactory): KtReturnExpression? { - val expressionPattern = getBoxingExpressionPattern(arguments) ?: return null - return psiFactory.createExpressionByPattern("return $expressionPattern", *arguments.toTypedArray()) as KtReturnExpression - } - - protected abstract fun extractExpressionByIndex(boxedExpression: KtExpression, index: Int): KtExpression? - - protected fun extractArgumentExpressionByIndex(boxedExpression: KtExpression, index: Int): KtExpression? { - val call: KtCallExpression? = when (boxedExpression) { - is KtCallExpression -> boxedExpression - is KtQualifiedExpression -> boxedExpression.selectorExpression as? KtCallExpression - else -> null - } - val arguments = call?.valueArguments - if (arguments == null || arguments.size <= index) return null - - return arguments[index].getArgumentExpression() - } - - fun extractExpressionByValue(boxedExpression: KtExpression, value: OutputValue): KtExpression? { - val index = outputValues.indexOf(value) - if (index < 0) return null - - return extractExpressionByIndex(boxedExpression, index) - } - - abstract fun getUnboxingExpressions(boxedText: String): Map - - class AsTuple( - outputValues: List, - val module: ModuleDescriptor - ) : OutputValueBoxer(outputValues) { - init { - assert(outputValues.size <= 3) { "At most 3 output values are supported" } - } - - companion object { - private val selectors = arrayOf("first", "second", "third") - } - - override val returnType: KotlinType by lazy { - fun getType(): KotlinType { - val boxingClass = when (outputValues.size) { - 1 -> return outputValues.first().valueType - 2 -> module.resolveTopLevelClass(FqName("kotlin.Pair"), NoLookupLocation.FROM_IDE)!! - 3 -> module.resolveTopLevelClass(FqName("kotlin.Triple"), NoLookupLocation.FROM_IDE)!! - else -> return module.builtIns.defaultReturnType - } - return TypeUtils.substituteParameters(boxingClass, outputValueTypes) - } - - getType() - } - - override val boxingRequired: Boolean = outputValues.size > 1 - - override fun getBoxingExpressionPattern(arguments: List): String? { - return when (arguments.size) { - 0 -> null - 1 -> "$0" - else -> { - val constructorName = DescriptorUtils.getFqName(returnType.constructor.declarationDescriptor!!).asString() - return arguments.indices.joinToString(prefix = "$constructorName(", separator = ", ", postfix = ")") { "\$$it" } - } - } - } - - override fun extractExpressionByIndex(boxedExpression: KtExpression, index: Int): KtExpression? { - if (outputValues.size == 1) return boxedExpression - return extractArgumentExpressionByIndex(boxedExpression, index) - } - - override fun getUnboxingExpressions(boxedText: String): Map { - return when (outputValues.size) { - 0 -> Collections.emptyMap() - 1 -> Collections.singletonMap(outputValues.first(), boxedText) - else -> { - var i = 0 - ContainerUtil.newMapFromKeys(outputValues.iterator()) { "$boxedText.${selectors[i++]}" } - } - } - } - } - - class AsList(outputValues: List) : OutputValueBoxer(outputValues) { - override val returnType: KotlinType by lazy { - assert(outputValues.isNotEmpty()) - val builtIns = outputValues.first().valueType.builtIns - TypeUtils.substituteParameters( - builtIns.list, - Collections.singletonList(CommonSupertypes.commonSupertype(outputValues.map { it.valueType })) - ) - } - - override val boxingRequired: Boolean = outputValues.isNotEmpty() - - override fun getBoxingExpressionPattern(arguments: List): String? { - if (arguments.isEmpty()) return null - return arguments.indices.joinToString(prefix = "kotlin.collections.listOf(", separator = ", ", postfix = ")") { "\$$it" } - } - - override fun extractExpressionByIndex(boxedExpression: KtExpression, index: Int): KtExpression? { - return extractArgumentExpressionByIndex(boxedExpression, index) - } - - override fun getUnboxingExpressions(boxedText: String): Map { - var i = 0 - return ContainerUtil.newMapFromKeys(outputValues.iterator()) { "$boxedText[${i++}]" } - } - } -} - -data class ControlFlow( - val outputValues: List, - val boxerFactory: (List) -> OutputValueBoxer, - val declarationsToCopy: List -) { - val outputValueBoxer = boxerFactory(outputValues) - - val defaultOutputValue: ExpressionValue? = with(outputValues.filterIsInstance()) { - if (size > 1) throw IllegalArgumentException("Multiple expression values: ${outputValues.joinToString()}") else firstOrNull() - } - - val jumpOutputValue: Jump? = with(outputValues.filterIsInstance()) { - val jumpCount = size - when { - isEmpty() -> - null - outputValues.size > jumpCount || jumpCount > 1 -> - throw IllegalArgumentException("Jump values must be the only value if it's present: ${outputValues.joinToString()}") - else -> - first() - } - } -} - -val ControlFlow.possibleReturnTypes: List +val ControlFlow.possibleReturnTypes: List get() { val returnType = outputValueBoxer.returnType return when { @@ -327,33 +41,46 @@ val ControlFlow.possibleReturnTypes: List } } -fun ControlFlow.toDefault(): ControlFlow = - copy(outputValues = outputValues.filterNot { it is Jump || it is ExpressionValue }) -fun ControlFlow.copy(oldToNewParameters: Map): ControlFlow { +fun ControlFlow.copy(oldToNewParameters: Map): ControlFlow { val newOutputValues = outputValues.map { if (it is ParameterUpdate) ParameterUpdate(oldToNewParameters[it.parameter]!!, it.originalExpressions) else it } return copy(outputValues = newOutputValues) } + +class WrapObjectInWithReplacement(val descriptor: ClassDescriptor) : WrapInWithReplacement() { + override val argumentText: String + get() = IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(descriptor) +} + data class ExtractableCodeDescriptor( - val extractionData: ExtractionData, + override val extractionData: ExtractionData, val originalContext: BindingContext, - val suggestedNames: List, - val visibility: KtModifierKeywordToken?, - val parameters: List, - val receiverParameter: Parameter?, - val typeParameters: List, - val replacementMap: MultiMap, - val controlFlow: ControlFlow, - val returnType: KotlinType, - val modifiers: List = emptyList(), + override val suggestedNames: List, + override val visibility: KtModifierKeywordToken?, + override val parameters: List, + override val receiverParameter: Parameter?, + override val typeParameters: List, + override val replacementMap: MultiMap>, + override val controlFlow: ControlFlow, + override val returnType: KotlinType, + override val modifiers: List = emptyList(), val annotations: List = emptyList(), - val optInMarkers: List = emptyList() -) { - val name: String get() = suggestedNames.firstOrNull() ?: "" - val duplicates: List by lazy { findDuplicates() } + override val optInMarkers: List = emptyList() +): IExtractableCodeDescriptor { + override val name: String get() = suggestedNames.firstOrNull() ?: "" + override val duplicates: List> by lazy { findDuplicates() } + override fun isUnitReturnType(): Boolean { + return returnType.isUnit() + } + + override val annotationsText: String + get() { + if (annotations.isEmpty()) return "" + return annotations.map { IdeDescriptorRenderers.SOURCE_CODE.renderAnnotation(it) }.joinToString("\n", postfix = "\n") + } } /** @@ -382,18 +109,18 @@ fun ExtractableCodeDescriptor.withVisibility( plugins = "Jetpack Compose plugin in IDEA" ) fun ExtractableCodeDescriptor.copy( - extractionData: ExtractionData = this.extractionData, - originalContext: BindingContext = this.originalContext, - suggestedNames: List = this.suggestedNames, - visibility: KtModifierKeywordToken? = this.visibility, - parameters: List = this.parameters, - receiverParameter: Parameter? = this.receiverParameter, - typeParameters: List = this.typeParameters, - replacementMap: MultiMap = this.replacementMap, - controlFlow: ControlFlow = this.controlFlow, - returnType: KotlinType = this.returnType, - modifiers: List = this.modifiers, - annotations: List = this.annotations + extractionData: ExtractionData = this.extractionData, + originalContext: BindingContext = this.originalContext, + suggestedNames: List = this.suggestedNames, + visibility: KtModifierKeywordToken? = this.visibility, + parameters: List = this.parameters, + receiverParameter: Parameter? = this.receiverParameter, + typeParameters: List = this.typeParameters, + replacementMap: MultiMap> = this.replacementMap, + controlFlow: ControlFlow = this.controlFlow, + returnType: KotlinType = this.returnType, + modifiers: List = this.modifiers, + annotations: List = this.annotations ) = copy( extractionData, originalContext, @@ -417,7 +144,7 @@ fun ExtractableCodeDescriptor.copy( newReceiver: Parameter?, returnType: KotlinType? ): ExtractableCodeDescriptor { - val newReplacementMap = MultiMap.create() + val newReplacementMap = MultiMap.create>() for ((ref, replacements) in replacementMap.entrySet()) { val newReplacements = replacements.map { if (it is ParameterReplacement) { @@ -446,165 +173,20 @@ fun ExtractableCodeDescriptor.copy( ) } -enum class ExtractionTarget(val targetName: String) { - FUNCTION(KotlinBundle.message("text.function")) { - override fun isAvailable(descriptor: ExtractableCodeDescriptor) = true - }, - - FAKE_LAMBDALIKE_FUNCTION(KotlinBundle.message("text.lambda.parameter")) { - override fun isAvailable(descriptor: ExtractableCodeDescriptor): Boolean { - return checkSimpleControlFlow(descriptor) || descriptor.controlFlow.outputValues.isEmpty() - } - }, - - PROPERTY_WITH_INITIALIZER(KotlinBundle.message("text.property.with.initializer")) { - override fun isAvailable(descriptor: ExtractableCodeDescriptor): Boolean { - return checkSignatureAndParent(descriptor) - && checkSimpleControlFlow(descriptor) - && checkSimpleBody(descriptor) - && checkNotTrait(descriptor) - && descriptor.receiverParameter == null - } - }, - - PROPERTY_WITH_GETTER(KotlinBundle.message("text.property.with.getter")) { - override fun isAvailable(descriptor: ExtractableCodeDescriptor): Boolean { - return checkSignatureAndParent(descriptor) - } - }, - - LAZY_PROPERTY(KotlinBundle.message("text.lazy.property")) { - override fun isAvailable(descriptor: ExtractableCodeDescriptor): Boolean { - return checkSignatureAndParent(descriptor) - && checkSimpleControlFlow(descriptor) - && checkNotTrait(descriptor) - && descriptor.receiverParameter == null - } - }; - - abstract fun isAvailable(descriptor: ExtractableCodeDescriptor): Boolean - - companion object { - fun checkNotTrait(descriptor: ExtractableCodeDescriptor): Boolean { - val parent = descriptor.extractionData.targetSibling.getStrictParentOfType() - return !(parent is KtClass && parent.isInterface()) - } - - fun checkSimpleBody(descriptor: ExtractableCodeDescriptor): Boolean { - val expression = descriptor.extractionData.expressions.singleOrNull() - return expression != null && expression !is KtDeclaration && expression !is KtBlockExpression - } - - fun checkSimpleControlFlow(descriptor: ExtractableCodeDescriptor): Boolean { - val outputValue = descriptor.controlFlow.outputValues.singleOrNull() - return (outputValue is ExpressionValue && !outputValue.callSiteReturn) || outputValue is Initializer - } - - fun checkSignatureAndParent(descriptor: ExtractableCodeDescriptor): Boolean { - if (!descriptor.parameters.isEmpty()) return false - if (descriptor.returnType.isUnit()) return false - - val parent = descriptor.extractionData.targetSibling.parent - return (parent is KtFile || parent is KtClassBody) - } - } -} - -val propertyTargets: List = listOf( - ExtractionTarget.PROPERTY_WITH_INITIALIZER, - ExtractionTarget.PROPERTY_WITH_GETTER, - ExtractionTarget.LAZY_PROPERTY -) - -data class ExtractionGeneratorOptions( - val inTempFile: Boolean = false, - val target: ExtractionTarget = ExtractionTarget.FUNCTION, - val dummyName: String? = null, - val allowExpressionBody: Boolean = true, - val delayInitialOccurrenceReplacement: Boolean = false, - val isConst: Boolean = false -) { - companion object { - @JvmField - val DEFAULT = ExtractionGeneratorOptions() - } -} - data class ExtractionGeneratorConfiguration( - val descriptor: ExtractableCodeDescriptor, - val generatorOptions: ExtractionGeneratorOptions -) + override val descriptor: ExtractableCodeDescriptor, + override val generatorOptions: ExtractionGeneratorOptions +): IExtractionGeneratorConfiguration data class ExtractionResult( - val config: ExtractionGeneratorConfiguration, - val declaration: KtNamedDeclaration, - val duplicateReplacers: Map Unit> -) : Disposable { + override val config: ExtractionGeneratorConfiguration, + override val declaration: KtNamedDeclaration, + override val duplicateReplacers: Map Unit> +) : IExtractionResult { override fun dispose() = unmarkReferencesInside(declaration) } -class AnalysisResult( - val descriptor: ExtractableCodeDescriptor?, - val status: Status, - val messages: List -) { - enum class Status { - SUCCESS, - NON_CRITICAL_ERROR, - CRITICAL_ERROR - } - - enum class ErrorMessage { - NO_EXPRESSION, - NO_CONTAINER, - SYNTAX_ERRORS, - SUPER_CALL, - DENOTABLE_TYPES, - ERROR_TYPES, - MULTIPLE_OUTPUT, - OUTPUT_AND_EXIT_POINT, - MULTIPLE_EXIT_POINTS, - DECLARATIONS_ARE_USED_OUTSIDE, - DECLARATIONS_OUT_OF_SCOPE; - - var additionalInfo: List? = null - - fun addAdditionalInfo(info: List): ErrorMessage { - additionalInfo = info - return this - } - - @Nls - fun renderMessage(): String { - val message = KotlinBundle.message( - when (this) { - NO_EXPRESSION -> "cannot.refactor.no.expression" - NO_CONTAINER -> "cannot.refactor.no.container" - SYNTAX_ERRORS -> "cannot.refactor.syntax.errors" - SUPER_CALL -> "cannot.extract.super.call" - DENOTABLE_TYPES -> "parameter.types.are.not.denotable" - ERROR_TYPES -> "error.types.in.generated.function" - MULTIPLE_OUTPUT -> "selected.code.fragment.has.multiple.output.values" - OUTPUT_AND_EXIT_POINT -> "selected.code.fragment.has.output.values.and.exit.points" - MULTIPLE_EXIT_POINTS -> "selected.code.fragment.has.multiple.exit.points" - DECLARATIONS_ARE_USED_OUTSIDE -> "declarations.are.used.outside.of.selected.code.fragment" - DECLARATIONS_OUT_OF_SCOPE -> "declarations.will.move.out.of.scope" - } - ) - - return additionalInfo?.let { "$message\n\n${it.joinToString("\n") { msg -> - @Suppress("HardCodedStringLiteral") - StringUtil.htmlEmphasize(msg) - }}" } ?: message - } - } -} - -sealed class ExtractableCodeDescriptorWithConflictsResult - data class ExtractableCodeDescriptorWithConflicts( - val descriptor: ExtractableCodeDescriptor, - val conflicts: MultiMap -): ExtractableCodeDescriptorWithConflictsResult() - -data class ExtractableCodeDescriptorWithException(val exception: RuntimeException): ExtractableCodeDescriptorWithConflictsResult() + override val descriptor: ExtractableCodeDescriptor, + override val conflicts: MultiMap +): ExtractableCodeDescriptorWithConflictsResult(), IExtractableCodeDescriptorWithConflicts diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionData.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionData.kt index d8a56e95e71f..6e7f776b044d 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionData.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionData.kt @@ -2,10 +2,7 @@ package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine -import com.intellij.openapi.Disposable -import com.intellij.openapi.application.runReadAction import com.intellij.openapi.project.Project -import com.intellij.openapi.util.Key import com.intellij.psi.PsiElement import com.intellij.psi.PsiNameIdentifierOwner import com.intellij.psi.util.PsiTreeUtil @@ -28,55 +25,38 @@ import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfoAfter -import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall -import org.jetbrains.kotlin.resolve.calls.util.getImplicitReceiverValue -import org.jetbrains.kotlin.resolve.calls.util.hasBothReceivers import org.jetbrains.kotlin.resolve.calls.tasks.isSynthesizedInvoke +import org.jetbrains.kotlin.resolve.calls.util.getImplicitReceiverValue +import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall +import org.jetbrains.kotlin.resolve.calls.util.hasBothReceivers import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver import org.jetbrains.kotlin.synthetic.SyntheticJavaPropertyDescriptor import org.jetbrains.kotlin.types.KotlinType -data class ResolveResult( - val originalRefExpr: KtSimpleNameExpression, - val declaration: PsiElement, - val descriptor: DeclarationDescriptor, - val resolvedCall: ResolvedCall<*>? -) - -data class ResolvedReferenceInfo( - val refExpr: KtSimpleNameExpression, - val resolveResult: ResolveResult, - val smartCast: KotlinType?, - val possibleTypes: Set, - val shouldSkipPrimaryReceiver: Boolean -) - -internal var KtSimpleNameExpression.resolveResult: ResolveResult? by CopyablePsiUserDataProperty(Key.create("RESOLVE_RESULT")) - data class ExtractionData( - val originalFile: KtFile, - val originalRange: KotlinPsiRange, - val targetSibling: PsiElement, - val duplicateContainer: PsiElement? = null, - val options: ExtractionOptions = ExtractionOptions.DEFAULT -) : Disposable { - val project: Project = originalFile.project - val originalElements: List = originalRange.elements - val physicalElements = originalElements.map { it.substringContextOrThis } + override val originalFile: KtFile, + override val originalRange: KotlinPsiRange, + override val targetSibling: PsiElement, + override val duplicateContainer: PsiElement? = null, + override val options: ExtractionOptions = ExtractionOptions.DEFAULT +) : IExtractionData { + override val project: Project = originalFile.project + override val originalElements: List = originalRange.elements + override val physicalElements = originalElements.map { it.substringContextOrThis } - val substringInfo: K1ExtractableSubstringInfo? + override val substringInfo: K1ExtractableSubstringInfo? get() = (originalElements.singleOrNull() as? KtExpression)?.extractableSubstringInfo as? K1ExtractableSubstringInfo - val insertBefore: Boolean = options.extractAsProperty + override val insertBefore: Boolean = options.extractAsProperty || targetSibling.getStrictParentOfType()?.let { it is KtDeclarationWithBody || it is KtAnonymousInitializer } ?: false - val expressions = originalElements.filterIsInstance() + override val expressions: List = originalElements.filterIsInstance() - val codeFragmentText: String by lazy { + override val codeFragmentText: String by lazy { val originalElements = originalElements when (originalElements.size) { 0 -> "" @@ -85,15 +65,22 @@ data class ExtractionData( } } - val commonParent = PsiTreeUtil.findCommonParent(physicalElements) as KtElement + override val commonParent: KtElement = PsiTreeUtil.findCommonParent(physicalElements) as KtElement - val bindingContext: BindingContext? by lazy { commonParent.analyze() } + val bindingContext: BindingContext by lazy { commonParent.analyze() } private val itFakeDeclaration by lazy { KtPsiFactory(project).createParameter("it: Any?") } private val synthesizedInvokeDeclaration by lazy { KtPsiFactory(project).createFunction("fun invoke() {}") } init { - markReferences() + encodeReferences>({ bindingContext[BindingContext.SMARTCAST, it] != null }) { physicalRef -> + val resolvedCall = physicalRef.getResolvedCall(bindingContext) + val descriptor = + bindingContext[BindingContext.REFERENCE_TARGET, physicalRef] + val declaration = descriptor?.let { getDeclaration(descriptor, bindingContext) } + + declaration?.let { ResolveResult>(physicalRef, declaration, descriptor, resolvedCall) } + } } private fun isExtractableIt(descriptor: DeclarationDescriptor, context: BindingContext): Boolean { @@ -116,44 +103,6 @@ data class ExtractionData( } } - private fun markReferences() { - val context = bindingContext ?: return - val visitor = object : KtTreeVisitorVoid() { - override fun visitQualifiedExpression(expression: KtQualifiedExpression) { - if (context[BindingContext.SMARTCAST, expression] != null) { - expression.selectorExpression?.accept(this) - return - } - - super.visitQualifiedExpression(expression) - } - - override fun visitSimpleNameExpression(ref: KtSimpleNameExpression) { - if (ref.parent is KtValueArgumentName) return - - val physicalRef = substringInfo?.let { - // If substring contains some references it must be extracted as a string template - val physicalExpression = expressions.single() as KtStringTemplateExpression - val extractedContentOffset = physicalExpression.getContentRange().startOffset + physicalExpression.startOffset - val offsetInExtracted = ref.startOffset - extractedContentOffset - val offsetInTemplate = it.relativeContentRange.startOffset + offsetInExtracted - it.template.findElementAt(offsetInTemplate)!!.getStrictParentOfType() - } ?: ref - - val resolvedCall = physicalRef.getResolvedCall(context) - val descriptor = context[BindingContext.REFERENCE_TARGET, physicalRef] ?: return - val declaration = getDeclaration(descriptor, context) ?: return - - val resolveResult = ResolveResult(physicalRef, declaration, descriptor, resolvedCall) - physicalRef.resolveResult = resolveResult - if (ref != physicalRef) { - ref.resolveResult = resolveResult - } - } - } - expressions.forEach { it.accept(visitor) } - } - private fun getPossibleTypes(expression: KtExpression, resolvedCall: ResolvedCall<*>?, context: BindingContext): Set { val dataFlowValueFactory = expression.getResolutionFacade().dataFlowValueFactory val dataFlowInfo = context.getDataFlowInfoAfter(expression) @@ -171,20 +120,19 @@ data class ExtractionData( return dataFlowInfo.getCollectedTypes(dataFlowValue, expression.languageVersionSettings) } - fun getBrokenReferencesInfo(body: KtBlockExpression): List { - val originalContext = bindingContext ?: return listOf() + fun getBrokenReferencesInfo(body: KtBlockExpression): List, KotlinType>> { + val originalContext = bindingContext val newReferences = body.collectDescendantsOfType { it.resolveResult != null } val context = body.analyze() - val referencesInfo = ArrayList() + val referencesInfo = ArrayList, KotlinType>>() for (newRef in newReferences) { - val originalResolveResult = newRef.resolveResult ?: continue + val originalResolveResult = newRef.resolveResult as? ResolveResult> ?: continue val smartCast: KotlinType? val possibleTypes: Set - val shouldSkipPrimaryReceiver: Boolean // Qualified property reference: a.b val qualifiedExpression = newRef.getQualifiedExpressionForSelector() @@ -194,7 +142,7 @@ data class ExtractionData( possibleTypes = getPossibleTypes(smartCastTarget, originalResolveResult.resolvedCall, originalContext) val receiverDescriptor = (originalResolveResult.resolvedCall?.dispatchReceiver as? ImplicitReceiver)?.declarationDescriptor - shouldSkipPrimaryReceiver = smartCast == null + val shouldSkipPrimaryReceiver = smartCast == null && !DescriptorUtils.isCompanionObject(receiverDescriptor) && qualifiedExpression.receiverExpression !is KtSuperExpression if (shouldSkipPrimaryReceiver && originalResolveResult.resolvedCall?.hasBothReceivers() != true) continue @@ -202,7 +150,6 @@ data class ExtractionData( if (newRef.getParentOfTypeAndBranch { callableReference } != null) continue smartCast = originalContext[BindingContext.SMARTCAST, originalResolveResult.originalRefExpr]?.defaultType possibleTypes = getPossibleTypes(originalResolveResult.originalRefExpr, originalResolveResult.resolvedCall, originalContext) - shouldSkipPrimaryReceiver = false } val parent = newRef.parent @@ -238,7 +185,6 @@ data class ExtractionData( variableResolveResult, smartCast, possibleTypes, - shouldSkipPrimaryReceiver ) ) referencesInfo.add( @@ -247,7 +193,6 @@ data class ExtractionData( functionResolveResult, smartCast, possibleTypes, - shouldSkipPrimaryReceiver ) ) } else { @@ -257,7 +202,6 @@ data class ExtractionData( originalResolveResult, smartCast, possibleTypes, - shouldSkipPrimaryReceiver ) ) } @@ -270,11 +214,4 @@ data class ExtractionData( override fun dispose() { expressions.forEach(::unmarkReferencesInside) } -} - -fun unmarkReferencesInside(root: PsiElement) { - runReadAction { - if (!root.isValid) return@runReadAction - root.forEachDescendantOfType { it.resolveResult = null } - } } \ No newline at end of file diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionEngine.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionEngine.kt index db9c13e892c2..e976d5adae0a 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionEngine.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionEngine.kt @@ -2,116 +2,23 @@ package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine -import com.intellij.openapi.actionSystem.ex.ActionUtil -import com.intellij.openapi.editor.Editor -import com.intellij.openapi.project.Project -import com.intellij.openapi.ui.MessageType -import com.intellij.openapi.ui.popup.Balloon -import com.intellij.openapi.ui.popup.JBPopupFactory import com.intellij.openapi.util.NlsContexts -import com.intellij.openapi.util.NlsSafe -import com.intellij.refactoring.BaseRefactoringProcessor -import com.intellij.ui.awt.RelativePoint -import org.jetbrains.kotlin.idea.base.resources.KotlinBundle -import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively -import org.jetbrains.kotlin.idea.refactoring.introduce.showErrorHint -import org.jetbrains.kotlin.idea.util.application.executeWriteCommand -import org.jetbrains.kotlin.idea.util.application.isUnitTestMode -import org.jetbrains.kotlin.idea.util.nonBlocking -import org.jetbrains.kotlin.utils.addToStdlib.cast -import org.jetbrains.kotlin.utils.addToStdlib.safeAs -import javax.swing.event.HyperlinkEvent +import org.jetbrains.kotlin.types.KotlinType -abstract class ExtractionEngineHelper(@NlsContexts.DialogTitle val operationName: String) { - open fun adjustExtractionData(data: ExtractionData): ExtractionData = data +abstract class ExtractionEngineHelper(@NlsContexts.DialogTitle operationName: String) + : IExtractionEngineHelper(operationName) { - fun doRefactor(config: ExtractionGeneratorConfiguration, onFinish: (ExtractionResult) -> Unit = {}) { - val project = config.descriptor.extractionData.project - onFinish(project.executeWriteCommand(operationName) { config.generateDeclaration() }) + override fun generateDeclaration(config: ExtractionGeneratorConfiguration): ExtractionResult { + return config.generateDeclaration() } - open fun validate(descriptor: ExtractableCodeDescriptor): ExtractableCodeDescriptorWithConflicts = descriptor.validate() - - abstract fun configureAndRun( - project: Project, - editor: Editor, - descriptorWithConflicts: ExtractableCodeDescriptorWithConflicts, - onFinish: (ExtractionResult) -> Unit = {} - ) + override fun validate(descriptor: ExtractableCodeDescriptor): ExtractableCodeDescriptorWithConflicts = descriptor.validate() } class ExtractionEngine( - val helper: ExtractionEngineHelper -) { - fun run( - editor: Editor, - extractionData: ExtractionData, - onFinish: (ExtractionResult) -> Unit = {} - ) { - val project = extractionData.project - - val adjustExtractionData = helper.adjustExtractionData(extractionData) - val analysisResult = ActionUtil.underModalProgress(project, KotlinBundle.message("progress.title.analyze.extraction.data")) { - adjustExtractionData.performAnalysis() - } - - if (isUnitTestMode() && analysisResult.status != AnalysisResult.Status.SUCCESS) { - throw BaseRefactoringProcessor.ConflictsInTestsException(analysisResult.messages.map { it.renderMessage() }) - } - - fun validateAndRefactor() { - nonBlocking(project, { - try { - helper.validate(analysisResult.descriptor!!) - } catch (e: RuntimeException) { - ExtractableCodeDescriptorWithException(e) - } - }) { result -> - result.safeAs()?.let { throw it.exception } - val validationResult = result.cast() - project.checkConflictsInteractively(validationResult.conflicts) { - helper.configureAndRun(project, editor, validationResult) { - try { - onFinish(it) - } finally { - it.dispose() - extractionData.dispose() - } - } - } - } - } - - val message = analysisResult.messages.joinToString("\n") { it.renderMessage() } - when (analysisResult.status) { - AnalysisResult.Status.CRITICAL_ERROR -> { - showErrorHint(project, editor, message, helper.operationName) - } - - AnalysisResult.Status.NON_CRITICAL_ERROR -> { - val anchorPoint = RelativePoint( - editor.contentComponent, - editor.visualPositionToXY(editor.selectionModel.selectionStartPosition!!) - ) - @NlsSafe val htmlContent = - "$message

${KotlinBundle.message("text.proceed.with.extraction")}" - JBPopupFactory.getInstance()!! - .createHtmlTextBalloonBuilder( - htmlContent, - MessageType.WARNING - ) { event -> - if (event?.eventType == HyperlinkEvent.EventType.ACTIVATED) { - validateAndRefactor() - } - } - .setHideOnClickOutside(true) - .setHideOnFrameResize(false) - .setHideOnLinkClick(true) - .createBalloon() - .show(anchorPoint, Balloon.Position.below) - } - - AnalysisResult.Status.SUCCESS -> validateAndRefactor() - } + helper: ExtractionEngineHelper +): IExtractionEngine(helper) { + override fun performAnalysis(extractionData: ExtractionData): AnalysisResult { + return extractionData.performAnalysis() } } diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt index eacb7a4e05aa..8218486ef9ee 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt @@ -4,10 +4,10 @@ package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine import com.intellij.psi.PsiElement import com.intellij.psi.PsiNamedElement -import com.intellij.psi.util.PsiTreeUtil import com.intellij.refactoring.util.RefactoringUIUtil import com.intellij.util.containers.MultiMap import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.builtins.isFunctionType import org.jetbrains.kotlin.cfg.containingDeclarationForPseudocode import org.jetbrains.kotlin.cfg.pseudocode.* import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction @@ -26,7 +26,6 @@ import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor import org.jetbrains.kotlin.diagnostics.Errors -import org.jetbrains.kotlin.idea.base.codeInsight.KotlinNameSuggester import org.jetbrains.kotlin.idea.base.resources.KotlinBundle import org.jetbrains.kotlin.idea.base.codeInsight.KotlinNameSuggestionProvider import org.jetbrains.kotlin.idea.base.fe10.codeInsight.newDeclaration.Fe10KotlinNameSuggester @@ -36,15 +35,11 @@ import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithContent import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde -import org.jetbrains.kotlin.idea.core.OLD_EXPERIMENTAL_FQ_NAME import org.jetbrains.kotlin.idea.core.OPT_IN_FQ_NAMES import org.jetbrains.kotlin.idea.core.compareDescriptors -import org.jetbrains.kotlin.idea.refactoring.createTempCopy -import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.AnalysisResult.ErrorMessage -import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.AnalysisResult.Status -import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.* import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.util.* +import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.lexer.KtModifierKeywordToken import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.name.FqName @@ -54,17 +49,16 @@ import org.jetbrains.kotlin.renderer.DescriptorRenderer import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsStatement -import org.jetbrains.kotlin.resolve.calls.util.getCalleeExpressionIfAny +import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.checkers.OptInNames import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor +import org.jetbrains.kotlin.resolve.descriptorUtil.resolveTopLevelClass import org.jetbrains.kotlin.resolve.scopes.LexicalScope import org.jetbrains.kotlin.types.* import org.jetbrains.kotlin.types.error.ErrorUtils import org.jetbrains.kotlin.types.typeUtil.makeNullable -import org.jetbrains.kotlin.util.capitalizeDecapitalize.capitalizeAsciiOnly -import org.jetbrains.kotlin.utils.DFS.* import org.jetbrains.kotlin.utils.addToStdlib.safeAs import java.util.* @@ -196,10 +190,6 @@ private fun getCommonNonTrivialSuccessorIfAny(instructions: List): return singleSuccessorCheckingVisitor.target ?: instructions.firstOrNull()?.owner?.sinkInstruction } -private fun KotlinType.isMeaningful(): Boolean { - return !KotlinBuiltIns.isUnit(this) && !KotlinBuiltIns.isNothing(this) -} - private fun ExtractionData.getLocalDeclarationsWithNonLocalUsages( pseudocode: Pseudocode, localInstructions: List, @@ -219,256 +209,33 @@ private fun ExtractionData.getLocalDeclarationsWithNonLocalUsages( return declarations.sortedBy { it.textRange!!.startOffset } } -private fun ExtractionData.analyzeControlFlow( - localInstructions: List, - pseudocode: Pseudocode, - module: ModuleDescriptor, - bindingContext: BindingContext, - modifiedVarDescriptors: Map>, - options: ExtractionOptions, - targetScope: LexicalScope?, - parameters: Set -): Pair { - val exitPoints = localInstructions.getExitPoints() - - val valuedReturnExits = ArrayList() - val defaultExits = ArrayList() - val jumpExits = ArrayList() - exitPoints.forEach { - val e = (it as? UnconditionalJumpInstruction)?.element - - when (val inst = when { - it !is ReturnValueInstruction && it !is ReturnNoValueInstruction && it.owner != pseudocode -> null - it is UnconditionalJumpInstruction && it.targetLabel.isJumpToError -> it - e != null && e !is KtBreakExpression && e !is KtContinueExpression -> it.previousInstructions.firstOrNull() - else -> it - }) { - is ReturnValueInstruction -> if (inst.owner == pseudocode) { - if (inst.returnExpressionIfAny == null) { - defaultExits.add(inst) - } else { - valuedReturnExits.add(inst) - } - } - - is AbstractJumpInstruction -> { - val element = inst.element - if ((element is KtReturnExpression && inst.owner == pseudocode) - || element is KtBreakExpression - || element is KtContinueExpression - ) jumpExits.add(inst) else if (element !is KtThrowExpression && !inst.targetLabel.isJumpToError) defaultExits.add(inst) - } - - else -> if (inst != null && inst !is LocalFunctionDeclarationInstruction) defaultExits.add(inst) - } - } - - val nonLocallyUsedDeclarations = getLocalDeclarationsWithNonLocalUsages(pseudocode, localInstructions, bindingContext) - val (declarationsToCopy, declarationsToReport) = nonLocallyUsedDeclarations.partition { it is KtProperty && it.isLocal } - - val (typeOfDefaultFlow, defaultResultExpressions) = getResultTypeAndExpressions( - defaultExits, - bindingContext, - targetScope, - options, - module - ) - - val (returnValueType, valuedReturnExpressions) = getResultTypeAndExpressions( - valuedReturnExits, - bindingContext, - targetScope, - options, - module - ) - - val emptyControlFlow = - ControlFlow(Collections.emptyList(), { OutputValueBoxer.AsTuple(it, module) }, declarationsToCopy) - - val defaultReturnType = if (returnValueType.isMeaningful()) returnValueType else typeOfDefaultFlow - if (defaultReturnType.isError) return emptyControlFlow to ErrorMessage.ERROR_TYPES - - val controlFlow = if (defaultReturnType.isMeaningful()) { - emptyControlFlow.copy(outputValues = Collections.singletonList(ExpressionValue(false, defaultResultExpressions, defaultReturnType))) - } else emptyControlFlow - - if (declarationsToReport.isNotEmpty()) { - val localVarStr = declarationsToReport.map { it.renderForMessage(bindingContext)!! }.distinct().sorted() - return controlFlow to ErrorMessage.DECLARATIONS_ARE_USED_OUTSIDE.addAdditionalInfo(localVarStr) - } - - val outParameters = - parameters.filter { it.mirrorVarName != null && modifiedVarDescriptors[it.originalDescriptor] != null }.sortedBy { it.nameForRef } - val outDeclarations = - declarationsToCopy.filter { modifiedVarDescriptors[bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, it]] != null } - val modifiedValueCount = outParameters.size + outDeclarations.size - - val outputValues = ArrayList() - - val multipleExitsError = controlFlow to ErrorMessage.MULTIPLE_EXIT_POINTS - val outputAndExitsError = controlFlow to ErrorMessage.OUTPUT_AND_EXIT_POINT - - if (typeOfDefaultFlow.isMeaningful()) { - if (valuedReturnExits.isNotEmpty() || jumpExits.isNotEmpty()) return multipleExitsError - - outputValues.add(ExpressionValue(false, defaultResultExpressions, typeOfDefaultFlow)) - } else if (valuedReturnExits.isNotEmpty()) { - if (jumpExits.isNotEmpty()) return multipleExitsError - - if (defaultExits.isNotEmpty()) { - if (modifiedValueCount != 0) return outputAndExitsError - if (valuedReturnExits.size != 1) return multipleExitsError - - val element = valuedReturnExits.first().element as KtExpression - return controlFlow.copy(outputValues = Collections.singletonList(Jump(listOf(element), element, true, module.builtIns))) to null - } - - if (getCommonNonTrivialSuccessorIfAny(valuedReturnExits) == null) return multipleExitsError - outputValues.add(ExpressionValue(true, valuedReturnExpressions, returnValueType)) - } - - outDeclarations.mapTo(outputValues) { - val descriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, it] as? CallableDescriptor - Initializer(it as KtProperty, descriptor?.returnType ?: module.builtIns.defaultParameterType) - } - outParameters.mapTo(outputValues) { ParameterUpdate(it, modifiedVarDescriptors[it.originalDescriptor]!!) } - - if (outputValues.isNotEmpty()) { - if (jumpExits.isNotEmpty()) return outputAndExitsError - - val boxerFactory: (List) -> OutputValueBoxer = when { - outputValues.size > 3 -> { - if (!options.enableListBoxing) { - val outValuesStr = - (outParameters.map { it.originalDescriptor.renderForMessage() } - + outDeclarations.map { it.renderForMessage(bindingContext)!! }).sorted() - return controlFlow to ErrorMessage.MULTIPLE_OUTPUT.addAdditionalInfo(outValuesStr) - } - { values -> OutputValueBoxer.AsList(values) } - } - - else -> controlFlow.boxerFactory - } - - return controlFlow.copy(outputValues = outputValues, boxerFactory = boxerFactory) to null - } - - if (jumpExits.isNotEmpty()) { - val jumpTarget = getCommonNonTrivialSuccessorIfAny(jumpExits) ?: return multipleExitsError - - val singleExit = getCommonNonTrivialSuccessorIfAny(defaultExits) == jumpTarget - val conditional = !singleExit && defaultExits.isNotEmpty() - val elements = jumpExits.map { it.element as KtExpression } - val elementToInsertAfterCall = if (singleExit) null else elements.first() - return controlFlow.copy( - outputValues = Collections.singletonList( - Jump( - elements, - elementToInsertAfterCall, - conditional, - module.builtIns - ) - ) - ) to null - } - - return controlFlow to null -} - -fun ExtractionData.createTemporaryDeclaration(pattern: String): KtNamedDeclaration { - val targetSiblingMarker = Any() - PsiTreeUtil.mark(targetSibling, targetSiblingMarker) - val tmpFile = originalFile.createTempCopy("") - tmpFile.deleteChildRange(tmpFile.firstChild, tmpFile.lastChild) - tmpFile.addRange(originalFile.firstChild, originalFile.lastChild) - val newTargetSibling = PsiTreeUtil.releaseMark(tmpFile, targetSiblingMarker)!! - val newTargetParent = newTargetSibling.parent - - val declaration = KtPsiFactory(project).createDeclarationByPattern( - pattern, - PsiChildRange(originalElements.firstOrNull(), originalElements.lastOrNull()) - ) - return if (insertBefore) { - newTargetParent.addBefore(declaration, newTargetSibling) as KtNamedDeclaration - } else { - newTargetParent.addAfter(declaration, newTargetSibling) as KtNamedDeclaration - } -} - -internal fun ExtractionData.createTemporaryCodeBlock(): KtBlockExpression { - if (options.extractAsProperty) { - return ((createTemporaryDeclaration("val = {\n$0\n}\n") as KtProperty).initializer as KtLambdaExpression).bodyExpression!! - } - return (createTemporaryDeclaration("fun() {\n$0\n}\n") as KtNamedFunction).bodyBlockExpression!! -} - -private fun KotlinType.collectReferencedTypes(processTypeArguments: Boolean): List { - if (!processTypeArguments) return Collections.singletonList(this) - return dfsFromNode( - this, - Neighbors { current -> current.arguments.map { it.type } }, - VisitedWithSet(), - object : CollectingNodeHandler>(ArrayList()) { - override fun afterChildren(current: KotlinType) { - result.add(current) - } - } - )!! -} - -fun KtTypeParameter.collectRelevantConstraints(): List { - val typeConstraints = getNonStrictParentOfType()?.typeConstraints ?: return Collections.emptyList() - return typeConstraints.filter { it.subjectTypeParameterName?.mainReference?.resolve() == this } -} - -fun TypeParameter.collectReferencedTypes(bindingContext: BindingContext): List { - val typeRefs = ArrayList() - originalDeclaration.extendsBound?.let { typeRefs.add(it) } - originalConstraints.mapNotNullTo(typeRefs) { it.boundTypeReference } - - return typeRefs.mapNotNull { bindingContext[BindingContext.TYPE, it] } -} - private fun KotlinType.isExtractable(targetScope: LexicalScope?): Boolean { - return collectReferencedTypes(true).fold(true) { extractable, typeToCheck -> - val parameterTypeDescriptor = typeToCheck.constructor.declarationDescriptor as? TypeParameterDescriptor - val typeParameter = parameterTypeDescriptor?.let { - DescriptorToSourceUtils.descriptorToDeclaration(it) - } as? KtTypeParameter - - extractable && (typeParameter != null || typeToCheck.isResolvableInScope(targetScope, false)) - } + return processTypeIfExtractable( + typeParameters = mutableSetOf(), + nonDenotableTypes = mutableSetOf(), + targetScope = targetScope, + processTypeArguments = true + ) } internal fun KotlinType.processTypeIfExtractable( typeParameters: MutableSet, nonDenotableTypes: MutableSet, - options: ExtractionOptions, targetScope: LexicalScope?, processTypeArguments: Boolean = true ): Boolean { - return collectReferencedTypes(processTypeArguments).fold(true) { extractable, typeToCheck -> + return processTypeIfExtractable(typeParameters, nonDenotableTypes, processTypeArguments, { type -> type.arguments.map { it.type } } ) { typeToCheck, typeParameters -> val parameterTypeDescriptor = typeToCheck.constructor.declarationDescriptor as? TypeParameterDescriptor val typeParameter = parameterTypeDescriptor?.let { DescriptorToSourceUtils.descriptorToDeclaration(it) } as? KtTypeParameter - when { - typeToCheck.isResolvableInScope(targetScope, true) -> - extractable - + typeToCheck.isResolvableInScope(targetScope, true) -> true typeParameter != null -> { typeParameters.add(TypeParameter(typeParameter, typeParameter.collectRelevantConstraints())) - extractable - } - - typeToCheck.isError -> - false - - else -> { - nonDenotableTypes.add(typeToCheck) - false + true } + else -> false } } } @@ -480,13 +247,13 @@ internal class MutableParameter( private val targetScope: LexicalScope?, private val originalType: KotlinType, private val possibleTypes: Set -) : Parameter { +) : Parameter, IMutableParameter { // All modifications happen in the same thread private var writable: Boolean = true private val defaultTypes = LinkedHashSet() private val typePredicates = HashSet() - var refCount: Int = 0 + override var refCount: Int = 0 fun addDefaultType(kotlinType: KotlinType) { assert(writable) { "Can't add type to non-writable parameter $currentName" } @@ -546,46 +313,10 @@ internal class MutableParameter( override val parameterType: KotlinType get() = getParameterTypeCandidates().firstOrNull() ?: defaultType - override fun copy(name: String, parameterType: KotlinType): Parameter = DelegatingParameter(this, name, parameterType) } -private class DelegatingParameter( - val original: Parameter, - override val name: String, - override val parameterType: KotlinType -) : Parameter by original { - override fun copy(name: String, parameterType: KotlinType): Parameter = DelegatingParameter(original, name, parameterType) -} - -private fun ExtractionData.checkDeclarationsMovingOutOfScope( - enclosingDeclaration: KtDeclaration, - controlFlow: ControlFlow, - bindingContext: BindingContext -): ErrorMessage? { - val declarationsOutOfScope = HashSet() - controlFlow.jumpOutputValue?.elementToInsertAfterCall?.accept( - object : KtTreeVisitorVoid() { - override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) { - val target = expression.mainReference.resolve() - if (target is KtNamedDeclaration - && target.isInsideOf(physicalElements) - && target.getStrictParentOfType() == enclosingDeclaration - ) { - declarationsOutOfScope.add(target) - } - } - } - ) - - if (declarationsOutOfScope.isNotEmpty()) { - val declStr = declarationsOutOfScope.map { it.renderForMessage(bindingContext)!! }.sorted() - return ErrorMessage.DECLARATIONS_OUT_OF_SCOPE.addAdditionalInfo(declStr) - } - - return null -} - -private fun ExtractionData.getLocalInstructions(pseudocode: Pseudocode): List { +private fun ExtractionData.getLocalInstructions(pseudocode: Pseudocode?): List? { + if (pseudocode == null) return null val instructions = ArrayList() pseudocode.traverse(TraversalOrder.FORWARD) { if (it is KtElementInstruction && it.element.isInsideOf(physicalElements)) { @@ -595,29 +326,6 @@ private fun ExtractionData.getLocalInstructions(pseudocode: Pseudocode): List() - if (parent is KtClass) { - if (parent.isInterface()) return null - if (parent.isEnum() && commonParent.getNonStrictParentOfType()?.getStrictParentOfType() == parent) return null - } - - return KtTokens.PRIVATE_KEYWORD -} - private data class ExperimentalMarkers( val propagatingMarkerDescriptors: List, val optInMarkers: List @@ -686,162 +394,281 @@ private fun ExtractionData.getExperimentalMarkers(): ExperimentalMarkers { ) } -fun ExtractionData.performAnalysis(): AnalysisResult { - if (originalElements.isEmpty()) return AnalysisResult(null, Status.CRITICAL_ERROR, listOf(ErrorMessage.NO_EXPRESSION)) +class KotlinTypeDescriptor(private val data: ExtractionData) : TypeDescriptor { + private val module = data.commonParent.containingKtFile.findModuleDescriptor() - val noContainerError = AnalysisResult(null, Status.CRITICAL_ERROR, listOf(ErrorMessage.NO_CONTAINER)) + override fun KotlinType.isMeaningful(): Boolean = !KotlinBuiltIns.isUnit(this) && !KotlinBuiltIns.isNothing(this) - val bindingContext = bindingContext ?: return noContainerError + override fun KotlinType.isError(): Boolean = isError - val declaration = commonParent.containingDeclarationForPseudocode ?: return noContainerError - val pseudocode = declaration.getContainingPseudocode(bindingContext) - ?: return AnalysisResult(null, Status.CRITICAL_ERROR, listOf(ErrorMessage.SYNTAX_ERRORS)) - val localInstructions = getLocalInstructions(pseudocode) + override val booleanType: KotlinType = module.builtIns.booleanType + override val unitType: KotlinType = module.builtIns.unitType + override val nullableAnyType: KotlinType = module.builtIns.nullableAnyType - val modifiedVarDescriptorsWithExpressions = localInstructions.getModifiedVarDescriptors(bindingContext) + override fun createListType(argTypes: List): KotlinType { + return TypeUtils.substituteParameters( + module.builtIns.list, + Collections.singletonList(CommonSupertypes.commonSupertype(argTypes)) + ) + } - val virtualBlock = createTemporaryCodeBlock() + override fun createTuple(outputValues: List>): KotlinType { + val boxingClass = when (outputValues.size) { + 1 -> return outputValues.first().valueType + 2 -> module.resolveTopLevelClass(FqName("kotlin.Pair"), NoLookupLocation.FROM_IDE)!! + 3 -> module.resolveTopLevelClass(FqName("kotlin.Triple"), NoLookupLocation.FROM_IDE)!! + else -> return module.builtIns.defaultReturnType + } + return TypeUtils.substituteParameters(boxingClass, outputValues.map { it.valueType }) + } - val targetScope = targetSibling.getResolutionScope(bindingContext, commonParent.getResolutionFacade()) - val paramsInfo = inferParametersInfo( - virtualBlock, - commonParent, - pseudocode, + override fun returnType(ktNamedDeclaration: KtNamedDeclaration): KotlinType? { + val descriptor = data.bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, ktNamedDeclaration] as? CallableDescriptor + return descriptor?.returnType + } + + override fun typeArguments(kotlinType: KotlinType): List { + return kotlinType.arguments.map { it.type } + } + + override fun renderType( + kotlinType: KotlinType, + isReceiver: Boolean, + variance: Variance + ): String { + val renderType = IdeDescriptorRenderers.SOURCE_CODE.renderType(kotlinType) + return if (kotlinType.isFunctionType && isReceiver) "($renderType)" else renderType + } + + override fun renderTypeWithoutApproximation(kotlinType: KotlinType): String { + return kotlinType.renderForMessage() + } + + override fun renderForMessage(ktNamedDeclaration: KtNamedDeclaration): String? { + return data.bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, ktNamedDeclaration]?.renderForMessage() + } + + override fun renderForMessage(param: IParameter): String { + return (param as Parameter).originalDescriptor.renderForMessage() + } + private val targetScope = data.targetSibling.getResolutionScope( + data.bindingContext, + data.commonParent.getResolutionFacade() + ) + + override fun isResolvableInScope( + typeToCheck: KotlinType, + typeParameters: MutableSet + ): Boolean { + val parameterTypeDescriptor = typeToCheck.constructor.declarationDescriptor as? TypeParameterDescriptor + val typeParameter = parameterTypeDescriptor?.let { + DescriptorToSourceUtils.descriptorToDeclaration(it) + } as? KtTypeParameter + return when { + typeToCheck.isResolvableInScope(targetScope, true) -> true + typeParameter != null -> { + typeParameters.add(TypeParameter(typeParameter, typeParameter.collectRelevantConstraints())) + true + } + else -> false + } + } +} + +object ExtractNameSuggester: IExtractionNameSuggester { + override fun suggestNamesByType( + kotlinType: KotlinType, + container: KtElement, + validator: (String) -> Boolean, + defaultName: String? + ): List { + if (KotlinBuiltIns.isUnit(kotlinType)) return emptyList() + return Fe10KotlinNameSuggester.suggestNamesByType(kotlinType, validator, defaultName) + } + + override fun createNameValidator( + container: KtElement, + anchor: PsiElement?, + validatorType: KotlinNameSuggestionProvider.ValidatorTarget + ): (String) -> Boolean { + return Fe10KotlinNewDeclarationNameValidator(container, anchor, validatorType) + } + + override fun suggestNameByName( + name: String, + container: KtElement, + anchor: PsiElement? + ): String { + return Fe10KotlinNameSuggester.suggestNameByName(name, createNameValidator(container, anchor, KotlinNameSuggestionProvider.ValidatorTarget.VARIABLE)) + } +} + +private class ExtractionDataAnalyzer(private val extractionData: ExtractionData): AbstractExtractionDataAnalyzer(extractionData) { + + private val pseudocode = extractionData.commonParent.containingDeclarationForPseudocode?.getContainingPseudocode(extractionData.bindingContext) + + override fun hasSyntaxErrors(): Boolean { + return pseudocode == null + } + + private val localInstructions = extractionData.getLocalInstructions(pseudocode) + + private val targetScope = extractionData.targetSibling.getResolutionScope( + extractionData.bindingContext, + extractionData.commonParent.getResolutionFacade() + ) + + private val modifiedVarDescriptorsWithExpressions = localInstructions?.getModifiedVarDescriptors(extractionData.bindingContext) + + override fun getLocalDeclarationsWithNonLocalUsages(): List { + return extractionData.getLocalDeclarationsWithNonLocalUsages(pseudocode!!, localInstructions!!, extractionData.bindingContext) + } + + override fun getVarDescriptorsAccessedAfterwards(): Set { + return localInstructions!!.getVarDescriptorsAccessedAfterwards(extractionData.bindingContext).map { it.name.asString() }.toSet() + } + + override fun getModifiedVars(): Map> { + return localInstructions!!.getModifiedVarDescriptors(extractionData.bindingContext).mapKeys { it.key.name.asString() } + } + + override fun createOutputDescriptor(): OutputDescriptor { + return extractionData.createOutputDescriptor(pseudocode!!, localInstructions!!, targetScope) + } + + override val nameSuggester: IExtractionNameSuggester = ExtractNameSuggester + + override val typeDescriptor: TypeDescriptor = KotlinTypeDescriptor(extractionData) + + override fun inferParametersInfo( + virtualBlock: KtBlockExpression, + modifiedVariables: Set + ): ParametersInfo { + return extractionData.inferParametersInfo( + virtualBlock, + pseudocode!!, + extractionData.bindingContext, + targetScope, + modifiedVarDescriptorsWithExpressions!!.keys + ) + } + + override fun createDescriptor( + suggestedFunctionNames: List, + defaultVisibility: KtModifierKeywordToken?, + parameters: List, + receiverParameter: MutableParameter?, + typeParameters: List, + replacementMap: MultiMap>, + flow: ControlFlow, + returnType: KotlinType + ): IExtractableCodeDescriptor { + val experimentalMarkers = extractionData.getExperimentalMarkers() + var descriptor = ExtractableCodeDescriptor( + extractionData, + extractionData.bindingContext, + suggestedFunctionNames, + defaultVisibility, + parameters, + receiverParameter as Parameter?, + typeParameters, + replacementMap, + flow, + returnType, + emptyList(), + annotations = experimentalMarkers.propagatingMarkerDescriptors, + optInMarkers = experimentalMarkers.optInMarkers + ) + + val generatedDeclaration = ExtractionGeneratorConfiguration( + descriptor, + ExtractionGeneratorOptions(inTempFile = true, allowExpressionBody = false) + ).generateDeclaration().declaration + val virtualContext = generatedDeclaration.analyzeWithContent() + if (virtualContext.diagnostics.all() + .any { it.factory == Errors.ILLEGAL_SUSPEND_FUNCTION_CALL || it.factory == Errors.ILLEGAL_SUSPEND_PROPERTY_ACCESS } + ) { + descriptor = descriptor.copy(modifiers = listOf(KtTokens.SUSPEND_KEYWORD)) + } + + for (analyser in AdditionalExtractableAnalyser.EP_NAME.extensions) { + descriptor = analyser.amendDescriptor(descriptor) + } + return descriptor + } +} + +fun ExtractionData.performAnalysis(): AnalysisResult { + return ExtractionDataAnalyzer(this).performAnalysis() +} + +private fun ExtractionData.createOutputDescriptor(pseudocode: Pseudocode, localInstructions: List, targetScope: LexicalScope): OutputDescriptor { + + val valuedReturnExits = ArrayList() + val defaultExits = ArrayList() + val jumpExits = ArrayList() + localInstructions.getExitPoints().forEach { + val e = (it as? UnconditionalJumpInstruction)?.element + + when (val inst = when { + it !is ReturnValueInstruction && it !is ReturnNoValueInstruction && it.owner != pseudocode -> null + it is UnconditionalJumpInstruction && it.targetLabel.isJumpToError -> it + e != null && e !is KtBreakExpression && e !is KtContinueExpression -> it.previousInstructions.firstOrNull() + else -> it + }) { + is ReturnValueInstruction -> if (inst.owner == pseudocode) { + if (inst.returnExpressionIfAny == null) { + defaultExits.add(inst) + } else { + valuedReturnExits.add(inst) + } + } + + is AbstractJumpInstruction -> { + val element = inst.element + if ((element is KtReturnExpression && inst.owner == pseudocode) + || element is KtBreakExpression + || element is KtContinueExpression + ) { + jumpExits.add(inst) + } else if (element !is KtThrowExpression && !inst.targetLabel.isJumpToError) defaultExits.add(inst) + } + + else -> if (inst != null && inst !is LocalFunctionDeclarationInstruction) defaultExits.add(inst) + } + } + + val module = originalFile.findModuleDescriptor() + val (typeOfDefaultFlow, defaultResultExpressions) = getResultTypeAndExpressions( + defaultExits, bindingContext, targetScope, - modifiedVarDescriptorsWithExpressions.keys + options, + module ) - if (paramsInfo.errorMessage != null) { - return AnalysisResult(null, Status.CRITICAL_ERROR, listOf(paramsInfo.errorMessage!!)) - } - val messages = ArrayList() - - val modifiedVarDescriptorsForControlFlow = HashMap(modifiedVarDescriptorsWithExpressions) - modifiedVarDescriptorsForControlFlow.keys.retainAll(localInstructions.getVarDescriptorsAccessedAfterwards(bindingContext)) - val (controlFlow, controlFlowMessage) = - analyzeControlFlow( - localInstructions, - pseudocode, - originalFile.findModuleDescriptor(), - bindingContext, - modifiedVarDescriptorsForControlFlow, - options, - targetScope, - paramsInfo.parameters - ) - controlFlowMessage?.let { messages.add(it) } - - val returnType = controlFlow.outputValueBoxer.returnType - returnType.processTypeIfExtractable(paramsInfo.typeParameters, paramsInfo.nonDenotableTypes, options, targetScope) - - if (paramsInfo.nonDenotableTypes.isNotEmpty()) { - val typeStr = paramsInfo.nonDenotableTypes.map { it.renderForMessage() }.sorted() - return AnalysisResult( - null, - Status.CRITICAL_ERROR, - listOf(ErrorMessage.DENOTABLE_TYPES.addAdditionalInfo(typeStr)) - ) - } - - commonParent.getStrictParentOfType()?.let { enclosingDeclaration -> - checkDeclarationsMovingOutOfScope(enclosingDeclaration, controlFlow, bindingContext)?.let { messages.add(it) } - } - - controlFlow.jumpOutputValue?.elementToInsertAfterCall?.accept( - object : KtTreeVisitorVoid() { - override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) { - paramsInfo.originalRefToParameter[expression].firstOrNull()?.let { it.refCount-- } - } - } - ) - val adjustedParameters = paramsInfo.parameters.filterTo(LinkedHashSet()) { it.refCount > 0 } - - val receiverCandidates = adjustedParameters.filterTo(hashSetOf()) { it.receiverCandidate } - val receiverParameter = if (receiverCandidates.size == 1 && !options.canWrapInWith) receiverCandidates.first() else null - receiverParameter?.let { adjustedParameters.remove(it) } - - val experimentalMarkers = getExperimentalMarkers() - var descriptor = ExtractableCodeDescriptor( - this, + val (returnValueType, valuedReturnExpressions) = getResultTypeAndExpressions( + valuedReturnExits, bindingContext, - suggestFunctionNames(returnType), - getDefaultVisibility(), - adjustedParameters.toList(), - receiverParameter, - paramsInfo.typeParameters.sortedBy { it.originalDeclaration.name!! }, - paramsInfo.replacementMap, - if (messages.isEmpty()) controlFlow else controlFlow.toDefault(), - returnType, - emptyList(), - annotations = experimentalMarkers.propagatingMarkerDescriptors, - optInMarkers = experimentalMarkers.optInMarkers + targetScope, + options, + module ) - val generatedDeclaration = ExtractionGeneratorConfiguration( - descriptor, - ExtractionGeneratorOptions(inTempFile = true, allowExpressionBody = false) - ).generateDeclaration().declaration - val virtualContext = generatedDeclaration.analyzeWithContent() - if (virtualContext.diagnostics.all() - .any { it.factory == Errors.ILLEGAL_SUSPEND_FUNCTION_CALL || it.factory == Errors.ILLEGAL_SUSPEND_PROPERTY_ACCESS } - ) { - descriptor = descriptor.copy(modifiers = listOf(KtTokens.SUSPEND_KEYWORD)) - } + val jumpTarget = getCommonNonTrivialSuccessorIfAny(jumpExits) - for (analyser in AdditionalExtractableAnalyser.EP_NAME.extensions) { - descriptor = analyser.amendDescriptor(descriptor) - } - - return AnalysisResult( - descriptor, - if (messages.isEmpty()) Status.SUCCESS else Status.NON_CRITICAL_ERROR, - messages + return OutputDescriptor( + defaultResultExpression = defaultResultExpressions.singleOrNull(), + typeOfDefaultFlow = typeOfDefaultFlow, + valuedReturnExpressions = valuedReturnExpressions, + returnValueType = returnValueType, + jumpExpressions = jumpExits.map { it.element as KtExpression }, + hasSingleTarget = valuedReturnExits.isNotEmpty() && getCommonNonTrivialSuccessorIfAny(valuedReturnExits) != null || + jumpExits.isNotEmpty() && jumpTarget != null, + sameExitForDefaultAndJump = getCommonNonTrivialSuccessorIfAny(defaultExits) == jumpTarget ) } -private fun ExtractionData.suggestFunctionNames(returnType: KotlinType): List { - val functionNames = LinkedHashSet() - - val validator = - Fe10KotlinNewDeclarationNameValidator( - targetSibling.parent, - if (targetSibling is KtAnonymousInitializer) targetSibling.parent else targetSibling, - when { - options.extractAsProperty -> KotlinNameSuggestionProvider.ValidatorTarget.VARIABLE - else -> KotlinNameSuggestionProvider.ValidatorTarget.FUNCTION - } - ) - if (!KotlinBuiltIns.isUnit(returnType)) { - functionNames.addAll(Fe10KotlinNameSuggester.suggestNamesByType(returnType, validator)) - } - - expressions.singleOrNull()?.let { expr -> - val property = expr.getStrictParentOfType() - if (property?.initializer == expr) { - property.name?.let { functionNames.add(KotlinNameSuggester.suggestNameByName("get" + it.capitalizeAsciiOnly(), validator)) } - } - } - - return functionNames.toList() -} - -internal fun KtNamedDeclaration.getGeneratedBody() = - when (this) { - is KtNamedFunction -> bodyExpression - else -> { - val property = this as KtProperty - - property.getter?.bodyExpression?.let { return it } - property.initializer?.let { return it } - // We assume lazy property here with delegate expression 'by Delegates.lazy { body }' - property.delegateExpression?.let { - val call = it.getCalleeExpressionIfAny()?.parent as? KtCallExpression - call?.lambdaArguments?.singleOrNull()?.getLambdaExpression()?.bodyExpression - } - } - } ?: throw AssertionError("Couldn't get block body for this declaration: ${getElementTextWithContext()}") - @JvmOverloads fun ExtractableCodeDescriptor.validate(target: ExtractionTarget = ExtractionTarget.FUNCTION): ExtractableCodeDescriptorWithConflicts { fun getDeclarationMessage(declaration: PsiElement, messageKey: String, capitalize: Boolean = true): String { @@ -863,7 +690,7 @@ fun ExtractableCodeDescriptor.validate(target: ExtractionTarget = ExtractionTarg val bindingContext = generatedDeclaration.analyzeWithContent() fun processReference(currentRefExpr: KtSimpleNameExpression) { - val resolveResult = currentRefExpr.resolveResult ?: return + val resolveResult = currentRefExpr.resolveResult as? ResolveResult>?: return if (currentRefExpr.parent is KtThisExpression) return val diagnostics = bindingContext.diagnostics.forElement(currentRefExpr) diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt index 6ba14b214ef9..9f1db0b4eb43 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt @@ -3,44 +3,23 @@ package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine import com.intellij.codeHighlighting.HighlightDisplayLevel -import com.intellij.openapi.util.Key import com.intellij.profile.codeInspection.ProjectInspectionProfileManager -import com.intellij.psi.PsiElement -import com.intellij.psi.codeStyle.CodeStyleManager -import com.intellij.psi.search.LocalSearchScope -import com.intellij.psi.search.searches.ReferencesSearch -import com.intellij.refactoring.BaseRefactoringProcessor -import org.jetbrains.kotlin.builtins.KotlinBuiltIns -import org.jetbrains.kotlin.builtins.isFunctionType import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.DescriptorVisibilities import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.idea.base.codeInsight.KotlinNameSuggester -import org.jetbrains.kotlin.idea.base.codeInsight.KotlinNameSuggestionProvider -import org.jetbrains.kotlin.idea.base.fe10.codeInsight.newDeclaration.Fe10KotlinNameSuggester -import org.jetbrains.kotlin.idea.base.fe10.codeInsight.newDeclaration.Fe10KotlinNewDeclarationNameValidator -import org.jetbrains.kotlin.idea.base.psi.isMultiLine -import org.jetbrains.kotlin.idea.base.psi.replaced import org.jetbrains.kotlin.idea.base.psi.unifier.KotlinPsiRange import org.jetbrains.kotlin.idea.base.psi.unifier.KotlinPsiUnificationResult import org.jetbrains.kotlin.idea.base.psi.unifier.KotlinPsiUnificationResult.StrictSuccess import org.jetbrains.kotlin.idea.base.psi.unifier.KotlinPsiUnificationResult.WeakSuccess import org.jetbrains.kotlin.idea.base.psi.unifier.toRange -import org.jetbrains.kotlin.idea.base.resources.KotlinBundle -import org.jetbrains.kotlin.idea.codeInsight.inspections.shared.convertInfixCallToOrdinary -import org.jetbrains.kotlin.idea.codeinsights.impl.base.inspections.OperatorToFunctionConverter -import org.jetbrains.kotlin.idea.core.ShortenReferences -import org.jetbrains.kotlin.idea.core.appendElement -import org.jetbrains.kotlin.idea.core.moveInsideParenthesesAndReplaceWith import org.jetbrains.kotlin.idea.core.toVisibility import org.jetbrains.kotlin.idea.inspections.PublicApiImplicitTypeInspection import org.jetbrains.kotlin.idea.inspections.UseExpressionBodyInspection import org.jetbrains.kotlin.idea.intentions.RemoveExplicitTypeArgumentsIntention import org.jetbrains.kotlin.idea.refactoring.introduce.* import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.* -import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValueBoxer.AsTuple import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor -import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.idea.util.getAllAccessibleVariables import org.jetbrains.kotlin.idea.util.getResolutionScope import org.jetbrains.kotlin.idea.util.psi.patternMatching.KotlinPsiUnifier @@ -49,137 +28,13 @@ import org.jetbrains.kotlin.idea.util.psi.patternMatching.match import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.KtPsiFactory.CallableBuilder import org.jetbrains.kotlin.psi.psiUtil.* -import org.jetbrains.kotlin.renderer.DescriptorRenderer -import org.jetbrains.kotlin.renderer.render import org.jetbrains.kotlin.resolve.DescriptorUtils -import org.jetbrains.kotlin.resolve.calls.util.getCalleeExpressionIfAny -import org.jetbrains.kotlin.resolve.checkers.OptInNames import org.jetbrains.kotlin.types.KotlinType -import org.jetbrains.kotlin.types.isError import org.jetbrains.kotlin.types.isFlexible import java.util.* -private fun buildSignature(config: ExtractionGeneratorConfiguration, renderer: DescriptorRenderer): CallableBuilder { - val extractionTarget = config.generatorOptions.target - if (!extractionTarget.isAvailable(config.descriptor)) { - val message = KotlinBundle.message("error.text.can.t.generate.0.1", - extractionTarget.targetName, - config.descriptor.extractionData.codeFragmentText - ) - throw BaseRefactoringProcessor.ConflictsInTestsException(listOf(message)) - } - - val builderTarget = when (extractionTarget) { - ExtractionTarget.FUNCTION, ExtractionTarget.FAKE_LAMBDALIKE_FUNCTION -> CallableBuilder.Target.FUNCTION - else -> CallableBuilder.Target.READ_ONLY_PROPERTY - } - return CallableBuilder(builderTarget).apply { - val visibility = config.descriptor.visibility?.value ?: "" - - fun TypeParameter.isReified() = originalDeclaration.hasModifier(KtTokens.REIFIED_KEYWORD) - val shouldBeInline = config.descriptor.typeParameters.any { it.isReified() } - - val optInAnnotation = if (config.generatorOptions.target != ExtractionTarget.FUNCTION || config.descriptor.optInMarkers.isEmpty()) { - "" - } else { - val innerText = config.descriptor.optInMarkers.joinToString(separator = ", ") { "${it.shortName().render()}::class" } - "@${OptInNames.OPT_IN_FQ_NAME.shortName().render()}($innerText)\n" - } - - val annotations = if (config.descriptor.annotations.isEmpty()) { - "" - } else { - config.descriptor.annotations.joinToString(separator = "\n", postfix = "\n") { renderer.renderAnnotation(it) } - } - val extraModifiers = config.descriptor.modifiers.map { it.value } + - listOfNotNull(if (shouldBeInline) KtTokens.INLINE_KEYWORD.value else null) + - listOfNotNull(if (config.generatorOptions.isConst) KtTokens.CONST_KEYWORD.value else null) - val modifiers = if (visibility.isNotEmpty()) listOf(visibility) + extraModifiers else extraModifiers - modifier(annotations + optInAnnotation + modifiers.joinToString(separator = " ")) - - typeParams( - config.descriptor.typeParameters.map { - val typeParameter = it.originalDeclaration - val bound = typeParameter.extendsBound - - buildString { - if (it.isReified()) { - append(KtTokens.REIFIED_KEYWORD.value) - append(' ') - } - append(typeParameter.name) - if (bound != null) { - append(" : ") - append(bound.text) - } - } - } - ) - - fun KotlinType.typeAsString() = renderer.renderType(this) - - config.descriptor.receiverParameter?.let { - val receiverType = it.parameterType - val receiverTypeAsString = receiverType.typeAsString() - receiver(if (receiverType.isFunctionType) "($receiverTypeAsString)" else receiverTypeAsString) - } - - name(config.generatorOptions.dummyName ?: config.descriptor.name) - - config.descriptor.parameters.forEach { parameter -> - param(parameter.name, parameter.parameterType.typeAsString()) - } - - with(config.descriptor.returnType) { - if (KotlinBuiltIns.isUnit(this) || isError || extractionTarget == ExtractionTarget.PROPERTY_WITH_INITIALIZER) { - noReturnType() - } else { - returnType(typeAsString()) - } - } - - typeConstraints(config.descriptor.typeParameters.flatMap { it.originalConstraints }.map { it.text!! }) - } -} - -fun ExtractionGeneratorConfiguration.getSignaturePreview(renderer: DescriptorRenderer) = buildSignature(this, renderer).asString() - -fun ExtractionGeneratorConfiguration.getDeclarationPattern( - descriptorRenderer: DescriptorRenderer = IdeDescriptorRenderers.SOURCE_CODE -): String { - val extractionTarget = generatorOptions.target - if (!extractionTarget.isAvailable(descriptor)) { - throw BaseRefactoringProcessor.ConflictsInTestsException( - listOf( - KotlinBundle.message("error.text.can.t.generate.0.1", - extractionTarget.targetName, - descriptor.extractionData.codeFragmentText - ) - ) - ) - } - - return buildSignature(this, descriptorRenderer).let { builder -> - builder.transform { - for (i in generateSequence(indexOf('$')) { indexOf('$', it + 2) }) { - if (i < 0) break - insert(i + 1, '$') - } - } - - when (extractionTarget) { - ExtractionTarget.FUNCTION, - ExtractionTarget.FAKE_LAMBDALIKE_FUNCTION, - ExtractionTarget.PROPERTY_WITH_GETTER -> builder.blockBody("$0") - ExtractionTarget.PROPERTY_WITH_INITIALIZER -> builder.initializer("$0") - ExtractionTarget.LAZY_PROPERTY -> builder.lazyBody("$0") - } - - builder.asString() - } -} +fun ExtractionGeneratorConfiguration.getSignaturePreview() = Generator.getSignaturePreview(this) fun KotlinType.isSpecial(): Boolean { val classDescriptor = this.constructor.declarationDescriptor as? ClassDescriptor ?: return false @@ -190,20 +45,16 @@ fun createNameCounterpartMap(from: KtElement, to: KtElement): Map().zip(to.collectDescendantsOfType()).toMap() } -class DuplicateInfo( - val range: KotlinPsiRange, - val controlFlow: ControlFlow, - val arguments: List -) -fun ExtractableCodeDescriptor.findDuplicates(): List { - fun processWeakMatch(match: WeakSuccess<*>, newControlFlow: ControlFlow): Boolean { + +fun ExtractableCodeDescriptor.findDuplicates(): List> { + fun processWeakMatch(match: WeakSuccess<*>, newControlFlow: ControlFlow): Boolean { val valueCount = controlFlow.outputValues.size val weakMatches = HashMap(match.weakMatches) - val currentValuesToNew = HashMap() + val currentValuesToNew = HashMap, OutputValue>() - fun matchValues(currentValue: OutputValue, newValue: OutputValue): Boolean { + fun matchValues(currentValue: OutputValue, newValue: OutputValue): Boolean { if ((currentValue is Jump) != (newValue is Jump)) return false if (currentValue.originalExpressions.zip(newValue.originalExpressions).all { weakMatches[it.first] == it.second }) { currentValuesToNew[currentValue] = newValue @@ -227,7 +78,7 @@ fun ExtractableCodeDescriptor.findDuplicates(): List { return currentValuesToNew.size == valueCount && weakMatches.isEmpty() } - fun getControlFlowIfMatched(match: KotlinPsiUnificationResult.Success<*>): ControlFlow? { + fun getControlFlowIfMatched(match: KotlinPsiUnificationResult.Success<*>): ControlFlow? { val analysisResult = extractionData.copy(originalRange = match.range).performAnalysis() if (analysisResult.status != AnalysisResult.Status.SUCCESS) return null @@ -270,237 +121,23 @@ fun ExtractableCodeDescriptor.findDuplicates(): List { .toList() } -private fun ExtractableCodeDescriptor.getOccurrenceContainer(): PsiElement? { - return extractionData.duplicateContainer ?: extractionData.targetSibling.parent -} +private object Generator: ExtractFunctionGenerator() { + override val nameGenerator: IExtractionNameSuggester = ExtractNameSuggester -private fun makeCall( - extractableDescriptor: ExtractableCodeDescriptor, - declaration: KtNamedDeclaration, - controlFlow: ControlFlow, - rangeToReplace: KotlinPsiRange, - arguments: List -) { - fun insertCall(anchor: PsiElement, wrappedCall: KtExpression): KtExpression? { - val firstExpression = rangeToReplace.elements.firstOrNull { it is KtExpression } as? KtExpression - if (firstExpression?.isLambdaOutsideParentheses() == true) { - val functionLiteralArgument = firstExpression.getStrictParentOfType()!! - return functionLiteralArgument.moveInsideParenthesesAndReplaceWith(wrappedCall, extractableDescriptor.originalContext) - } - - if (anchor is KtOperationReferenceExpression) { - val newNameExpression = when (val operationExpression = anchor.parent as? KtOperationExpression ?: return null) { - is KtUnaryExpression -> OperatorToFunctionConverter.convert(operationExpression).second - is KtBinaryExpression -> { - convertInfixCallToOrdinary(operationExpression).getCalleeExpressionIfAny() - } - else -> null - } - return newNameExpression?.replaced(wrappedCall) - } - - (anchor as? KtExpression)?.extractableSubstringInfo?.let { - return it.replaceWith(wrappedCall) - } - - return anchor.replaced(wrappedCall) + override fun createTypeDescriptor(data: IExtractionData): TypeDescriptor { + return KotlinTypeDescriptor(data as ExtractionData) } - if (rangeToReplace.isEmpty) return - - val anchor = rangeToReplace.elements.first() - val anchorParent = anchor.parent!! - - anchor.nextSibling?.let { from -> - val to = rangeToReplace.elements.last() - if (to != anchor) { - anchorParent.deleteChildRange(from, to) + override fun IExtractionGeneratorConfiguration.collapseBody(body: KtBlockExpression) { + val bodyExpression = body.statements.singleOrNull() + val bodyOwner = body.parent as KtDeclarationWithBody + val useExpressionBodyInspection = UseExpressionBodyInspection() + if (bodyExpression != null && useExpressionBodyInspection.isActiveFor(bodyOwner)) { + useExpressionBodyInspection.simplify(bodyOwner, !useExplicitReturnType(this)) } } - val calleeName = declaration.name?.quoteIfNeeded() - val callText = when (declaration) { - is KtNamedFunction -> { - val argumentsText = arguments.joinToString(separator = ", ", prefix = "(", postfix = ")") - val typeArguments = extractableDescriptor.typeParameters.map { it.originalDeclaration.name } - val typeArgumentsText = with(typeArguments) { - if (isNotEmpty()) joinToString(separator = ", ", prefix = "<", postfix = ">") else "" - } - "$calleeName$typeArgumentsText$argumentsText" - } - else -> calleeName - } - - val anchorInBlock = generateSequence(anchor) { it.parent }.firstOrNull { it.parent is KtBlockExpression } - val block = (anchorInBlock?.parent as? KtBlockExpression) ?: anchorParent - - val psiFactory = KtPsiFactory(anchor.project) - val newLine = psiFactory.createNewLine() - - if (controlFlow.outputValueBoxer is AsTuple && controlFlow.outputValues.size > 1 && controlFlow.outputValues - .all { it is Initializer } - ) { - val declarationsToMerge = controlFlow.outputValues.map { (it as Initializer).initializedDeclaration } - val isVar = declarationsToMerge.first().isVar - if (declarationsToMerge.all { it.isVar == isVar }) { - controlFlow.declarationsToCopy.subtract(declarationsToMerge).forEach { - block.addBefore(psiFactory.createDeclaration(it.text!!), anchorInBlock) as KtDeclaration - block.addBefore(newLine, anchorInBlock) - } - - val entries = declarationsToMerge.map { p -> p.name + (p.typeReference?.let { ": ${it.text}" } ?: "") } - anchorInBlock?.replace( - psiFactory.createDestructuringDeclaration("${if (isVar) "var" else "val"} (${entries.joinToString()}) = $callText") - ) - - return - } - } - - val inlinableCall = controlFlow.outputValues.size <= 1 - val unboxingExpressions = - if (inlinableCall) { - controlFlow.outputValueBoxer.getUnboxingExpressions(callText ?: return) - } else { - val varNameValidator = Fe10KotlinNewDeclarationNameValidator(block, anchorInBlock, KotlinNameSuggestionProvider.ValidatorTarget.VARIABLE) - val resultVal = Fe10KotlinNameSuggester.suggestNamesByType(extractableDescriptor.returnType, varNameValidator, null).first() - block.addBefore(psiFactory.createDeclaration("val $resultVal = $callText"), anchorInBlock) - block.addBefore(newLine, anchorInBlock) - controlFlow.outputValueBoxer.getUnboxingExpressions(resultVal) - } - - val copiedDeclarations = HashMap() - for (decl in controlFlow.declarationsToCopy) { - val declCopy = psiFactory.createDeclaration(decl.text!!) - copiedDeclarations[decl] = block.addBefore(declCopy, anchorInBlock) as KtDeclaration - block.addBefore(newLine, anchorInBlock) - } - - if (controlFlow.outputValues.isEmpty()) { - anchor.replace(psiFactory.createExpression(callText!!)) - return - } - - fun wrapCall(outputValue: OutputValue, callText: String): List { - return when (outputValue) { - is ExpressionValue -> { - val exprText = if (outputValue.callSiteReturn) { - val firstReturn = outputValue.originalExpressions.asSequence().filterIsInstance().firstOrNull() - val label = firstReturn?.getTargetLabel()?.text ?: "" - "return$label $callText" - } else { - callText - } - Collections.singletonList(psiFactory.createExpression(exprText)) - } - - is ParameterUpdate -> - Collections.singletonList( - psiFactory.createExpression("${outputValue.parameter.argumentText} = $callText") - ) - - is Jump -> { - when { - outputValue.elementToInsertAfterCall == null -> Collections.singletonList(psiFactory.createExpression(callText)) - outputValue.conditional -> Collections.singletonList( - psiFactory.createExpression("if ($callText) ${outputValue.elementToInsertAfterCall.text}") - ) - else -> listOf( - psiFactory.createExpression(callText), - newLine, - psiFactory.createExpression(outputValue.elementToInsertAfterCall.text!!) - ) - } - } - - is Initializer -> { - val newProperty = copiedDeclarations[outputValue.initializedDeclaration] as KtProperty - newProperty.initializer = psiFactory.createExpression(callText) - Collections.emptyList() - } - - else -> throw IllegalArgumentException("Unknown output value: $outputValue") - } - } - - val defaultValue = controlFlow.defaultOutputValue - - controlFlow.outputValues - .filter { it != defaultValue } - .flatMap { wrapCall(it, unboxingExpressions.getValue(it)) } - .withIndex() - .forEach { - val (i, e) = it - - if (i > 0) { - block.addBefore(newLine, anchorInBlock) - } - block.addBefore(e, anchorInBlock) - } - - defaultValue?.let { - if (!inlinableCall) { - block.addBefore(newLine, anchorInBlock) - } - insertCall(anchor, wrapCall(it, unboxingExpressions.getValue(it)).first() as KtExpression)?.removeTemplateEntryBracesIfPossible() - } - - if (anchor.isValid) { - anchor.delete() - } -} - -private var KtExpression.isJumpElementToReplace: Boolean - by NotNullablePsiCopyableUserDataProperty(Key.create("IS_JUMP_ELEMENT_TO_REPLACE"), false) - -private var KtReturnExpression.isReturnForLabelRemoval: Boolean - by NotNullablePsiCopyableUserDataProperty(Key.create("IS_RETURN_FOR_LABEL_REMOVAL"), false) - -fun ExtractionGeneratorConfiguration.generateDeclaration( - declarationToReplace: KtNamedDeclaration? = null -): ExtractionResult { - val psiFactory = KtPsiFactory(descriptor.extractionData.project) - - fun getReturnsForLabelRemoval() = descriptor.controlFlow.outputValues - .flatMapTo(arrayListOf()) { it.originalExpressions.filterIsInstance() } - - fun createDeclaration(): KtNamedDeclaration { - descriptor.controlFlow.jumpOutputValue?.elementsToReplace?.forEach { it.isJumpElementToReplace = true } - getReturnsForLabelRemoval().forEach { it.isReturnForLabelRemoval = true } - - return with(descriptor.extractionData) { - if (generatorOptions.inTempFile) { - createTemporaryDeclaration("${getDeclarationPattern()}\n") - } else { - psiFactory.createDeclarationByPattern( - getDeclarationPattern(), - PsiChildRange(originalElements.firstOrNull(), originalElements.lastOrNull()) - ) - } - } - } - - fun getReturnArguments(resultExpression: KtExpression?): List { - return descriptor.controlFlow.outputValues - .mapNotNull { - when (it) { - is ExpressionValue -> resultExpression - is Jump -> if (it.conditional) psiFactory.createExpression("false") else null - is ParameterUpdate -> psiFactory.createExpression(it.parameter.nameForRef) - is Initializer -> psiFactory.createExpression(it.initializedDeclaration.name!!) - else -> throw IllegalArgumentException("Unknown output value: $it") - } - } - } - - fun KtExpression.replaceWithReturn(replacingExpression: KtReturnExpression) { - descriptor.controlFlow.defaultOutputValue?.let { - val boxedExpression = replaced(replacingExpression).returnedExpression!! - descriptor.controlFlow.outputValueBoxer.extractExpressionByValue(boxedExpression, it) - } - } - - fun getPublicApiInspectionIfEnabled(): PublicApiImplicitTypeInspection? { + private fun getPublicApiInspectionIfEnabled(descriptor: IExtractableCodeDescriptor): PublicApiImplicitTypeInspection? { val project = descriptor.extractionData.project val inspectionProfileManager = ProjectInspectionProfileManager.getInstance(project) val inspectionProfile = inspectionProfileManager.currentProfile @@ -509,9 +146,10 @@ fun ExtractionGeneratorConfiguration.generateDeclaration( return state.tool.tool as? PublicApiImplicitTypeInspection } - fun useExplicitReturnType(): Boolean { + private fun useExplicitReturnType(config: IExtractionGeneratorConfiguration): Boolean { + val descriptor = config.descriptor if (descriptor.returnType.isFlexible()) return true - val inspection = getPublicApiInspectionIfEnabled() ?: return false + val inspection = getPublicApiInspectionIfEnabled(descriptor) ?: return false val targetClass = (descriptor.extractionData.targetSibling.parent as? KtClassBody)?.parent as? KtClassOrObject if ((targetClass != null && targetClass.isLocal) || descriptor.extractionData.isLocal()) return false val visibility = (descriptor.visibility ?: KtTokens.DEFAULT_VISIBILITY_KEYWORD).toVisibility() @@ -523,208 +161,30 @@ fun ExtractionGeneratorConfiguration.generateDeclaration( } } - fun adjustDeclarationBody(declaration: KtNamedDeclaration) { - val body = declaration.getGeneratedBody() + override fun checkTypeArgumentsAreRedundant(args: KtTypeArgumentList): Boolean { + return RemoveExplicitTypeArgumentsIntention.isApplicableTo(args, false) + } - (body.blockExpressionsOrSingle().singleOrNull() as? KtExpression)?.let { - if (it.mustBeParenthesizedInInitializerPosition()) { - it.replace(psiFactory.createExpressionByPattern("($0)", it)) - } - } - - val jumpValue = descriptor.controlFlow.jumpOutputValue - if (jumpValue != null) { - val replacingReturn = psiFactory.createExpression(if (jumpValue.conditional) "return true" else "return") - body.collectDescendantsOfType { it.isJumpElementToReplace }.forEach { - it.replace(replacingReturn) - it.isJumpElementToReplace = false - } - } - - body.collectDescendantsOfType { it.isReturnForLabelRemoval }.forEach { - it.getTargetLabel()?.delete() - it.isReturnForLabelRemoval = false - } - - /* - * Sort by descending position so that internals of value/type arguments in calls and qualified types are replaced - * before calls/types themselves - */ - val currentRefs = body - .collectDescendantsOfType { it.resolveResult != null } - .sortedByDescending { it.startOffset } - - currentRefs.forEach { - val resolveResult = it.resolveResult!! - val currentRef = if (it.isValid) { - it - } else { - body.findDescendantOfType { expr -> expr.resolveResult == resolveResult } ?: return@forEach - } - val originalRef = resolveResult.originalRefExpr - val newRef = descriptor.replacementMap[originalRef] - .fold(currentRef as KtElement) { ref, replacement -> replacement(descriptor, ref) } - (newRef as? KtSimpleNameExpression)?.resolveResult = resolveResult - } - - if (generatorOptions.target == ExtractionTarget.PROPERTY_WITH_INITIALIZER) return - - if (body !is KtBlockExpression) throw AssertionError("Block body expected: ${descriptor.extractionData.codeFragmentText}") - - val firstExpression = body.statements.firstOrNull() - if (firstExpression != null) { - for (param in descriptor.parameters) { - param.mirrorVarName?.let { varName -> - body.addBefore(psiFactory.createProperty(varName, null, true, param.name), firstExpression) - body.addBefore(psiFactory.createNewLine(), firstExpression) - } - } - } - - val defaultValue = descriptor.controlFlow.defaultOutputValue - - val lastExpression = body.statements.lastOrNull() - if (lastExpression is KtReturnExpression) return - - val defaultExpression = - if (!generatorOptions.inTempFile && defaultValue != null && descriptor.controlFlow.outputValueBoxer - .boxingRequired && lastExpression!!.isMultiLine() - ) { - val varNameValidator = Fe10KotlinNewDeclarationNameValidator(body, lastExpression, KotlinNameSuggestionProvider.ValidatorTarget.VARIABLE) - val resultVal = Fe10KotlinNameSuggester.suggestNamesByType(defaultValue.valueType, varNameValidator, null).first() - body.addBefore(psiFactory.createDeclaration("val $resultVal = ${lastExpression.text}"), lastExpression) - body.addBefore(psiFactory.createNewLine(), lastExpression) - psiFactory.createExpression(resultVal) - } else lastExpression - - val returnExpression = - descriptor.controlFlow.outputValueBoxer.getReturnExpression(getReturnArguments(defaultExpression), psiFactory) ?: return - - when (generatorOptions.target) { - ExtractionTarget.LAZY_PROPERTY, ExtractionTarget.FAKE_LAMBDALIKE_FUNCTION -> { - // In the case of lazy property absence of default value means that output values are of OutputValue.Initializer type - // We just add resulting expressions without return, since returns are prohibited in the body of lazy property - if (defaultValue == null) { - body.appendElement(returnExpression.returnedExpression!!) - } - return - } - else -> {} - } - - when { - defaultValue == null -> body.appendElement(returnExpression) - !defaultValue.callSiteReturn -> lastExpression!!.replaceWithReturn(returnExpression) - } - - if (generatorOptions.allowExpressionBody) { - val bodyExpression = body.statements.singleOrNull() - val bodyOwner = body.parent as KtDeclarationWithBody - val useExpressionBodyInspection = UseExpressionBodyInspection() - if (bodyExpression != null && useExpressionBodyInspection.isActiveFor(bodyOwner)) { - useExpressionBodyInspection.simplify(bodyOwner, !useExplicitReturnType()) + override fun resolveNameConflict(property: KtProperty) { + if ((property.descriptor as? PropertyDescriptor)?.let { DescriptorUtils.isOverride(it) } == true) { + val scope = property.getResolutionScope() + val newName = KotlinNameSuggester.suggestNameByName(property.name!!) { + it != property.name && scope.getAllAccessibleVariables(Name.identifier(it)).isEmpty() } + property.setName(newName) } } - fun insertDeclaration(declaration: KtNamedDeclaration, anchor: PsiElement): KtNamedDeclaration { - declarationToReplace?.let { return it.replace(declaration) as KtNamedDeclaration } - - return with(descriptor.extractionData) { - val targetContainer = anchor.parent!! - // TODO: Get rid of explicit new-lines in favor of formatter rules - val emptyLines = psiFactory.createWhiteSpace("\n\n") - if (insertBefore) { - (targetContainer.addBefore(declaration, anchor) as KtNamedDeclaration).apply { - targetContainer.addBefore(emptyLines, anchor) - } - } else { - (targetContainer.addAfter(declaration, anchor) as KtNamedDeclaration).apply { - if (!(targetContainer is KtClassBody && (targetContainer.parent as? KtClass)?.isEnum() == true)) { - targetContainer.addAfter(emptyLines, anchor) - } - val insertedDeclaration = this - PostInsertDeclarationCallback.EP_NAME.forEachExtensionSafe { extension -> - extension.declarationInserted(insertedDeclaration, targetContainer, psiFactory) - } - } - } - } + override fun IExtractionGeneratorConfiguration.createExtractionResult( + declaration: KtNamedDeclaration, + duplicatesReplacer: Map Unit> + ): ExtractionResult { + return ExtractionResult(this as ExtractionGeneratorConfiguration, declaration, duplicatesReplacer) } - - val duplicates = if (generatorOptions.inTempFile) Collections.emptyList() else descriptor.duplicates - - val anchor = with(descriptor.extractionData) { - val targetParent = targetSibling.parent - - val anchorCandidates = duplicates.mapTo(arrayListOf()) { it.range.elements.first().substringContextOrThis } - anchorCandidates.add(targetSibling) - if (targetSibling is KtEnumEntry) { - anchorCandidates.add(targetSibling.siblings().last { it is KtEnumEntry }) - } - - val marginalCandidate = if (insertBefore) { - anchorCandidates.minByOrNull { it.startOffset }!! - } else { - anchorCandidates.maxByOrNull { it.startOffset }!! - } - - // Ascend to the level of targetSibling - marginalCandidate.parentsWithSelf.first { it.parent == targetParent } - } - - val shouldInsert = !(generatorOptions.inTempFile || generatorOptions.target == ExtractionTarget.FAKE_LAMBDALIKE_FUNCTION) - val declaration = createDeclaration().let { if (shouldInsert) insertDeclaration(it, anchor) else it } - adjustDeclarationBody(declaration) - - if (generatorOptions.inTempFile) return ExtractionResult(this, declaration, Collections.emptyMap()) - - val replaceInitialOccurrence = { - val arguments = descriptor.parameters.map { it.argumentText } - makeCall(descriptor, declaration, descriptor.controlFlow, descriptor.extractionData.originalRange, arguments) - } - - if (!generatorOptions.delayInitialOccurrenceReplacement) replaceInitialOccurrence() - - if (shouldInsert) { - ShortenReferences.DEFAULT.process(declaration) - } - - val duplicateReplacers = HashMap Unit>().apply { - if (generatorOptions.delayInitialOccurrenceReplacement) { - put(descriptor.extractionData.originalRange, replaceInitialOccurrence) - } - putAll(duplicates.map { - val smartListRange = KotlinPsiRange.SmartListRange(it.range.elements) - smartListRange to { makeCall(descriptor, declaration, it.controlFlow, smartListRange, it.arguments) } - }) - } - - if (descriptor.typeParameters.isNotEmpty()) { - for (ref in ReferencesSearch.search(declaration, LocalSearchScope(descriptor.getOccurrenceContainer()!!))) { - val typeArgumentList = (ref.element.parent as? KtCallExpression)?.typeArgumentList ?: continue - if (RemoveExplicitTypeArgumentsIntention.isApplicableTo(typeArgumentList, false)) { - typeArgumentList.delete() - } - } - } - - if (declaration is KtProperty) { - if (declaration.isExtensionDeclaration() && !declaration.isTopLevel) { - val receiverTypeReference = (declaration as? KtCallableDeclaration)?.receiverTypeReference - receiverTypeReference?.siblings(withItself = false)?.firstOrNull { it.node.elementType == KtTokens.DOT }?.delete() - receiverTypeReference?.delete() - } - if ((declaration.descriptor as? PropertyDescriptor)?.let { DescriptorUtils.isOverride(it) } == true) { - val scope = declaration.getResolutionScope() - val newName = KotlinNameSuggester.suggestNameByName(descriptor.name) { - it != descriptor.name && scope.getAllAccessibleVariables(Name.identifier(it)).isEmpty() - } - declaration.setName(newName) - } - } - - CodeStyleManager.getInstance(descriptor.extractionData.project).reformat(declaration) - - return ExtractionResult(this, declaration, duplicateReplacers) } + +fun ExtractionGeneratorConfiguration.generateDeclaration( + declarationToReplace: KtNamedDeclaration? = null +): ExtractionResult { + return Generator.generateDeclaration(this, declarationToReplace) +} \ No newline at end of file diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/inferParameterInfo.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/inferParameterInfo.kt index 06b30fe0366d..8db13f3d894b 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/inferParameterInfo.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/inferParameterInfo.kt @@ -2,9 +2,7 @@ package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine -import com.intellij.psi.PsiElement import com.intellij.psi.PsiNameIdentifierOwner -import com.intellij.util.containers.MultiMap import org.jetbrains.kotlin.builtins.createFunctionType import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode import org.jetbrains.kotlin.cfg.pseudocode.SingleType @@ -15,8 +13,6 @@ import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.idea.base.codeInsight.KotlinNameSuggester import org.jetbrains.kotlin.idea.base.codeInsight.KotlinNameSuggestionProvider -import org.jetbrains.kotlin.idea.base.fe10.codeInsight.newDeclaration.Fe10KotlinNameSuggester -import org.jetbrains.kotlin.idea.base.fe10.codeInsight.newDeclaration.Fe10KotlinNewDeclarationNameValidator import org.jetbrains.kotlin.idea.base.projectStructure.languageVersionSettings import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde @@ -45,24 +41,14 @@ import org.jetbrains.kotlin.types.CommonSupertypes import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.expressions.OperatorConventions -internal class ParametersInfo { - var errorMessage: AnalysisResult.ErrorMessage? = null - val originalRefToParameter = MultiMap.create() - val parameters = LinkedHashSet() - val typeParameters = HashSet() - val nonDenotableTypes = HashSet() - val replacementMap = MultiMap.create() -} - internal fun ExtractionData.inferParametersInfo( virtualBlock: KtBlockExpression, - commonParent: PsiElement, pseudocode: Pseudocode, bindingContext: BindingContext, targetScope: LexicalScope, modifiedVarDescriptors: Set -): ParametersInfo { - val info = ParametersInfo() +): ParametersInfo { + val info = ParametersInfo() val extractedDescriptorToParameter = LinkedHashMap() @@ -98,18 +84,16 @@ internal fun ExtractionData.inferParametersInfo( continue } - if (!refInfo.shouldSkipPrimaryReceiver) { - extractReceiver( - receiverToExtract, - info, - targetScope, - refInfo, - extractedDescriptorToParameter, - pseudocode, - bindingContext, - false - ) - } + extractReceiver( + receiverToExtract, + info, + targetScope, + refInfo, + extractedDescriptorToParameter, + pseudocode, + bindingContext, + false + ) if (options.canWrapInWith && twoReceivers) { extractReceiver( @@ -125,7 +109,7 @@ internal fun ExtractionData.inferParametersInfo( } } - val varNameValidator = Fe10KotlinNewDeclarationNameValidator( + val varNameValidator = ExtractNameSuggester.createNameValidator( commonParent.getNonStrictParentOfType()!!, physicalElements.firstOrNull(), KotlinNameSuggestionProvider.ValidatorTarget.PARAMETER @@ -135,12 +119,12 @@ internal fun ExtractionData.inferParametersInfo( for ((descriptorToExtract, parameter) in extractedDescriptorToParameter) { if (!parameter .parameterType - .processTypeIfExtractable(info.typeParameters, info.nonDenotableTypes, options, targetScope) + .processTypeIfExtractable(info.typeParameters, info.nonDenotableTypes, targetScope) ) continue with(parameter) { if (currentName == null) { - currentName = Fe10KotlinNameSuggester.suggestNamesByType(parameterType, varNameValidator, "p").first() + currentName = ExtractNameSuggester.suggestNamesByType(parameterType, commonParent, varNameValidator, "p").first() } require(currentName != null) @@ -162,8 +146,8 @@ internal fun ExtractionData.inferParametersInfo( } } - for (typeToCheck in info.typeParameters.flatMapTo(HashSet()) { it.collectReferencedTypes(bindingContext) }) { - typeToCheck.processTypeIfExtractable(info.typeParameters, info.nonDenotableTypes, options, targetScope) + for (typeToCheck in info.typeParameters.flatMap { it.collectReferencedTypes() }.mapNotNull { bindingContext[BindingContext.TYPE, it] }) { + typeToCheck.processTypeIfExtractable(info.typeParameters, info.nonDenotableTypes, targetScope) } @@ -172,9 +156,9 @@ internal fun ExtractionData.inferParametersInfo( private fun ExtractionData.extractReceiver( receiverToExtract: ReceiverValue?, - info: ParametersInfo, + info: ParametersInfo, targetScope: LexicalScope, - refInfo: ResolvedReferenceInfo, + refInfo: ResolvedReferenceInfo, KotlinType>, extractedDescriptorToParameter: HashMap, pseudocode: Pseudocode, bindingContext: BindingContext, @@ -211,7 +195,7 @@ private fun ExtractionData.extractReceiver( if (referencedClassifierDescriptor != null) { if (!referencedClassifierDescriptor.defaultType.processTypeIfExtractable( - info.typeParameters, info.nonDenotableTypes, options, targetScope, referencedClassifierDescriptor is TypeParameterDescriptor + info.typeParameters, info.nonDenotableTypes, targetScope, referencedClassifierDescriptor is TypeParameterDescriptor ) ) return diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceConstant/KotlinIntroduceConstantHandler.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceConstant/KotlinIntroduceConstantHandler.kt index 62fdf8792245..8595dee6a442 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceConstant/KotlinIntroduceConstantHandler.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceConstant/KotlinIntroduceConstantHandler.kt @@ -11,10 +11,9 @@ import com.intellij.psi.PsiFile import com.intellij.psi.codeStyle.NameUtil import com.intellij.refactoring.RefactoringActionHandler import org.jetbrains.annotations.Nls -import org.jetbrains.kotlin.idea.base.resources.KotlinBundle import org.jetbrains.kotlin.idea.base.psi.unifier.toRange +import org.jetbrains.kotlin.idea.base.resources.KotlinBundle import org.jetbrains.kotlin.idea.caches.resolve.analyze -import org.jetbrains.kotlin.idea.util.ElementKind import org.jetbrains.kotlin.idea.refactoring.getExtractionContainers import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.* import org.jetbrains.kotlin.idea.refactoring.introduce.introduceProperty.KotlinInplacePropertyIntroducer @@ -22,6 +21,7 @@ import org.jetbrains.kotlin.idea.refactoring.introduce.selectElementsWithTargetS import org.jetbrains.kotlin.idea.refactoring.introduce.showErrorHint import org.jetbrains.kotlin.idea.refactoring.introduce.showErrorHintByKey import org.jetbrains.kotlin.idea.refactoring.introduce.validateExpressionElements +import org.jetbrains.kotlin.idea.util.ElementKind import org.jetbrains.kotlin.idea.util.application.isUnitTestMode import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.plainContent @@ -70,6 +70,7 @@ class KotlinIntroduceConstantHandler( INTRODUCE_CONSTANT ) } + else -> { val options = ExtractionOptions(extractAsProperty = true) val extractionData = ExtractionData(file, adjustedElements.toRange(), target, null, options) @@ -147,6 +148,7 @@ class KotlinIntroduceConstantHandler( } -> KotlinBundle.message( "error.text.can.t.introduce.constant.for.this.expression.because.not.constant" ) + else -> null } } diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/ui/KotlinIntroduceTypeAliasDialog.java b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/ui/KotlinIntroduceTypeAliasDialog.java index 2f20028d5359..5150ce8f2914 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/ui/KotlinIntroduceTypeAliasDialog.java +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceTypeAlias/ui/KotlinIntroduceTypeAliasDialog.java @@ -32,6 +32,8 @@ import java.awt.event.ItemListener; import java.util.Collections; import java.util.List; +import static org.jetbrains.kotlin.idea.refactoring.KotlinCommonRefactoringUtilKt.checkConflictsInteractively; + public class KotlinIntroduceTypeAliasDialog extends DialogWrapper { private JPanel contentPane; private TitledSeparator inputParametersPanel; @@ -181,7 +183,7 @@ public class KotlinIntroduceTypeAliasDialog extends DialogWrapper { @Override protected void doOKAction() { MultiMap conflicts = IntroduceTypeAliasImplKt.validate(currentDescriptor).getConflicts(); - KotlinRefactoringUtilKt.checkConflictsInteractively( + checkConflictsInteractively( project, conflicts, new Function0<>() { diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/kotlinRefactoringUtil.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/kotlinRefactoringUtil.kt index f8736b1bd910..8cd66b72d963 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/kotlinRefactoringUtil.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/kotlinRefactoringUtil.kt @@ -124,26 +124,6 @@ fun PsiElement.getUsageContext(): PsiElement { fun PsiElement.isInKotlinAwareSourceRoot(): Boolean = !isOutsideKotlinAwareSourceRoot(containingFile) -fun Project.checkConflictsInteractively( - conflicts: MultiMap, - onShowConflicts: () -> Unit = {}, - onAccept: () -> Unit -) { - if (!conflicts.isEmpty) { - if (isUnitTestMode()) throw ConflictsInTestsException(conflicts.values()) - - val dialog = ConflictsDialog(this, conflicts) { onAccept() } - dialog.show() - if (!dialog.isOK) { - if (dialog.isShowConflicts) { - onShowConflicts() - } - return - } - } - - onAccept() -} fun reportDeclarationConflict( conflicts: MultiMap, diff --git a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index 420e41bf72f4..29f5c650b506 100644 --- a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -17800,6 +17800,11 @@ public abstract class LocalInspectionTestGenerated extends AbstractLocalInspecti runTest("testData/inspectionsLocal/useExpressionBody/convertToExpressionBody/assignment.kt"); } + @TestMetadata("block.kt") + public void testBlock() throws Exception { + runTest("testData/inspectionsLocal/useExpressionBody/convertToExpressionBody/block.kt"); + } + @TestMetadata("constructorWithEmptyBody.kt") public void testConstructorWithEmptyBody() throws Exception { runTest("testData/inspectionsLocal/useExpressionBody/convertToExpressionBody/constructorWithEmptyBody.kt"); diff --git a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractExtractionTest.kt b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractExtractionTest.kt index 621073b145e2..03cbc820253e 100644 --- a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractExtractionTest.kt +++ b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractExtractionTest.kt @@ -7,12 +7,14 @@ import com.intellij.codeInsight.completion.JavaCompletionUtil import com.intellij.codeInsight.template.impl.TemplateManagerImpl import com.intellij.codeInsight.template.impl.TemplateState import com.intellij.ide.DataManager +import com.intellij.openapi.application.impl.NonBlockingReadActionImpl import com.intellij.openapi.application.runReadAction import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.impl.DocumentImpl import com.intellij.openapi.project.Project import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.util.registry.Registry import com.intellij.openapi.vfs.VfsUtil import com.intellij.psi.* import com.intellij.psi.codeStyle.JavaCodeStyleManager @@ -30,9 +32,7 @@ import com.intellij.refactoring.util.JavaNameSuggestionUtil import com.intellij.refactoring.util.occurrences.ExpressionOccurrenceManager import com.intellij.testFramework.EditorTestUtil import com.intellij.testFramework.IdeaTestUtil -import com.intellij.testFramework.fixtures.CodeInsightTestFixture -import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture -import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase +import com.intellij.testFramework.fixtures.* import com.intellij.testFramework.runInEdtAndWait import org.jetbrains.kotlin.asJava.elements.KtLightMethod import org.jetbrains.kotlin.idea.KotlinFileType @@ -68,19 +68,88 @@ import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType import org.jetbrains.kotlin.renderer.DescriptorRenderer import org.jetbrains.kotlin.idea.base.test.IgnoreTests import org.jetbrains.kotlin.idea.base.test.InTextDirectivesUtils +import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.AbstractExtractKotlinFunctionHandler import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance import org.junit.Assert import java.io.File import java.util.* import kotlin.test.assertEquals +private const val DUMMY_FUN_NAME = "__dummyTestFun__" abstract class AbstractExtractionTest : KotlinLightCodeInsightFixtureTestCase() { - override fun getProjectDescriptor() = LightCodeInsightFixtureTestCase.JAVA_LATEST + override fun getProjectDescriptor() = JAVA_LATEST + + override fun setUp() { + super.setUp() + Registry.get("kotlin.extract.function.default.name").setValue(DUMMY_FUN_NAME, testRootDisposable) + } val fixture: JavaCodeInsightTestFixture get() = myFixture protected open fun getIntroduceVariableHandler(): RefactoringActionHandler = K1IntroduceVariableHandler + protected open fun getExtractFunctionHandler( + explicitPreviousSibling: PsiElement?, + expectedNames: List, + expectedReturnTypes: List, + expectedDescriptors: String, + expectedTypes: String, + extractionOptions: ExtractionOptions + ): AbstractExtractKotlinFunctionHandler { + return ExtractKotlinFunctionHandler( + helper = object : ExtractionEngineHelper(EXTRACT_FUNCTION) { + override fun adjustExtractionData(data: ExtractionData): ExtractionData { + return data.copy(options = extractionOptions) + } + + override fun configureAndRun( + project: Project, + editor: Editor, + descriptorWithConflicts: ExtractableCodeDescriptorWithConflicts, + onFinish: (ExtractionResult) -> Unit + ) { + val renderer = DescriptorRenderer.FQ_NAMES_IN_TYPES + val descriptor = descriptorWithConflicts.descriptor + val actualNames = descriptor.suggestedNames + val actualReturnTypes = descriptor.controlFlow.possibleReturnTypes.map { + IdeDescriptorRenderers.SOURCE_CODE.renderType(it) + } + val allParameters = listOfNotNull(descriptor.receiverParameter) + descriptor.parameters + val actualDescriptors = allParameters.map { renderer.render(it.originalDescriptor) }.joinToString() + val actualTypes = allParameters.map { param -> + param.getParameterTypeCandidates().joinToString(", ", "[", "]") { type -> renderer.renderType(type) } + }.joinToString() + + if (actualNames.size != 1 || expectedNames.isNotEmpty()) { + assertEquals(expectedNames, actualNames, "Expected names mismatch.") + } + if (actualReturnTypes.size != 1 || expectedReturnTypes.isNotEmpty()) { + assertEquals(expectedReturnTypes, actualReturnTypes, "Expected return types mismatch.") + } + KotlinLightCodeInsightFixtureTestCaseBase.assertEquals( + "Expected descriptors mismatch.", + expectedDescriptors, + actualDescriptors + ) + KotlinLightCodeInsightFixtureTestCaseBase.assertEquals("Expected types mismatch.", expectedTypes, actualTypes) + + val newDescriptor = if (descriptor.name == "") { + descriptor.copy(suggestedNames = Collections.singletonList(DUMMY_FUN_NAME)) + } else { + descriptor + } + + fun afterFinish(extraction: ExtractionResult) { + processDuplicates(extraction.duplicateReplacers, project, editor) + onFinish(extraction) + } + doRefactor(ExtractionGeneratorConfiguration(newDescriptor, ExtractionGeneratorOptions.DEFAULT), ::afterFinish) + } + } + ) + } + + protected open fun doIntroduceVariableTest(unused: String) { doTestIfNotDisabledByFileDirective { file -> TemplateManagerImpl.setTemplateTesting(getTestRootDisposable()) @@ -134,7 +203,7 @@ abstract class AbstractExtractionTest : KotlinLightCodeInsightFixtureTestCase() class LambdaHelperImpl : HelperImpl(), KotlinIntroduceLambdaParameterHelper { override fun configureExtractLambda(descriptor: ExtractableCodeDescriptor): ExtractableCodeDescriptor = with(descriptor) { - if (name.isEmpty()) copy(suggestedNames = listOf("__dummyTestFun__")) else this + if (name.isEmpty()) copy(suggestedNames = listOf(DUMMY_FUN_NAME)) else this } } @@ -275,7 +344,7 @@ abstract class AbstractExtractionTest : KotlinLightCodeInsightFixtureTestCase() } protected fun doExtractFunctionTest(unused: String) { - doTest { file -> doExtractFunction(myFixture, file as KtFile) } + doTestIfNotDisabledByFileDirective { file -> doExtractFunction(myFixture, file as KtFile) } } protected fun doIntroduceTypeParameterTest(unused: String) { @@ -448,6 +517,32 @@ abstract class AbstractExtractionTest : KotlinLightCodeInsightFixtureTestCase() } } } + + fun doExtractFunction(fixture: CodeInsightTestFixture, file: KtFile) { + val explicitPreviousSibling = file.findElementByCommentPrefix("// SIBLING:") + val fileText = file.getText() ?: "" + val expectedNames = InTextDirectivesUtils.findListWithPrefixes(fileText, "// SUGGESTED_NAMES: ") + val expectedReturnTypes = InTextDirectivesUtils.findListWithPrefixes(fileText, "// SUGGESTED_RETURN_TYPES: ") + val expectedDescriptors = + InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, "// PARAM_DESCRIPTOR: ").joinToString() + val expectedTypes = + InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, "// PARAM_TYPES: ").map { "[$it]" }.joinToString() + + val extractionOptions = InTextDirectivesUtils.findListWithPrefixes(fileText, "// OPTIONS: ").let { + if (it.isNotEmpty()) { + @Suppress("UNCHECKED_CAST") + val args = it.map { it.toBoolean() }.toTypedArray() as Array + ExtractionOptions::class.java.constructors.first { it.parameterTypes.size == args.size }.newInstance(*args) as ExtractionOptions + } else ExtractionOptions.DEFAULT + } + + val editor = fixture.editor + val handler = getExtractFunctionHandler(explicitPreviousSibling, expectedNames, expectedReturnTypes, expectedDescriptors, expectedTypes, extractionOptions) + handler.selectElements(editor, file) { elements, previousSibling -> + handler.doInvoke(editor, file, elements, explicitPreviousSibling ?: previousSibling) + } + } + } class ExtractTestFiles( @@ -457,11 +552,21 @@ class ExtractTestFiles( val extraFilesToPsi: Map = emptyMap() ) { constructor(path: String, mainFile: PsiFile, extraFilesToPsi: Map = emptyMap(), isFirPlugin: Boolean) : - this(mainFile, getAfterFile(path, isFirPlugin), File("$path.conflicts"), extraFilesToPsi) + this(mainFile, getAfterFile(path, isFirPlugin), getConflictsFile(path, isFirPlugin), extraFilesToPsi) } +private fun getConflictsFile(path: String, isFirPlugin: Boolean): File { + if (isFirPlugin) { + val firSpecific = File("$path.fir.conflicts") + if (firSpecific.exists()) { + return firSpecific + } + } + return File("$path.conflicts") +} + private fun getAfterFile(path: String, isFirPlugin: Boolean): File { var file = File("$path.after") if (isFirPlugin) { @@ -518,79 +623,3 @@ private fun assertEqualsToFile(expectedFile: File, actualText: String, generateM } KotlinTestUtils.assertEqualsToFile(expectedFile, actualText) } - -fun doExtractFunction(fixture: CodeInsightTestFixture, file: KtFile) { - val explicitPreviousSibling = file.findElementByCommentPrefix("// SIBLING:") - val fileText = file.getText() ?: "" - val expectedNames = InTextDirectivesUtils.findListWithPrefixes(fileText, "// SUGGESTED_NAMES: ") - val expectedReturnTypes = InTextDirectivesUtils.findListWithPrefixes(fileText, "// SUGGESTED_RETURN_TYPES: ") - val expectedDescriptors = - InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, "// PARAM_DESCRIPTOR: ").joinToString() - val expectedTypes = - InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, "// PARAM_TYPES: ").map { "[$it]" }.joinToString() - - val extractionOptions = InTextDirectivesUtils.findListWithPrefixes(fileText, "// OPTIONS: ").let { - if (it.isNotEmpty()) { - @Suppress("UNCHECKED_CAST") - val args = it.map { it.toBoolean() }.toTypedArray() as Array - ExtractionOptions::class.java.constructors.first { it.parameterTypes.size == args.size }.newInstance(*args) as ExtractionOptions - } else ExtractionOptions.DEFAULT - } - - val renderer = DescriptorRenderer.FQ_NAMES_IN_TYPES - - val editor = fixture.editor - val handler = ExtractKotlinFunctionHandler( - helper = object : ExtractionEngineHelper(EXTRACT_FUNCTION) { - override fun adjustExtractionData(data: ExtractionData): ExtractionData { - return data.copy(options = extractionOptions) - } - - override fun configureAndRun( - project: Project, - editor: Editor, - descriptorWithConflicts: ExtractableCodeDescriptorWithConflicts, - onFinish: (ExtractionResult) -> Unit - ) { - val descriptor = descriptorWithConflicts.descriptor - val actualNames = descriptor.suggestedNames - val actualReturnTypes = descriptor.controlFlow.possibleReturnTypes.map { - IdeDescriptorRenderers.SOURCE_CODE.renderType(it) - } - val allParameters = listOfNotNull(descriptor.receiverParameter) + descriptor.parameters - val actualDescriptors = allParameters.map { renderer.render(it.originalDescriptor) }.joinToString() - val actualTypes = allParameters.map { - it.getParameterTypeCandidates().map { renderer.renderType(it) }.joinToString(", ", "[", "]") - }.joinToString() - - if (actualNames.size != 1 || expectedNames.isNotEmpty()) { - assertEquals(expectedNames, actualNames, "Expected names mismatch.") - } - if (actualReturnTypes.size != 1 || expectedReturnTypes.isNotEmpty()) { - assertEquals(expectedReturnTypes, actualReturnTypes, "Expected return types mismatch.") - } - KotlinLightCodeInsightFixtureTestCaseBase.assertEquals( - "Expected descriptors mismatch.", - expectedDescriptors, - actualDescriptors - ) - KotlinLightCodeInsightFixtureTestCaseBase.assertEquals("Expected types mismatch.", expectedTypes, actualTypes) - - val newDescriptor = if (descriptor.name == "") { - descriptor.copy(suggestedNames = Collections.singletonList("__dummyTestFun__")) - } else { - descriptor - } - - fun afterFinish(extraction: ExtractionResult) { - processDuplicates(extraction.duplicateReplacers, project, editor) - onFinish(extraction) - } - doRefactor(ExtractionGeneratorConfiguration(newDescriptor, ExtractionGeneratorOptions.DEFAULT), ::afterFinish) - } - } - ) - handler.selectElements(editor, file) { elements, previousSibling -> - handler.doInvoke(editor, file, elements, explicitPreviousSibling ?: previousSibling) - } -} diff --git a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java index 07a447f51620..b5b9477d1802 100644 --- a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java +++ b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java @@ -1010,6 +1010,11 @@ public abstract class ExtractionTestGenerated extends AbstractExtractionTest { runTest("testData/refactoring/extractFunction/basic/noConflictWithInnerFunction.kt"); } + @TestMetadata("noConflictWithInnerFunction1.kt") + public void testNoConflictWithInnerFunction1() throws Exception { + runTest("testData/refactoring/extractFunction/basic/noConflictWithInnerFunction1.kt"); + } + @TestMetadata("objectQualifier.kt") public void testObjectQualifier() throws Exception { runTest("testData/refactoring/extractFunction/basic/objectQualifier.kt"); @@ -2010,6 +2015,11 @@ public abstract class ExtractionTestGenerated extends AbstractExtractionTest { runTest("testData/refactoring/extractFunction/optIn/filterPropagatingMarkers.kt"); } + @TestMetadata("filterPropagatingMarkersWitArgs.kt") + public void testFilterPropagatingMarkersWitArgs() throws Exception { + runTest("testData/refactoring/extractFunction/optIn/filterPropagatingMarkersWitArgs.kt"); + } + @TestMetadata("noExperimentalApi.kt") public void testNoExperimentalApi() throws Exception { runTest("testData/refactoring/extractFunction/optIn/noExperimentalApi.kt"); @@ -2195,6 +2205,11 @@ public abstract class ExtractionTestGenerated extends AbstractExtractionTest { KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath); } + @TestMetadata("bothReceivers.kt") + public void testBothReceivers() throws Exception { + runTest("testData/refactoring/extractFunction/parameters/extractThis/bothReceivers.kt"); + } + @TestMetadata("explicitLabeledThisInMember.kt") public void testExplicitLabeledThisInMember() throws Exception { runTest("testData/refactoring/extractFunction/parameters/extractThis/explicitLabeledThisInMember.kt"); diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/useExpressionBody/convertToExpressionBody/block.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/useExpressionBody/convertToExpressionBody/block.kt new file mode 100644 index 000000000000..a177dfd8d20b --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/useExpressionBody/convertToExpressionBody/block.kt @@ -0,0 +1,8 @@ +// PROBLEM: none + +private fun bool(a: Int, b: Int): Boolean { + if (a > 0) throw Exception("") + if (a + b > 0) return true + println(a - b) + return false +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/callWithPlatformTypeReceiver.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/callWithPlatformTypeReceiver.kt.fir.after new file mode 100644 index 000000000000..5270af3a25b6 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/callWithPlatformTypeReceiver.kt.fir.after @@ -0,0 +1,9 @@ +// WITH_STDLIB +// SUGGESTED_RETURN_TYPES: kotlin.Boolean?, kotlin.Boolean +// PARAM_DESCRIPTOR: value-parameter it: kotlin.collections.Map.Entry defined in test.`` +// PARAM_TYPES: kotlin.collections.Map.Entry +fun test() { + J.getMap().filter { __dummyTestFun__(it) } +} + +private fun __dummyTestFun__(entry: Map.Entry): Boolean? = entry.key diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/callableReferenceSelector.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/callableReferenceSelector.kt.fir.after new file mode 100644 index 000000000000..7839d6fd3b96 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/callableReferenceSelector.kt.fir.after @@ -0,0 +1,16 @@ +// WITH_STDLIB +// SUGGESTED_NAMES: map, stringStringMap, stringMap +// PARAM_DESCRIPTOR: value-parameter arg: kotlin.String defined in Foo.Companion.bar +// PARAM_TYPES: kotlin.String + +class Foo { + var arguments: Map? = null + + companion object { + fun bar(arg: String) = Foo().apply { + arguments = map(arg) + } + + private fun map(arg: String): Map = mapOf(Foo::arguments.name to arg) + } +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/calleeExpression.kt b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/calleeExpression.kt index b8b1a1a7b621..0f838badd351 100644 --- a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/calleeExpression.kt +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/calleeExpression.kt @@ -3,4 +3,6 @@ fun foo(x : String, y : String.() -> Unit) { x.y() -} \ No newline at end of file +} + +// IGNORE_K2 \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/calleeExpression.kt.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/calleeExpression.kt.after index 1a4d720e61af..2768ad3144cd 100644 --- a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/calleeExpression.kt.after +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/calleeExpression.kt.after @@ -5,4 +5,6 @@ fun foo(x : String, y : String.() -> Unit) { x.(function(y))() } -private fun function(y: String.() -> Unit) = y \ No newline at end of file +private fun function(y: String.() -> Unit) = y + +// IGNORE_K2 \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/classQualifier.kt b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/classQualifier.kt index 10b9f850cb6b..419240367fc0 100644 --- a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/classQualifier.kt +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/classQualifier.kt @@ -8,4 +8,6 @@ class A { fun bar() { A.foo() -} \ No newline at end of file +} + +// IGNORE_K2 \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/convertInfixExpression.kt b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/convertInfixExpression.kt index 31ad42b97e89..64a586443c43 100644 --- a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/convertInfixExpression.kt +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/convertInfixExpression.kt @@ -3,4 +3,6 @@ fun foo(x : String.(String) -> Unit) { "A" x "B" -} \ No newline at end of file +} + +// IGNORE_K2 \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/convertInfixExpression.kt.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/convertInfixExpression.kt.after index 0725f0e80cda..b4d7fc2c6906 100644 --- a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/convertInfixExpression.kt.after +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/convertInfixExpression.kt.after @@ -5,4 +5,6 @@ fun foo(x : String.(String) -> Unit) { "A".(function(x))("B") } -private fun function(x: String.(String) -> Unit) = x \ No newline at end of file +private fun function(x: String.(String) -> Unit) = x + +// IGNORE_K2 diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/extractFromPrivateInlineFun.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/extractFromPrivateInlineFun.kt.fir.after new file mode 100644 index 000000000000..695d5cae5f68 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/extractFromPrivateInlineFun.kt.fir.after @@ -0,0 +1,7 @@ +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo, value-parameter b: kotlin.Int defined in foo +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int + +private inline fun foo(a: Int, b: Int, f: (Int) -> Int) = f(i(a, b)) + +private fun i(a: Int, b: Int): Int = a + b diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/extractFromPublicInlineFun.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/extractFromPublicInlineFun.kt.fir.after new file mode 100644 index 000000000000..ccafec2e6936 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/extractFromPublicInlineFun.kt.fir.after @@ -0,0 +1,7 @@ +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo, value-parameter b: kotlin.Int defined in foo +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int + +inline fun foo(a: Int, b: Int, f: (Int) -> Int) = f(i(a, b)) + +fun i(a: Int, b: Int): Int = a + b diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/extractSuspend.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/extractSuspend.kt.fir.after new file mode 100644 index 000000000000..431631bcb8f9 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/extractSuspend.kt.fir.after @@ -0,0 +1,11 @@ +// PARAM_DESCRIPTOR: public suspend inline fun ExtendMe.receive(): T defined in root package in file extractSuspend.kt +// PARAM_TYPES: ExtendMe +suspend inline fun ExtendMe.receive(): T = any() as T + +suspend fun ExtendMe.any(): Any = receive("") + +class ExtendMe { + suspend fun receive(s: String): Any { + return Any() + } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/extractToEnumClassBody.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/extractToEnumClassBody.kt.fir.after new file mode 100644 index 000000000000..a055a58f0844 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/extractToEnumClassBody.kt.fir.after @@ -0,0 +1,12 @@ +// PARAM_DESCRIPTOR: value-parameter n: kotlin.Int defined in E.A.foo +// PARAM_TYPES: kotlin.Int +enum class E { + // SIBLING: + A { + fun foo(n: Int) = i(n) + }, + B, + C; + + fun i(n: Int): Int = n + 1 +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/fragmentWithComment.fir.kt.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/fragmentWithComment.fir.kt.after new file mode 100644 index 000000000000..3db244cb6f53 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/fragmentWithComment.fir.kt.after @@ -0,0 +1,14 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + if (bool(a)) return a + return -a +} + +private fun bool(a: Int): Boolean { + // test + println(a) + if (a > 0) return true + return false +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/fragmentWithComment.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/fragmentWithComment.kt.fir.after new file mode 100644 index 000000000000..3db244cb6f53 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/fragmentWithComment.kt.fir.after @@ -0,0 +1,14 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + if (bool(a)) return a + return -a +} + +private fun bool(a: Int): Boolean { + // test + println(a) + if (a > 0) return true + return false +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/fragmentWithMultilineComment.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/fragmentWithMultilineComment.kt.fir.after new file mode 100644 index 000000000000..36ae5d95ff30 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/fragmentWithMultilineComment.kt.fir.after @@ -0,0 +1,16 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + if (bool(a)) return a + return -a +} + +private fun bool(a: Int): Boolean { + /* + test + */ + println(a) + if (a > 0) return true + return false +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/identityEquals.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/identityEquals.kt.fir.after new file mode 100644 index 000000000000..56964b6bfda7 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/identityEquals.kt.fir.after @@ -0,0 +1,11 @@ +// SUGGESTED_NAMES: b, getT +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in test +// PARAM_DESCRIPTOR: value-parameter b: kotlin.Int defined in test +// PARAM_TYPES: kotlin.Int, kotlin.Number, kotlin.Comparable, java.io.Serializable, kotlin.Any +// PARAM_TYPES: kotlin.Int, kotlin.Number, kotlin.Comparable, java.io.Serializable, kotlin.Any + +fun test(a: Int, b: Int): Boolean { + val t = bool(a, b) +} + +private fun bool(a: Int, b: Int): Boolean = a === b diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/interfaceFunction.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/interfaceFunction.kt.fir.after new file mode 100644 index 000000000000..90ca902e3d7f --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/interfaceFunction.kt.fir.after @@ -0,0 +1,7 @@ +interface T{ + fun foo(): Int { + return i() + } + + fun i(): Int = 1 +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/invisibleType.kt.fir.conflicts b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/invisibleType.kt.fir.conflicts new file mode 100644 index 000000000000..e8b0919426f5 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/invisibleType.kt.fir.conflicts @@ -0,0 +1 @@ +Class A.Inner will become invisible after extraction Function Inner.foo() will become invisible after extraction diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/localClassExtraction.kt.fir.conflicts b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/localClassExtraction.kt.fir.conflicts new file mode 100644 index 000000000000..82377f1b8217 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/localClassExtraction.kt.fir.conflicts @@ -0,0 +1 @@ +Following declarations are used outside of selected code fragment: class A(n: Int) constructor(n: Int) n: Int diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/localExtraction.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/localExtraction.kt.fir.after new file mode 100644 index 000000000000..91aad9c919ca --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/localExtraction.kt.fir.after @@ -0,0 +1,6 @@ +fun foo(a: Int): Int { + // SIBLING: + fun i(): Int = a + 10 + + return i() +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/localFunExtraction.kt.fir.conflicts b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/localFunExtraction.kt.fir.conflicts new file mode 100644 index 000000000000..38beeb24649e --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/localFunExtraction.kt.fir.conflicts @@ -0,0 +1 @@ +Following declarations are used outside of selected code fragment: fun bar(): Int diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/memberValUnderSmartCast.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/memberValUnderSmartCast.kt.fir.after new file mode 100644 index 000000000000..e899fee69e86 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/memberValUnderSmartCast.kt.fir.after @@ -0,0 +1,14 @@ +// SUGGESTED_NAMES: i, getKm +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: public final val meters: kotlin.Int? defined in A +class A { + val meters: Int? = 1 +} + +fun test() { + val a = A() + if (a.meters == null) return + val km = i(a.meters) +} + +private fun i(meters: Int): Int = meters / 10 diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/memberValUnderSmartCastAndImplicitReceiver.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/memberValUnderSmartCastAndImplicitReceiver.kt.fir.after new file mode 100644 index 000000000000..96f9aeed67fe --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/memberValUnderSmartCastAndImplicitReceiver.kt.fir.after @@ -0,0 +1,17 @@ +// WITH_STDLIB +// SUGGESTED_NAMES: i, getKm +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: public final val meters: kotlin.Int? defined in A +class A { + val meters: Int? = 1 +} + +fun test() { + val a = A() + with (a) { + if (meters == null) return + val km = i(meters) + } +} + +private fun i(meters: Int): Int = meters / 10 diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/noConflictWithInnerFunction.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/noConflictWithInnerFunction.kt.fir.after new file mode 100644 index 000000000000..6a9bbb4d5e7c --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/noConflictWithInnerFunction.kt.fir.after @@ -0,0 +1,10 @@ +fun foo() { + val a = 1 + // SIBLING: + fun bool(): Boolean = a > 0 + + if (bool()) { + fun b(): Int { return 0 } + println(b()) + } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/noConflictWithInnerFunction1.kt b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/noConflictWithInnerFunction1.kt new file mode 100644 index 000000000000..33845d4a9049 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/noConflictWithInnerFunction1.kt @@ -0,0 +1,8 @@ +fun foo() { + val a = 1 + // SIBLING: + if (a > 0) { + fun bool(): Int { return 0 } + println(bool()) + } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/noConflictWithInnerFunction1.kt.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/noConflictWithInnerFunction1.kt.after new file mode 100644 index 000000000000..6f8083b8ccb8 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/noConflictWithInnerFunction1.kt.after @@ -0,0 +1,10 @@ +fun foo() { + val a = 1 + // SIBLING: + fun b() = a > 0 + + if (b()) { + fun bool(): Int { return 0 } + println(bool()) + } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/noConflictWithInnerFunction1.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/noConflictWithInnerFunction1.kt.fir.after new file mode 100644 index 000000000000..7f05e197cf8a --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/noConflictWithInnerFunction1.kt.fir.after @@ -0,0 +1,10 @@ +fun foo() { + val a = 1 + // SIBLING: + fun b(): Boolean = a > 0 + + if (b()) { + fun bool(): Int { return 0 } + println(bool()) + } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/objectQualifier.kt b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/objectQualifier.kt index 7cb42b9d9395..8525520d6372 100644 --- a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/objectQualifier.kt +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/objectQualifier.kt @@ -6,4 +6,6 @@ object A { fun bar() { A.foo() -} \ No newline at end of file +} + +// IGNORE_K2 \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/objectQualifier.kt.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/objectQualifier.kt.after index ec21b78fbb1b..4b69aa9b6f6e 100644 --- a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/objectQualifier.kt.after +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/objectQualifier.kt.after @@ -8,4 +8,6 @@ fun bar() { a().foo() } -private fun a() = A \ No newline at end of file +private fun a() = A + +// IGNORE_K2 \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/refInReturn.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/refInReturn.kt.fir.after new file mode 100644 index 000000000000..35b45422f632 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/refInReturn.kt.fir.after @@ -0,0 +1,13 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + if (bool(a)) return a + return -a +} + +private fun bool(a: Int): Boolean { + println(a) + if (a > 0) return true + return false +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/sameParameterNames.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/sameParameterNames.kt.fir.after new file mode 100644 index 000000000000..9d66d7a80952 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/sameParameterNames.kt.fir.after @@ -0,0 +1,23 @@ +// SUGGESTED_NAMES: s, getA +// PARAM_TYPES: kotlin.String +// PARAM_TYPES: kotlin.String, kotlin.Comparable, kotlin.CharSequence, java.io.Serializable, kotlin.Any +// PARAM_TYPES: kotlin.String, kotlin.Comparable, kotlin.CharSequence, java.io.Serializable, kotlin.Any +// PARAM_DESCRIPTOR: local final fun kotlin.String.``(): kotlin.Unit defined in Foo.foo.``.``, local final fun kotlin.String.``(): kotlin.Unit defined in Foo.foo.``, local final fun kotlin.String.``(): kotlin.Unit defined in Foo.foo +class Foo { + fun foo() { + block("a") a@ { + block("b") b@ { + block("c") c@ { + val a = string(this@c, this@b, this@a) + } + } + } + } + + private fun string(string: String, string0: String, string1: String): String = + string + string0 + string1 + string + this@Foo.toString() +} + +private inline fun block(t: T, block: T.() -> Unit) { + t.block() +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/topLevelValUnderSmartCast.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/topLevelValUnderSmartCast.kt.fir.after new file mode 100644 index 000000000000..5def920101aa --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/topLevelValUnderSmartCast.kt.fir.after @@ -0,0 +1,11 @@ +// SUGGESTED_NAMES: i, getKm +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: public val meters: kotlin.Int? defined in root package in file topLevelValUnderSmartCast.kt +val meters: Int? = 1 + +fun test() { + if (meters == null) return + val km = i(meters) +} + +private fun i(meters: Int): Int = meters / 10 diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/unresolvedWrongReceiver.kt.fir.conflicts b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/unresolvedWrongReceiver.kt.fir.conflicts new file mode 100644 index 000000000000..8a7dfaa62d7e --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/unresolvedWrongReceiver.kt.fir.conflicts @@ -0,0 +1 @@ +Function A.unresolved() on B will no longer be accessible after extraction diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/conditionalBreakWithIf.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/conditionalBreakWithIf.kt.fir.after new file mode 100644 index 000000000000..02ef410fd84e --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/conditionalBreakWithIf.kt.fir.after @@ -0,0 +1,20 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + val b: Int = 1 + for (n in 1..a) { + if (bool(a, b)) break + } + return 1 +} + +private fun bool(a: Int, b: Int): Boolean { + if (a + b > 0) return true + println(a - b) + if (a - b > 0) return true + println(a + b) + return false +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/conditionalBreakWithIfAndExtraVars.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/conditionalBreakWithIfAndExtraVars.kt.fir.after new file mode 100644 index 000000000000..e001bf7a2f90 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/conditionalBreakWithIfAndExtraVars.kt.fir.after @@ -0,0 +1,24 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + val b: Int = 1 + for (n in 1..a) { + val c: Int + if (bool(a, b)) break + c = 1 + println(c) + } + return 1 +} + +private fun bool(a: Int, b: Int): Boolean { + if (a + b > 0) return true + val c: Int + println(a - b) + if (a - b > 0) return true + println(a + b) + return false +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/conditionalBreakWithIfElse.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/conditionalBreakWithIfElse.kt.fir.after new file mode 100644 index 000000000000..b3dc92722f3d --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/conditionalBreakWithIfElse.kt.fir.after @@ -0,0 +1,22 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + val b: Int = 1 + for (n in 1..a) { + if (bool(a, b)) break + + } + return 1 +} + +private fun bool(a: Int, b: Int): Boolean { + if (a + b > 0) return true + else { + println(a - b) + if (a - b > 0) return true else println(a + b) + } + return false +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/conditionalBreakWithWhen.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/conditionalBreakWithWhen.kt.fir.after new file mode 100644 index 000000000000..f9e3c54e951a --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/conditionalBreakWithWhen.kt.fir.after @@ -0,0 +1,21 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + val b: Int = 1 + for (n in 1..a) { + if (bool(a, b)) break + } + return 1 +} + +private fun bool(a: Int, b: Int): Boolean { + when { + a + b > 0 -> return true + a - b > 0 -> return true + else -> println(0) + } + return false +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/conditionalReturnWithIf.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/conditionalReturnWithIf.kt.fir.after new file mode 100644 index 000000000000..db7250f572e2 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/conditionalReturnWithIf.kt.fir.after @@ -0,0 +1,16 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + val b: Int = 1 + if (bool(a, b)) return 0 + return 1 +} + +private fun bool(a: Int, b: Int): Boolean { + if (a + b > 0) return true + println(a - b) + return false +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/conditionalReturnWithIfElse.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/conditionalReturnWithIfElse.kt.fir.after new file mode 100644 index 000000000000..bcbfa0125a19 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/conditionalReturnWithIfElse.kt.fir.after @@ -0,0 +1,17 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + val b: Int = 1 + if (bool(a, b)) return 0 + return 1 +} + +private fun bool(a: Int, b: Int): Boolean { + if (a + b > 0) return true + else if (a - b < 0) println(a - b) + else println(0) + return false +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/conditionalReturnWithWhen.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/conditionalReturnWithWhen.kt.fir.after new file mode 100644 index 000000000000..67b44f59d85d --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/conditionalReturnWithWhen.kt.fir.after @@ -0,0 +1,20 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + val b: Int = 1 + if (bool(a, b)) return 0 + + return 1 +} + +private fun bool(a: Int, b: Int): Boolean { + when (a + b) { + 0 -> return true + 1 -> println(1) + else -> println(2) + } + return false +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/unconditionalBreakWithIf.kt b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/unconditionalBreakWithIf.kt index 2858333959a5..09fa2724aa4a 100644 --- a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/unconditionalBreakWithIf.kt +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/unconditionalBreakWithIf.kt @@ -11,4 +11,6 @@ fun foo(a: Int): Int { break } return 1 -} \ No newline at end of file +} + +// IGNORE_K2 \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/unconditionalBreakWithIf.kt.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/unconditionalBreakWithIf.kt.after index 147df799e961..d9abec36331e 100644 --- a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/unconditionalBreakWithIf.kt.after +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/unconditionalBreakWithIf.kt.after @@ -16,4 +16,6 @@ private fun __dummyTestFun__(a: Int, b: Int) { if (a + b > 0) return println(a - b) return -} \ No newline at end of file +} + +// IGNORE_K2 \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/default/defaultCF.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/default/defaultCF.kt.fir.after new file mode 100644 index 000000000000..49a0b0155ecc --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/default/defaultCF.kt.fir.after @@ -0,0 +1,18 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo +// WITH_STDLIB +// SIBLING: +fun foo(a: Int) { + val b: Int = 1 + + __dummyTestFun__(a, b) +} + +private fun __dummyTestFun__(a: Int, b: Int) { + if (a > 0) { + println(a) + } + println(b) +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/definiteReturns/labeledAndUnlabeledReturn1.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/definiteReturns/labeledAndUnlabeledReturn1.kt.fir.after new file mode 100644 index 000000000000..8de4ee570470 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/definiteReturns/labeledAndUnlabeledReturn1.kt.fir.after @@ -0,0 +1,13 @@ +// WITH_STDLIB +// PARAM_DESCRIPTOR: value-parameter it: kotlin.Int defined in foo.`` +// PARAM_TYPES: kotlin.Int +fun foo(a: Int): Int { + a.let { + return i(it) + } + return 0 +} + +private fun i(i: Int): Int { + if (i > 0) return i else return -i +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/definiteReturns/labeledAndUnlabeledReturn2.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/definiteReturns/labeledAndUnlabeledReturn2.kt.fir.after new file mode 100644 index 000000000000..380f0e3d244e --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/definiteReturns/labeledAndUnlabeledReturn2.kt.fir.after @@ -0,0 +1,13 @@ +// WITH_STDLIB +// PARAM_DESCRIPTOR: value-parameter it: kotlin.Int defined in foo.`` +// PARAM_TYPES: kotlin.Int +fun foo(a: Int): Int { + a.let { + return@foo i(it) + } + return 0 +} + +private fun i(i: Int): Int { + if (i > 0) return i else return -i +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/definiteReturns/nonLocalReturn.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/definiteReturns/nonLocalReturn.kt.fir.after new file mode 100644 index 000000000000..15a6356b0061 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/definiteReturns/nonLocalReturn.kt.fir.after @@ -0,0 +1,10 @@ +// WITH_STDLIB +// PARAM_DESCRIPTOR: value-parameter it: kotlin.Int defined in foo.`` +// PARAM_TYPES: kotlin.Int +fun foo(): Int { + 1.let { return i(it) } +} + +private fun i(i: Int): Int { + return i + 1 +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/definiteReturns/nonLocalReturnWithLabel.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/definiteReturns/nonLocalReturnWithLabel.kt.fir.after new file mode 100644 index 000000000000..12c4f07a1fe8 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/definiteReturns/nonLocalReturnWithLabel.kt.fir.after @@ -0,0 +1,10 @@ +// WITH_STDLIB +// PARAM_DESCRIPTOR: value-parameter it: kotlin.Int defined in foo.`` +// PARAM_TYPES: kotlin.Int +fun foo(): Int { + 1.let { return@foo i(it) } +} + +private fun i(i: Int): Int { + return i + 1 +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInIfCondition.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInIfCondition.kt.fir.after new file mode 100644 index 000000000000..5d145867cd0c --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInIfCondition.kt.fir.after @@ -0,0 +1,11 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + val b: Int = 1 + return if (bool(a, b)) 1 else if (a - b < 0) 2 else b +} + +private fun bool(a: Int, b: Int): Boolean = a + b > 0 diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInIfElse.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInIfElse.kt.fir.after new file mode 100644 index 000000000000..4c8cdbeac536 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInIfElse.kt.fir.after @@ -0,0 +1,11 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + val b: Int = 1 + return if (a + b > 0) 1 else i(a, b) +} + +private fun i(a: Int, b: Int): Int = if (a - b < 0) 2 else b diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInIfThen.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInIfThen.kt.fir.after new file mode 100644 index 000000000000..510fd8646985 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInIfThen.kt.fir.after @@ -0,0 +1,7 @@ +// SIBLING: +fun foo(a: Int): Int { + val b: Int = 1 + return if (a + b > 0) i() else if (a - b < 0) 2 else b +} + +private fun i(): Int = 1 diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInWhenBranch.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInWhenBranch.kt.fir.after new file mode 100644 index 000000000000..847a9021e977 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInWhenBranch.kt.fir.after @@ -0,0 +1,13 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + val b: Int = 1 + return when (a + b) { + 0 -> i(b) + 1 -> -b + else -> a - b + } +} + +private fun i(b: Int): Int = b diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInWhenCondition.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInWhenCondition.kt.fir.after new file mode 100644 index 000000000000..55408f4134fc --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInWhenCondition.kt.fir.after @@ -0,0 +1,11 @@ +// SIBLING: +fun foo(a: Int): Int { + val b: Int = 1 + return when (a + b) { + i() -> b + 1 -> -b + else -> a - b + } +} + +private fun i(): Int = 0 diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInWhenSubject.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInWhenSubject.kt.fir.after new file mode 100644 index 000000000000..dd7a27be50d5 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInWhenSubject.kt.fir.after @@ -0,0 +1,15 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + val b: Int = 1 + return when (i(a, b)) { + 0 -> b + 1 -> -b + else -> a - b + } +} + +private fun i(a: Int, b: Int): Int = a + b diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExpressionBodyFunction.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExpressionBodyFunction.kt.fir.after new file mode 100644 index 000000000000..adf290b0f49d --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExpressionBodyFunction.kt.fir.after @@ -0,0 +1,8 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: value-parameter b: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int, b: Int): Int = i(a, b) + +private fun i(a: Int, b: Int): Int = a + b diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalIfExpr.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalIfExpr.kt.fir.after new file mode 100644 index 000000000000..809a6c360fab --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalIfExpr.kt.fir.after @@ -0,0 +1,11 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + val b: Int = 1 + return i(a, b) +} + +private fun i(a: Int, b: Int): Int = if (a + b > 0) 1 else if (a - b < 0) 2 else b diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalWhenExpr.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalWhenExpr.kt.fir.after new file mode 100644 index 000000000000..92ddf9f6a4fa --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalWhenExpr.kt.fir.after @@ -0,0 +1,15 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + val b: Int = 1 + return i(a, b) +} + +private fun i(a: Int, b: Int): Int = when (a + b) { + 0 -> b + 1 -> -b + else -> a - b +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/simpleEvalExpr.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/simpleEvalExpr.kt.fir.after new file mode 100644 index 000000000000..c887095fcde6 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/simpleEvalExpr.kt.fir.after @@ -0,0 +1,11 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + val b: Int = 1 + return i(a, b) +} + +private fun i(a: Int, b: Int): Int = a + b diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/exitPointEquivalence/breakAndReturn.kt b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/exitPointEquivalence/breakAndReturn.kt index 26b16a269d21..d7ab3ff0438f 100644 --- a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/exitPointEquivalence/breakAndReturn.kt +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/exitPointEquivalence/breakAndReturn.kt @@ -11,4 +11,6 @@ fun foo(a: Int) { println(a - b) return } -} \ No newline at end of file +} + +// IGNORE_K2 \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/exitPointEquivalence/breakAndReturn.kt.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/exitPointEquivalence/breakAndReturn.kt.after index e98460d75ba4..e177b2f2f96f 100644 --- a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/exitPointEquivalence/breakAndReturn.kt.after +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/exitPointEquivalence/breakAndReturn.kt.after @@ -16,4 +16,6 @@ private fun __dummyTestFun__(a: Int, b: Int) { if (a + b > 0) return println(a - b) return -} \ No newline at end of file +} + +// IGNORE_K2 \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/exitPointEquivalence/breakContinueAndDefault.kt b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/exitPointEquivalence/breakContinueAndDefault.kt index 28fe8859fbaf..32ba7525e0be 100644 --- a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/exitPointEquivalence/breakContinueAndDefault.kt +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/exitPointEquivalence/breakContinueAndDefault.kt @@ -12,4 +12,6 @@ fun foo(a: Int) { if (a - b > 0) continue@loop1 } } -} \ No newline at end of file +} + +// IGNORE_K2 \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/exitPointEquivalence/breakContinueAndDefault.kt.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/exitPointEquivalence/breakContinueAndDefault.kt.after index 7257cfd48f9f..e40b86cbdccb 100644 --- a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/exitPointEquivalence/breakContinueAndDefault.kt.after +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/exitPointEquivalence/breakContinueAndDefault.kt.after @@ -17,4 +17,6 @@ private fun b(a: Int, b: Int): Boolean { if (a + b > 0) return true if (a - b > 0) return true return false -} \ No newline at end of file +} + +// IGNORE_K2 \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/exitPointEquivalence/defaultAndBreak.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/exitPointEquivalence/defaultAndBreak.kt.fir.after new file mode 100644 index 000000000000..7ace33351397 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/exitPointEquivalence/defaultAndBreak.kt.fir.after @@ -0,0 +1,18 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int) { + val b: Int = 1 + for (n in 1..b) { + if (bool(a, b)) break + } +} + +private fun bool(a: Int, b: Int): Boolean { + if (a > 0) throw Exception("") + if (a + b > 0) return true + println(a - b) + return false +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/exitPointEquivalence/defaultAndReturn.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/exitPointEquivalence/defaultAndReturn.kt.fir.after new file mode 100644 index 000000000000..eb9c6af4b2cf --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/exitPointEquivalence/defaultAndReturn.kt.fir.after @@ -0,0 +1,16 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int) { + val b: Int = 1 + + __dummyTestFun__(a, b) +} + +private fun __dummyTestFun__(a: Int, b: Int) { + if (a > 0) throw Exception("") + if (b + a > 0) return + println(a - b) +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNotNull.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNotNull.kt.fir.after new file mode 100644 index 000000000000..173462b2288c --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNotNull.kt.fir.after @@ -0,0 +1,7 @@ +// WITH_STDLIB +// SUGGESTED_NAMES: s, getS +fun test() { + val s = string() +} + +private fun string(): String = J.notNull() diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNullable.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNullable.kt.fir.after new file mode 100644 index 000000000000..6a83c61e2f04 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNullable.kt.fir.after @@ -0,0 +1,6 @@ +// SUGGESTED_NAMES: s, getS +fun test() { + val s = string() +} + +private fun string(): String? = J.nullable() diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaNoNullabilityAnnotation.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaNoNullabilityAnnotation.kt.fir.after new file mode 100644 index 000000000000..851cc24ac1e1 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaNoNullabilityAnnotation.kt.fir.after @@ -0,0 +1,7 @@ +// SUGGESTED_NAMES: s, getS +// SUGGESTED_RETURN_TYPES: kotlin.String?, kotlin.String +fun test() { + val s = getS() +} + +private fun getS(): String? = J.unknown() diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/throws/breakWithThrow.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/throws/breakWithThrow.kt.fir.after new file mode 100644 index 000000000000..41129d157d13 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/throws/breakWithThrow.kt.fir.after @@ -0,0 +1,18 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + val b: Int = 1 + for (n in 1..b) { + if (bool(a, b)) break + } +} + +private fun bool(a: Int, b: Int): Boolean { + if (a > 0) throw Exception("") + if (a + b > 0) return true + println(a - b) + return false +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/throws/continueWithThrow.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/throws/continueWithThrow.kt.fir.after new file mode 100644 index 000000000000..6f0f1177586a --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/throws/continueWithThrow.kt.fir.after @@ -0,0 +1,19 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + val b: Int = 1 + for (n in 1..b) { + if (bool(a, b)) continue + println(a + b) + } +} + +private fun bool(a: Int, b: Int): Boolean { + if (a > 0) throw Exception("") + if (a + b > 0) return true + println(a - b) + return false +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/throws/evalExpressionWithThrow.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/throws/evalExpressionWithThrow.kt.fir.after new file mode 100644 index 000000000000..71b1511d0271 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/throws/evalExpressionWithThrow.kt.fir.after @@ -0,0 +1,14 @@ +// SUGGESTED_NAMES: i, getT +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: val b: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int): Int { + val b: Int = 1 + + val t = i(a, b) + return t +} + +private fun i(a: Int, b: Int): Int = if (a > 0) throw Exception("") else a + b diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/throws/nonValuedReturnWithThrow.kt b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/throws/nonValuedReturnWithThrow.kt index 83e80b433963..85580491b087 100644 --- a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/throws/nonValuedReturnWithThrow.kt +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/throws/nonValuedReturnWithThrow.kt @@ -10,4 +10,6 @@ fun foo(a: Int) { if (b + a > 0) return println(a - b) println(a + b) -} \ No newline at end of file +} + +// IGNORE_K2 \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/throws/nonValuedReturnWithThrow.kt.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/throws/nonValuedReturnWithThrow.kt.after index 3380115aeb3b..788b42f68de3 100644 --- a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/throws/nonValuedReturnWithThrow.kt.after +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/controlFlow/throws/nonValuedReturnWithThrow.kt.after @@ -15,4 +15,6 @@ private fun b(a: Int, b: Int): Boolean { if (b + a > 0) return true println(a - b) return false -} \ No newline at end of file +} + +// IGNORE_K2 \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/defaultContainer/anonymousObject.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/defaultContainer/anonymousObject.kt.fir.after new file mode 100644 index 000000000000..5e284f48d5c6 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/defaultContainer/anonymousObject.kt.fir.after @@ -0,0 +1,11 @@ +class A { + fun foo(a: Int, b: Int): Int { + return object: Function0 { + override fun invoke(): Int { + return i() + } + + private fun i(): Int = a + b - 1 + } + } +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/defaultContainer/classFunction.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/defaultContainer/classFunction.kt.fir.after new file mode 100644 index 000000000000..52c8e615e7bd --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/defaultContainer/classFunction.kt.fir.after @@ -0,0 +1,13 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in A.B.foo +// PARAM_DESCRIPTOR: value-parameter b: kotlin.Int defined in A.B.foo +class A { + class B { + fun foo(a: Int, b: Int): Int { + return i(a, b) + } + + private fun i(a: Int, b: Int): Int = a + b - 1 + } +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/defaultContainer/lambda.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/defaultContainer/lambda.kt.fir.after new file mode 100644 index 000000000000..d821ad4044cb --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/defaultContainer/lambda.kt.fir.after @@ -0,0 +1,13 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in A.foo +// PARAM_DESCRIPTOR: value-parameter b: kotlin.Int defined in A.foo +class A { + fun foo(a: Int, b: Int): Int { + return { + i(a, b) + }.invoke() + } + + private fun i(a: Int, b: Int): Int = a + b - 1 +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/defaultContainer/localClass.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/defaultContainer/localClass.kt.fir.after new file mode 100644 index 000000000000..d40edd224888 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/defaultContainer/localClass.kt.fir.after @@ -0,0 +1,13 @@ +class A { + fun foo(a: Int, b: Int): Int { + class L: Function0 { + override fun invoke(): Int { + return i() + } + + private fun i(): Int = a + b - 1 + } + + return L().invoke() + } +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/defaultContainer/localFunction.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/defaultContainer/localFunction.kt.fir.after new file mode 100644 index 000000000000..f0b43a593a0f --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/defaultContainer/localFunction.kt.fir.after @@ -0,0 +1,9 @@ +fun foo(a: Int, b: Int): Int { + fun i(): Int = a + b - 1 + + fun bar() { + return i() + } + + return bar() +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/defaultContainer/nestedLambda.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/defaultContainer/nestedLambda.kt.fir.after new file mode 100644 index 000000000000..f83b00035964 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/defaultContainer/nestedLambda.kt.fir.after @@ -0,0 +1,13 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in A.foo +// PARAM_DESCRIPTOR: value-parameter b: kotlin.Int defined in A.foo +class A { + fun foo(a: Int, b: Int): Int { + return { + { i(a, b) }.invoke() + }.invoke() + } + + private fun i(a: Int, b: Int): Int = a + b - 1 +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/defaultContainer/topLevelFunction.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/defaultContainer/topLevelFunction.kt.fir.after new file mode 100644 index 000000000000..a10ad4e4a64b --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/defaultContainer/topLevelFunction.kt.fir.after @@ -0,0 +1,9 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: value-parameter b: kotlin.Int defined in foo +fun foo(a: Int, b: Int): Int { + return i(a, b) +} + +private fun i(a: Int, b: Int): Int = a + b - 1 diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/delegation/delegationByExpression.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/delegation/delegationByExpression.kt.fir.after new file mode 100644 index 000000000000..24bd957c0c79 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/delegation/delegationByExpression.kt.fir.after @@ -0,0 +1,11 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in B.`` +// PARAM_DESCRIPTOR: value-parameter b: kotlin.Int defined in B.`` +interface T + +class A(a: Int, b: Int): T + +class B(a: Int, b: Int): T by A(i(a, b), a - b) + +private fun i(a: Int, b: Int): Int = a + b diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/delegation/delegationByExpressionFull.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/delegation/delegationByExpressionFull.kt.fir.after new file mode 100644 index 000000000000..944b0c92693f --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/delegation/delegationByExpressionFull.kt.fir.after @@ -0,0 +1,9 @@ +// PARAM_TYPES: T +// PARAM_DESCRIPTOR: value-parameter t: T defined in B.`` +interface T + +class A(a: Int, b: Int): T + +class B(t: T): T by t(t) + +private fun t(t: T): T = t diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/delegation/delegationBySuperCall.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/delegation/delegationBySuperCall.kt.fir.after new file mode 100644 index 000000000000..676e4e7fbe45 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/delegation/delegationBySuperCall.kt.fir.after @@ -0,0 +1,9 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in B.`` +// PARAM_DESCRIPTOR: value-parameter b: kotlin.Int defined in B.`` +open class A(a: Int, b: Int) + +class B(a: Int, b: Int): A(i(a, b), a - b) + +private fun i(a: Int, b: Int): Int = a + b diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/accessors/memberProperty.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/accessors/memberProperty.kt.fir.after new file mode 100644 index 000000000000..0707446e6938 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/accessors/memberProperty.kt.fir.after @@ -0,0 +1,5 @@ +class A(val a: Int, val b: Int) { + val foo: Int get() = i() - 1 + + private fun i(): Int = a + b +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/accessors/memberPropertyWithLambda.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/accessors/memberPropertyWithLambda.kt.fir.after new file mode 100644 index 000000000000..faeaa6c0f444 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/accessors/memberPropertyWithLambda.kt.fir.after @@ -0,0 +1,5 @@ +class A(val a: Int, val b: Int) { + val foo: Int get() = { i() - 1 }.invoke() + + private fun i(): Int = a + b +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/accessors/topLevelProperty.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/accessors/topLevelProperty.kt.fir.after new file mode 100644 index 000000000000..56fff278ad64 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/accessors/topLevelProperty.kt.fir.after @@ -0,0 +1,5 @@ +val a = 1 +val b = 1 +val foo: Int get() = i() - 1 + +private fun i(): Int = a + b diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/accessors/topLevelPropertyWithLambda.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/accessors/topLevelPropertyWithLambda.kt.fir.after new file mode 100644 index 000000000000..609bad22c815 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/accessors/topLevelPropertyWithLambda.kt.fir.after @@ -0,0 +1,5 @@ +val a = 1 +val b = 1 +val foo: Int get() = { i() - 1 }.invoke() + +private fun i(): Int = a + b diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/classes/classInitializer.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/classes/classInitializer.kt.fir.after new file mode 100644 index 000000000000..bc32fc836df8 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/classes/classInitializer.kt.fir.after @@ -0,0 +1,9 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter b: kotlin.Int defined in A.`` +class A(val a: Int, b: Int) { + init { + println(i(b) - 1) + } + + private fun i(b: Int): Int = a + b +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/classes/classInitializerWithLambda.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/classes/classInitializerWithLambda.kt.fir.after new file mode 100644 index 000000000000..d91ba4f503a9 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/classes/classInitializerWithLambda.kt.fir.after @@ -0,0 +1,9 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter b: kotlin.Int defined in A.`` +class A(val a: Int, b: Int) { + init { + println({ i(b) - 1 }.invoke()) + } + + private fun i(b: Int): Int = a + b +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/classes/classParameters.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/classes/classParameters.kt.fir.after new file mode 100644 index 000000000000..0971cd1ea0c9 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/classes/classParameters.kt.fir.after @@ -0,0 +1,6 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in A.`` +val n = 1 +class A(val a: Int, val b: Int = i(a)) + +private fun i(a: Int): Int = a + n diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/classes/classParametersWithLambda.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/classes/classParametersWithLambda.kt.fir.after new file mode 100644 index 000000000000..951674915d4f --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/classes/classParametersWithLambda.kt.fir.after @@ -0,0 +1,6 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in A.`` +val n = 1 +class A(val a: Int, val b: Int = { i(a) }.invoke()) + +private fun i(a: Int): Int = a + n diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/localFunction.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/localFunction.kt.fir.after new file mode 100644 index 000000000000..43195571b21b --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/localFunction.kt.fir.after @@ -0,0 +1,9 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in bar.foo +// PARAM_DESCRIPTOR: value-parameter b: kotlin.Int defined in bar.foo +fun bar(n: Int) { + fun i(a: Int, b: Int): Int = a + b - n + + fun foo(a: Int, b: Int) = i(a, b) - 1 +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/localFunctionParameters.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/localFunctionParameters.kt.fir.after new file mode 100644 index 000000000000..7eadf0c0a32a --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/localFunctionParameters.kt.fir.after @@ -0,0 +1,7 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in bar.foo +fun bar(n: Int) { + fun i(a: Int): Int = a + n + + fun foo(a: Int, b: Int = i(a)) = a + b - n - 1 +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/localFunctionParametersWithLambda.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/localFunctionParametersWithLambda.kt.fir.after new file mode 100644 index 000000000000..5c85350a71ef --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/localFunctionParametersWithLambda.kt.fir.after @@ -0,0 +1,7 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in bar.foo +fun bar(n: Int) { + fun i(a: Int): Int = a + n + + fun foo(a: Int, b: Int = { i(a) }.invoke()) = a + b - n - 1 +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/localFunctionWithLambda.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/localFunctionWithLambda.kt.fir.after new file mode 100644 index 000000000000..08a4f2fef19e --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/localFunctionWithLambda.kt.fir.after @@ -0,0 +1,9 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in bar.foo +// PARAM_DESCRIPTOR: value-parameter b: kotlin.Int defined in bar.foo +fun bar(n: Int) { + fun i(a: Int, b: Int): Int = a + b - n + + fun foo(a: Int, b: Int) = { i(a, b) - 1 }.invoke() +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/memberFunction.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/memberFunction.kt.fir.after new file mode 100644 index 000000000000..b0e28ea3878a --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/memberFunction.kt.fir.after @@ -0,0 +1,9 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in A.foo +// PARAM_DESCRIPTOR: value-parameter b: kotlin.Int defined in A.foo +class A(val n: Int) { + fun foo(a: Int, b: Int) = i(a, b) - 1 + + private fun i(a: Int, b: Int): Int = a + b - n +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/memberFunctionParameters.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/memberFunctionParameters.kt.fir.after new file mode 100644 index 000000000000..8e97b88fd515 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/memberFunctionParameters.kt.fir.after @@ -0,0 +1,7 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in A.foo +class A(val n: Int) { + fun foo(a: Int, b: Int = i(a)) = a + b - n - 1 + + private fun i(a: Int): Int = a + n +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/memberFunctionParametersWithLambda.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/memberFunctionParametersWithLambda.kt.fir.after new file mode 100644 index 000000000000..80620911e964 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/memberFunctionParametersWithLambda.kt.fir.after @@ -0,0 +1,7 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in A.foo +class A(val n: Int) { + fun foo(a: Int, b: Int = { i(a) }.invoke()) = a + b - n - 1 + + private fun i(a: Int): Int = a + n +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/memberFunctionWithLambda.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/memberFunctionWithLambda.kt.fir.after new file mode 100644 index 000000000000..db39754e7317 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/memberFunctionWithLambda.kt.fir.after @@ -0,0 +1,9 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in A.foo +// PARAM_DESCRIPTOR: value-parameter b: kotlin.Int defined in A.foo +class A(val n: Int) { + fun foo(a: Int, b: Int) = { i(a, b) - 1 }.invoke() + + private fun i(a: Int, b: Int): Int = a + b - n +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/topLevelFunction.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/topLevelFunction.kt.fir.after new file mode 100644 index 000000000000..00918540deb1 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/topLevelFunction.kt.fir.after @@ -0,0 +1,7 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: value-parameter b: kotlin.Int defined in foo +fun foo(a: Int, b: Int) = i(a, b) - 1 + +private fun i(a: Int, b: Int): Int = a + b diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/topLevelFunctionParameters.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/topLevelFunctionParameters.kt.fir.after new file mode 100644 index 000000000000..66048dd98b8a --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/topLevelFunctionParameters.kt.fir.after @@ -0,0 +1,6 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +val n = 1 +fun foo(a: Int, b: Int = i(a)) = a + b - 1 + +private fun i(a: Int): Int = a + n diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/topLevelFunctionParametersWithLambda.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/topLevelFunctionParametersWithLambda.kt.fir.after new file mode 100644 index 000000000000..eeabc96137a6 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/topLevelFunctionParametersWithLambda.kt.fir.after @@ -0,0 +1,6 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +val n = 1 +fun foo(a: Int, b: Int = { i(a) }) = a + b - 1 + +private fun i(a: Int): Int = a + n diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/topLevelFunctionWithLambda.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/topLevelFunctionWithLambda.kt.fir.after new file mode 100644 index 000000000000..28a2b8acbf42 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/functions/topLevelFunctionWithLambda.kt.fir.after @@ -0,0 +1,7 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: value-parameter b: kotlin.Int defined in foo +fun foo(a: Int, b: Int) = { i(a, b) - 1 }.invoke() + +private fun i(a: Int, b: Int): Int = a + b diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/properties/localProperty.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/properties/localProperty.kt.fir.after new file mode 100644 index 000000000000..9804ee85baec --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/properties/localProperty.kt.fir.after @@ -0,0 +1,9 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in bar +// PARAM_DESCRIPTOR: value-parameter b: kotlin.Int defined in bar +fun bar(a: Int, b: Int) { + val foo = i(a, b) - 1 +} + +private fun i(a: Int, b: Int): Int = a + b \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/properties/localPropertyWithLambda.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/properties/localPropertyWithLambda.kt.fir.after new file mode 100644 index 000000000000..8c29d13536dc --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/properties/localPropertyWithLambda.kt.fir.after @@ -0,0 +1,9 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in bar +// PARAM_DESCRIPTOR: value-parameter b: kotlin.Int defined in bar +fun bar(a: Int, b: Int) { + val foo = { i(a, b) - 1 }.invoke() +} + +private fun i(a: Int, b: Int): Int = a + b diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/properties/memberProperty.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/properties/memberProperty.kt.fir.after new file mode 100644 index 000000000000..c3b68359c229 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/properties/memberProperty.kt.fir.after @@ -0,0 +1,7 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter b: kotlin.Int defined in A.`` +class A(val a: Int, b: Int) { + val foo = i(b) - 1 + + private fun i(b: Int): Int = a + b +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/properties/memberPropertyWithLambda.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/properties/memberPropertyWithLambda.kt.fir.after new file mode 100644 index 000000000000..b2ab48f67a0c --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/properties/memberPropertyWithLambda.kt.fir.after @@ -0,0 +1,7 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter b: kotlin.Int defined in A.`` +class A(val a: Int, b: Int) { + val foo = { i(b) - 1 }.invoke() + + private fun i(b: Int): Int = a + b +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/properties/multiDeclaration.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/properties/multiDeclaration.kt.fir.after new file mode 100644 index 000000000000..3264da5d060b --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/properties/multiDeclaration.kt.fir.after @@ -0,0 +1,8 @@ +// SUGGESTED_NAMES: pair, intIntPair, intPair +// WITH_STDLIB +// SIBLING: +fun foo() { + val (a, b) = pair() +} + +private fun pair(): Pair = 1 to 2 diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/properties/topLevelProperty.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/properties/topLevelProperty.kt.fir.after new file mode 100644 index 000000000000..5aadcdf6d46d --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/properties/topLevelProperty.kt.fir.after @@ -0,0 +1,5 @@ +val a = 1 +val b = 1 +val foo = i() - 1 + +private fun i(): Int = a + b diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/properties/topLevelPropertyWithLambda.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/properties/topLevelPropertyWithLambda.kt.fir.after new file mode 100644 index 000000000000..e269efcbbb1d --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/initializers/properties/topLevelPropertyWithLambda.kt.fir.after @@ -0,0 +1,5 @@ +val a = 1 +val b = 1 +val foo = { i() - 1 }.invoke() + +private fun i(): Int = a + b diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/multiline/multilineBinaryExpression.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/multiline/multilineBinaryExpression.kt.fir.after new file mode 100644 index 000000000000..1e7bc50339a0 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/multiline/multilineBinaryExpression.kt.fir.after @@ -0,0 +1,8 @@ +fun foo(i: Int) { } + +fun test() { + foo(i()) +} + +private fun i(): Int = (1 + + 2) diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/multiline/multilineBinaryExpressionWithComments.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/multiline/multilineBinaryExpressionWithComments.kt.fir.after new file mode 100644 index 000000000000..7d73b7bb5d0d --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/multiline/multilineBinaryExpressionWithComments.kt.fir.after @@ -0,0 +1,8 @@ +fun foo(i: Int) { } + +fun test() { + foo(i()) +} + +private fun i(): Int = (1 // abc + /*def*/ + 2) diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/multiline/multilineNestedBinaryExpression1.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/multiline/multilineNestedBinaryExpression1.kt.fir.after new file mode 100644 index 000000000000..ffe34ff3dcd0 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/multiline/multilineNestedBinaryExpression1.kt.fir.after @@ -0,0 +1,8 @@ +fun foo(i: Int) { } + +fun test() { + foo(i()) +} + +private fun i(): Int = (1 + + 2 - 3) diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/multiline/multilineNestedBinaryExpression2.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/multiline/multilineNestedBinaryExpression2.kt.fir.after new file mode 100644 index 000000000000..ce5fe4607f02 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/multiline/multilineNestedBinaryExpression2.kt.fir.after @@ -0,0 +1,8 @@ +fun foo(i: Int) { } + +fun test() { + foo(i()) +} + +private fun i(): Int = (1 + 2 + - 3) diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/optIn/filterPropagatingMarkersWitArgs.kt b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/optIn/filterPropagatingMarkersWitArgs.kt new file mode 100644 index 000000000000..1818579488f7 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/optIn/filterPropagatingMarkersWitArgs.kt @@ -0,0 +1,26 @@ +// WITH_STDLIB +// PARAM_DESCRIPTOR: val z: kotlin.Int defined in baz +// PARAM_TYPES: kotlin.Int +import kotlin.reflect.KClass + +@RequiresOptIn +annotation class Marker + +@RequiresOptIn +annotation class Another(val bar: String, val cl: KClass<*>) + +@Marker +fun foo(x: Int): Int = x + +@Another("a", Marker::class) +fun bar(x: Int) { + println(x) +} + +@Marker +@Another("b", Marker::class) +fun baz() { + val z = foo(1) + println(z) + bar(z) +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/optIn/filterPropagatingMarkersWitArgs.kt.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/optIn/filterPropagatingMarkersWitArgs.kt.after new file mode 100644 index 000000000000..963d3c0e8d3e --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/optIn/filterPropagatingMarkersWitArgs.kt.after @@ -0,0 +1,31 @@ +// WITH_STDLIB +// PARAM_DESCRIPTOR: val z: kotlin.Int defined in baz +// PARAM_TYPES: kotlin.Int +import kotlin.reflect.KClass + +@RequiresOptIn +annotation class Marker + +@RequiresOptIn +annotation class Another(val bar: String, val cl: KClass<*>) + +@Marker +fun foo(x: Int): Int = x + +@Another("a", Marker::class) +fun bar(x: Int) { + println(x) +} + +@Marker +@Another("b", Marker::class) +fun baz() { + val z = foo(1) + __dummyTestFun__(z) +} + +@Another(bar = "b", cl = Marker::class) +private fun __dummyTestFun__(z: Int) { + println(z) + bar(z) +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/cantLiftAnonymousToSupertype.kt.fir.conflicts b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/cantLiftAnonymousToSupertype.kt.fir.conflicts new file mode 100644 index 000000000000..f4847ae3ab4b --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/cantLiftAnonymousToSupertype.kt.fir.conflicts @@ -0,0 +1 @@ +Property <no name provided>.t will no longer be accessible after extraction diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/flexibleTypesWithNull.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/flexibleTypesWithNull.kt.fir.after new file mode 100644 index 000000000000..91e41fdec0f9 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/flexibleTypesWithNull.kt.fir.after @@ -0,0 +1,10 @@ +// WITH_STDLIB +// SUGGESTED_NAMES: i, getN +// PARAM_TYPES: kotlin.String?, kotlin.String, kotlin.CharSequence?, kotlin.CharSequence +// PARAM_DESCRIPTOR: val property: kotlin.String! defined in test +fun test() { + val property = System.getProperty("some") + val n = i(property) +} + +private fun i(property: String?): Int? = property?.length diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/flexibleTypesWithoutNull.kt b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/flexibleTypesWithoutNull.kt index e9035f2dcb00..be86e32ba800 100644 --- a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/flexibleTypesWithoutNull.kt +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/flexibleTypesWithoutNull.kt @@ -5,4 +5,6 @@ fun test() { val property = System.getProperty("some") val n = property.length -} \ No newline at end of file +} + +// IGNORE_K2 \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/flexibleTypesWithoutNull.kt.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/flexibleTypesWithoutNull.kt.after index 75f8b279bff7..c098da25a000 100644 --- a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/flexibleTypesWithoutNull.kt.after +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/flexibleTypesWithoutNull.kt.after @@ -7,4 +7,6 @@ fun test() { val n = i(property) } -private fun i(property: String) = property.length \ No newline at end of file +private fun i(property: String) = property.length + +// IGNORE_K2 \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes.kt.fir.after new file mode 100644 index 000000000000..586fa1c37055 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes.kt.fir.after @@ -0,0 +1,16 @@ +// PARAM_TYPES: kotlin.Any +// PARAM_DESCRIPTOR: value-parameter p: kotlin.Any defined in foo + +fun foo(p: Any) { + if (p is String) { + __dummyTestFun__(p) + } +} + +private fun __dummyTestFun__(p: String) { + f(p) + g(p) +} + +fun f(s: String){} +fun g(o: Any){} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes2.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes2.kt.fir.after new file mode 100644 index 000000000000..eefd75508a05 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes2.kt.fir.after @@ -0,0 +1,27 @@ +// SUGGESTED_NAMES: i, getX +// PARAM_TYPES: kotlin.Any +// PARAM_DESCRIPTOR: value-parameter o: kotlin.Any defined in foo + +open class A { + val a = 1 +} + +interface T { + val t: Int +} + +class B : A(), T { + override val t: Int = 2 +} + +fun foo(o: Any) { + val x = i(o) +} + +private fun i(o: Any): Int = when (o) { + is A -> { + if (o is T) o.a + o.t else o.a + } + + else -> o.hashCode() +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes3.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes3.kt.fir.after new file mode 100644 index 000000000000..64396e55a93c --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes3.kt.fir.after @@ -0,0 +1,25 @@ +// PARAM_TYPES: A +// PARAM_DESCRIPTOR: value-parameter o: kotlin.Any defined in foo + +open class A { + val a = 1 +} + +interface T { + val t: Int +} + +class B : A(), T { + override val t: Int = 2 +} + +fun foo(o: Any) { + val x = when (o) { + is A -> { + i(o) + } + else -> o.hashCode() + } +} + +private fun i(o: A): Int = if (o is T) o.a + o.t else o.a diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes4.kt b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes4.kt index 3c9308393e57..7f7cc58a67da 100644 --- a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes4.kt +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes4.kt @@ -17,4 +17,6 @@ fun foo(o: Any) { } else -> o.hashCode() } -} \ No newline at end of file +} + +// IGNORE_K2 \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/mutablyFlexibleType.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/mutablyFlexibleType.kt.fir.after new file mode 100644 index 000000000000..c7b7eda48513 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/mutablyFlexibleType.kt.fir.after @@ -0,0 +1,10 @@ +// WITH_STDLIB +// PARAM_DESCRIPTOR: val data: kotlin.collections.(Mutable)List defined in test +// PARAM_TYPES: kotlin.collections.List, kotlin.collections.MutableList, kotlin.collections.MutableCollection, kotlin.collections.Collection +fun test(): Boolean { + val j: J? = null + val data = j?.getData() ?: return false + return bool(data) +} + +private fun bool(data: List): Boolean = data.contains("foo") diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/nonNullableTypes.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/nonNullableTypes.kt.fir.after new file mode 100644 index 000000000000..8bb90cc0ac3a --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/nonNullableTypes.kt.fir.after @@ -0,0 +1,20 @@ +// PARAM_TYPES: X +// PARAM_TYPES: kotlin.String? +// PARAM_DESCRIPTOR: value-parameter x: X defined in foo +// PARAM_DESCRIPTOR: value-parameter s: kotlin.String? defined in foo +class X { + fun add(t: T) { + + } +} + +// SIBLING: +fun foo(s: String?, x: X) { + when { + s != null -> __dummyTestFun__(x, s) + } +} + +private fun __dummyTestFun__(x: X, s: String) { + x.add(s) +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/nullableTypes.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/nullableTypes.kt.fir.after new file mode 100644 index 000000000000..54fe25854f68 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/nullableTypes.kt.fir.after @@ -0,0 +1,20 @@ +// PARAM_TYPES: X +// PARAM_TYPES: kotlin.String?, kotlin.Comparable?, kotlin.CharSequence?, java.io.Serializable?, kotlin.Any? +// PARAM_DESCRIPTOR: value-parameter x: X defined in foo +// PARAM_DESCRIPTOR: value-parameter s: kotlin.String? defined in foo +class X { + fun add(t: T) { + + } +} + +// SIBLING: +fun foo(s: String?, x: X) { + when { + s != null -> __dummyTestFun__(x, s) + } +} + +private fun __dummyTestFun__(x: X, s: String) { + x.add(s) +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/capturedFunctions/deeplyLocalFun.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/capturedFunctions/deeplyLocalFun.kt.fir.after new file mode 100644 index 000000000000..14e2ccf9df83 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/capturedFunctions/deeplyLocalFun.kt.fir.after @@ -0,0 +1,18 @@ +// OPTIONS: true, false, false, true, false +// PARAM_DESCRIPTOR: local final fun baz(m: kotlin.Int): kotlin.Int defined in foo.bar +// PARAM_DESCRIPTOR: value-parameter n: kotlin.Int defined in foo +// PARAM_TYPES: (m: kotlin.Int) -> kotlin.Int +// PARAM_TYPES: kotlin.Int + +// SIBLING: +fun foo(n: Int): Int { + fun bar(): Int { + fun baz(m: Int) = m * n + + return i(::baz, n) + } + + return bar() +} + +private fun i(baz: (Int) -> Int, n: Int): Int = baz(n + 1) diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/capturedFunctions/localExtensionFun.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/capturedFunctions/localExtensionFun.kt.fir.after new file mode 100644 index 000000000000..77398de57a35 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/capturedFunctions/localExtensionFun.kt.fir.after @@ -0,0 +1,12 @@ +// OPTIONS: true, false, false, true, false +// PARAM_DESCRIPTOR: value-parameter n: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: local final fun kotlin.Int.bar(m: kotlin.Int): kotlin.Int defined in foo +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int.(m: kotlin.Int) -> kotlin.Int +fun foo(n: Int): Int { + fun Int.bar(m: Int) = this * m * n + + return i(n, Int::bar) +} + +private fun i(n: Int, bar: Int.(Int) -> Int): Int = n.bar(n + 1) diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/capturedFunctions/localExtensionFunChainedCalls.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/capturedFunctions/localExtensionFunChainedCalls.kt.fir.after new file mode 100644 index 000000000000..b9546067407c --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/capturedFunctions/localExtensionFunChainedCalls.kt.fir.after @@ -0,0 +1,20 @@ +// OPTIONS: true, false, false, true, false +// PARAM_DESCRIPTOR: value-parameter n: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: local final fun kotlin.Int.bar1(m: kotlin.Int): kotlin.Int defined in foo +// PARAM_DESCRIPTOR: local final fun kotlin.Int.bar2(m: kotlin.Int): kotlin.Int defined in foo +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int.(m: kotlin.Int) -> kotlin.Int +// PARAM_TYPES: kotlin.Int.(m: kotlin.Int) -> kotlin.Int + +fun foo(n: Int): Int { + fun Int.bar1(m: Int) = this + m + n + fun Int.bar2(m: Int) = this * m * n + + return i(n, Int::bar1, Int::bar2) +} + +private fun i( + n: Int, + bar1: Int.(Int) -> Int, + bar2: Int.(Int) -> Int +): Int = n.bar1(n + 1).bar2(n + 2) diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/capturedFunctions/localExtensionFunInfixCall.kt b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/capturedFunctions/localExtensionFunInfixCall.kt index 8ef42ae50036..195c245f086f 100644 --- a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/capturedFunctions/localExtensionFunInfixCall.kt +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/capturedFunctions/localExtensionFunInfixCall.kt @@ -1,10 +1,10 @@ // OPTIONS: true, false, false, true, false // PARAM_DESCRIPTOR: value-parameter n: kotlin.Int defined in foo -// PARAM_DESCRIPTOR: local final fun kotlin.Int.bar(m: kotlin.Int): kotlin.Int defined in foo +// PARAM_DESCRIPTOR: local final infix fun kotlin.Int.bar(m: kotlin.Int): kotlin.Int defined in foo // PARAM_TYPES: kotlin.Int // PARAM_TYPES: kotlin.Int.(m: kotlin.Int) -> kotlin.Int fun foo(n: Int): Int { - fun Int.bar(m: Int) = this * m * n + infix fun Int.bar(m: Int) = this * m * n return n bar (n + 1) } \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/capturedFunctions/localExtensionFunInfixCall.kt.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/capturedFunctions/localExtensionFunInfixCall.kt.after index 35d29f08d43b..2cf3d277924d 100644 --- a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/capturedFunctions/localExtensionFunInfixCall.kt.after +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/capturedFunctions/localExtensionFunInfixCall.kt.after @@ -1,10 +1,10 @@ // OPTIONS: true, false, false, true, false // PARAM_DESCRIPTOR: value-parameter n: kotlin.Int defined in foo -// PARAM_DESCRIPTOR: local final fun kotlin.Int.bar(m: kotlin.Int): kotlin.Int defined in foo +// PARAM_DESCRIPTOR: local final infix fun kotlin.Int.bar(m: kotlin.Int): kotlin.Int defined in foo // PARAM_TYPES: kotlin.Int // PARAM_TYPES: kotlin.Int.(m: kotlin.Int) -> kotlin.Int fun foo(n: Int): Int { - fun Int.bar(m: Int) = this * m * n + infix fun Int.bar(m: Int) = this * m * n return i(n, Int::bar) } diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/capturedFunctions/localExtensionFunInfixCall.kt.fir.conflicts b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/capturedFunctions/localExtensionFunInfixCall.kt.fir.conflicts new file mode 100644 index 000000000000..daf8674fe0a9 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/capturedFunctions/localExtensionFunInfixCall.kt.fir.conflicts @@ -0,0 +1 @@ +Function foo.bar(Int) on Int will no longer be accessible after extraction diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/capturedFunctions/localFun.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/capturedFunctions/localFun.kt.fir.after new file mode 100644 index 000000000000..113e472fdedf --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/capturedFunctions/localFun.kt.fir.after @@ -0,0 +1,12 @@ +// OPTIONS: true, false, false, true, false +// PARAM_DESCRIPTOR: local final fun bar(m: kotlin.Int): kotlin.Int defined in foo +// PARAM_DESCRIPTOR: value-parameter n: kotlin.Int defined in foo +// PARAM_TYPES: (m: kotlin.Int) -> kotlin.Int +// PARAM_TYPES: kotlin.Int +fun foo(n: Int): Int { + fun bar(m: Int) = m * n + + return i(::bar, n) +} + +private fun i(bar: (Int) -> Int, n: Int): Int = bar(n + 1) diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/bothReceivers.kt b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/bothReceivers.kt new file mode 100644 index 000000000000..750d0c5d6371 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/bothReceivers.kt @@ -0,0 +1,14 @@ +// PARAM_TYPES: B, kotlin.Any +// PARAM_DESCRIPTOR: value-parameter b: B defined in n +class B +class A { + fun B.m() {} +} + +fun n(a: A, b: B) { + with(a) { + b.m() + } +} + +// IGNORE_K1 \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/bothReceivers.kt.conflicts b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/bothReceivers.kt.conflicts new file mode 100644 index 000000000000..e5088a41fbcc --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/bothReceivers.kt.conflicts @@ -0,0 +1 @@ +Function A.m() on B will no longer be accessible after extraction diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/explicitLabeledThisInMember.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/explicitLabeledThisInMember.kt.fir.after new file mode 100644 index 000000000000..125edeaaa73a --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/explicitLabeledThisInMember.kt.fir.after @@ -0,0 +1,22 @@ +// PARAM_TYPES: A +// PARAM_TYPES: A.B +// PARAM_DESCRIPTOR: public final class A : Z defined in root package in file explicitLabeledThisInMember.kt +// PARAM_DESCRIPTOR: public final inner class B : Z defined in A +public open class Z { + val z: Int = 0 +} + +// SIBLING: +public class A(): Z() { + var a: Int = 1 + + public inner class B(): Z() { + var b: Int = 1 + + fun foo(): Int { + return i(this@A, this@B) + } + } +} + +private fun i(a1: A, b1: A.B): Int = a1.a + b1.b + a1.z + b1.z diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/explicitThisInExtension.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/explicitThisInExtension.kt.fir.after new file mode 100644 index 000000000000..31cc86a3d914 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/explicitThisInExtension.kt.fir.after @@ -0,0 +1,10 @@ +// PARAM_TYPES: Z +// PARAM_DESCRIPTOR: public fun Z.foo(): kotlin.Int defined in root package in file explicitThisInExtension.kt +class Z(val a: Int) + +// SIBLING: +fun Z.foo(): Int { + return i() + 1 +} + +private fun Z.i(): Int = this.a diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/explicitThisInMember.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/explicitThisInMember.kt.fir.after new file mode 100644 index 000000000000..1f80b0346854 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/explicitThisInMember.kt.fir.after @@ -0,0 +1,22 @@ +// PARAM_TYPES: A +// PARAM_TYPES: A.B +// PARAM_DESCRIPTOR: public final class A : Z defined in root package in file explicitThisInMember.kt +// PARAM_DESCRIPTOR: public final inner class B : Z defined in A +public open class Z { + val z: Int = 0 +} + +// SIBLING: +public class A(): Z() { + var a: Int = 1 + + public inner class B(): Z() { + var b: Int = 1 + + fun foo(): Int { + return i(this@A, this) + } + } +} + +private fun i(a1: A, b1: A.B): Int = a1.a + b1.b + b1.z diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/explicitThisWithSmartCast.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/explicitThisWithSmartCast.kt.fir.after new file mode 100644 index 000000000000..44ac02de0ab1 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/explicitThisWithSmartCast.kt.fir.after @@ -0,0 +1,19 @@ +// SUGGESTED_NAMES: i, getB +// PARAM_DESCRIPTOR: public fun A.ext(): kotlin.Unit defined in root package in file explicitThisWithSmartCast.kt +// PARAM_TYPES: B +fun main(args: Array) { + val a: A = B() + a.ext() +} + +fun A.ext() { + if (this !is B) return + val b = i() +} + +private fun B.i(): Int = this.foo() + +open class A +class B: A() { + fun foo() = 1 +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/implicitAndExplicitLabeledThisInMember.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/implicitAndExplicitLabeledThisInMember.kt.fir.after new file mode 100644 index 000000000000..fc6db31734f8 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/implicitAndExplicitLabeledThisInMember.kt.fir.after @@ -0,0 +1,22 @@ +// PARAM_TYPES: A +// PARAM_TYPES: A.B +// PARAM_DESCRIPTOR: public final class A : Z defined in root package in file implicitAndExplicitLabeledThisInMember.kt +// PARAM_DESCRIPTOR: public final inner class B : Z defined in A +public open class Z { + val z: Int = 0 +} + +// SIBLING: +public class A(): Z() { + var a: Int = 1 + + public inner class B(): Z() { + var b: Int = 1 + + fun foo(): Int { + return i(this@A, this@B) + } + } +} + +private fun i(a1: A, b1: A.B): Int = a1.a + b1.b + a1.z + b1.z diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/implicitAndExplicitThisInExtension.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/implicitAndExplicitThisInExtension.kt.fir.after new file mode 100644 index 000000000000..f58b9d7cefa1 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/implicitAndExplicitThisInExtension.kt.fir.after @@ -0,0 +1,10 @@ +// PARAM_TYPES: Z +// PARAM_DESCRIPTOR: public fun Z.foo(): kotlin.Int defined in root package in file implicitAndExplicitThisInExtension.kt +class Z(val a: Int) + +// SIBLING: +fun Z.foo(): Int { + return i() + 1 +} + +private fun Z.i(): Int = this.a + a diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/implicitThisInExtension.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/implicitThisInExtension.kt.fir.after new file mode 100644 index 000000000000..af0d3d24f199 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/implicitThisInExtension.kt.fir.after @@ -0,0 +1,10 @@ +// PARAM_TYPES: Z +// PARAM_DESCRIPTOR: public fun Z.foo(): kotlin.Int defined in root package in file implicitThisInExtension.kt +class Z(val a: Int) + +// SIBLING: +fun Z.foo(): Int { + return i() + 1 +} + +private fun Z.i(): Int = a diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/implicitThisInMember.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/implicitThisInMember.kt.fir.after new file mode 100644 index 000000000000..554e82ec9a54 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/implicitThisInMember.kt.fir.after @@ -0,0 +1,22 @@ +// PARAM_TYPES: A +// PARAM_TYPES: A.B +// PARAM_DESCRIPTOR: public final class A : Z defined in root package in file implicitThisInMember.kt +// PARAM_DESCRIPTOR: public final inner class B : Z defined in A +public open class Z { + val z: Int = 0 +} + +// SIBLING: +public class A(): Z() { + var a: Int = 1 + + public inner class B(): Z() { + var b: Int = 1 + + fun foo(): Int { + return i(this@A, this@B) + } + } +} + +private fun i(a1: A, b1: A.B): Int = a1.a + b1.b + b1.z diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/implicitThisWithSmartCast.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/implicitThisWithSmartCast.kt.fir.after new file mode 100644 index 000000000000..5529d3bc4e6b --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/implicitThisWithSmartCast.kt.fir.after @@ -0,0 +1,19 @@ +// SUGGESTED_NAMES: i, getB +// PARAM_DESCRIPTOR: public fun A.ext(): kotlin.Unit defined in root package in file implicitThisWithSmartCast.kt +// PARAM_TYPES: B +fun main(args: Array) { + val a: A = B() + a.ext() +} + +fun A.ext() { + if (this !is B) return + val b = i() +} + +private fun B.i(): Int = foo() + +open class A +class B: A() { + fun foo() = 1 +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/javaSyntheticProperty.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/javaSyntheticProperty.kt.fir.after new file mode 100644 index 000000000000..db359dfa125c --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/javaSyntheticProperty.kt.fir.after @@ -0,0 +1,8 @@ +// WITH_STDLIB +// PARAM_DESCRIPTOR: private final fun Named.foo(): kotlin.String defined in Test +// PARAM_TYPES: Named +public class Test { + private fun Named.foo() = string() + + private fun Named.string(): String = name +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/javaSyntheticPropertyWithDelegation.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/javaSyntheticPropertyWithDelegation.kt.fir.after new file mode 100644 index 000000000000..49d6ceff2cdc --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/javaSyntheticPropertyWithDelegation.kt.fir.after @@ -0,0 +1,13 @@ +// WITH_STDLIB +// PARAM_DESCRIPTOR: private final fun NamedEx.foo(): kotlin.String defined in Test +// PARAM_TYPES: NamedEx +// SIBLING: +public class Test { + private fun NamedEx.foo() = string() +} + +private fun NamedEx.string(): String = name + +public class NamedEx : Named by object : Named { + override fun getName(): String = "foo" +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/javaSyntheticPropertyWithOverride.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/javaSyntheticPropertyWithOverride.kt.fir.after new file mode 100644 index 000000000000..4b8186beedaa --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/javaSyntheticPropertyWithOverride.kt.fir.after @@ -0,0 +1,9 @@ +// WITH_STDLIB +// PARAM_DESCRIPTOR: private final fun NamedEx.foo(): kotlin.String defined in Test +// PARAM_TYPES: NamedEx +// SIBLING: +public class Test { + private fun NamedEx.foo() = string() +} + +private fun NamedEx.string(): String = name diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/propertyWithExplicitInvoke.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/propertyWithExplicitInvoke.kt.fir.after new file mode 100644 index 000000000000..e61fccd7efaf --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/propertyWithExplicitInvoke.kt.fir.after @@ -0,0 +1,9 @@ +// PARAM_DESCRIPTOR: public final class A defined in root package in file propertyWithExplicitInvoke.kt +// PARAM_TYPES: A +// SIBLING: +class A { + val foo: () -> String = { "OK" } + fun bar() = string() +} + +private fun A.string(): String = foo.invoke() diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/propertyWithImplicitInvoke.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/propertyWithImplicitInvoke.kt.fir.after new file mode 100644 index 000000000000..187878ca6987 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/propertyWithImplicitInvoke.kt.fir.after @@ -0,0 +1,9 @@ +// PARAM_DESCRIPTOR: public final class A defined in root package in file propertyWithImplicitInvoke.kt +// PARAM_TYPES: A +// SIBLING: +class A { + val foo: () -> String = { "OK" } + fun bar() = string() +} + +private fun A.string(): String = foo() diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/qualifiedThis.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/qualifiedThis.kt.fir.after new file mode 100644 index 000000000000..ce342e9d7b3a --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/qualifiedThis.kt.fir.after @@ -0,0 +1,13 @@ +// PARAM_TYPES: kotlin.String, kotlin.Comparable, kotlin.CharSequence, java.io.Serializable, kotlin.Any +// PARAM_DESCRIPTOR: public fun kotlin.String.test(): kotlin.Unit defined in root package in file qualifiedThis.kt +fun String.foo(f: () -> Unit) { + f() +} + +fun String.test() { + "sss".foo { + println(string()) + } +} + +private fun String.string(): String = this diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/qualifiedThisAsArgument.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/qualifiedThisAsArgument.kt.fir.after new file mode 100644 index 000000000000..d85466685e8d --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/extractThis/qualifiedThisAsArgument.kt.fir.after @@ -0,0 +1,16 @@ +// PARAM_TYPES: A +// PARAM_TYPES: B +// PARAM_DESCRIPTOR: public final class A defined in root package in file qualifiedThisAsArgument.kt +// PARAM_DESCRIPTOR: public final fun B.foo(): kotlin.Int defined in A +// SIBLING: +class A { + val a = 1 + + fun B.foo() = i(this@A, this@B) +} + +private fun i(a1: A, b1: B): Int = a1.a + b1.b + +class B { + val b = 1 +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/it/innerIt.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/it/innerIt.kt.fir.after new file mode 100644 index 000000000000..8b8db24beb07 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/it/innerIt.kt.fir.after @@ -0,0 +1,12 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter it: kotlin.Int defined in foo.``.`` +fun Array.check(f: (T) -> Boolean): Boolean = false + +// SIBLING: +fun foo(t: Array>) { + if (t.check { it.check{ i(it) > 1 } }) { + println("OK") + } +} + +private fun i(i: Int): Int = i + 1 diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/it/lambdaWithIt.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/it/lambdaWithIt.kt.fir.after new file mode 100644 index 000000000000..ed9ec82e50f7 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/it/lambdaWithIt.kt.fir.after @@ -0,0 +1,12 @@ +// PARAM_TYPES: kotlin.Array +// PARAM_DESCRIPTOR: value-parameter t: kotlin.Array defined in foo +fun Array.check(f: (T) -> Boolean): Boolean = false + +// SIBLING: +fun foo(t: Array) { + if (bool(t)) { + println("OK") + } +} + +private fun bool(t: Array): Boolean = t.check { it + 1 > 1 } diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/it/outerIt.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/it/outerIt.kt.fir.after new file mode 100644 index 000000000000..b5d1bf388839 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/it/outerIt.kt.fir.after @@ -0,0 +1,12 @@ +// PARAM_TYPES: kotlin.Array +// PARAM_DESCRIPTOR: value-parameter it: kotlin.Array defined in foo.`` +fun Array.check(f: (T) -> Boolean): Boolean = false + +// SIBLING: +fun foo(t: Array>) { + if (t.check { bool(it) }) { + println("OK") + } +} + +private fun bool(ints: Array): Boolean = ints.check { it + 1 > 1 } diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/it/simpleIt.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/it/simpleIt.kt.fir.after new file mode 100644 index 000000000000..96c3580d71bc --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/it/simpleIt.kt.fir.after @@ -0,0 +1,12 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter it: kotlin.Int defined in foo.`` +fun Array.check(f: (T) -> Boolean): Boolean = false + +// SIBLING: +fun foo(t: Array) { + if (t.check { i(it) > 1 }) { + println("OK") + } +} + +private fun i(i: Int): Int = i + 1 diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/addPrefixToBackticks.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/addPrefixToBackticks.kt.fir.after new file mode 100644 index 000000000000..c0e49482bcaa --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/addPrefixToBackticks.kt.fir.after @@ -0,0 +1,16 @@ +// PARAM_TYPES: X +// PARAM_TYPES: Y +// PARAM_DESCRIPTOR: public final fun X.test(): kotlin.Unit defined in Y +// PARAM_DESCRIPTOR: public final class Y defined in root package in file addPrefixToBackticks.kt +class X(val x: Int) + +// SIBLING: +class Y(val y: Int) { + fun X.test() { + __dummyTestFun__(this@X, this@Y) + } +} + +private fun __dummyTestFun__(x1: X, y1: Y) { + x1.`x` plus y1.y +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/classObject.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/classObject.kt.fir.after new file mode 100644 index 000000000000..0d5ae8c4cd77 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/classObject.kt.fir.after @@ -0,0 +1,19 @@ +// SIBLING: +class MyClass { + fun test(): Int { + return i() + } + + companion object { + val coProp = 1 + + fun coFun() { + + } + } +} + +private fun i(): Int { + MyClass.Companion.coFun() + return MyClass.Companion.coProp + 10 +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/extensionLambdaAsParameter.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/extensionLambdaAsParameter.kt.fir.after new file mode 100644 index 000000000000..d088a920ff34 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/extensionLambdaAsParameter.kt.fir.after @@ -0,0 +1,12 @@ +// PARAM_TYPES: kotlin.String.() -> T +// PARAM_DESCRIPTOR: value-parameter f: kotlin.String.() -> T defined in test +fun test(f: String.() -> T): T { + return __dummyTestFun__(f) +} + +private fun __dummyTestFun__(f: String.() -> T): T { + while (true) { + val answer = "Hey!".f() + return answer + } +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/innerClassObject.kt.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/innerClassObject.kt.after index 2eb5fbed2470..446704cc50f6 100644 --- a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/innerClassObject.kt.after +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/innerClassObject.kt.after @@ -19,5 +19,3 @@ private fun i(): Int { A.B.coFun() return A.B.coProp + 10 } - - diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/innerClassObject.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/innerClassObject.kt.fir.after new file mode 100644 index 000000000000..bbf2125b708c --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/innerClassObject.kt.fir.after @@ -0,0 +1,23 @@ +// SIBLING: +class A { + class B { + fun test(): Int { + return i() + } + + companion object { + val coProp = 1 + + fun coFun() { + + } + } + } +} + +private fun i(): Int { + A.B.Companion.coFun() + return A.B.Companion.coProp + 10 +} + + diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/kt5001.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/kt5001.kt.fir.after new file mode 100644 index 000000000000..bb80c962e8b8 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/kt5001.kt.fir.after @@ -0,0 +1,16 @@ +fun Int.bar(n: Int): Boolean { + return true +} + +// SIBLING: +fun main(args: Array) { + val t = if (args.size > 0) { + bool() + } + else false +} + +private fun bool(): Boolean { + val al = 0 + return al.bar(1) +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/multipleOccurrences.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/multipleOccurrences.kt.fir.after new file mode 100644 index 000000000000..e46517a63207 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/multipleOccurrences.kt.fir.after @@ -0,0 +1,12 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: value-parameter b: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: value-parameter c: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int, b: Int, c: Int): Int { + return i(a, b, c) +} + +private fun i(a: Int, b: Int, c: Int): Int = (a + b * a - c) + b * c diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/reducedParameterList.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/reducedParameterList.kt.fir.after new file mode 100644 index 000000000000..c99f9fa38a14 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/reducedParameterList.kt.fir.after @@ -0,0 +1,12 @@ +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// SIBLING: +fun foo(a: Int, b: Int): Int { + if (bool(a)) return a + b + return 0 +} + +private fun bool(a: Int): Boolean { + if (a > 0) return true + return false +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/usagesInCallArgs.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/usagesInCallArgs.kt.fir.after new file mode 100644 index 000000000000..213f4c028eb3 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/usagesInCallArgs.kt.fir.after @@ -0,0 +1,22 @@ +// PARAM_TYPES: A +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_TYPES: kotlin.Int +// PARAM_DESCRIPTOR: value-parameter a: A defined in foo +// PARAM_DESCRIPTOR: val t: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: value-parameter x: kotlin.Int defined in foo +// PARAM_DESCRIPTOR: val u: kotlin.Int defined in foo +// SIBLING: +public class A() { + fun bar(a: Int, b: Int): Int { + return a + b + } +} + +private fun i(a: A, t: Int, x: Int, u: Int): Int = a.bar(t - x, u + x) + +fun foo(a: A, x: Int): Int { + val t = 10 + val u = 20 + return i(a, t, x, u) +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/nonDenotableTypes/anonymousObject.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/nonDenotableTypes/anonymousObject.kt.fir.after new file mode 100644 index 000000000000..67461b7eff12 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/nonDenotableTypes/anonymousObject.kt.fir.after @@ -0,0 +1,7 @@ +interface T + +fun foo(): T { + return t() +} + +private fun t(): T = object : T() {} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/nonDenotableTypes/anonymousObjectWithCall.kt.fir.conflicts b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/nonDenotableTypes/anonymousObjectWithCall.kt.fir.conflicts new file mode 100644 index 000000000000..078a167bc719 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/nonDenotableTypes/anonymousObjectWithCall.kt.fir.conflicts @@ -0,0 +1 @@ +Property <no name provided>.b will no longer be accessible after extraction diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClass.kt.fir.conflicts b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClass.kt.fir.conflicts new file mode 100644 index 000000000000..791f854478ce --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClass.kt.fir.conflicts @@ -0,0 +1 @@ +Parameter b will no longer be accessible after extraction diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclass.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclass.kt.fir.after new file mode 100644 index 000000000000..e5cddd1fbd7a --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclass.kt.fir.after @@ -0,0 +1,12 @@ +interface T + +fun foo(): T { + class A: T + + fun t(): T = A() + + // SIBLING: + fun bar(): T { + return t() + } +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameter.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameter.kt.fir.after new file mode 100644 index 000000000000..695ca89635e3 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameter.kt.fir.after @@ -0,0 +1,15 @@ +// PARAM_DESCRIPTOR: val a: A defined in foo.bar +// PARAM_TYPES: T +interface T + +// SIBLING: +fun foo(): T { + class A: T + + fun bar(): T { + val a = A() + return t(a) + } +} + +private fun t(a: T): T = a diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameterInLocalFun.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameterInLocalFun.kt.fir.after new file mode 100644 index 000000000000..8b12f42b0cf5 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameterInLocalFun.kt.fir.after @@ -0,0 +1,15 @@ +// PARAM_DESCRIPTOR: val a: A defined in foo.bar +// PARAM_TYPES: A, T +interface T + +fun foo(): T { + class A: T + + fun t(a: T): T = a + + // SIBLING: + fun bar(): T { + val a = A() + return t(a) + } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/extractFalse.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/extractFalse.kt.fir.after new file mode 100644 index 000000000000..9b1456586320 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/extractFalse.kt.fir.after @@ -0,0 +1,9 @@ +// SUGGESTED_NAMES: b, getX +fun foo(param: Int): String { + val x = "xyfalsez" + val y = "xyFalsez" + val z = false + return "ab${string()}def" +} + +private fun string(): String = "false" diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/extractIntegerLiteral.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/extractIntegerLiteral.kt.fir.after new file mode 100644 index 000000000000..7b253bcf640e --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/extractIntegerLiteral.kt.fir.after @@ -0,0 +1,10 @@ +// SUGGESTED_NAMES: i, getX +fun foo(param: Int): String { + val x = "a1234_" + val y = "-4123a" + val z = "+1243a" + val u = 123 + return "ab${string()}def" +} + +private fun string(): String = "123" diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/extractTrue.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/extractTrue.kt.fir.after new file mode 100644 index 000000000000..1d08a7411dfc --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/extractTrue.kt.fir.after @@ -0,0 +1,9 @@ +// SUGGESTED_NAMES: b, getX +fun foo(param: Int): String { + val x = "atrue123" + val x = "aTRUE123" + val z = true + return "ab${string()}def" +} + +private fun string(): String = "true" diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/fullContent.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/fullContent.kt.fir.after new file mode 100644 index 000000000000..5c16ddbfdbdf --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/fullContent.kt.fir.after @@ -0,0 +1,10 @@ +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// PARAM_TYPES: kotlin.Int, kotlin.Number, kotlin.Comparable, java.io.Serializable, kotlin.Any +fun foo(a: Int): String { + val x = "abc$a" + val y = "abc${a}" + val z = "abc{$a}def" + return string(a) + "def" +} + +private fun string(a: Int): String = "abc$a" diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/fullEntryWithBlockExpr.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/fullEntryWithBlockExpr.kt.fir.after new file mode 100644 index 000000000000..4ca38c35287f --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/fullEntryWithBlockExpr.kt.fir.after @@ -0,0 +1,10 @@ +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// PARAM_TYPES: kotlin.Int +fun foo(a: Int): String { + val x = "-${a + 1}" + val y = "x${a + 1}y" + val z = "x${a - 1}y" + return "abc${i(a)}def" +} + +private fun i(a: Int): Int = a + 1 diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/fullEntryWithSimpleName.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/fullEntryWithSimpleName.kt.fir.after new file mode 100644 index 000000000000..fa977e17caaa --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/fullEntryWithSimpleName.kt.fir.after @@ -0,0 +1,10 @@ +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// PARAM_TYPES: kotlin.Int, kotlin.Number, kotlin.Comparable, java.io.Serializable, kotlin.Any +fun foo(a: Int): String { + val x = "-$a" + val y = "x${a}y" + val z = "x$ay" + return "abc${i(a)}def" +} + +private fun i(a: Int): Int = a diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/multipleEntriesWithPrefix.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/multipleEntriesWithPrefix.kt.fir.after new file mode 100644 index 000000000000..331cec3b5253 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/multipleEntriesWithPrefix.kt.fir.after @@ -0,0 +1,10 @@ +// SUGGESTED_NAMES: s, getX +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// PARAM_TYPES: kotlin.Int +fun foo(a: Int): String { + val x = "+cd$a:${a + 1}efg" + val y = "+cd$a${a + 1}efg" + return "ab${string(a)}" +} + +private fun string(a: Int): String = "cd$a:${a + 1}ef" diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/multipleEntriesWithSubstring.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/multipleEntriesWithSubstring.kt.fir.after new file mode 100644 index 000000000000..eb09d05eb18f --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/multipleEntriesWithSubstring.kt.fir.after @@ -0,0 +1,10 @@ +// SUGGESTED_NAMES: s, getX +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// PARAM_TYPES: kotlin.Int +fun foo(a: Int): String { + val x = "_c$a:${a + 1}d_" + val y = "_$a:${a + 1}d_" + return "ab${string(a)}ef" +} + +private fun string(a: Int): String = "c$a:${a + 1}d" diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/multipleEntriesWithSuffix.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/multipleEntriesWithSuffix.kt.fir.after new file mode 100644 index 000000000000..017c2a38a07f --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/multipleEntriesWithSuffix.kt.fir.after @@ -0,0 +1,10 @@ +// SUGGESTED_NAMES: s, getX +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// PARAM_TYPES: kotlin.Int +fun foo(a: Int): String { + val x = "_ab$a:${a + 1}cd__" + val y = "_a$a:${a + 1}cd__" + return "${string(a)}ef" +} + +private fun string(a: Int): String = "ab$a:${a + 1}cd" diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/rawTemplateWithSubstring.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/rawTemplateWithSubstring.kt.fir.after new file mode 100644 index 000000000000..bc1884e007f3 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/rawTemplateWithSubstring.kt.fir.after @@ -0,0 +1,14 @@ +// SUGGESTED_NAMES: s, getX +// PARAM_DESCRIPTOR: value-parameter a: kotlin.Int defined in foo +// PARAM_TYPES: kotlin.Int +fun foo(a: Int): String { + val x = """_c$a + :${a + 1}d_""" + val y = "_$a:${a + 1}d_" + val z = """_c$a:${a + 1}d_""" + val u = "_c$a\n:${a + 1}d_" + return """ab${string(a)}ef""" +} + +private fun string(a: Int): String = """c$a + :${a + 1}d""" diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/singleEntryPrefix.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/singleEntryPrefix.kt.fir.after new file mode 100644 index 000000000000..cd4d738a52cb --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/singleEntryPrefix.kt.fir.after @@ -0,0 +1,9 @@ +// SUGGESTED_NAMES: s, getX +fun foo(a: Int): String { + val x = "xabc$a" + val y = "${a}abcx" + val z = "xacb$a" + return "${string()}def" +} + +private fun string(): String = "abc" diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/singleEntrySubstring.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/singleEntrySubstring.kt.fir.after new file mode 100644 index 000000000000..84c7e7000058 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/singleEntrySubstring.kt.fir.after @@ -0,0 +1,9 @@ +// SUGGESTED_NAMES: s, getX +fun foo(a: Int): String { + val x = "xcd$a" + val y = "${a}cdx" + val z = "xcf$a" + return "ab${string()}ef" +} + +private fun string(): String = "cd" diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/singleEntrySuffix.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/singleEntrySuffix.kt.fir.after new file mode 100644 index 000000000000..a5982fad77b9 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/stringTemplates/singleEntrySuffix.kt.fir.after @@ -0,0 +1,9 @@ +// SUGGESTED_NAMES: s, getX +fun foo(a: Int): String { + val x = "xdef$a" + val y = "${a}defx" + val z = "xddf$a" + return "abc${string()}" +} + +private fun string(): String = "def" diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/simpleTypeParameter.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/simpleTypeParameter.kt.fir.after new file mode 100644 index 000000000000..d60146521df3 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/simpleTypeParameter.kt.fir.after @@ -0,0 +1,13 @@ +// SUGGESTED_NAMES: pair, intVPair, vPair +// PARAM_TYPES: V +// PARAM_DESCRIPTOR: value-parameter v: V defined in foo +open class Data(val x: Int) + +class Pair(val a: A, val b: B) + +// SIBLING: +fun foo(v: V): Pair { + return pair(v) +} + +private fun pair(v: V): Pair = Pair(v.x + 10, v) diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/simpleTypeParameterWithConstraint.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/simpleTypeParameterWithConstraint.kt.fir.after new file mode 100644 index 000000000000..bbfc0d4cef5c --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/simpleTypeParameterWithConstraint.kt.fir.after @@ -0,0 +1,14 @@ +// SUGGESTED_NAMES: pair, intVPair, vPair +// PARAM_TYPES: V +// PARAM_DESCRIPTOR: value-parameter v: V defined in foo +open class Data(val x: Int) +interface DataEx + +class Pair(val a: A, val b: B) + +// SIBLING: +fun foo(v: V): Pair where V: DataEx { + return pair(v) +} + +private fun pair(v: V): Pair where V : DataEx = Pair(v.x + 10, v) diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParamInArgument.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParamInArgument.kt.fir.after new file mode 100644 index 000000000000..08f64732251b --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParamInArgument.kt.fir.after @@ -0,0 +1,12 @@ +// PARAM_TYPES: Data +// PARAM_DESCRIPTOR: value-parameter d: Data defined in A.foo +class Data(val t: Int) + +// SIBLING: +class A { + fun foo(d: Data): Int { + return i(d) + } +} + +private fun i(d: Data): Int = d.t + 1 diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParameterNotResolvableInTargetScope.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParameterNotResolvableInTargetScope.kt.fir.after new file mode 100644 index 000000000000..7f53ec0e3e9b --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParameterNotResolvableInTargetScope.kt.fir.after @@ -0,0 +1,18 @@ +// WITH_STDLIB +// PARAM_TYPES: Foo +// PARAM_TYPES: kotlin.String +// PARAM_DESCRIPTOR: internal final class Foo defined in root package in file typeParameterNotResolvableInTargetScope.kt +// PARAM_DESCRIPTOR: value-parameter l: kotlin.String defined in Foo.test + +import java.util.* + +// SIBLING: +internal class Foo { + val map = HashMap() + + fun test(l: String): T { + return __dummyTestFun__(l) + } +} + +private fun Foo.__dummyTestFun__(l: String): T? = map[l] diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParameterRef.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParameterRef.kt.fir.after new file mode 100644 index 000000000000..c7fdd8dc3940 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParameterRef.kt.fir.after @@ -0,0 +1,8 @@ +// SUGGESTED_NAMES: i, getA +fun foo() = 1 + +fun test() { + val a = i() +} + +private fun i(): Int = foo() diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParameterRefCanOmit.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParameterRefCanOmit.kt.fir.after new file mode 100644 index 000000000000..5c0395e3b698 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParameterRefCanOmit.kt.fir.after @@ -0,0 +1,10 @@ +// SUGGESTED_NAMES: i, getA +// PARAM_DESCRIPTOR: value-parameter t: T defined in test +// PARAM_TYPES: T +fun foo(u: U) = 1 + +fun test(t: T) { + val a = i(t) +} + +private fun i(t: T): Int = foo(t) diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParameterResolvableInTargetScope.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParameterResolvableInTargetScope.kt.fir.after new file mode 100644 index 000000000000..21c427a14485 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParameterResolvableInTargetScope.kt.fir.after @@ -0,0 +1,15 @@ +// WITH_STDLIB +// PARAM_TYPES: kotlin.String +// PARAM_DESCRIPTOR: value-parameter l: kotlin.String defined in Foo.test + +import java.util.* + +class Foo { + val map = HashMap() + + fun test(l: String): T { + return __dummyTestFun__(l) + } + + private fun __dummyTestFun__(l: String): T? = map[l] +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParametersAndConstraintsCombined1.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParametersAndConstraintsCombined1.kt.fir.after new file mode 100644 index 000000000000..3da73a488583 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParametersAndConstraintsCombined1.kt.fir.after @@ -0,0 +1,24 @@ +// PARAM_TYPES: A +// PARAM_TYPES: A.B +// PARAM_TYPES: V, Data +// PARAM_DESCRIPTOR: public final class A where T : DataEx defined in root package in file typeParametersAndConstraintsCombined1.kt +// PARAM_DESCRIPTOR: public final inner class B where U : DataExEx defined in A +// PARAM_DESCRIPTOR: value-parameter v: V defined in A.B.foo +open class Data(val x: Int) +interface DataEx +interface DataExEx + +// SIBLING: +class A(val t: T) where T: DataEx { + inner class B(val u: U) where U: DataExEx { + fun foo(v: V): Int where V: DataEx { + return i(this@A, this@B, v) + } + } +} + +private fun i( + a: A, + b: A.B, + v: V +): Int where T : DataEx, U : DataExEx, V : DataEx = a.t.x + b.u.x + v.x diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParametersAndConstraintsCombined2.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParametersAndConstraintsCombined2.kt.fir.after new file mode 100644 index 000000000000..b1d45d6967b5 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParametersAndConstraintsCombined2.kt.fir.after @@ -0,0 +1,18 @@ +// PARAM_TYPES: A.B +// PARAM_TYPES: V, Data +// PARAM_DESCRIPTOR: public final inner class B where U : DataExEx defined in A +// PARAM_DESCRIPTOR: value-parameter v: V defined in A.B.foo +open class Data(val x: Int) +interface DataEx +interface DataExEx + +class A(val t: T) where T: DataEx { + // SIBLING: + inner class B(val u: U) where U: DataExEx { + fun foo(v: V): Int where V: DataEx { + return i(v) + } + } + + private fun A.B.i(v: V): Int where U : DataExEx, V : DataEx = t.x + u.x + v.x +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParametersAndConstraintsCombined3.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParametersAndConstraintsCombined3.kt.fir.after new file mode 100644 index 000000000000..3808490f4534 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParametersAndConstraintsCombined3.kt.fir.after @@ -0,0 +1,16 @@ +// PARAM_TYPES: V, Data +// PARAM_DESCRIPTOR: value-parameter v: V defined in A.B.foo +open class Data(val x: Int) +interface DataEx +interface DataExEx + +class A(val t: T) where T: DataEx { + inner class B(val u: U) where U: DataExEx { + // SIBLING: + fun foo(v: V): Int where V: DataEx { + return i(v) + } + + private fun i(v: V): Int where V : DataEx = t.x + u.x + v.x + } +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParametersCombined1.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParametersCombined1.kt.fir.after new file mode 100644 index 000000000000..6ae6fbe40b54 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParametersCombined1.kt.fir.after @@ -0,0 +1,18 @@ +// PARAM_TYPES: A +// PARAM_TYPES: A.B +// PARAM_TYPES: V, Data +// PARAM_DESCRIPTOR: public final class A defined in root package in file typeParametersCombined1.kt +// PARAM_DESCRIPTOR: public final inner class B defined in A +// PARAM_DESCRIPTOR: value-parameter v: V defined in A.B.foo +open class Data(val x: Int) + +// SIBLING: +class A(val t: T) { + inner class B(val u: U) { + fun foo(v: V): Int { + return i(this@A, this@B, v) + } + } +} + +private fun i(a: A, b: A.B, v: V): Int = a.t.x + b.u.x + v.x diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParametersCombined2.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParametersCombined2.kt.fir.after new file mode 100644 index 000000000000..c8606d39d923 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParametersCombined2.kt.fir.after @@ -0,0 +1,16 @@ +// PARAM_TYPES: A.B +// PARAM_TYPES: V, Data +// PARAM_DESCRIPTOR: public final inner class B defined in A +// PARAM_DESCRIPTOR: value-parameter v: V defined in A.B.foo +open class Data(val x: Int) + +class A(val t: T) { + // SIBLING: + inner class B(val u: U) { + fun foo(v: V): Int { + return i(v) + } + } + + private fun A.B.i(v: V): Int = t.x + u.x + v.x +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParametersCombined3.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParametersCombined3.kt.fir.after new file mode 100644 index 000000000000..079de5507f09 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParametersCombined3.kt.fir.after @@ -0,0 +1,14 @@ +// PARAM_TYPES: V, Data +// PARAM_DESCRIPTOR: value-parameter v: V defined in A.B.foo +open class Data(val x: Int) + +class A(val t: T) { + inner class B(val u: U) { + // SIBLING: + fun foo(v: V): Int { + return i(v) + } + + private fun i(v: V): Int = t.x + u.x + v.x + } +} diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParametersCombinedAndThis.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParametersCombinedAndThis.kt.fir.after new file mode 100644 index 000000000000..a43db3bf97cf --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/typeParameters/typeParametersCombinedAndThis.kt.fir.after @@ -0,0 +1,15 @@ +// PARAM_TYPES: A +// PARAM_TYPES: V, Data +// PARAM_DESCRIPTOR: public final class A where T : DataEx defined in root package in file typeParametersCombinedAndThis.kt +// PARAM_DESCRIPTOR: value-parameter v: V defined in A.foo +open class Data(val x: Int) +interface DataEx + +// SIBLING: +class A(val t: T) where T: DataEx { + fun foo(v: V): Int { + return i(v) + } +} + +private fun A.i(v: V): Int where T : DataEx = t.x + v.x diff --git a/plugins/kotlin/plugin/common/resources/META-INF/refactorings.xml b/plugins/kotlin/plugin/common/resources/META-INF/refactorings.xml index 32b8f3cef14b..526bf10f599b 100644 --- a/plugins/kotlin/plugin/common/resources/META-INF/refactorings.xml +++ b/plugins/kotlin/plugin/common/resources/META-INF/refactorings.xml @@ -4,9 +4,14 @@ qualifiedName="org.jetbrains.kotlin.renameHandler" interface="com.intellij.refactoring.rename.RenameHandler" dynamic="true"/> + + diff --git a/plugins/kotlin/plugin/k1/resources/META-INF/refactorings-fe10.xml b/plugins/kotlin/plugin/k1/resources/META-INF/refactorings-fe10.xml index b13efbdf73fb..8f9f6fc2e6df 100644 --- a/plugins/kotlin/plugin/k1/resources/META-INF/refactorings-fe10.xml +++ b/plugins/kotlin/plugin/k1/resources/META-INF/refactorings-fe10.xml @@ -8,10 +8,6 @@ qualifiedName="org.jetbrains.kotlin.foreignUsagesRenameProcessor" interface="org.jetbrains.kotlin.idea.refactoring.rename.ForeignUsagesRenameProcessor" dynamic="true"/> - diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/AbstractExtractKotlinFunctionHandler.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/AbstractExtractKotlinFunctionHandler.kt index 04a484370758..f0dce2cd8fe1 100644 --- a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/AbstractExtractKotlinFunctionHandler.kt +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/AbstractExtractKotlinFunctionHandler.kt @@ -15,6 +15,7 @@ import org.jetbrains.kotlin.idea.refactoring.introduce.validateExpressionElement import org.jetbrains.kotlin.idea.util.ElementKind import org.jetbrains.kotlin.psi.KtFile + abstract class AbstractExtractKotlinFunctionHandler(private val allContainersEnabled: Boolean = false) : RefactoringActionHandler { abstract fun doInvoke( editor: Editor, @@ -25,14 +26,14 @@ abstract class AbstractExtractKotlinFunctionHandler(private val allContainersEna fun selectElements(editor: Editor, file: KtFile, continuation: (elements: List, targetSibling: PsiElement) -> Unit) { selectElementsWithTargetSibling( - EXTRACT_FUNCTION, - editor, - file, - KotlinBundle.message("title.select.target.code.block"), - listOf(ElementKind.EXPRESSION), - ::validateExpressionElements, - { elements, parent -> parent.getExtractionContainers(elements.size == 1, allContainersEnabled) }, - continuation + EXTRACT_FUNCTION, + editor, + file, + KotlinBundle.message("title.select.target.code.block"), + listOf(ElementKind.EXPRESSION), + ::validateExpressionElements, + { elements, parent -> parent.getExtractionContainers(elements.size == 1, allContainersEnabled) }, + continuation ) } diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/AbstractInplaceExtractionHelper.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/AbstractInplaceExtractionHelper.kt new file mode 100644 index 000000000000..68309a9f6765 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/AbstractInplaceExtractionHelper.kt @@ -0,0 +1,147 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction + +import com.intellij.codeInsight.template.impl.TemplateManagerImpl +import com.intellij.injected.editor.EditorWindow +import com.intellij.openapi.command.WriteCommandAction +import com.intellij.openapi.command.impl.FinishMarkAction +import com.intellij.openapi.command.impl.StartMarkAction +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.keymap.KeymapUtil +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Disposer +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiElement +import com.intellij.psi.SmartPointerManager +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.refactoring.RefactoringBundle +import com.intellij.refactoring.extractMethod.newImpl.inplace.EditorState +import com.intellij.refactoring.extractMethod.newImpl.inplace.ExtractMethodTemplateBuilder +import com.intellij.refactoring.extractMethod.newImpl.inplace.InplaceExtractUtils +import com.intellij.refactoring.extractMethod.newImpl.inplace.TemplateField +import org.jetbrains.annotations.Nls +import org.jetbrains.kotlin.idea.base.psi.unifier.KotlinPsiRange +import org.jetbrains.kotlin.idea.refactoring.introduce.extractableSubstringInfo +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.IExtractableCodeDescriptor +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.IExtractableCodeDescriptorWithConflicts +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.IExtractionResult +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtFile + +interface AbstractInplaceExtractionHelper, + DescriptorWithConflicts : IExtractableCodeDescriptorWithConflicts> { + + fun doRefactor(descriptor: IExtractableCodeDescriptor, onFinish: (Result) -> Unit = {}) + + fun createRestartHandler(): AbstractExtractKotlinFunctionHandler + fun extractDuplicates( + duplicateReplacers: Map Unit>, + project: Project, + editor: Editor + ) + + @Nls + fun getIdentifierError(file: KtFile, variableRange: TextRange): String? + + + fun configureAndRun( + project: Project, + editor: Editor, + descriptorWithConflicts: DescriptorWithConflicts, + onFinish: (Result) -> Unit + ) { + val activeTemplateState = TemplateManagerImpl.getTemplateState(editor) + if (activeTemplateState != null) { + activeTemplateState.gotoEnd(true) + createRestartHandler() + .invoke(project, editor, descriptorWithConflicts.descriptor.extractionData.originalFile, null) + } + val descriptor = descriptorWithConflicts.descriptor + val elements = descriptor.extractionData.originalElements + val file = descriptor.extractionData.originalFile + val callTextRange = TextRange(rangeOf(elements.first()).startOffset, rangeOf(elements.last()).endOffset) + + val commonParent = descriptor.extractionData.commonParent + val container = commonParent.takeIf { commonParent != elements.firstOrNull() } ?: commonParent.parent + val callRangeProvider: () -> TextRange? = createSmartRangeProvider(container, callTextRange) + val editorState = EditorState(project, editor) + val disposable = Disposer.newDisposable() + WriteCommandAction.writeCommandAction(project).run { + val startMarkAction = StartMarkAction.start(editor, project, EXTRACT_FUNCTION) + Disposer.register(disposable) { FinishMarkAction.finish(project, editor, startMarkAction) } + } + fun afterFinish(extraction: Result) { + // Templates do not work well in injected editors, see InlayModelWindow + if (editor is EditorWindow) { + Disposer.dispose(disposable) + return + } + val callRange: TextRange = callRangeProvider.invoke() ?: throw IllegalStateException() + val callIdentifier = findSingleCallExpression(file, callRange)?.calleeExpression ?: throw IllegalStateException() + val methodIdentifier = extraction.declaration.nameIdentifier ?: throw IllegalStateException() + val methodRange = extraction.declaration.textRange + val methodOffset = extraction.declaration.navigationElement.textRange.endOffset + val callOffset = callIdentifier.textRange.endOffset + val preview = InplaceExtractUtils.createPreview(editor, methodRange, methodOffset, callRange, callOffset) + Disposer.register(disposable, preview) + val templateField = TemplateField(callIdentifier.textRange, listOf(methodIdentifier.textRange)) + .withCompletionNames(descriptor.suggestedNames) + .withCompletionHint(getDialogAdvertisement()) + .withValidation { variableRange -> + val error = getIdentifierError(file, variableRange) + if (error != null) { + InplaceExtractUtils.showErrorHint(editor, variableRange.endOffset, error) + } + error == null + } + ExtractMethodTemplateBuilder(editor, EXTRACT_FUNCTION) + .enableRestartForHandler(createRestartHandler()::class.java) + .onBroken { + editorState.revert() + } + .onSuccess { + extractDuplicates(extraction.duplicateReplacers, file.project, editor) + } + .disposeWithTemplate(disposable) + .createTemplate(file, listOf(templateField)) + onFinish(extraction) + } + try { + doRefactor(descriptor, ::afterFinish) + } catch (e: Throwable) { + Disposer.dispose(disposable) + throw e + } + } + + @Nls + private fun getDialogAdvertisement(): String { + val shortcut = KeymapUtil.getPrimaryShortcut("ExtractFunction") ?: throw IllegalStateException("Action is not found") + return RefactoringBundle.message("inplace.refactoring.advertisement.text", KeymapUtil.getShortcutText(shortcut)) + } + + fun rangeOf(element: PsiElement): TextRange { + return (element as? KtExpression)?.extractableSubstringInfo?.contentRange ?: element.textRange + } + + fun createSmartRangeProvider(container: PsiElement, range: TextRange): () -> TextRange? { + val offsetFromStart = range.startOffset - container.textRange.startOffset + val offsetFromEnd = container.textRange.endOffset - range.endOffset + val pointer = SmartPointerManager.createPointer(container) + fun findRange(): TextRange? { + val containerRange = pointer.range ?: return null + return TextRange(containerRange.startOffset + offsetFromStart, containerRange.endOffset - offsetFromEnd) + } + return ::findRange + } + + + fun findSingleCallExpression(file: KtFile, range: TextRange?): KtCallExpression? { + if (range == null) return null + val container = PsiTreeUtil.findCommonParent(file.findElementAt(range.startOffset), file.findElementAt(range.endOffset)) + val callExpressions = PsiTreeUtil.findChildrenOfType(container, KtCallExpression::class.java) + return callExpressions.singleOrNull { it.textRange in range } + } +} \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/AbstractExtractionDataAnalyzer.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/AbstractExtractionDataAnalyzer.kt new file mode 100644 index 000000000000..db8cdf99f80b --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/AbstractExtractionDataAnalyzer.kt @@ -0,0 +1,200 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine + +import com.intellij.openapi.util.registry.Registry +import com.intellij.psi.util.PsiTreeUtil.getParentOfType +import com.intellij.util.containers.MultiMap +import org.jetbrains.kotlin.idea.base.codeInsight.KotlinNameSuggester +import org.jetbrains.kotlin.idea.base.codeInsight.KotlinNameSuggestionProvider +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.AnalysisResult.ErrorMessage +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.AnalysisResult.Status +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.ExpressionValue +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.Jump +import org.jetbrains.kotlin.lexer.KtModifierKeywordToken +import org.jetbrains.kotlin.load.java.JvmAbi +import org.jetbrains.kotlin.psi.KtAnonymousInitializer +import org.jetbrains.kotlin.psi.KtBlockExpression +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtDeclarationWithBody +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtNamedDeclaration +import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.psi.KtScript +import org.jetbrains.kotlin.psi.KtSimpleNameExpression +import org.jetbrains.kotlin.psi.KtTreeVisitorVoid +import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType +import org.jetbrains.kotlin.util.capitalizeDecapitalize.capitalizeAsciiOnly + +abstract class AbstractExtractionDataAnalyzer>(private val data: IExtractionData) { + abstract val typeDescriptor: TypeDescriptor + + abstract val nameSuggester: IExtractionNameSuggester + + abstract fun createOutputDescriptor(): OutputDescriptor + abstract fun getLocalDeclarationsWithNonLocalUsages(): List + abstract fun getModifiedVars(): Map> + abstract fun getVarDescriptorsAccessedAfterwards(): Set + abstract fun inferParametersInfo( + virtualBlock: KtBlockExpression, + modifiedVariables: Set + ): ParametersInfo + + abstract fun hasSyntaxErrors(): Boolean + + abstract fun createDescriptor( + suggestFunctionNames: List, + defaultVisibility: KtModifierKeywordToken?, + parameters: List

, + receiverParameter: P?, + typeParameters: List, + replacementMap: MultiMap>, + flow: ControlFlow, + returnType: KotlinType + ): IExtractableCodeDescriptor + + fun performAnalysis(): AnalysisResult { + if (data.originalElements.isEmpty()) return AnalysisResult( + null, + Status.CRITICAL_ERROR, + listOf(ErrorMessage.NO_EXPRESSION) + ) + + if (getParentOfType(data.commonParent, KtDeclarationWithBody::class.java, KtClassOrObject::class.java, KtScript::class.java) == null + && data.commonParent.getNonStrictParentOfType() == null + ) { + return AnalysisResult(null, Status.CRITICAL_ERROR, listOf(ErrorMessage.NO_CONTAINER)) + } + + if (hasSyntaxErrors()) return AnalysisResult(null, Status.CRITICAL_ERROR, listOf(ErrorMessage.SYNTAX_ERRORS)) + + val modifiedVarDescriptorsWithExpressions = getModifiedVars() + + val virtualBlock = data.createTemporaryCodeBlock() + val paramsInfo = inferParametersInfo( + virtualBlock, + modifiedVarDescriptorsWithExpressions.keys + ) + + if (paramsInfo.errorMessage != null) { + return AnalysisResult(null, Status.CRITICAL_ERROR, listOf(paramsInfo.errorMessage!!)) + } + + val nonLocallyUsedDeclarations = getLocalDeclarationsWithNonLocalUsages() + val (localVariablesToCopy, declarationsToReport) = nonLocallyUsedDeclarations.partition { it is KtProperty && it.isLocal } + if (declarationsToReport.isNotEmpty()) { + val localVarStr = declarationsToReport.map { typeDescriptor.renderForMessage(it)!! }.distinct().sorted() + return AnalysisResult( + null, + Status.CRITICAL_ERROR, + listOf(ErrorMessage.DECLARATIONS_ARE_USED_OUTSIDE.addAdditionalInfo(localVarStr)) + ) + } + + val messages = ArrayList() + + val modifiedVarDescriptorsForControlFlow = HashMap(modifiedVarDescriptorsWithExpressions) + modifiedVarDescriptorsForControlFlow.keys.retainAll(getVarDescriptorsAccessedAfterwards()) + + val outputDescriptor = createOutputDescriptor() + + val (controlFlow, controlFlowMessage) = + ControlFlowBuilder.analyzeControlFlow( + data, + outputDescriptor, + modifiedVarDescriptorsForControlFlow, + paramsInfo.parameters, + localVariablesToCopy.mapNotNull { it as? KtProperty }, + typeDescriptor + ) + controlFlowMessage?.let { messages.add(it) } + val returnType = controlFlow.outputValueBoxer.returnType + returnType.processTypeIfExtractable( + paramsInfo.typeParameters, + paramsInfo.nonDenotableTypes, + true, + typeDescriptor::typeArguments, + typeDescriptor::isResolvableInScope + ) + + if (paramsInfo.nonDenotableTypes.isNotEmpty()) { + val typeStr = paramsInfo.nonDenotableTypes.map { typeDescriptor.renderTypeWithoutApproximation(it) }.sorted() + return AnalysisResult( + null, + Status.CRITICAL_ERROR, + listOf(ErrorMessage.DENOTABLE_TYPES.addAdditionalInfo(typeStr)) + ) + } + + data.commonParent.getStrictParentOfType()?.let { enclosingDeclaration -> + data.checkDeclarationsMovingOutOfScope(enclosingDeclaration, controlFlow) { typeDescriptor.renderForMessage(it)!! } + ?.let { messages.add(it) } + } + + controlFlow.jumpOutputValue?.elementToInsertAfterCall?.accept( + object : KtTreeVisitorVoid() { + override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) { + paramsInfo.originalRefToParameter[expression].firstOrNull()?.let { it.refCount-- } + } + } + ) + + val adjustedParameters = paramsInfo.parameters.filterTo(LinkedHashSet

()) { it.refCount > 0 } + + val receiverCandidates = adjustedParameters.filterTo(hashSetOf()) { it.receiverCandidate } + val receiverParameter = if (receiverCandidates.size == 1 && !data.options.canWrapInWith) receiverCandidates.first() else null + receiverParameter?.let { adjustedParameters.remove(it) } + + var descriptor = createDescriptor( + suggestFunctionNames(returnType), + data.getDefaultVisibility(), + adjustedParameters.toList(), + receiverParameter, + paramsInfo.typeParameters.sortedBy { it.originalDeclaration.name!! }, + paramsInfo.replacementMap, + if (messages.isEmpty()) controlFlow else controlFlow.toDefault(), + returnType, + ) + return AnalysisResult( + descriptor, + if (messages.isEmpty()) Status.SUCCESS else Status.NON_CRITICAL_ERROR, + messages + ) + } + + private fun ControlFlow.toDefault(): ControlFlow = + copy(outputValues = outputValues.filterNot { it is Jump || it is ExpressionValue }) + + private fun suggestFunctionNames(returnType: KotlinType): List { + val functionNames = LinkedHashSet() + + val validatorTarget = when { + data.options.extractAsProperty -> KotlinNameSuggestionProvider.ValidatorTarget.VARIABLE + else -> KotlinNameSuggestionProvider.ValidatorTarget.FUNCTION + } + val validator = nameSuggester.createNameValidator( + data.targetSibling.parent as KtElement, + if (data.targetSibling is KtAnonymousInitializer) data.targetSibling.parent else data.targetSibling, + validatorTarget + ) + + functionNames.addAll( + nameSuggester.suggestNamesByType( + returnType, + data.commonParent, + validator + ) + ) + + data.expressions.singleOrNull()?.let { expr -> + val property = expr.getStrictParentOfType() + if (property?.initializer == expr) { + property.name?.let { functionNames.add(KotlinNameSuggester.suggestNameByName(JvmAbi.getterName(it), validator)) } + } + } + + return functionNames.toList().takeIf { it.isNotEmpty() } ?: listOf(Registry.stringValue("kotlin.extract.function.default.name")) + } +} diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/AnalysisResult.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/AnalysisResult.kt new file mode 100644 index 000000000000..77edb028dc76 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/AnalysisResult.kt @@ -0,0 +1,71 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine + +import com.intellij.openapi.util.text.StringUtil +import org.jetbrains.annotations.Nls +import org.jetbrains.kotlin.idea.base.resources.KotlinBundle + +/** + * Represents the [IExtractableCodeDescriptor] and if the extraction fails, + * then status and error messages provide exact information about the failure + */ +class AnalysisResult( + val descriptor: IExtractableCodeDescriptor?, + val status: Status, + val messages: List +) { + enum class Status { + SUCCESS, + NON_CRITICAL_ERROR, + CRITICAL_ERROR + } + + enum class ErrorMessage { + NO_EXPRESSION, + NO_CONTAINER, + SYNTAX_ERRORS, + SUPER_CALL, + DENOTABLE_TYPES, + ERROR_TYPES, + MULTIPLE_OUTPUT, + OUTPUT_AND_EXIT_POINT, + MULTIPLE_EXIT_POINTS, + DECLARATIONS_ARE_USED_OUTSIDE, + DECLARATIONS_OUT_OF_SCOPE; + + var additionalInfo: List? = null + + fun addAdditionalInfo(info: List): ErrorMessage { + additionalInfo = info + return this + } + + @Nls + fun renderMessage(): String { + val message = KotlinBundle.message( + when (this) { + NO_EXPRESSION -> "cannot.refactor.no.expression" + NO_CONTAINER -> "cannot.refactor.no.container" + SYNTAX_ERRORS -> "cannot.refactor.syntax.errors" + SUPER_CALL -> "cannot.extract.super.call" + DENOTABLE_TYPES -> "parameter.types.are.not.denotable" + ERROR_TYPES -> "error.types.in.generated.function" + MULTIPLE_OUTPUT -> "selected.code.fragment.has.multiple.output.values" + OUTPUT_AND_EXIT_POINT -> "selected.code.fragment.has.output.values.and.exit.points" + MULTIPLE_EXIT_POINTS -> "selected.code.fragment.has.multiple.exit.points" + DECLARATIONS_ARE_USED_OUTSIDE -> "declarations.are.used.outside.of.selected.code.fragment" + DECLARATIONS_OUT_OF_SCOPE -> "declarations.will.move.out.of.scope" + } + ) + + return additionalInfo?.let { + "$message\n\n${ + it.joinToString("\n") { msg -> + @Suppress("HardCodedStringLiteral") + StringUtil.htmlEmphasize(msg) + } + }" + } ?: message + } + } +} diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ControlFlow.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ControlFlow.kt new file mode 100644 index 000000000000..062ac2c74891 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ControlFlow.kt @@ -0,0 +1,19 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine + +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.ExpressionValue +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.Jump +import org.jetbrains.kotlin.psi.KtDeclaration + + +data class ControlFlow( + val outputValues: List>, + val boxerFactory: (List>) -> OutputValueBoxer, + val declarationsToCopy: List +) { + val outputValueBoxer = boxerFactory(outputValues) + + val defaultOutputValue: ExpressionValue? = outputValues.filterIsInstance>().firstOrNull() + + val jumpOutputValue: Jump? = outputValues.filterIsInstance>().firstOrNull() +} \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ControlFlowBuilder.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ControlFlowBuilder.kt new file mode 100644 index 000000000000..6eabe6b8d3d5 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ControlFlowBuilder.kt @@ -0,0 +1,159 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine + +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.AnalysisResult.ErrorMessage +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.ExpressionValue +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.Initializer +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.Jump +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.ParameterUpdate +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtProperty + +object ControlFlowBuilder { + fun analyzeControlFlow( + data: IExtractionData, + outputDescriptor: OutputDescriptor, + modifiedVarDescriptors: Map>, + parameters: Set>, + localVariablesToCopy: List, + typeDescriptor: TypeDescriptor + ): Pair, ErrorMessage?> { + return outputDescriptor.analyzeControlFlow( + data, + modifiedVarDescriptors, + typeDescriptor, + parameters, + localVariablesToCopy + ) + } + + private fun OutputDescriptor.analyzeControlFlow( + data: IExtractionData, + modifiedVarDescriptors: Map>, + typeDescriptor: TypeDescriptor, + parameters: Set>, + localVariablesToCopy: List + ): Pair, ErrorMessage?> { + with(typeDescriptor) { + val emptyControlFlow = + ControlFlow(emptyList(), { + object : OutputValueBoxer.AsTuple(it) { + override val returnType: KotlinType by lazy { + createTuple(outputValues) + } + } + }, localVariablesToCopy) + + val defaultReturnType = if (returnValueType.isMeaningful()) returnValueType else typeOfDefaultFlow + if (defaultReturnType.isError()) return emptyControlFlow to ErrorMessage.ERROR_TYPES + + val controlFlow = if (defaultReturnType.isMeaningful()) { + emptyControlFlow.copy( + outputValues = listOf( + ExpressionValue( + false, + listOfNotNull(defaultResultExpression), + defaultReturnType + ) + ) + ) + } else emptyControlFlow + + val outParameters = + parameters.filter { it.mirrorVarName != null && modifiedVarDescriptors[it.name] != null } + .sortedBy { it.nameForRef } + val outDeclarations = + localVariablesToCopy.filter { modifiedVarDescriptors[it.name!!] != null } + val modifiedValueCount = outParameters.size + outDeclarations.size + + val outputValues = mutableListOf>() + + val multipleExitsError = controlFlow to ErrorMessage.MULTIPLE_EXIT_POINTS + val outputAndExitsError = controlFlow to ErrorMessage.OUTPUT_AND_EXIT_POINT + + if (typeOfDefaultFlow.isMeaningful()) { + if (valuedReturnExpressions.isNotEmpty() || jumpExpressions.isNotEmpty()) return multipleExitsError + + outputValues.add(ExpressionValue(false, listOfNotNull(defaultResultExpression), typeOfDefaultFlow)) + } else if (valuedReturnExpressions.isNotEmpty()) { + if (jumpExpressions.isNotEmpty()) return multipleExitsError + + if (defaultResultExpression != null) { + if (modifiedValueCount != 0) return outputAndExitsError + if (valuedReturnExpressions.size != 1) { + return multipleExitsError + } + + val element = valuedReturnExpressions.first() + return controlFlow.copy( + outputValues = listOf( + Jump( + listOf(element), + element, + true, + booleanType, + unitType + ) + ) + ) to null + } + + if (!hasSingleTarget) return multipleExitsError + outputValues.add(ExpressionValue(true, valuedReturnExpressions, returnValueType)) + } + + outDeclarations.mapTo(outputValues) { + Initializer(it, returnType(it) ?: nullableAnyType) + } + outParameters.mapTo(outputValues) { ParameterUpdate(it, modifiedVarDescriptors[it.name]!!) } + + if (outputValues.isNotEmpty()) { + if (jumpExpressions.isNotEmpty()) return outputAndExitsError + + val boxerFactory: (List>) -> OutputValueBoxer = when { + outputValues.size > 3 -> { + if (!data.options.enableListBoxing) { + val outValuesStr = + (outParameters.map { typeDescriptor.renderForMessage(it) } + + outDeclarations.map { typeDescriptor.renderForMessage(it)!! }).sorted() + return controlFlow to ErrorMessage.MULTIPLE_OUTPUT.addAdditionalInfo(outValuesStr) + } + { values -> + object : OutputValueBoxer.AsList(values) { + override val returnType: KotlinType by lazy { + assert(outputValues.isNotEmpty()) + createListType(outputValues.map { it.valueType }) + } + } + } + } + + else -> controlFlow.boxerFactory + } + + return controlFlow.copy(outputValues = outputValues, boxerFactory = boxerFactory) to null + } + + if (jumpExpressions.isNotEmpty()) { + if (!hasSingleTarget) return multipleExitsError + + val conditional = !sameExitForDefaultAndJump && defaultResultExpression != null + + val elementToInsertAfterCall = if (sameExitForDefaultAndJump) null else jumpExpressions.first() + return controlFlow.copy( + outputValues = listOf( + Jump( + jumpExpressions, + elementToInsertAfterCall, + conditional, + booleanType, + unitType + ) + ) + ) to null + } + + return controlFlow to null + } + } +} \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/DuplicateInfo.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/DuplicateInfo.kt new file mode 100644 index 000000000000..0667478882b3 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/DuplicateInfo.kt @@ -0,0 +1,10 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine + +import org.jetbrains.kotlin.idea.base.psi.unifier.KotlinPsiRange + +class DuplicateInfo( + val range: KotlinPsiRange, + val controlFlow: ControlFlow, + val arguments: List +) \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractFunctionGenerator.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractFunctionGenerator.kt new file mode 100644 index 000000000000..d2013cd96f40 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractFunctionGenerator.kt @@ -0,0 +1,683 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine + +import com.intellij.openapi.util.Key +import com.intellij.psi.PsiElement +import com.intellij.psi.codeStyle.CodeStyleManager +import com.intellij.psi.search.LocalSearchScope +import com.intellij.psi.search.searches.ReferencesSearch +import com.intellij.refactoring.BaseRefactoringProcessor +import org.jetbrains.kotlin.analysis.api.KtAllowAnalysisFromWriteAction +import org.jetbrains.kotlin.analysis.api.KtAllowAnalysisOnEdt +import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.analysis.api.lifetime.allowAnalysisFromWriteAction +import org.jetbrains.kotlin.analysis.api.lifetime.allowAnalysisOnEdt +import org.jetbrains.kotlin.idea.base.codeInsight.ShortenReferencesFacility +import org.jetbrains.kotlin.idea.base.psi.isMultiLine +import org.jetbrains.kotlin.idea.base.psi.moveInsideParenthesesAndReplaceWith +import org.jetbrains.kotlin.idea.base.psi.replaced +import org.jetbrains.kotlin.idea.base.psi.shouldLambdaParameterBeNamed +import org.jetbrains.kotlin.idea.base.psi.unifier.KotlinPsiRange +import org.jetbrains.kotlin.idea.base.resources.KotlinBundle +import org.jetbrains.kotlin.idea.codeinsight.utils.NamedArgumentUtils +import org.jetbrains.kotlin.idea.codeinsights.impl.base.inspections.OperatorToFunctionConverter +import org.jetbrains.kotlin.idea.refactoring.appendElement +import org.jetbrains.kotlin.idea.refactoring.introduce.extractableSubstringInfo +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.ExpressionValue +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.Initializer +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.Jump +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.ParameterUpdate +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValueBoxer.AsTuple +import org.jetbrains.kotlin.idea.refactoring.introduce.getContainingLambdaOutsideParentheses +import org.jetbrains.kotlin.idea.refactoring.introduce.getGeneratedBody +import org.jetbrains.kotlin.idea.refactoring.introduce.mustBeParenthesizedInInitializerPosition +import org.jetbrains.kotlin.idea.refactoring.introduce.removeTemplateEntryBracesIfPossible +import org.jetbrains.kotlin.idea.refactoring.introduce.replaceWith +import org.jetbrains.kotlin.idea.refactoring.introduce.substringContextOrThis +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.KtBinaryExpression +import org.jetbrains.kotlin.psi.KtBlockExpression +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtCallableDeclaration +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtClassBody +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtEnumEntry +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtLambdaArgument +import org.jetbrains.kotlin.psi.KtNamedDeclaration +import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.psi.KtOperationExpression +import org.jetbrains.kotlin.psi.KtOperationReferenceExpression +import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.KtPsiFactory.CallableBuilder +import org.jetbrains.kotlin.psi.KtReturnExpression +import org.jetbrains.kotlin.psi.KtTypeArgumentList +import org.jetbrains.kotlin.psi.KtUnaryExpression +import org.jetbrains.kotlin.psi.NotNullablePsiCopyableUserDataProperty +import org.jetbrains.kotlin.psi.createDeclarationByPattern +import org.jetbrains.kotlin.psi.createExpressionByPattern +import org.jetbrains.kotlin.psi.psiUtil.PsiChildRange +import org.jetbrains.kotlin.psi.psiUtil.blockExpressionsOrSingle +import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType +import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType +import org.jetbrains.kotlin.psi.psiUtil.isExtensionDeclaration +import org.jetbrains.kotlin.psi.psiUtil.isLambdaOutsideParentheses +import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf +import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded +import org.jetbrains.kotlin.psi.psiUtil.siblings +import org.jetbrains.kotlin.psi.psiUtil.startOffset +import org.jetbrains.kotlin.renderer.render +import org.jetbrains.kotlin.resolve.calls.util.getCalleeExpressionIfAny +import org.jetbrains.kotlin.resolve.checkers.OptInNames +import org.jetbrains.kotlin.types.Variance +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull +import java.util.Collections + +private var KtExpression.isJumpElementToReplace: Boolean + by NotNullablePsiCopyableUserDataProperty(Key.create("IS_JUMP_ELEMENT_TO_REPLACE"), false) + +private var KtReturnExpression.isReturnForLabelRemoval: Boolean + by NotNullablePsiCopyableUserDataProperty(Key.create("IS_RETURN_FOR_LABEL_REMOVAL"), false) + +abstract class ExtractFunctionGenerator> { + abstract val nameGenerator: IExtractionNameSuggester + abstract fun createTypeDescriptor(data: IExtractionData): TypeDescriptor + + abstract fun IExtractionGeneratorConfiguration.collapseBody(blockExpression: KtBlockExpression) + + abstract fun resolveNameConflict(property: KtProperty) + + abstract fun checkTypeArgumentsAreRedundant(args: KtTypeArgumentList): Boolean + + abstract fun IExtractionGeneratorConfiguration.createExtractionResult( + declaration: KtNamedDeclaration, + duplicatesReplacer: Map Unit> + ): ExtractionResult + + fun getSignaturePreview(config: IExtractionGeneratorConfiguration) = + buildSignature(config.generatorOptions, config.descriptor, false).asString() + + fun generateDeclaration( + config: IExtractionGeneratorConfiguration, + declarationToReplace: KtNamedDeclaration? = null, + ): ExtractionResult { + val descriptor = config.descriptor + val generatorOptions = config.generatorOptions + val psiFactory = KtPsiFactory(descriptor.extractionData.project) + val defaultValue = descriptor.controlFlow.defaultOutputValue + val byReturnType = if (descriptor.isUnitReturnType()) null else + nameGenerator.suggestNamesByType(descriptor.returnType, + descriptor.extractionData.commonParent, + validator = { true }).firstOrNull() + + val resultNamesByValue = defaultValue?.valueType?.let { + nameGenerator.suggestNamesByType( + it, + descriptor.extractionData.commonParent, + validator = { true }) + } + + fun getReturnsForLabelRemoval() = descriptor.controlFlow.outputValues + .flatMapTo(arrayListOf()) { it.originalExpressions.filterIsInstance() } + + fun createDeclaration(): KtNamedDeclaration { + descriptor.controlFlow.jumpOutputValue?.elementsToReplace?.forEach { it.isJumpElementToReplace = true } + getReturnsForLabelRemoval().forEach { it.isReturnForLabelRemoval = true } + + return with(descriptor.extractionData) { + if (generatorOptions.inTempFile) { + createTemporaryDeclaration("${getDeclarationPattern(generatorOptions, descriptor)}\n") + } else { + psiFactory.createDeclarationByPattern( + getDeclarationPattern(generatorOptions, descriptor), + PsiChildRange(originalElements.firstOrNull(), originalElements.lastOrNull()) + ) + } + } + } + + fun getReturnArguments(resultExpression: KtExpression?): List { + return descriptor.controlFlow.outputValues + .mapNotNull { + when (it) { + is ExpressionValue -> resultExpression + is Jump -> if (it.conditional) psiFactory.createExpression("false") else null + is ParameterUpdate -> psiFactory.createExpression(it.parameter.nameForRef) + is Initializer -> psiFactory.createExpression(it.initializedDeclaration.name!!) + else -> throw IllegalArgumentException("Unknown output value: $it") + } + } + } + + fun KtExpression.replaceWithReturn(replacingExpression: KtReturnExpression) { + descriptor.controlFlow.defaultOutputValue?.let { + val boxedExpression = replaced(replacingExpression).returnedExpression!! + descriptor.controlFlow.outputValueBoxer.extractExpressionByValue(boxedExpression, it) + } + } + + fun adjustDeclarationBody(declaration: KtNamedDeclaration) { + val body = declaration.getGeneratedBody() + + (body.blockExpressionsOrSingle().singleOrNull() as? KtExpression)?.let { + if (it.mustBeParenthesizedInInitializerPosition()) { + it.replace(psiFactory.createExpressionByPattern("($0)", it)) + } + } + + val jumpValue = descriptor.controlFlow.jumpOutputValue + if (jumpValue != null) { + val replacingReturn = psiFactory.createExpression(if (jumpValue.conditional) "return true" else "return") + body.collectDescendantsOfType { it.isJumpElementToReplace }.forEach { + it.replace(replacingReturn) + it.isJumpElementToReplace = false + } + } + + body.collectDescendantsOfType { it.isReturnForLabelRemoval }.forEach { + it.getTargetLabel()?.delete() + it.isReturnForLabelRemoval = false + } + + /* + * Sort by descending position so that internals of value/type arguments in calls and qualified types are replaced + * before calls/types themselves + */ + val currentRefs = body + .collectDescendantsOfType { it.resolveResult != null } + .sortedByDescending { it.startOffset } + + currentRefs.forEach { + val resolveResult = it.resolveResult!! + val currentRef = if (it.isValid) { + it + } else { + body.findDescendantOfType { expr -> expr.resolveResult == resolveResult } ?: return@forEach + } + val originalRef = resolveResult.originalRefExpr + val newRef = descriptor.replacementMap[originalRef] + .fold(currentRef as KtElement) { ref, replacement -> replacement(descriptor, ref) } + (newRef as? org.jetbrains.kotlin.psi.KtSimpleNameExpression)?.resolveResult = resolveResult + } + + if (generatorOptions.target == ExtractionTarget.PROPERTY_WITH_INITIALIZER) return + + if (body !is KtBlockExpression) throw AssertionError("Block body expected: ${descriptor.extractionData.codeFragmentText}") + + val firstExpression = body.statements.firstOrNull() + if (firstExpression != null) { + for (param in descriptor.parameters) { + param.mirrorVarName?.let { varName -> + body.addBefore(psiFactory.createProperty(varName, null, true, param.name), firstExpression) + body.addBefore(psiFactory.createNewLine(), firstExpression) + } + } + } + + val lastExpression = body.statements.lastOrNull() + if (lastExpression is KtReturnExpression) return + + val defaultExpression = + if (!generatorOptions.inTempFile && defaultValue != null && descriptor.controlFlow.outputValueBoxer + .boxingRequired && lastExpression!!.isMultiLine() + ) { + require(resultNamesByValue != null) { "defaultValue is checked to be not-null" } + val resultVal = resultNamesByValue.map { nameGenerator.suggestNameByName(it, body, lastExpression) }.first() + body.addBefore(psiFactory.createDeclaration("val $resultVal = ${lastExpression.text}"), lastExpression) + body.addBefore(psiFactory.createNewLine(), lastExpression) + psiFactory.createExpression(resultVal) + } else lastExpression + + val returnExpression = + descriptor.controlFlow.outputValueBoxer.getReturnExpression(getReturnArguments(defaultExpression), psiFactory) ?: return + + when (generatorOptions.target) { + ExtractionTarget.LAZY_PROPERTY, ExtractionTarget.FAKE_LAMBDALIKE_FUNCTION -> { + // In the case of lazy property absence of default value means that output values are of OutputValue.Initializer type + // We just add resulting expressions without return, since returns are prohibited in the body of lazy property + if (defaultValue == null) { + body.appendElement(returnExpression.returnedExpression!!) + } + return + } + + else -> {} + } + + when { + defaultValue == null -> body.appendElement(returnExpression) + !defaultValue.callSiteReturn -> lastExpression!!.replaceWithReturn(returnExpression) + } + + if (generatorOptions.allowExpressionBody) { + config.collapseBody(body) + } + } + + @OptIn(KtAllowAnalysisFromWriteAction::class, KtAllowAnalysisOnEdt::class) + fun makeCall( + extractableDescriptor: IExtractableCodeDescriptor, + declaration: KtNamedDeclaration, + controlFlow: ControlFlow, + rangeToReplace: KotlinPsiRange, + arguments: List + ) { + fun insertCall(anchor: PsiElement, wrappedCall: KtExpression): KtExpression? { + val firstExpression = rangeToReplace.elements.firstIsInstanceOrNull() + if (firstExpression?.isLambdaOutsideParentheses() == true) { + val functionLiteralArgument = firstExpression.getStrictParentOfType()!! + val lambdaArgument = firstExpression.getContainingLambdaOutsideParentheses() + ?.takeIf { shouldLambdaParameterBeNamed(it) } + + val lambdaName = if (lambdaArgument != null) { + allowAnalysisOnEdt { + allowAnalysisFromWriteAction { + analyze(lambdaArgument) { + NamedArgumentUtils.getStableNameFor(lambdaArgument) + } + } + } + } else null + return functionLiteralArgument.moveInsideParenthesesAndReplaceWith(wrappedCall, lambdaName) + } + + if (anchor is KtOperationReferenceExpression) { + val newNameExpression = when (val operationExpression = anchor.parent as? KtOperationExpression ?: return null) { + is KtUnaryExpression -> OperatorToFunctionConverter.convert(operationExpression).second + is KtBinaryExpression -> { + convertInfixCallToOrdinary(operationExpression).getCalleeExpressionIfAny() + } + + else -> null + } + return newNameExpression?.replaced(wrappedCall) + } + + (anchor as? KtExpression)?.extractableSubstringInfo?.let { + return it.replaceWith(wrappedCall) + } + + return anchor.replaced(wrappedCall) + } + + if (rangeToReplace.isEmpty) return + + val anchor = rangeToReplace.elements.first() + val anchorParent = anchor.parent!! + + anchor.nextSibling?.let { from -> + val to = rangeToReplace.elements.last() + if (to != anchor) { + anchorParent.deleteChildRange(from, to) + } + } + + val calleeName = declaration.name?.quoteIfNeeded() + val callText = when (declaration) { + is KtNamedFunction -> { + val argumentsText = arguments.joinToString(separator = ", ", prefix = "(", postfix = ")") + val typeArguments = extractableDescriptor.typeParameters.map { it.originalDeclaration.name } + val typeArgumentsText = with(typeArguments) { + if (isNotEmpty()) joinToString(separator = ", ", prefix = "<", postfix = ">") else "" + } + "$calleeName$typeArgumentsText$argumentsText" + } + + else -> calleeName + } + + val anchorInBlock = generateSequence(anchor) { it.parent }.firstOrNull { it.parent is KtBlockExpression } + val block = (anchorInBlock?.parent as? KtBlockExpression) ?: anchorParent as KtElement + + val psiFactory = KtPsiFactory(anchor.project) + val newLine = psiFactory.createNewLine() + + if (controlFlow.outputValueBoxer is AsTuple && controlFlow.outputValues.size > 1 && controlFlow.outputValues + .all { it is Initializer } + ) { + val declarationsToMerge = controlFlow.outputValues.map { (it as Initializer).initializedDeclaration } + val isVar = declarationsToMerge.first().isVar + if (declarationsToMerge.all { it.isVar == isVar }) { + controlFlow.declarationsToCopy.subtract(declarationsToMerge).forEach { + block.addBefore(psiFactory.createDeclaration(it.text!!), anchorInBlock) as KtDeclaration + block.addBefore(newLine, anchorInBlock) + } + + val entries = declarationsToMerge.map { p -> p.name + (p.typeReference?.let { ": ${it.text}" } ?: "") } + anchorInBlock?.replace( + psiFactory.createDestructuringDeclaration("${if (isVar) "var" else "val"} (${entries.joinToString()}) = $callText") + ) + + return + } + } + + val inlinableCall = controlFlow.outputValues.size <= 1 + val unboxingExpressions = + if (inlinableCall) { + controlFlow.outputValueBoxer.getUnboxingExpressions(callText ?: return) + } else { + val resultVal = nameGenerator.suggestNameByName(byReturnType ?: "result", block, anchorInBlock) + + block.addBefore(psiFactory.createDeclaration("val $resultVal = $callText"), anchorInBlock) + block.addBefore(newLine, anchorInBlock) + controlFlow.outputValueBoxer.getUnboxingExpressions(resultVal) + } + + val copiedDeclarations = HashMap() + for (decl in controlFlow.declarationsToCopy) { + val declCopy = psiFactory.createDeclaration(decl.text!!) + copiedDeclarations[decl] = block.addBefore(declCopy, anchorInBlock) as KtDeclaration + block.addBefore(newLine, anchorInBlock) + } + + if (controlFlow.outputValues.isEmpty()) { + anchor.replace(psiFactory.createExpression(callText!!)) + return + } + + fun wrapCall(outputValue: OutputValue, callText: String): List { + return when (outputValue) { + is ExpressionValue -> { + val exprText = if (outputValue.callSiteReturn) { + val firstReturn = + outputValue.originalExpressions.asSequence().filterIsInstance().firstOrNull() + val label = firstReturn?.getTargetLabel()?.text ?: "" + "return$label $callText" + } else { + callText + } + Collections.singletonList(psiFactory.createExpression(exprText)) + } + + is ParameterUpdate -> + Collections.singletonList( + psiFactory.createExpression("${outputValue.parameter.argumentText} = $callText") + ) + + is Jump -> { + val elementToInsertAfterCall = outputValue.elementToInsertAfterCall + when { + elementToInsertAfterCall == null -> Collections.singletonList(psiFactory.createExpression(callText)) + outputValue.conditional -> Collections.singletonList( + psiFactory.createExpression("if ($callText) ${elementToInsertAfterCall.text}") + ) + + else -> listOf( + psiFactory.createExpression(callText), + newLine, + psiFactory.createExpression(elementToInsertAfterCall.text!!) + ) + } + } + + is Initializer -> { + val newProperty = copiedDeclarations[outputValue.initializedDeclaration] as KtProperty + newProperty.initializer = psiFactory.createExpression(callText) + Collections.emptyList() + } + + else -> throw IllegalArgumentException("Unknown output value: $outputValue") + } + } + + val defaultValue = controlFlow.defaultOutputValue + + controlFlow.outputValues + .filter { it != defaultValue } + .flatMap { wrapCall(it, unboxingExpressions.getValue(it)) } + .withIndex() + .forEach { (i, wrappedCall) -> + if (i > 0) { + block.addBefore(newLine, anchorInBlock) + } + block.addBefore(wrappedCall, anchorInBlock) + } + + defaultValue?.let { + if (!inlinableCall) { + block.addBefore(newLine, anchorInBlock) + } + insertCall( + anchor, + wrapCall(it, unboxingExpressions.getValue(it)).first() as KtExpression + )?.removeTemplateEntryBracesIfPossible() + } + + if (anchor.isValid) { + anchor.delete() + } + } + + val duplicates = if (generatorOptions.inTempFile) Collections.emptyList() else descriptor.duplicates + + val anchor = with(descriptor.extractionData) { + val targetParent = targetSibling.parent + + val anchorCandidates = duplicates.mapTo(arrayListOf()) { it.range.elements.first().substringContextOrThis } + anchorCandidates.add(targetSibling) + if (targetSibling is KtEnumEntry) { + anchorCandidates.add(targetSibling.siblings().last { it is KtEnumEntry }) + } + + val marginalCandidate = if (insertBefore) { + anchorCandidates.minByOrNull { it.startOffset }!! + } else { + anchorCandidates.maxByOrNull { it.startOffset }!! + } + + // Ascend to the level of targetSibling + marginalCandidate.parentsWithSelf.first { it.parent == targetParent } + } + + val shouldInsert = !(generatorOptions.inTempFile || generatorOptions.target == ExtractionTarget.FAKE_LAMBDALIKE_FUNCTION) + val declaration = + createDeclaration().let { if (shouldInsert) insertDeclaration(declarationToReplace, descriptor, it, anchor) else it } + adjustDeclarationBody(declaration) + + if (generatorOptions.inTempFile) return config.createExtractionResult(declaration, Collections.emptyMap()) + + val replaceInitialOccurrence = { + val arguments = descriptor.parameters.map { it.argumentText } + makeCall(descriptor, declaration, descriptor.controlFlow, descriptor.extractionData.originalRange, arguments) + } + + if (!generatorOptions.delayInitialOccurrenceReplacement) replaceInitialOccurrence() + + if (shouldInsert) { + ShortenReferencesFacility.getInstance().shorten(declaration) + } + + val duplicateReplacers = HashMap Unit>().apply { + if (generatorOptions.delayInitialOccurrenceReplacement) { + put(descriptor.extractionData.originalRange, replaceInitialOccurrence) + } + putAll(duplicates.map { + val smartListRange = KotlinPsiRange.SmartListRange(it.range.elements) + smartListRange to { makeCall(descriptor, declaration, it.controlFlow, smartListRange, it.arguments) } + }) + } + + if (descriptor.typeParameters.isNotEmpty()) { + for (ref in ReferencesSearch.search(declaration, LocalSearchScope(descriptor.getOccurrenceContainer()!!))) { + val typeArgumentList = (ref.element.parent as? KtCallExpression)?.typeArgumentList ?: continue + if (checkTypeArgumentsAreRedundant(typeArgumentList)) { + typeArgumentList.delete() + } + } + } + + if (declaration is KtProperty) { + if (declaration.isExtensionDeclaration() && !declaration.isTopLevel) { + val receiverTypeReference = (declaration as? KtCallableDeclaration)?.receiverTypeReference + receiverTypeReference?.siblings(withItself = false)?.firstOrNull { it.node.elementType == KtTokens.DOT }?.delete() + receiverTypeReference?.delete() + } + resolveNameConflict(declaration) + } + + CodeStyleManager.getInstance(descriptor.extractionData.project).reformat(declaration) + + return config.createExtractionResult(declaration, duplicateReplacers) + } + + private fun getDeclarationPattern( + options: ExtractionGeneratorOptions, + descriptor: IExtractableCodeDescriptor, + ): String { + val extractionTarget = options.target + if (!extractionTarget.isAvailable(descriptor)) { + throw BaseRefactoringProcessor.ConflictsInTestsException( + listOf( + KotlinBundle.message( + "error.text.can.t.generate.0.1", + extractionTarget.targetName, + descriptor.extractionData.codeFragmentText + ) + ) + ) + } + + return buildSignature(options, descriptor, true).let { builder -> + builder.transform { + for (i in generateSequence(indexOf('$')) { indexOf('$', it + 2) }) { + if (i < 0) break + insert(i + 1, '$') + } + } + + when (extractionTarget) { + ExtractionTarget.FUNCTION, + ExtractionTarget.FAKE_LAMBDALIKE_FUNCTION, + ExtractionTarget.PROPERTY_WITH_GETTER -> builder.blockBody("$0") + + ExtractionTarget.PROPERTY_WITH_INITIALIZER -> builder.initializer("$0") + ExtractionTarget.LAZY_PROPERTY -> builder.lazyBody("$0") + } + + builder.asString() + } + } + + private fun buildSignature( + options: ExtractionGeneratorOptions, + descriptor: IExtractableCodeDescriptor, + renderAnnotations: Boolean + ): CallableBuilder { + val extractionTarget = options.target + if (!extractionTarget.isAvailable(descriptor)) { + val message = KotlinBundle.message( + "error.text.can.t.generate.0.1", + extractionTarget.targetName, + descriptor.extractionData.codeFragmentText + ) + throw BaseRefactoringProcessor.ConflictsInTestsException(listOf(message)) + } + + val builderTarget = when (extractionTarget) { + ExtractionTarget.FUNCTION, ExtractionTarget.FAKE_LAMBDALIKE_FUNCTION -> CallableBuilder.Target.FUNCTION + else -> CallableBuilder.Target.READ_ONLY_PROPERTY + } + return CallableBuilder(builderTarget).apply { + val visibility = descriptor.visibility?.value ?: "" + + fun TypeParameter.isReified() = originalDeclaration.hasModifier(KtTokens.REIFIED_KEYWORD) + val shouldBeInline = descriptor.typeParameters.any { it.isReified() } + + val optInAnnotation = if (extractionTarget != ExtractionTarget.FUNCTION || descriptor.optInMarkers.isEmpty()) { + "" + } else { + val innerText = descriptor.optInMarkers.joinToString(separator = ", ") { "${it.shortName().render()}::class" } + "@${OptInNames.OPT_IN_FQ_NAME.shortName().render()}($innerText)\n" + } + + val annotations = if (descriptor.annotationsText.isEmpty() || !renderAnnotations) { + "" + } else { + descriptor.annotationsText + } + val extraModifiers = descriptor.modifiers.map { it.value } + + listOfNotNull(if (shouldBeInline) KtTokens.INLINE_KEYWORD.value else null) + + listOfNotNull(if (options.isConst) KtTokens.CONST_KEYWORD.value else null) + val modifiers = if (visibility.isNotEmpty()) listOf(visibility) + extraModifiers else extraModifiers + modifier(annotations + optInAnnotation + modifiers.joinToString(separator = " ")) + + typeParams( + descriptor.typeParameters.map { + val typeParameter = it.originalDeclaration + val bound = typeParameter.extendsBound + + buildString { + if (it.isReified()) { + append(KtTokens.REIFIED_KEYWORD.value) + append(' ') + } + append(typeParameter.name) + if (bound != null) { + append(" : ") + append(bound.text) + } + } + } + ) + + val typeDescriptor = createTypeDescriptor(descriptor.extractionData) + descriptor.receiverParameter?.let { + val receiverType = it.parameterType + val receiverTypeAsString = typeDescriptor.renderType(receiverType, isReceiver = true, Variance.IN_VARIANCE) + receiver(receiverTypeAsString) + } + + name(descriptor.name) + + descriptor.parameters.forEach { parameter -> + param(parameter.name, typeDescriptor.renderType(parameter.parameterType, isReceiver = false, Variance.IN_VARIANCE)) + } + + val returnType = descriptor.returnType + val presentation = typeDescriptor.renderType(returnType, isReceiver = false, Variance.OUT_VARIANCE) + if (typeDescriptor.unitType == returnType || with(typeDescriptor) { returnType.isError() } || extractionTarget == ExtractionTarget.PROPERTY_WITH_INITIALIZER) { + noReturnType() + } else { + returnType(presentation) + } + + typeConstraints(descriptor.typeParameters.flatMap { it.originalConstraints }.map { it.text!! }) + } + } + + private fun insertDeclaration( + declarationToReplace: KtNamedDeclaration? = null, + descriptor: IExtractableCodeDescriptor<*>, + declaration: KtNamedDeclaration, anchor: PsiElement + ): KtNamedDeclaration { + declarationToReplace?.let { return it.replace(declaration) as KtNamedDeclaration } + + val psiFactory = KtPsiFactory(descriptor.extractionData.project) + return with(descriptor.extractionData) { + val targetContainer = anchor.parent!! + // TODO: Get rid of explicit new-lines in favor of formatter rules + val emptyLines = psiFactory.createWhiteSpace("\n\n") + if (insertBefore) { + (targetContainer.addBefore(declaration, anchor) as KtNamedDeclaration).apply { + targetContainer.addBefore(emptyLines, anchor) + } + } else { + (targetContainer.addAfter(declaration, anchor) as KtNamedDeclaration).apply { + if (!(targetContainer is KtClassBody && (targetContainer.parent as? KtClass)?.isEnum() == true)) { + targetContainer.addAfter(emptyLines, anchor) + } + val insertedDeclaration = this + PostInsertDeclarationCallback.EP_NAME.forEachExtensionSafe { extension -> + extension.declarationInserted(insertedDeclaration, targetContainer, psiFactory) + } + } + } + } + } + +} \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptorWithConflictsResult.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptorWithConflictsResult.kt new file mode 100644 index 000000000000..30f4772506cc --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptorWithConflictsResult.kt @@ -0,0 +1,13 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine + +import com.intellij.psi.PsiElement +import com.intellij.util.containers.MultiMap + +open class ExtractableCodeDescriptorWithConflictsResult +interface IExtractableCodeDescriptorWithConflicts { + val descriptor: IExtractableCodeDescriptor + val conflicts: MultiMap +} + +data class ExtractableCodeDescriptorWithException(val exception: RuntimeException) : ExtractableCodeDescriptorWithConflictsResult() \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionGeneratorOptions.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionGeneratorOptions.kt new file mode 100644 index 000000000000..cd12eea16c3b --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionGeneratorOptions.kt @@ -0,0 +1,26 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine + +/** + * @property inTempFile Boolean indicating whether to extract the code into a temporary file. + * If extracted to temp file, duplicates won't be searched, so validation can be made faster + * + * @property allowExpressionBody If true, refactoring would try to collapse function block body to expression. + * Because changing user code is undesired e.g., extract parameter should keep this option as false + * + * @property delayInitialOccurrenceReplacement if true, extracted declaration would be renamed when inserted TODO reconsider fix for KTIJ-4200 + * + * @property isConst true for extract constant refactoring, false otherwise + */ +data class ExtractionGeneratorOptions( + val inTempFile: Boolean = false, + val target: ExtractionTarget = ExtractionTarget.FUNCTION, + val allowExpressionBody: Boolean = true, + val delayInitialOccurrenceReplacement: Boolean = false, + val isConst: Boolean = false +) { + companion object { + @JvmField + val DEFAULT = ExtractionGeneratorOptions() + } +} \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionTarget.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionTarget.kt new file mode 100644 index 000000000000..d74aa6320a12 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionTarget.kt @@ -0,0 +1,83 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine + +import org.jetbrains.kotlin.idea.base.resources.KotlinBundle +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.ExpressionValue +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.Initializer +import org.jetbrains.kotlin.psi.KtBlockExpression +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtClassBody +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType + +enum class ExtractionTarget(val targetName: String) { + FUNCTION(KotlinBundle.message("text.function")) { + override fun isAvailable(descriptor: IExtractableCodeDescriptor<*>) = true + }, + + FAKE_LAMBDALIKE_FUNCTION(KotlinBundle.message("text.lambda.parameter")) { + override fun isAvailable(descriptor: IExtractableCodeDescriptor<*>): Boolean { + return checkSimpleControlFlow(descriptor) || descriptor.controlFlow.outputValues.isEmpty() + } + }, + + PROPERTY_WITH_INITIALIZER(KotlinBundle.message("text.property.with.initializer")) { + override fun isAvailable(descriptor: IExtractableCodeDescriptor<*>): Boolean { + return checkSignatureAndParent(descriptor) + && checkSimpleControlFlow(descriptor) + && checkSimpleBody(descriptor) + && checkNotInterface(descriptor) + && descriptor.receiverParameter == null + } + }, + + PROPERTY_WITH_GETTER(KotlinBundle.message("text.property.with.getter")) { + override fun isAvailable(descriptor: IExtractableCodeDescriptor<*>): Boolean { + return checkSignatureAndParent(descriptor) + } + }, + + LAZY_PROPERTY(KotlinBundle.message("text.lazy.property")) { + override fun isAvailable(descriptor: IExtractableCodeDescriptor<*>): Boolean { + return checkSignatureAndParent(descriptor) + && checkSimpleControlFlow(descriptor) + && checkNotInterface(descriptor) + && descriptor.receiverParameter == null + } + }; + + abstract fun isAvailable(descriptor: IExtractableCodeDescriptor<*>): Boolean + + companion object { + fun checkNotInterface(descriptor: IExtractableCodeDescriptor<*>): Boolean { + val parent = descriptor.extractionData.targetSibling.getStrictParentOfType() + return !(parent is KtClass && parent.isInterface()) + } + + fun checkSimpleBody(descriptor: IExtractableCodeDescriptor<*>): Boolean { + val expression = descriptor.extractionData.expressions.singleOrNull() + return expression != null && expression !is KtDeclaration && expression !is KtBlockExpression + } + + fun checkSimpleControlFlow(descriptor: IExtractableCodeDescriptor<*>): Boolean { + val outputValue = descriptor.controlFlow.outputValues.singleOrNull() + return (outputValue is ExpressionValue && !outputValue.callSiteReturn) || outputValue is Initializer + } + + fun checkSignatureAndParent(descriptor: IExtractableCodeDescriptor<*>): Boolean { + if (!descriptor.parameters.isEmpty()) return false + if (descriptor.isUnitReturnType()) return false + + val parent = descriptor.extractionData.targetSibling.parent + return (parent is KtFile || parent is KtClassBody) + } + } +} + + +val propertyTargets: List = listOf( + ExtractionTarget.PROPERTY_WITH_INITIALIZER, + ExtractionTarget.PROPERTY_WITH_GETTER, + ExtractionTarget.LAZY_PROPERTY +) \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/IExtractableCodeDescriptor.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/IExtractableCodeDescriptor.kt new file mode 100644 index 000000000000..917fc7d01064 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/IExtractableCodeDescriptor.kt @@ -0,0 +1,31 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine + +import com.intellij.util.containers.MultiMap +import org.jetbrains.kotlin.lexer.KtKeywordToken +import org.jetbrains.kotlin.lexer.KtModifierKeywordToken +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtSimpleNameExpression + +/** + * Interface for describing extractable code. + */ +interface IExtractableCodeDescriptor { + val extractionData: IExtractionData + val suggestedNames: List + val visibility: KtModifierKeywordToken? + val parameters: List> + val receiverParameter: IParameter? + val typeParameters: List + val replacementMap: MultiMap> + val controlFlow: ControlFlow + val returnType: KotlinType + val modifiers: List + val annotationsText: String + val optInMarkers: List + + val name: String get() = suggestedNames.firstOrNull() ?: "" + fun isUnitReturnType(): Boolean + + val duplicates: List> +} \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/IExtractionData.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/IExtractionData.kt new file mode 100644 index 000000000000..055878e227d8 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/IExtractionData.kt @@ -0,0 +1,27 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine + +import com.intellij.openapi.Disposable +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.idea.base.psi.unifier.KotlinPsiRange +import org.jetbrains.kotlin.idea.refactoring.introduce.ExtractableSubstringInfo +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtFile + +interface IExtractionData : Disposable { + val originalFile: KtFile + val originalRange: KotlinPsiRange + val targetSibling: PsiElement + val duplicateContainer: PsiElement? + val options: ExtractionOptions + val expressions: List + val codeFragmentText: String + val project: Project + val insertBefore: Boolean + val originalElements: List + val physicalElements: List + val substringInfo: ExtractableSubstringInfo? + val commonParent: KtElement +} \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/IExtractionEngine.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/IExtractionEngine.kt new file mode 100644 index 000000000000..c7dc41137770 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/IExtractionEngine.kt @@ -0,0 +1,151 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine + +import com.intellij.openapi.actionSystem.ex.ActionUtil +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.diagnostic.ControlFlowException +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.MessageType +import com.intellij.openapi.ui.popup.Balloon +import com.intellij.openapi.ui.popup.JBPopupFactory +import com.intellij.openapi.util.NlsContexts +import com.intellij.openapi.util.NlsSafe +import com.intellij.refactoring.BaseRefactoringProcessor +import com.intellij.ui.awt.RelativePoint +import org.jetbrains.kotlin.idea.base.resources.KotlinBundle +import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively +import org.jetbrains.kotlin.idea.refactoring.introduce.showErrorHint +import org.jetbrains.kotlin.idea.util.application.executeWriteCommand +import javax.swing.event.HyperlinkEvent + +abstract class IExtractionEngineHelper, + Result : IExtractionResult, + Descriptor : IExtractableCodeDescriptor, + DescriptorWithConflicts : IExtractableCodeDescriptorWithConflicts>(@NlsContexts.DialogTitle val operationName: String) { + + open fun adjustExtractionData(data: ExtractionData): ExtractionData = data + + fun doRefactor(config: Config, onFinish: (Result) -> Unit = {}) { + val project = config.descriptor.extractionData.project + onFinish(project.executeWriteCommand(operationName) { generateDeclaration(config) }) + } + + /** + * Prepare extract descriptor + */ + abstract fun generateDeclaration(config: Config): Result + + /** + * Search for potential conflicts + */ + abstract fun validate(descriptor: Descriptor): DescriptorWithConflicts + + /** + * Shows dialog/start template and starts refactoring + */ + abstract fun configureAndRun( + project: Project, + editor: Editor, + descriptorWithConflicts: DescriptorWithConflicts, + onFinish: (Result) -> Unit = {} + ) +} + +abstract class IExtractionEngine, + Result : IExtractionResult, + Descriptor : IExtractableCodeDescriptor, + DescriptorWithConflicts : IExtractableCodeDescriptorWithConflicts> + (val helper: IExtractionEngineHelper) { + /** + * Prepare extraction result + * + * @param extractionData The extraction data to be analyzed. + * @return The analysis result, which includes the descriptor, status, and messages. + */ + abstract fun performAnalysis(extractionData: ExtractionData): AnalysisResult + + fun run( + editor: Editor, + extractionData: ExtractionData, + onFinish: (Result) -> Unit = {} + ) { + val project = extractionData.project + + val adjustExtractionData = helper.adjustExtractionData(extractionData) + val analysisResult = ActionUtil.underModalProgress(project, KotlinBundle.message("progress.title.analyze.extraction.data")) { + performAnalysis(adjustExtractionData) + } + + if (ApplicationManager.getApplication().isUnitTestMode() && analysisResult.status != AnalysisResult.Status.SUCCESS) { + throw BaseRefactoringProcessor.ConflictsInTestsException(analysisResult.messages.map { it.renderMessage() }) + } + + fun validateAndRefactor() { + val callable: () -> Any = { + try { + helper.validate(analysisResult.descriptor as Descriptor) + } catch (e: RuntimeException) { + if (e is ControlFlowException) { + throw e + } + ExtractableCodeDescriptorWithException(e) + } + } + val finishOnUIThread: (Any) -> Unit = { result -> + (result as? ExtractableCodeDescriptorWithException)?.let { throw it.exception } + val validationResult = result as DescriptorWithConflicts + project.checkConflictsInteractively(validationResult.conflicts) { + helper.configureAndRun(project, editor, validationResult) { + try { + onFinish(it) + } finally { + it.dispose() + extractionData.dispose() + } + } + } + } + val result = ActionUtil.underModalProgress(project, KotlinBundle.message("progress.title.check.for.conflicts"), callable) + if (result != null) { + finishOnUIThread(result) + } + } + + val message = analysisResult.messages.joinToString("\n") { it.renderMessage() } + when (analysisResult.status) { + AnalysisResult.Status.CRITICAL_ERROR -> { + showErrorHint(project, editor, message, helper.operationName) + } + + AnalysisResult.Status.NON_CRITICAL_ERROR -> { + val anchorPoint = RelativePoint( + editor.contentComponent, + editor.visualPositionToXY(editor.selectionModel.selectionStartPosition!!) + ) + @NlsSafe val htmlContent = + "$message

${KotlinBundle.message("text.proceed.with.extraction")}" + JBPopupFactory.getInstance()!! + .createHtmlTextBalloonBuilder( + htmlContent, + MessageType.WARNING + ) { event -> + if (event?.eventType == HyperlinkEvent.EventType.ACTIVATED) { + validateAndRefactor() + } + } + .setHideOnClickOutside(true) + .setHideOnFrameResize(false) + .setHideOnLinkClick(true) + .createBalloon() + .show(anchorPoint, Balloon.Position.below) + } + + AnalysisResult.Status.SUCCESS -> validateAndRefactor() + } + } +} diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/IExtractionGeneratorConfiguration.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/IExtractionGeneratorConfiguration.kt new file mode 100644 index 000000000000..0b47ff5859a5 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/IExtractionGeneratorConfiguration.kt @@ -0,0 +1,7 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine + +interface IExtractionGeneratorConfiguration { + val descriptor: IExtractableCodeDescriptor + val generatorOptions: ExtractionGeneratorOptions +} \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/IExtractionNameSuggester.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/IExtractionNameSuggester.kt new file mode 100644 index 000000000000..222d3f912542 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/IExtractionNameSuggester.kt @@ -0,0 +1,24 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.idea.base.codeInsight.KotlinNameSuggestionProvider +import org.jetbrains.kotlin.psi.KtElement + +interface IExtractionNameSuggester { + fun suggestNamesByType( + kotlinType: KotlinType, + container: KtElement, + validator: (String) -> Boolean, + defaultName: String? = null + ): List + + fun createNameValidator( + container: KtElement, + anchor: PsiElement?, + validatorType: KotlinNameSuggestionProvider.ValidatorTarget + ): (String) -> Boolean + + fun suggestNameByName(name: String, container: KtElement, anchor: PsiElement?): String + +} \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/IExtractionResult.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/IExtractionResult.kt new file mode 100644 index 000000000000..24e810810c49 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/IExtractionResult.kt @@ -0,0 +1,12 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine + +import com.intellij.openapi.Disposable +import org.jetbrains.kotlin.idea.base.psi.unifier.KotlinPsiRange +import org.jetbrains.kotlin.psi.KtNamedDeclaration + +interface IExtractionResult : Disposable { + val config: IExtractionGeneratorConfiguration + val declaration: KtNamedDeclaration + val duplicateReplacers: Map Unit> +} \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/IParameter.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/IParameter.kt new file mode 100644 index 000000000000..5315197319f4 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/IParameter.kt @@ -0,0 +1,19 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine + +interface IParameter { + val argumentText: String + val name: String + val mirrorVarName: String? + val receiverCandidate: Boolean + + val parameterType: KotlinType + + fun getParameterTypeCandidates(): List +} + +val IParameter<*>.nameForRef: String get() = mirrorVarName ?: name + +interface IMutableParameter : IParameter { + var refCount: Int +} diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/IReplacement.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/IReplacement.kt new file mode 100644 index 000000000000..022ee03429ed --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/IReplacement.kt @@ -0,0 +1,90 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine + +import org.jetbrains.kotlin.idea.base.psi.replaced +import org.jetbrains.kotlin.idea.references.KtSimpleNameReference.ShorteningMode +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtOperationReferenceExpression +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.KtPsiUtil +import org.jetbrains.kotlin.psi.KtQualifiedExpression +import org.jetbrains.kotlin.psi.KtSimpleNameExpression +import org.jetbrains.kotlin.psi.KtThisExpression +import org.jetbrains.kotlin.psi.createExpressionByPattern +import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElement +import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElementSelector +import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelector +import org.jetbrains.kotlin.psi.psiUtil.isIdentifier + +interface IReplacement : Function2, KtElement, KtElement> + +interface ParameterReplacement : IReplacement { + val parameter: IParameter + fun copy(parameter: IParameter): ParameterReplacement +} + +class RenameReplacement(override val parameter: IParameter) : ParameterReplacement { + override fun copy(parameter: IParameter) = RenameReplacement(parameter) + + override fun invoke(descriptor: IExtractableCodeDescriptor, e: KtElement): KtElement { + val expressionToReplace = (e.parent as? KtThisExpression ?: e).let { it.getQualifiedExpressionForSelector() ?: it } + val parameterName = KtPsiUtil.unquoteIdentifier(parameter.nameForRef) + val replacingName = + if (e.text.startsWith('`') || !parameterName.isIdentifier()) "`$parameterName`" else parameterName + val psiFactory = KtPsiFactory(e.project) + val replacement = when { + parameter == descriptor.receiverParameter -> psiFactory.createExpression("this") + expressionToReplace is KtOperationReferenceExpression -> psiFactory.createOperationName(replacingName) + else -> psiFactory.createSimpleName(replacingName) + } + return expressionToReplace.replaced(replacement) + } +} + +abstract class WrapInWithReplacement : IReplacement { + abstract val argumentText: String + + override fun invoke(descriptor: IExtractableCodeDescriptor, e: KtElement): KtElement { + val call = (e as? KtSimpleNameExpression)?.getQualifiedElement() ?: return e + val replacingExpression = KtPsiFactory(e.project).createExpressionByPattern("with($0) { $1 }", argumentText, call) + val replace = call.replace(replacingExpression) + return (replace as KtCallExpression).lambdaArguments.first().getLambdaExpression()!!.bodyExpression!!.statements.first() + } +} + +class WrapParameterInWithReplacement(override val parameter: IParameter) : WrapInWithReplacement(), + ParameterReplacement { + override val argumentText: String + get() = parameter.name + + override fun copy(parameter: IParameter) = WrapParameterInWithReplacement(parameter) +} + + +class AddPrefixReplacement(override val parameter: IParameter) : ParameterReplacement { + override fun copy(parameter: IParameter) = AddPrefixReplacement(parameter) + + override fun invoke(descriptor: IExtractableCodeDescriptor, e: KtElement): KtElement { + if (descriptor.receiverParameter == parameter) return e + + val selector = (e.parent as? KtCallExpression) ?: e + val replacingExpression = KtPsiFactory(e.project).createExpressionByPattern("${parameter.nameForRef}.$0", selector) + val newExpr = (selector.replace(replacingExpression) as KtQualifiedExpression).selectorExpression!! + return (newExpr as? KtCallExpression)?.calleeExpression ?: newExpr + } +} + +class FqNameReplacement(val fqName: FqName) : IReplacement { + override fun invoke(descriptor: IExtractableCodeDescriptor, e: KtElement): KtElement { + val thisExpr = e.parent as? KtThisExpression + if (thisExpr != null) { + return thisExpr.replaced(KtPsiFactory(e.project).createExpression(fqName.asString())).getQualifiedElementSelector()!! + } + + val newExpr = (e as? KtSimpleNameExpression)?.mainReference?.bindToFqName(fqName, ShorteningMode.NO_SHORTENING) as KtElement + return if (newExpr is KtQualifiedExpression) newExpr.selectorExpression!! else newExpr + } +} diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/OutputDescriptor.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/OutputDescriptor.kt new file mode 100644 index 000000000000..f730892335ef --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/OutputDescriptor.kt @@ -0,0 +1,29 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine + +import org.jetbrains.kotlin.psi.KtExpression + +data class OutputDescriptor( + val defaultResultExpression: KtExpression?, + val typeOfDefaultFlow: KotlinType, + val valuedReturnExpressions: List, + val returnValueType: KotlinType, + val jumpExpressions: List, + val hasSingleTarget: Boolean, + val sameExitForDefaultAndJump: Boolean +) { + override fun toString(): String { + return buildString { + append("OutputDescriptor[").append("\n") + defaultResultExpression?.let { + append("defaultResultExpressions = ").append(it.text).append("\n") + } + append("typeOfDefaultFlow = ").append(typeOfDefaultFlow).append("\n") + append("valuedReturnExpressions = ").append(valuedReturnExpressions.joinToString(separator = ",\n") { it.text }).append("\n") + append("returnValueType = ").append(returnValueType).append("\n") + append("jumpExpressions = ").append(jumpExpressions.joinToString(separator = ",\n") { it.text }).append("\n") + append("hasSingleTarget = ").append(hasSingleTarget).append("\n") + append("]") + } + } +} diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/OutputValue.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/OutputValue.kt new file mode 100644 index 000000000000..7cfa0d978a43 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/OutputValue.kt @@ -0,0 +1,43 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine + +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtProperty +import java.util.Collections + +interface OutputValue { + val originalExpressions: List + val valueType: KotlinType + + class ExpressionValue( + val callSiteReturn: Boolean, + override val originalExpressions: List, + override val valueType: KotlinType + ) : OutputValue + + class Jump( + val elementsToReplace: List, + val elementToInsertAfterCall: KtElement?, + val conditional: Boolean, + private val booleanType: KotlinType, + private val unitType: KotlinType + ) : OutputValue { + override val originalExpressions: List get() = elementsToReplace + override val valueType: KotlinType = if (conditional) booleanType else unitType + } + + class ParameterUpdate( + val parameter: IParameter, + override val originalExpressions: List + ) : OutputValue { + override val valueType: KotlinType get() = parameter.parameterType + } + + class Initializer( + val initializedDeclaration: KtProperty, + override val valueType: KotlinType + ) : OutputValue { + override val originalExpressions: List get() = Collections.singletonList(initializedDeclaration) + } +} diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/OutputValueBoxer.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/OutputValueBoxer.kt new file mode 100644 index 000000000000..bd0735ca631b --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/OutputValueBoxer.kt @@ -0,0 +1,108 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine + +import com.intellij.util.containers.ContainerUtil +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.KtQualifiedExpression +import org.jetbrains.kotlin.psi.KtReturnExpression +import org.jetbrains.kotlin.psi.createExpressionByPattern +import java.util.Collections + +abstract class OutputValueBoxer(val outputValues: List>) { + + abstract val returnType: KotlinType + + protected abstract fun getBoxingExpressionPattern(arguments: List): String? + + abstract val boxingRequired: Boolean + + fun getReturnExpression(arguments: List, psiFactory: KtPsiFactory): KtReturnExpression? { + val expressionPattern = getBoxingExpressionPattern(arguments) ?: return null + return psiFactory.createExpressionByPattern("return $expressionPattern", *arguments.toTypedArray()) as KtReturnExpression + } + + protected abstract fun extractExpressionByIndex(boxedExpression: KtExpression, index: Int): KtExpression? + + protected fun extractArgumentExpressionByIndex(boxedExpression: KtExpression, index: Int): KtExpression? { + val call: KtCallExpression? = when (boxedExpression) { + is KtCallExpression -> boxedExpression + is KtQualifiedExpression -> boxedExpression.selectorExpression as? KtCallExpression + else -> null + } + val arguments = call?.valueArguments + if (arguments == null || arguments.size <= index) return null + + return arguments[index].getArgumentExpression() + } + + fun extractExpressionByValue(boxedExpression: KtExpression, value: OutputValue): KtExpression? { + val index = outputValues.indexOf(value) + if (index < 0) return null + + return extractExpressionByIndex(boxedExpression, index) + } + + abstract fun getUnboxingExpressions(boxedText: String): Map, String> + + abstract class AsTuple( + outputValues: List>, + ) : OutputValueBoxer(outputValues) { + init { + assert(outputValues.size <= 3) { "At most 3 output values are supported" } + } + + companion object { + private val selectors = arrayOf("first", "second", "third") + } + + override val boxingRequired: Boolean = outputValues.size > 1 + + override fun getBoxingExpressionPattern(arguments: List): String? { + return when (arguments.size) { + 0 -> null + 1 -> "$0" + else -> { + val constructorName = if (arguments.size == 2) "kotlin.Pair" else "kotlin.Triple" + return arguments.indices.joinToString(prefix = "$constructorName(", separator = ", ", postfix = ")") { "\$$it" } + } + } + } + + override fun extractExpressionByIndex(boxedExpression: KtExpression, index: Int): KtExpression? { + if (outputValues.size == 1) return boxedExpression + return extractArgumentExpressionByIndex(boxedExpression, index) + } + + override fun getUnboxingExpressions(boxedText: String): Map, String> { + return when (outputValues.size) { + 0 -> Collections.emptyMap() + 1 -> Collections.singletonMap(outputValues.first(), boxedText) + else -> { + var i = 0 + ContainerUtil.newMapFromKeys(outputValues.iterator()) { "$boxedText.${selectors[i++]}" } + } + } + } + } + + abstract class AsList(outputValues: List>) : OutputValueBoxer(outputValues) { + + override val boxingRequired: Boolean = outputValues.isNotEmpty() + + override fun getBoxingExpressionPattern(arguments: List): String? { + if (arguments.isEmpty()) return null + return arguments.indices.joinToString(prefix = "kotlin.collections.listOf(", separator = ", ", postfix = ")") { "\$$it" } + } + + override fun extractExpressionByIndex(boxedExpression: KtExpression, index: Int): KtExpression? { + return extractArgumentExpressionByIndex(boxedExpression, index) + } + + override fun getUnboxingExpressions(boxedText: String): Map, String> { + var i = 0 + return ContainerUtil.newMapFromKeys(outputValues.iterator()) { "$boxedText[${i++}]" } + } + } +} diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ParametersInfo.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ParametersInfo.kt new file mode 100644 index 000000000000..d546125d6c1d --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ParametersInfo.kt @@ -0,0 +1,14 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine + +import com.intellij.util.containers.MultiMap +import org.jetbrains.kotlin.psi.KtSimpleNameExpression + +class ParametersInfo> { + var errorMessage: AnalysisResult.ErrorMessage? = null + val originalRefToParameter = MultiMap.create() + val parameters = LinkedHashSet() + val typeParameters = HashSet() + val nonDenotableTypes = HashSet() + val replacementMap = MultiMap.create>() +} \ No newline at end of file diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/PostInsertDeclarationCallback.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/PostInsertDeclarationCallback.kt similarity index 100% rename from plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/PostInsertDeclarationCallback.kt rename to plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/PostInsertDeclarationCallback.kt diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/TypeDescriptor.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/TypeDescriptor.kt new file mode 100644 index 000000000000..84e5bbfc74b2 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/TypeDescriptor.kt @@ -0,0 +1,28 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine + +import org.jetbrains.kotlin.psi.KtNamedDeclaration +import org.jetbrains.kotlin.types.Variance + +interface TypeDescriptor { + fun KotlinType.isMeaningful(): Boolean + fun KotlinType.isError(): Boolean + val booleanType: KotlinType + val unitType: KotlinType + val nullableAnyType: KotlinType + fun createListType(argTypes: List): KotlinType + fun createTuple(outputValues: List>): KotlinType + fun returnType(ktNamedDeclaration: KtNamedDeclaration): KotlinType? + fun typeArguments(kotlinType: KotlinType): List + + fun renderType(type: KotlinType, isReceiver: Boolean, variance: Variance): String + fun renderTypeWithoutApproximation(kotlinType: KotlinType): String + + fun renderForMessage(ktNamedDeclaration: KtNamedDeclaration): String? + fun renderForMessage(param: IParameter): String + + fun isResolvableInScope( + typeToCheck: KotlinType, + typeParameters: MutableSet + ): Boolean +} \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/TypeParameter.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/TypeParameter.kt new file mode 100644 index 000000000000..78b5aff5bbf9 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/TypeParameter.kt @@ -0,0 +1,10 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine + +import org.jetbrains.kotlin.psi.KtTypeConstraint +import org.jetbrains.kotlin.psi.KtTypeParameter + +data class TypeParameter( + val originalDeclaration: KtTypeParameter, + val originalConstraints: List +) \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/encodeDecodeUtil.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/encodeDecodeUtil.kt new file mode 100644 index 000000000000..80d9342386e2 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/encodeDecodeUtil.kt @@ -0,0 +1,75 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine + +import com.intellij.openapi.application.runReadAction +import com.intellij.openapi.util.Key +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.psi.CopyablePsiUserDataProperty +import org.jetbrains.kotlin.psi.KtQualifiedExpression +import org.jetbrains.kotlin.psi.KtSimpleNameExpression +import org.jetbrains.kotlin.psi.KtStringTemplateExpression +import org.jetbrains.kotlin.psi.KtTreeVisitorVoid +import org.jetbrains.kotlin.psi.KtValueArgumentName +import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType +import org.jetbrains.kotlin.psi.psiUtil.getContentRange +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType +import org.jetbrains.kotlin.psi.psiUtil.startOffset + +data class ResolveResult( + val originalRefExpr: KtSimpleNameExpression, + val declaration: PsiElement, + val descriptor: Descriptor, + val resolvedCall: ResolvedCall? +) + +data class ResolvedReferenceInfo( + val refExpr: KtSimpleNameExpression, + val resolveResult: ResolveResult, + val smartCast: KotlinType?, + val possibleTypes: Set +) + +var KtSimpleNameExpression.resolveResult: ResolveResult<*, *>? by CopyablePsiUserDataProperty(Key.create("RESOLVE_RESULT")) + +fun unmarkReferencesInside(root: PsiElement) { + runReadAction { + if (!root.isValid) return@runReadAction + root.forEachDescendantOfType { it.resolveResult = null } + } +} + +fun IExtractionData.encodeReferences( + hasSmartCast: (KtQualifiedExpression) -> Boolean, + resolveResultProvider: (KtSimpleNameExpression) -> ResolveResult? +) { + val visitor = object : KtTreeVisitorVoid() { + override fun visitQualifiedExpression(expression: KtQualifiedExpression) { + if (hasSmartCast(expression)) { + expression.selectorExpression?.accept(this) + return + } + + super.visitQualifiedExpression(expression) + } + + override fun visitSimpleNameExpression(ref: KtSimpleNameExpression) { + if (ref.parent is KtValueArgumentName) return + + val physicalRef = substringInfo?.let { + // If substring contains some references it must be extracted as a string template + val physicalExpression = expressions.single() as KtStringTemplateExpression + val extractedContentOffset = physicalExpression.getContentRange().startOffset + physicalExpression.startOffset + val offsetInExtracted = ref.startOffset - extractedContentOffset + val offsetInTemplate = it.relativeContentRange.startOffset + offsetInExtracted + it.template.findElementAt(offsetInTemplate)!!.getStrictParentOfType() + } ?: ref + + val resolveResult = resolveResultProvider(physicalRef) ?: return + physicalRef.resolveResult = resolveResult + if (ref != physicalRef) { + ref.resolveResult = resolveResult + } + } + } + expressions.forEach { it.accept(visitor) } +} \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractUtil.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractUtil.kt new file mode 100644 index 000000000000..4985941d5080 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractUtil.kt @@ -0,0 +1,186 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine + +import com.intellij.psi.PsiElement +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.kotlin.idea.refactoring.createTempCopy +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.AnalysisResult.ErrorMessage +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.lexer.KtModifierKeywordToken +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.KtBinaryExpression +import org.jetbrains.kotlin.psi.KtBlockExpression +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtClassBody +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtEnumEntry +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtLambdaExpression +import org.jetbrains.kotlin.psi.KtNamedDeclaration +import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.KtPsiUtil +import org.jetbrains.kotlin.psi.KtSimpleNameExpression +import org.jetbrains.kotlin.psi.KtTreeVisitorVoid +import org.jetbrains.kotlin.psi.KtTypeConstraint +import org.jetbrains.kotlin.psi.KtTypeParameter +import org.jetbrains.kotlin.psi.KtTypeParameterListOwner +import org.jetbrains.kotlin.psi.KtTypeReference +import org.jetbrains.kotlin.psi.createDeclarationByPattern +import org.jetbrains.kotlin.psi.createExpressionByPattern +import org.jetbrains.kotlin.psi.psiUtil.PsiChildRange +import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType +import org.jetbrains.kotlin.psi.psiUtil.isInsideOf +import org.jetbrains.kotlin.psi.psiUtil.isPublic +import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf +import org.jetbrains.kotlin.utils.DFS.CollectingNodeHandler +import org.jetbrains.kotlin.utils.DFS.Neighbors +import org.jetbrains.kotlin.utils.DFS.VisitedWithSet +import org.jetbrains.kotlin.utils.DFS.dfsFromNode +import java.util.Collections + + +internal fun IExtractionData.createTemporaryDeclaration(pattern: String): KtNamedDeclaration { + val targetSiblingMarker = Any() + PsiTreeUtil.mark(targetSibling, targetSiblingMarker) + val tmpFile = originalFile.createTempCopy("") + tmpFile.deleteChildRange(tmpFile.firstChild, tmpFile.lastChild) + tmpFile.addRange(originalFile.firstChild, originalFile.lastChild) + val newTargetSibling = PsiTreeUtil.releaseMark(tmpFile, targetSiblingMarker)!! + val newTargetParent = newTargetSibling.parent + + val declaration = KtPsiFactory(project).createDeclarationByPattern( + pattern, + PsiChildRange(originalElements.firstOrNull(), originalElements.lastOrNull()) + ) + return if (insertBefore) { + newTargetParent.addBefore(declaration, newTargetSibling) as KtNamedDeclaration + } else { + newTargetParent.addAfter(declaration, newTargetSibling) as KtNamedDeclaration + } +} + +fun IExtractionData.createTemporaryCodeBlock(): KtBlockExpression { + if (options.extractAsProperty) { + return ((createTemporaryDeclaration("val = {\n$0\n}\n") as KtProperty).initializer as KtLambdaExpression).bodyExpression!! + } + return (createTemporaryDeclaration("fun() {\n$0\n}\n") as KtNamedFunction).bodyBlockExpression!! +} + +fun IExtractionData.isLocal(): Boolean { + val parent = targetSibling.parent + return parent !is KtClassBody && (parent !is KtFile || parent.isScript()) +} + +fun IExtractionData.isVisibilityApplicable(): Boolean { + if (isLocal()) return false + if (commonParent.parentsWithSelf.any { it is KtNamedFunction && it.hasModifier(KtTokens.INLINE_KEYWORD) && it.isPublic }) return false + return true +} + +fun IExtractionData.getDefaultVisibility(): KtModifierKeywordToken? { + if (!isVisibilityApplicable()) return null + + val parent = targetSibling.getStrictParentOfType() + if (parent is KtClass) { + if (parent.isInterface()) return null + if (parent.isEnum() && commonParent.getNonStrictParentOfType()?.getStrictParentOfType() == parent) return null + } + + return KtTokens.PRIVATE_KEYWORD +} + +fun KtTypeParameter.collectRelevantConstraints(): List { + val typeConstraints = getNonStrictParentOfType()?.typeConstraints ?: return Collections.emptyList() + return typeConstraints.filter { it.subjectTypeParameterName?.mainReference?.resolve() == this } +} + +fun TypeParameter.collectReferencedTypes(): ArrayList { + val typeRefs = ArrayList() + originalDeclaration.extendsBound?.let { typeRefs.add(it) } + originalConstraints.mapNotNullTo(typeRefs) { it.boundTypeReference } + + return typeRefs +} + +fun KotlinType.collectReferencedTypes( + processTypeArguments: Boolean, + args: (KotlinType) -> List +): List { + if (!processTypeArguments) return Collections.singletonList(this) + return dfsFromNode( + this!!, + Neighbors { current -> args(current) }, + VisitedWithSet(), + object : CollectingNodeHandler>(ArrayList()) { + override fun afterChildren(current: KotlinType) { + result.add(current) + } + } + )!! +} + +fun KotlinType.processTypeIfExtractable( + typeParameters: MutableSet, + nonDenotableTypes: MutableSet, + processTypeArguments: Boolean = true, + args: (KotlinType) -> List, + isResolvableInScope: (typeToCheck: KotlinType, typeParameters: MutableSet) -> Boolean +): Boolean { + return collectReferencedTypes(processTypeArguments, args).fold(true) { extractable, typeToCheck -> + when { + isResolvableInScope(typeToCheck, typeParameters) -> + extractable + + else -> { + nonDenotableTypes.add(typeToCheck) + false + } + } + } +} + +fun IExtractableCodeDescriptor<*>.getOccurrenceContainer(): PsiElement? { + return extractionData.duplicateContainer ?: extractionData.targetSibling.parent +} + +fun IExtractionData.checkDeclarationsMovingOutOfScope( + enclosingDeclaration: KtDeclaration, controlFlow: ControlFlow, textPresentation: (KtNamedDeclaration) -> String +): ErrorMessage? { + val declarationsOutOfScope = HashSet() + controlFlow.jumpOutputValue?.elementToInsertAfterCall?.accept(object : KtTreeVisitorVoid() { + override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) { + val target = expression.mainReference.resolve() + if (target is KtNamedDeclaration && target.isInsideOf(physicalElements) && target.getStrictParentOfType() == enclosingDeclaration) { + declarationsOutOfScope.add(target) + } + } + }) + + if (declarationsOutOfScope.isNotEmpty()) { + val declStr = declarationsOutOfScope.map(textPresentation).sorted() + return ErrorMessage.DECLARATIONS_OUT_OF_SCOPE.addAdditionalInfo(declStr) + } + + return null +} + +fun convertInfixCallToOrdinary(element: KtBinaryExpression): KtExpression { + val argument = KtPsiUtil.safeDeparenthesize(element.right!!) + val pattern = "$0.$1" + when (argument) { + is KtLambdaExpression -> " $2:'{}'" + else -> "($2)" + } + + val replacement = KtPsiFactory(element.project).createExpressionByPattern( + pattern, + element.left!!, + element.operationReference, + argument + ) + + return element.replace(replacement) as KtExpression +} \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/package-info.java b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/package-info.java new file mode 100644 index 000000000000..2d0d5e6a2082 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/package-info.java @@ -0,0 +1,5 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +@ApiStatus.Internal +package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine; + +import org.jetbrains.annotations.ApiStatus; \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceUtils.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceUtils.kt index f3db5cee47ca..bfc51ff69f3c 100644 --- a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceUtils.kt +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceUtils.kt @@ -24,6 +24,7 @@ import org.jetbrains.kotlin.idea.util.ElementKind import org.jetbrains.kotlin.idea.util.findElements import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.* +import org.jetbrains.kotlin.resolve.calls.util.getCalleeExpressionIfAny import kotlin.math.min fun KtExpression.removeTemplateEntryBracesIfPossible(): KtExpression { @@ -267,4 +268,20 @@ fun showErrorHint(project: Project, editor: Editor, @NlsContexts.DialogMessage m fun showErrorHintByKey(project: Project, editor: Editor, messageKey: String, @NlsContexts.DialogTitle title: String) { showErrorHint(project, editor, KotlinBundle.message(messageKey), title) -} \ No newline at end of file +} + +fun KtNamedDeclaration.getGeneratedBody(): KtExpression = + when (this) { + is KtNamedFunction -> bodyExpression + else -> { + val property = this as KtProperty + + property.getter?.bodyExpression?.let { return it } + property.initializer?.let { return it } + // We assume lazy property here with delegate expression 'by Delegates.lazy { body }' + property.delegateExpression?.let { + val call = it.getCalleeExpressionIfAny()?.parent as? KtCallExpression + call?.lambdaArguments?.singleOrNull()?.getLambdaExpression()?.bodyExpression + } + } + } ?: throw AssertionError("Couldn't get block body for this declaration: ${getElementTextWithContext()}") diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/ui/AbstractParameterTablePanel.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/ui/AbstractParameterTablePanel.kt similarity index 100% rename from plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/ui/AbstractParameterTablePanel.kt rename to plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/ui/AbstractParameterTablePanel.kt diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/kotlinCommonRefactoringUtil.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/kotlinCommonRefactoringUtil.kt index a02c947044e8..3619ab8ebca4 100644 --- a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/kotlinCommonRefactoringUtil.kt +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/kotlinCommonRefactoringUtil.kt @@ -2,6 +2,7 @@ package org.jetbrains.kotlin.idea.refactoring import com.intellij.lang.java.JavaLanguage +import com.intellij.openapi.project.Project import com.intellij.psi.* import com.intellij.psi.PsiComment import com.intellij.psi.PsiDirectory @@ -12,13 +13,17 @@ import com.intellij.psi.PsiPackage import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.elementType +import com.intellij.refactoring.BaseRefactoringProcessor.ConflictsInTestsException import com.intellij.refactoring.changeSignature.ChangeInfo import com.intellij.refactoring.suggested.endOffset +import com.intellij.refactoring.ui.ConflictsDialog import com.intellij.refactoring.util.ConflictsUtil import com.intellij.usageView.UsageInfo +import com.intellij.util.containers.MultiMap import org.jetbrains.kotlin.idea.base.projectStructure.RootKindFilter import org.jetbrains.kotlin.idea.base.projectStructure.matches import org.jetbrains.kotlin.idea.refactoring.memberInfo.KtPsiClassWrapper +import org.jetbrains.kotlin.idea.util.application.isUnitTestMode import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.* @@ -279,4 +284,41 @@ fun PsiElement.getExtractionContainers(strict: Boolean = true, includeAll: Boole if (targetContainer is KtBlockExpression) Collections.singletonList(targetContainer) else Collections.emptyList() } } -} \ No newline at end of file +} + +fun KtBlockExpression.appendElement(element: KtElement, addNewLine: Boolean = false): KtElement { + val rBrace = rBrace + val newLine = KtPsiFactory(project).createNewLine() + val anchor = if (rBrace == null) { + val lastChild = lastChild + lastChild as? PsiWhiteSpace ?: addAfter(newLine, lastChild)!! + } else { + rBrace.prevSibling!! + } + val addedElement = addAfter(element, anchor)!! as KtElement + if (addNewLine) { + addAfter(newLine, addedElement) + } + return addedElement +} + +fun Project.checkConflictsInteractively( + conflicts: MultiMap, + onShowConflicts: () -> Unit = {}, + onAccept: () -> Unit +) { + if (!conflicts.isEmpty) { + if (isUnitTestMode()) throw ConflictsInTestsException(conflicts.values()) + + val dialog = ConflictsDialog(this, conflicts) { onAccept() } + dialog.show() + if (!dialog.isOK) { + if (dialog.isShowConflicts) { + onShowConflicts() + } + return + } + } + + onAccept() +} diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.k2/resources/kotlin.refactorings.k2.xml b/plugins/kotlin/refactorings/kotlin.refactorings.k2/resources/kotlin.refactorings.k2.xml index bb29217c1c59..1817a5d62ad0 100644 --- a/plugins/kotlin/refactorings/kotlin.refactorings.k2/resources/kotlin.refactorings.k2.xml +++ b/plugins/kotlin/refactorings/kotlin.refactorings.k2/resources/kotlin.refactorings.k2.xml @@ -1,4 +1,5 @@ + messages.KotlinBundle @@ -35,4 +36,12 @@ + + + + + + + diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/ExtractK2FunctionAction.kt b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/ExtractK2FunctionAction.kt new file mode 100644 index 000000000000..4d4150e5fe17 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/ExtractK2FunctionAction.kt @@ -0,0 +1,22 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. + +package org.jetbrains.kotlin.idea.k2.refactoring.extractFunction + +import com.intellij.lang.refactoring.RefactoringSupportProvider +import com.intellij.psi.PsiElement +import com.intellij.refactoring.RefactoringActionHandler +import com.intellij.refactoring.actions.BasePlatformRefactoringAction +import org.jetbrains.kotlin.idea.k2.refactoring.introduce.extractFunction.KotlinFirExtractFunctionHandler +import org.jetbrains.kotlin.psi.KtElement + +class ExtractK2FunctionAction : BasePlatformRefactoringAction() { + override fun getRefactoringHandler(provider: RefactoringSupportProvider): RefactoringActionHandler? = + KotlinFirExtractFunctionHandler() + + override fun isAvailableInEditorOnly(): Boolean { + return true + } + + override fun isEnabledOnElements(elements: Array): Boolean = + elements.all { it is KtElement } +} \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/ExtractableCodeDescriptor.kt b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/ExtractableCodeDescriptor.kt new file mode 100644 index 000000000000..979546f5c95b --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/ExtractableCodeDescriptor.kt @@ -0,0 +1,86 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.k2.refactoring.extractFunction + +import com.intellij.psi.PsiElement +import com.intellij.util.containers.MultiMap +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.analysis.api.annotations.KtAnnotated +import org.jetbrains.kotlin.analysis.api.annotations.KtAnnotationApplication +import org.jetbrains.kotlin.analysis.api.annotations.KtAnnotationApplicationWithArgumentsInfo +import org.jetbrains.kotlin.analysis.api.annotations.KtConstantAnnotationValue +import org.jetbrains.kotlin.analysis.api.base.KtConstantValue +import org.jetbrains.kotlin.analysis.api.renderer.base.annotations.KtRendererAnnotationsFilter +import org.jetbrains.kotlin.analysis.api.renderer.declarations.impl.KtDeclarationRendererForSource +import org.jetbrains.kotlin.analysis.api.types.KtType +import org.jetbrains.kotlin.analysis.utils.printer.PrettyPrinter +import org.jetbrains.kotlin.analysis.utils.relfection.renderAsDataClassToString +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ControlFlow +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.DuplicateInfo +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ExtractableCodeDescriptorWithConflictsResult +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.IExtractableCodeDescriptor +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.IExtractableCodeDescriptorWithConflicts +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.IReplacement +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.TypeParameter +import org.jetbrains.kotlin.lexer.KtKeywordToken +import org.jetbrains.kotlin.lexer.KtModifierKeywordToken +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.psi.KtSimpleNameExpression +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType +import org.jetbrains.kotlin.utils.mapToSetOrEmpty + +data class ExtractableCodeDescriptor( + val context: KtElement, + override val extractionData: ExtractionData, + override val suggestedNames: List, + override val visibility: KtModifierKeywordToken?, + override val parameters: List, + override val receiverParameter: Parameter?, + override val typeParameters: List, + override val replacementMap: MultiMap>, + override val controlFlow: ControlFlow, + override val returnType: KtType, + override val modifiers: List = emptyList(), + override val optInMarkers: List = emptyList(), + val annotations: List = emptyList() +) : IExtractableCodeDescriptor { + override val name: String get() = suggestedNames.firstOrNull() ?: "" + + override val duplicates: List> = emptyList() + + private val isUnitReturn: Boolean = analyze(context) { returnType.isUnit } + + override fun isUnitReturnType(): Boolean = isUnitReturn + + override val annotationsText: String + get() { + if (annotations.isEmpty()) return "" + val container = extractionData.commonParent.getStrictParentOfType() ?: return "" + val classIds = annotations.mapNotNull { it.classId }.toSet() + return analyze(container) { + val filteredRenderer = KtDeclarationRendererForSource.WITH_QUALIFIED_NAMES.annotationRenderer.with { + annotationFilter = annotationFilter.and(object : KtRendererAnnotationsFilter { + context(KtAnalysisSession) + override fun filter( + annotation: KtAnnotationApplication, owner: KtAnnotated + ): Boolean = annotation.classId in classIds + }) + + } + val printer = PrettyPrinter() + filteredRenderer.renderAnnotations(container.getSymbol(), printer) + printer.toString() + "\n" + } + } +} + +internal fun getPossibleReturnTypes(cfg: ControlFlow): List { + return cfg.possibleReturnTypes +} + +data class ExtractableCodeDescriptorWithConflicts( + override val descriptor: ExtractableCodeDescriptor, + override val conflicts: MultiMap +) : ExtractableCodeDescriptorWithConflictsResult(), IExtractableCodeDescriptorWithConflicts diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/ExtractionData.kt b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/ExtractionData.kt new file mode 100644 index 000000000000..c53b8e4b3ea1 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/ExtractionData.kt @@ -0,0 +1,84 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.k2.refactoring.extractFunction + +import com.intellij.openapi.application.runReadAction +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Key +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMember +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.refactoring.suggested.endOffset +import com.intellij.refactoring.suggested.startOffset +import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.idea.base.psi.unifier.KotlinPsiRange +import org.jetbrains.kotlin.idea.refactoring.introduce.ExtractableSubstringInfo +import org.jetbrains.kotlin.idea.refactoring.introduce.extractableSubstringInfo +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ExtractionOptions +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.IExtractionData +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ResolveResult +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.encodeReferences +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.unmarkReferencesInside +import org.jetbrains.kotlin.idea.refactoring.introduce.substringContextOrThis +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.* + +data class ExtractionData( + override val originalFile: KtFile, + override val originalRange: KotlinPsiRange, + override val targetSibling: PsiElement, + override val duplicateContainer: PsiElement? = null, + override val options: ExtractionOptions = ExtractionOptions.DEFAULT +) : IExtractionData { + + override val project: Project = originalFile.project + override val originalElements: List = originalRange.elements + override val physicalElements = originalElements.map { it.substringContextOrThis } + + override val substringInfo: ExtractableSubstringInfo? + get() = (originalElements.singleOrNull() as? KtExpression)?.extractableSubstringInfo + + override val insertBefore: Boolean = options.extractAsProperty + || targetSibling.getStrictParentOfType()?.let { + it is KtDeclarationWithBody || it is KtAnonymousInitializer + } ?: false + + override val expressions = originalElements.filterIsInstance() + + override val codeFragmentText: String by lazy { + val originalElements = originalElements + when (originalElements.size) { + 0 -> "" + 1 -> originalElements.first().text + else -> originalFile.text.substring(originalElements.first().startOffset, originalElements.last().endOffset) + } + } + + override val commonParent: KtElement = PsiTreeUtil.findCommonParent(physicalElements) as KtElement + + init { + analyze(commonParent) { + encodeReferences({ expr -> expr.getSmartCastInfo() != null }) { physicalRef -> + val resolve = physicalRef.mainReference.resolve() + val declaration = + resolve as? KtNamedDeclaration ?: resolve as? PsiMember + //if this resolves to the receiver, then retrieve corresponding class + ?: ((resolve as? KtTypeReference)?.typeElement as? KtUserType)?.referenceExpression?.mainReference?.resolve() + declaration?.putCopyableUserData(targetKey, physicalRef) + declaration?.let { ResolveResult(physicalRef, declaration, declaration, physicalRef) } + } + } + } + + override fun dispose() { + expressions.forEach(::unmarkReferencesInside) + expressions.forEach { e -> + runReadAction { + if (!e.isValid) return@runReadAction + e.forEachDescendantOfType { it.putCopyableUserData(targetKey, null) } + } + } + } +} + +internal val targetKey: Key = Key.create("RESOLVE_RESULT") \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/ExtractionGeneratorConfiguration.kt b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/ExtractionGeneratorConfiguration.kt new file mode 100644 index 000000000000..43b83f0be836 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/ExtractionGeneratorConfiguration.kt @@ -0,0 +1,11 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.k2.refactoring.extractFunction + +import org.jetbrains.kotlin.analysis.api.types.KtType +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ExtractionGeneratorOptions +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.IExtractionGeneratorConfiguration + +data class ExtractionGeneratorConfiguration( + override val descriptor: ExtractableCodeDescriptor, + override val generatorOptions: ExtractionGeneratorOptions +) : IExtractionGeneratorConfiguration \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/ExtractionResult.kt b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/ExtractionResult.kt new file mode 100644 index 000000000000..d745f128f4cd --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/ExtractionResult.kt @@ -0,0 +1,16 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.k2.refactoring.extractFunction + +import org.jetbrains.kotlin.analysis.api.types.KtType +import org.jetbrains.kotlin.idea.base.psi.unifier.KotlinPsiRange +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.IExtractionResult +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.unmarkReferencesInside +import org.jetbrains.kotlin.psi.KtNamedDeclaration + +data class ExtractionResult( + override val config: ExtractionGeneratorConfiguration, + override val declaration: KtNamedDeclaration, + override val duplicateReplacers: Map Unit> +) : IExtractionResult { + override fun dispose() = unmarkReferencesInside(declaration) +} \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/KotlinFirExtractFunctionHandler.kt b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/KotlinFirExtractFunctionHandler.kt new file mode 100644 index 000000000000..46c2e1ba5296 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/KotlinFirExtractFunctionHandler.kt @@ -0,0 +1,126 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. + +package org.jetbrains.kotlin.idea.k2.refactoring.introduce.extractFunction + +import com.intellij.java.refactoring.JavaRefactoringBundle +import com.intellij.openapi.actionSystem.ex.ActionUtil +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiElement +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.annotations.Nls +import org.jetbrains.kotlin.analysis.api.KtAllowAnalysisOnEdt +import org.jetbrains.kotlin.analysis.api.lifetime.allowAnalysisOnEdt +import org.jetbrains.kotlin.analysis.api.types.KtType +import org.jetbrains.kotlin.idea.base.analysis.api.utils.analyzeInModalWindow +import org.jetbrains.kotlin.idea.base.psi.unifier.KotlinPsiRange +import org.jetbrains.kotlin.idea.base.psi.unifier.toRange +import org.jetbrains.kotlin.idea.base.resources.KotlinBundle +import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.ExtractableCodeDescriptor +import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.ExtractableCodeDescriptorWithConflicts +import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.ExtractionData +import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.ExtractionGeneratorConfiguration +import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.ExtractionResult +import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.ui.KotlinFirExtractFunctionDialog +import org.jetbrains.kotlin.idea.k2.refactoring.introduce.extractionEngine.ExtractionDataAnalyzer +import org.jetbrains.kotlin.idea.k2.refactoring.introduce.extractionEngine.ExtractionEngineHelper +import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.AbstractExtractKotlinFunctionHandler +import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.AbstractInplaceExtractionHelper +import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.EXTRACT_FUNCTION +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.AnalysisResult +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ExtractionGeneratorOptions +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.IExtractableCodeDescriptor +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.IExtractionEngine +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.isIdentifier +import org.jetbrains.kotlin.utils.addToStdlib.safeAs + +class KotlinFirExtractFunctionHandler(private val helper: ExtractionEngineHelper = InplaceExtractionHelper) : + AbstractExtractKotlinFunctionHandler() { + + object InteractiveExtractionHelper : ExtractionEngineHelper(EXTRACT_FUNCTION) { + @OptIn(KtAllowAnalysisOnEdt::class) + override fun configureAndRun( + project: Project, + editor: Editor, + descriptorWithConflicts: ExtractableCodeDescriptorWithConflicts, + onFinish: (ExtractionResult) -> Unit + ) { + fun afterFinish(extraction: ExtractionResult) { + // TODO duplicates are not supported yet https://youtrack.jetbrains.com/issue/KTIJ-29165 + onFinish(extraction) + } + allowAnalysisOnEdt { + KotlinFirExtractFunctionDialog(descriptorWithConflicts.descriptor.extractionData.project, descriptorWithConflicts) { + doRefactor(ExtractionGeneratorConfiguration(it, ExtractionGeneratorOptions.DEFAULT), ::afterFinish) + }.show() + } + } + } + + object InplaceExtractionHelper : ExtractionEngineHelper(EXTRACT_FUNCTION), + AbstractInplaceExtractionHelper { + override fun createRestartHandler(): AbstractExtractKotlinFunctionHandler = + KotlinFirExtractFunctionHandler(InteractiveExtractionHelper) + + override fun extractDuplicates( + duplicateReplacers: Map Unit>, project: Project, editor: Editor + ) { + // TODO duplicates are not supported yet https://youtrack.jetbrains.com/issue/KTIJ-29165 + } + + override fun doRefactor( + descriptor: IExtractableCodeDescriptor, onFinish: (ExtractionResult) -> Unit + ) { + val configuration = + ExtractionGeneratorConfiguration(descriptor as ExtractableCodeDescriptor, ExtractionGeneratorOptions.DEFAULT) + doRefactor(configuration, onFinish) + } + + override fun configureAndRun( + project: Project, + editor: Editor, + descriptorWithConflicts: ExtractableCodeDescriptorWithConflicts, + onFinish: (ExtractionResult) -> Unit + ) { + super.configureAndRun(project, editor, descriptorWithConflicts, onFinish) + } + + @Nls + override fun getIdentifierError(file: KtFile, variableRange: TextRange): String? { + val call = PsiTreeUtil.findElementOfClassAtOffset(file, variableRange.startOffset, KtCallExpression::class.java, false) + val name = file.viewProvider.document.getText(variableRange) + return if (!name.isIdentifier()) { + JavaRefactoringBundle.message("template.error.invalid.identifier.name") + } else if (analyzeInModalWindow(file, KotlinBundle.message("fix.change.signature.prepare")) { call?.resolveCall() == null }) { + JavaRefactoringBundle.message("extract.method.error.method.conflict") + } else { + null + } + } + } + + override fun doInvoke( + editor: Editor, + file: KtFile, + elements: List, + targetSibling: PsiElement + ) { + + val data = ActionUtil.underModalProgress(file.project, KotlinBundle.message("fix.change.signature.prepare")) { + val adjustedElements = elements.singleOrNull().safeAs()?.statements ?: elements + ExtractionData(file, adjustedElements.toRange(false), targetSibling) + } + + val engine = object : + IExtractionEngine( + helper + ) { + override fun performAnalysis(extractionData: ExtractionData): AnalysisResult { + return ExtractionDataAnalyzer(extractionData).performAnalysis() + } + } + engine.run(editor, data) + } +} \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/Parameter.kt b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/Parameter.kt new file mode 100644 index 000000000000..44b62b0800c8 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/Parameter.kt @@ -0,0 +1,112 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.k2.refactoring.extractFunction + +import com.intellij.psi.PsiNamedElement +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.analysis.api.symbols.KtAnonymousObjectSymbol +import org.jetbrains.kotlin.analysis.api.types.KtFlexibleType +import org.jetbrains.kotlin.analysis.api.types.KtNonErrorClassType +import org.jetbrains.kotlin.analysis.api.types.KtType +import org.jetbrains.kotlin.analysis.api.types.KtTypeNullability +import org.jetbrains.kotlin.idea.k2.refactoring.introduce.extractionEngine.isResolvableInScope +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ControlFlow +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.IMutableParameter +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.IParameter +import org.jetbrains.kotlin.psi.KtElement + +interface Parameter : IParameter { + val originalDescriptor: PsiNamedElement +} + +internal sealed class TypePredicate { + context(KtAnalysisSession) + abstract fun isApplicable(ktType: KtType): Boolean +} + +internal class SubTypePredicate(private val type: KtType) : TypePredicate() { + context(KtAnalysisSession) + override fun isApplicable(ktType: KtType): Boolean = ktType.isSubTypeOf(type) +} + +internal class SuperTypePredicate(private val type: KtType) : TypePredicate() { + context(KtAnalysisSession) + override fun isApplicable(ktType: KtType): Boolean = ktType.isSubTypeOf(type) +} + +internal class ExactTypePredicate(private val type: KtType) : TypePredicate() { + context(KtAnalysisSession) + override fun isApplicable(ktType: KtType): Boolean = ktType.isEqualTo(type) +} + +internal class AndPredicate(val predicates: Set) : TypePredicate() { + context(KtAnalysisSession) + override fun isApplicable(ktType: KtType): Boolean = predicates.all { it.isApplicable(ktType) } +} + +internal class MutableParameter( + override val argumentText: String, + override val originalDescriptor: PsiNamedElement, + override val receiverCandidate: Boolean, + private val originalType: KtType, + private val scope: KtElement +) : Parameter, IMutableParameter { + + private val typePredicates = mutableSetOf() + + fun addTypePredicate(typePredicate: TypePredicate) { + typePredicates.add(typePredicate) + } + + override var refCount = 0 + var currentName: String? = null + override val name: String get() = currentName!! + + override var mirrorVarName: String? = null + + context(KtAnalysisSession) + private fun allParameterTypeCandidates(): List { + val andPredicate = AndPredicate(typePredicates) + val typeSet = if (originalType is KtFlexibleType) { + val lower = originalType.lowerBound + val upper = originalType.upperBound + LinkedHashSet().apply { + if (andPredicate.isApplicable(upper)) add(upper) + if (andPredicate.isApplicable(lower)) add(lower) + } + } else linkedSetOf(originalType) + + val addNullableTypes = originalType is KtFlexibleType && + originalType.lowerBound.nullability != originalType.upperBound.nullability && + typeSet.size > 1 + val superTypes = originalType.getAllSuperTypes().filter { + andPredicate.isApplicable(it) + } + + for (superType in superTypes) { + if (addNullableTypes) { + typeSet.add(superType.withNullability(KtTypeNullability.NULLABLE)) + } + typeSet.add(superType) + } + + return typeSet.toList() + } + + override fun getParameterTypeCandidates(): List { + analyze(scope) { + return allParameterTypeCandidates().filter { + !(it is KtNonErrorClassType && it.classSymbol is KtAnonymousObjectSymbol) && + isResolvableInScope(it, scope, mutableSetOf()) + } + } + } + + override val parameterType: KtType + get() = getParameterTypeCandidates().firstOrNull() ?: originalType +} + +val ControlFlow.possibleReturnTypes: List + get() { + return listOf(outputValueBoxer.returnType) + } diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/package-info.java b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/package-info.java new file mode 100644 index 000000000000..ca4dd1a70e52 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/package-info.java @@ -0,0 +1,5 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +@ApiStatus.Internal +package org.jetbrains.kotlin.idea.k2.refactoring.extractFunction; + +import org.jetbrains.annotations.ApiStatus; \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/parametersUtil.kt b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/parametersUtil.kt new file mode 100644 index 000000000000..370f5fd89e13 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/parametersUtil.kt @@ -0,0 +1,487 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.k2.refactoring.extractFunction + +import com.intellij.psi.PsiMember +import com.intellij.psi.PsiNameIdentifierOwner +import com.intellij.psi.PsiNamedElement +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.psi.util.parentOfType +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.analysis.api.calls.KtCallableMemberCall +import org.jetbrains.kotlin.analysis.api.calls.KtExplicitReceiverValue +import org.jetbrains.kotlin.analysis.api.calls.KtImplicitReceiverValue +import org.jetbrains.kotlin.analysis.api.calls.KtPartiallyAppliedSymbol +import org.jetbrains.kotlin.analysis.api.calls.KtReceiverValue +import org.jetbrains.kotlin.analysis.api.calls.KtSmartCastedReceiverValue +import org.jetbrains.kotlin.analysis.api.calls.singleCallOrNull +import org.jetbrains.kotlin.analysis.api.calls.symbol +import org.jetbrains.kotlin.analysis.api.components.KtDiagnosticCheckerFilter +import org.jetbrains.kotlin.analysis.api.components.buildClassType +import org.jetbrains.kotlin.analysis.api.fir.diagnostics.KtFirDiagnostic +import org.jetbrains.kotlin.analysis.api.signatures.KtCallableSignature +import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtClassKind +import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtClassifierSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtConstructorSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtReceiverParameterSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtTypeParameterSymbol +import org.jetbrains.kotlin.analysis.api.types.KtNonErrorClassType +import org.jetbrains.kotlin.analysis.api.types.KtType +import org.jetbrains.kotlin.idea.base.codeInsight.KotlinDeclarationNameValidator +import org.jetbrains.kotlin.idea.base.codeInsight.KotlinNameSuggester +import org.jetbrains.kotlin.idea.base.codeInsight.KotlinNameSuggester.Companion.suggestNameByName +import org.jetbrains.kotlin.idea.base.codeInsight.KotlinNameSuggestionProvider +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.AddPrefixReplacement +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.AnalysisResult +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.FqNameReplacement +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ParametersInfo +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.RenameReplacement +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ResolveResult +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ResolvedReferenceInfo +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.TypeDescriptor +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.TypeParameter +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.WrapParameterInWithReplacement +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.collectReferencedTypes +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.collectRelevantConstraints +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.processTypeIfExtractable +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.resolveResult +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.lexer.KtToken +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.KtBlockExpression +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtCallableDeclaration +import org.jetbrains.kotlin.psi.KtCallableReferenceExpression +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtConstructor +import org.jetbrains.kotlin.psi.KtDestructuringDeclarationEntry +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtFunctionLiteral +import org.jetbrains.kotlin.psi.KtNamedDeclaration +import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.psi.KtOperationReferenceExpression +import org.jetbrains.kotlin.psi.KtParameter +import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.psi.KtPropertyAccessor +import org.jetbrains.kotlin.psi.KtQualifiedExpression +import org.jetbrains.kotlin.psi.KtSimpleNameExpression +import org.jetbrains.kotlin.psi.KtSuperExpression +import org.jetbrains.kotlin.psi.KtThisExpression +import org.jetbrains.kotlin.psi.KtTypeParameter +import org.jetbrains.kotlin.psi.KtTypeParameterListOwner +import org.jetbrains.kotlin.psi.KtTypeReference +import org.jetbrains.kotlin.psi.KtUserType +import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType +import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType +import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch +import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelector +import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelectorOrThis +import org.jetbrains.kotlin.psi.psiUtil.isInsideOf +import org.jetbrains.kotlin.types.Variance +import org.jetbrains.kotlin.types.expressions.OperatorConventions + +context(KtAnalysisSession) +internal fun ExtractionData.inferParametersInfo( + virtualBlock: KtBlockExpression, + modifiedVariables: Set, + typeDescriptor: TypeDescriptor, +): ParametersInfo { + val info = ParametersInfo() + + val extractedDescriptorToParameter = LinkedHashMap() + + for (refInfo in getBrokenReferencesInfo(virtualBlock)) { + val ref = refInfo.refExpr + + val selector = (ref.parent as? KtCallExpression) ?: ref + val superExpr = (selector.parent as? KtQualifiedExpression)?.receiverExpression as? KtSuperExpression + if (superExpr != null) { + info.errorMessage = AnalysisResult.ErrorMessage.SUPER_CALL + return info + } + + registerParameter( + info, + refInfo, + extractedDescriptorToParameter, + false + ) + + } + + val varNameValidator = KotlinDeclarationNameValidator( + commonParent, + true, + KotlinNameSuggestionProvider.ValidatorTarget.PARAMETER + ) + + val existingParameterNames = hashSetOf() + val generateArguments: (KtType) -> List = + { ktType -> (ktType as? KtNonErrorClassType)?.ownTypeArguments?.mapNotNull { it.type } ?: emptyList() } + for ((namedElement, parameter) in extractedDescriptorToParameter) { + if (!parameter + .parameterType + .processTypeIfExtractable( + info.typeParameters, + info.nonDenotableTypes, + true, + generateArguments, + typeDescriptor::isResolvableInScope + ) + + ) { + continue + } + + with(parameter) { + if (currentName == null) { + currentName = with(KotlinNameSuggester()) { + suggestTypeNames(parameterType) + }.map { nameByType -> suggestNameByName(nameByType) { varNameValidator.validate(it) } }.firstOrNull() + } + + require(currentName != null || parameter.receiverCandidate) + + if (currentName != null) { + if ("$currentName" in existingParameterNames) { + var index = 0 + while ("$currentName$index" in existingParameterNames) { + index++ + } + currentName = "$currentName$index" + } + currentName?.let { existingParameterNames += it } + } else { + currentName = "receiver" + } + + mirrorVarName = if (namedElement.name in modifiedVariables) suggestNameByName( + name + ) { varNameValidator.validate(it) } else null + info.parameters.add(this) + } + } + + for (typeToCheck in info.typeParameters.flatMap { it.collectReferencedTypes() }.map { it.getKtType() } ) { + typeToCheck.processTypeIfExtractable( + info.typeParameters, + info.nonDenotableTypes, + true, + generateArguments, + typeDescriptor::isResolvableInScope + ) + } + + return info +} + +context(KtAnalysisSession) +private fun ExtractionData.registerParameter( + info: ParametersInfo, + refInfo: ResolvedReferenceInfo, + extractedDescriptorToParameter: HashMap, + isMemberExtension: Boolean +) { + val (originalRef, _, originalDeclaration, resolvedCall) = refInfo.resolveResult + + val partiallyAppliedSymbol = + resolvedCall?.resolveCall()?.singleCallOrNull>()?.partiallyAppliedSymbol + val dispatchReceiver = partiallyAppliedSymbol?.dispatchReceiver + val extensionReceiver = partiallyAppliedSymbol?.extensionReceiver + //Context receivers are not supported. + //So if both receivers are provided, + //unresolved conflict is generated by `validate` check and + //if "Proceed Anyway" is selected, the `extensionReceiver` is chosen to generate partly broken code + val receiverToExtract = extensionReceiver ?: dispatchReceiver + val receiverSymbol = + (((receiverToExtract as? KtSmartCastedReceiverValue)?.original ?: receiverToExtract) as? KtImplicitReceiverValue)?.symbol + val thisSymbol = (receiverSymbol as? KtReceiverParameterSymbol)?.type?.expandedClassSymbol ?: receiverSymbol + val hasThisReceiver = thisSymbol != null + val thisExpr = refInfo.refExpr.parent as? KtThisExpression + + val referencedClassifierSymbol: KtClassifierSymbol? = getReferencedClassifierSymbol(thisSymbol, originalDeclaration, refInfo, partiallyAppliedSymbol) + + if (referencedClassifierSymbol != null) { + registerQualifierReplacements(referencedClassifierSymbol, info, originalDeclaration, originalRef) + } else { + val extractThis = (hasThisReceiver && refInfo.smartCast == null) || thisExpr != null + val extractOrdinaryParameter = + originalDeclaration is KtDestructuringDeclarationEntry || + originalDeclaration is KtProperty || + originalDeclaration is KtParameter || + originalDeclaration is KtFunctionLiteral + + val extractFunctionRef = + options.captureLocalFunctions + && originalRef.getReferencedName() == originalDeclaration.name // to forbid calls by convention + && originalDeclaration is KtNamedFunction && originalDeclaration.isLocal + + val elementToExtract = (if (extractThis) thisSymbol?.psi as? PsiNamedElement else null) ?: originalDeclaration + + if (extractThis || extractOrdinaryParameter || extractFunctionRef) { + val parameterExpression = getParameterArgumentExpression(originalRef, receiverToExtract, refInfo.smartCast) + val parameter = extractedDescriptorToParameter.getOrPut(elementToExtract) { + var argumentText = + calculateArgumentText( + hasThisReceiver, + extractThis, + extractFunctionRef, + elementToExtract, + thisExpr ?: refInfo.refExpr, + originalDeclaration + ) + + val originalType = createOriginalType( + extractFunctionRef, + originalDeclaration, + parameterExpression, + receiverToExtract + ) + + MutableParameter(argumentText, elementToExtract, extractThis, originalType, targetSibling as KtElement) + } + + // TODO add type predicate based on called functions https://youtrack.jetbrains.com/issue/KTIJ-29166 + if (extractFunctionRef) { + parameter.addTypePredicate(ExactTypePredicate(parameter.parameterType)) + } else if (extractOrdinaryParameter) { + parameterExpression?.getExpectedType()?.let { + parameter.addTypePredicate(SubTypePredicate(it)) + } + } + + parameter.refCount++ + + if (!extractThis) { + parameter.currentName = when (originalDeclaration) { + is PsiNameIdentifierOwner -> originalDeclaration.nameIdentifier?.text + else -> null + } + } + + // register parameter replacements + info.originalRefToParameter.putValue(originalRef, parameter) + + val replacement = when { + isMemberExtension -> WrapParameterInWithReplacement(parameter) + hasThisReceiver && extractThis -> AddPrefixReplacement(parameter) + else -> RenameReplacement(parameter) + } + info.replacementMap.putValue(originalRef, replacement) + } + } +} + +private fun getParameterArgumentExpression( + originalRef: KtSimpleNameExpression, + receiverToExtract: KtReceiverValue?, + smartCast: KtType? +): KtExpression? = when { + receiverToExtract is KtExplicitReceiverValue -> { + val receiverExpression = receiverToExtract.expression + // If p.q has a smart-cast, then extract the entire qualified expression + if (smartCast != null) receiverExpression.parent as KtExpression else receiverExpression + } + + receiverToExtract != null && smartCast == null -> null + else -> (originalRef.parent as? KtThisExpression) ?: originalRef +} + +private fun ExtractionData.calculateArgumentText( + hasThisReceiver: Boolean, + extractThis: Boolean, + extractFunctionRef: Boolean, + elementToExtract: PsiNamedElement, + argExpr: KtExpression, + originalDeclaration: PsiNamedElement +): String { + var argumentText = + if (hasThisReceiver && extractThis) { + val label = elementToExtract.name?.let { "@$it" } ?: "" + "this$label" + } else { + val argumentExpr = argExpr.getQualifiedExpressionForSelectorOrThis() + if (argumentExpr is KtOperationReferenceExpression) { + val nameElement = argumentExpr.getReferencedNameElement() + val nameElementType = nameElement.node.elementType + (nameElementType as? KtToken)?.let { + OperatorConventions.getNameForOperationSymbol(it)?.asString() + } ?: nameElement.text + } else argumentExpr.text + ?: throw AssertionError("reference shouldn't be empty: code fragment = $codeFragmentText") + } + if (extractFunctionRef) { + val receiverTypeText = (originalDeclaration as KtCallableDeclaration).receiverTypeReference?.text ?: "" + argumentText = "$receiverTypeText::$argumentText" + } + return argumentText +} + +/** + * Register replacements which expand locally available types to FQ names if possible. + */ +context(KtAnalysisSession) +private fun ExtractionData.registerQualifierReplacements( + referencedClassifierSymbol: KtClassifierSymbol, + parametersInfo: ParametersInfo, + originalDeclaration: PsiNamedElement, + originalRef: KtSimpleNameExpression +) { + if (referencedClassifierSymbol is KtTypeParameterSymbol) { + val typeParameter = referencedClassifierSymbol.psi as KtTypeParameter + val listOwner = typeParameter.parentOfType() + if (listOwner == null || !PsiTreeUtil.isAncestor(listOwner, targetSibling, true)) { + parametersInfo.typeParameters.add(TypeParameter(typeParameter, typeParameter.collectRelevantConstraints())) + } + } else if (referencedClassifierSymbol is KtClassOrObjectSymbol) { + val fqName = referencedClassifierSymbol.classIdIfNonLocal?.asSingleFqName() + if (fqName != null) { + val name = when (originalDeclaration) { + is KtConstructor<*> -> null + is KtPropertyAccessor -> originalDeclaration.property.name + is KtClassOrObject -> null + else -> originalDeclaration.name + } + val fqNameChild = if (name != null) fqName.child(Name.identifier(name)) else fqName + parametersInfo.replacementMap.putValue(originalRef, FqNameReplacement(fqNameChild)) + } else { + parametersInfo.nonDenotableTypes.add(buildClassType(referencedClassifierSymbol)) + } + } +} + +context(KtAnalysisSession) +private fun getReferencedClassifierSymbol( + thisSymbol: KtSymbol?, + originalDeclaration: PsiNamedElement, + refInfo: ResolvedReferenceInfo, + partiallyAppliedSymbol: KtPartiallyAppliedSymbol>? +): KtClassifierSymbol? { + val referencedSymbol = (thisSymbol ?: (originalDeclaration as? KtNamedDeclaration)?.getSymbol() + ?: (originalDeclaration as? PsiMember)?.getCallableSymbol()) ?: return null + return when (referencedSymbol) { + is KtClassOrObjectSymbol -> when (referencedSymbol.classKind) { + KtClassKind.OBJECT, KtClassKind.COMPANION_OBJECT, KtClassKind.ENUM_CLASS -> referencedSymbol + //if type reference or call to implicit constructor, then type expansion might be required + else -> if (refInfo.refExpr.getNonStrictParentOfType() != null || partiallyAppliedSymbol?.symbol is KtConstructorSymbol) referencedSymbol else null + } + + is KtTypeParameterSymbol -> referencedSymbol + + is KtConstructorSymbol -> referencedSymbol.getContainingSymbol() as? KtClassifierSymbol + + else -> null + } +} + +context(KtAnalysisSession) +private fun createOriginalType( + extractFunctionRef: Boolean, + originalDeclaration: PsiNamedElement, + parameterExpression: KtExpression?, + receiverToExtract: KtReceiverValue? +): KtType = (if (extractFunctionRef) { + val functionSymbol = (originalDeclaration as KtNamedFunction).getSymbol() as KtFunctionSymbol + val typeString = + buildString { //todo rewrite as soon as functional type can be created by api call: https://youtrack.jetbrains.com/issue/KT-66566 + functionSymbol.receiverParameter?.type?.render(position = Variance.INVARIANT)?.let { + append(it) + append(".") + } + functionSymbol.valueParameters.joinTo( + this, + ", ", + "(", + ")" + ) { //names provided here are removed due to https://youtrack.jetbrains.com/issue/KT-65846 + it.name.asString() + ": " + it.returnType.render(position = Variance.INVARIANT) + } + + append(" -> ") + append(functionSymbol.returnType.render(position = Variance.INVARIANT)) + } + + org.jetbrains.kotlin.psi.KtPsiFactory(originalDeclaration.project).createTypeCodeFragment(typeString, originalDeclaration) + .getContentElement()?.getKtType() +} else { + parameterExpression?.getKtType() ?: receiverToExtract?.type +}) ?: builtinTypes.NULLABLE_ANY + + +private fun ExtractionData.getBrokenReferencesInfo(body: KtBlockExpression): List> { + val newReferences = body.collectDescendantsOfType { it.resolveResult != null } + + val referencesInfo = ArrayList>() + for (newRef in newReferences) { + val originalResolveResult = newRef.resolveResult as? ResolveResult ?: continue + val originalRefExpr = originalResolveResult.originalRefExpr + + val smartCast: KtType? + val possibleTypes: Set + + // Qualified property reference: a.b + val qualifiedExpression = newRef.getQualifiedExpressionForSelector() + if (qualifiedExpression != null) { + val smartCastTarget = originalRefExpr.parent as KtExpression + smartCast = analyze(smartCastTarget) { smartCastTarget.getSmartCastInfo()?.smartCastType } + possibleTypes = analyze(smartCastTarget) { smartCastTarget.getExpectedType()?.let { setOf(it) } ?: emptySet() } + val (isCompanionObject, bothReceivers) = analyze(smartCastTarget) { + val symbol = originalRefExpr.resolveCall()?.singleCallOrNull>()?.partiallyAppliedSymbol + val receiverSymbol = (symbol?.dispatchReceiver as? KtImplicitReceiverValue)?.symbol + ((receiverSymbol?.getContainingSymbol() as? KtClassOrObjectSymbol)?.classKind == KtClassKind.COMPANION_OBJECT) to + (symbol?.dispatchReceiver != null && symbol.extensionReceiver != null) + } + val shouldSkipPrimaryReceiver = smartCast == null + && !isCompanionObject + && qualifiedExpression.receiverExpression !is KtSuperExpression + if (shouldSkipPrimaryReceiver && !bothReceivers) continue + } else { + if (newRef.getParentOfTypeAndBranch { callableReference } != null) continue + smartCast = analyze(originalRefExpr) { originalRefExpr.getSmartCastInfo()?.smartCastType } + possibleTypes = analyze(originalRefExpr) { originalRefExpr.getExpectedType()?.let { setOf(it) } ?: emptySet() } + } + + val parent = newRef.parent + + // Skip P in type references like 'P.Q' + if (parent is KtUserType && (parent.parent as? KtUserType)?.qualifier == parent) continue + + val descriptor = newRef.mainReference.resolve() + val originalDescriptor = originalRefExpr.mainReference.resolve() + val isBadRef = descriptor != originalDescriptor + + //if resolves to the same element in copy, then no additional parameter is required + if (isBadRef && + descriptor != null && originalDescriptor != null && + originalDescriptor.getCopyableUserData(targetKey) == descriptor.getCopyableUserData(targetKey) + ) { + continue + } + + fun hasResolveErrors(): Boolean = + analyze(newRef) { newRef.getDiagnostics(KtDiagnosticCheckerFilter.ONLY_COMMON_CHECKERS) } + .any { + it.diagnosticClass == KtFirDiagnostic.UnresolvedReferenceWrongReceiver::class || + it.diagnosticClass == KtFirDiagnostic.UnresolvedReference::class + } + if ((isBadRef || hasResolveErrors() || smartCast != null) && + !originalResolveResult.declaration.isInsideOf(physicalElements) + ) { + referencesInfo.add( + ResolvedReferenceInfo( + newRef, + originalResolveResult, + smartCast, + possibleTypes, + ) + ) + } + } + + return referencesInfo +} \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/ui/FirExtractFunctionParameterTablePanel.kt b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/ui/FirExtractFunctionParameterTablePanel.kt new file mode 100644 index 000000000000..8091b3bdae2b --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/ui/FirExtractFunctionParameterTablePanel.kt @@ -0,0 +1,136 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. + +package org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ui + +import com.intellij.openapi.util.NlsSafe +import com.intellij.ui.components.JBComboBoxLabel +import com.intellij.ui.components.editors.JBComboBoxTableCellEditorComponent +import com.intellij.util.ui.AbstractTableCellEditor +import org.jetbrains.kotlin.analysis.api.KtAllowAnalysisOnEdt +import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.analysis.api.lifetime.allowAnalysisOnEdt +import org.jetbrains.kotlin.analysis.api.types.KtType +import org.jetbrains.kotlin.idea.base.resources.KotlinBundle +import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.Parameter +import org.jetbrains.kotlin.idea.refactoring.introduce.ui.AbstractParameterTablePanel +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.types.Variance + +import java.awt.Component +import javax.swing.JTable +import javax.swing.table.DefaultTableCellRenderer + +abstract class FirExtractFunctionParameterTablePanel : + AbstractParameterTablePanel() { + companion object { + const val PARAMETER_TYPE_COLUMN = 2 + } + + class ParameterInfo( + originalParameter: Parameter, + val isReceiver: Boolean + ) : AbstractParameterInfo(originalParameter) { + var type = originalParameter.parameterType + + init { + name = if (isReceiver) KotlinBundle.message("text.receiver") else originalParameter.name + } + + override fun toParameter() = object : Parameter by originalParameter { + override val name: String = this@ParameterInfo.name + override val parameterType: KtType = this@ParameterInfo.type + } + } + + abstract val context: KtElement + + override fun createTableModel(): TableModelBase = MyTableModel() + + @OptIn(KtAllowAnalysisOnEdt::class) + override fun createAdditionalColumns() { + with(table.columnModel.getColumn(PARAMETER_TYPE_COLUMN)) { + headerValue = KotlinBundle.message("text.type") + cellRenderer = object : DefaultTableCellRenderer() { + private val myLabel = JBComboBoxLabel() + + override fun getTableCellRendererComponent( + table: JTable, value: Any, isSelected: Boolean, hasFocus: Boolean, row: Int, column: Int + ): Component { + @NlsSafe val renderType = + allowAnalysisOnEdt { analyze(context) { (value as KtType).render(position = Variance.IN_VARIANCE) } } + myLabel.text = renderType + myLabel.background = if (isSelected) table.selectionBackground else table.background + myLabel.foreground = if (isSelected) table.selectionForeground else table.foreground + if (isSelected) { + myLabel.setSelectionIcon() + } else { + myLabel.setRegularIcon() + } + return myLabel + } + } + cellEditor = object : AbstractTableCellEditor() { + val myEditorComponent = JBComboBoxTableCellEditorComponent() + + override fun getCellEditorValue() = myEditorComponent.editorValue + + override fun getTableCellEditorComponent( + table: JTable, value: Any, isSelected: Boolean, row: Int, column: Int + ): Component { + val info = parameterInfos[row] + + myEditorComponent.setCell(table, row, column) + myEditorComponent.setOptions(*info.originalParameter.getParameterTypeCandidates().toTypedArray()) + myEditorComponent.setDefaultValue(info.type) + myEditorComponent.setToString { + analyze(context) { (it as KtType).render(position = Variance.IN_VARIANCE) } + } + + return myEditorComponent + } + } + } + } + + fun init(receiver: Parameter?, parameters: List) { + parameterInfos = parameters.mapTo( + if (receiver != null) arrayListOf(ParameterInfo(receiver, true)) else arrayListOf() + ) { ParameterInfo(it, false) } + + super.init() + } + + private inner class MyTableModel : TableModelBase() { + override fun getColumnCount() = 3 + + override fun getValueAt(rowIndex: Int, columnIndex: Int): Any? { + if (columnIndex == PARAMETER_TYPE_COLUMN) return parameterInfos[rowIndex].type + return super.getValueAt(rowIndex, columnIndex) + } + + override fun setValueAt(aValue: Any?, rowIndex: Int, columnIndex: Int) { + if (columnIndex == PARAMETER_TYPE_COLUMN) { + parameterInfos[rowIndex].type = aValue as KtType + updateSignature() + return + } + + super.setValueAt(aValue, rowIndex, columnIndex) + } + + override fun isCellEditable(rowIndex: Int, columnIndex: Int): Boolean { + val info = parameterInfos[rowIndex] + return when (columnIndex) { + PARAMETER_NAME_COLUMN -> super.isCellEditable(rowIndex, columnIndex) && !info.isReceiver + PARAMETER_TYPE_COLUMN -> isEnabled && info.isEnabled && info.originalParameter.getParameterTypeCandidates().size > 1 + else -> super.isCellEditable(rowIndex, columnIndex) + } + } + } + + val selectedReceiverInfo: ParameterInfo? + get() = parameterInfos.singleOrNull { it.isEnabled && it.isReceiver } + + val selectedParameterInfos: List + get() = parameterInfos.filter { it.isEnabled && !it.isReceiver } +} diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/ui/KotlinFirExtractFunctionDialog.form b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/ui/KotlinFirExtractFunctionDialog.form new file mode 100644 index 000000000000..2b27a1ca5d8e --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/ui/KotlinFirExtractFunctionDialog.form @@ -0,0 +1,167 @@ + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/ui/KotlinFirExtractFunctionDialog.java b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/ui/KotlinFirExtractFunctionDialog.java new file mode 100644 index 000000000000..a813811d425a --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/ui/KotlinFirExtractFunctionDialog.java @@ -0,0 +1,309 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. + +package org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.ui; + +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.application.ReadAction; +import com.intellij.openapi.fileTypes.PlainTextFileType; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.util.NlsSafe; +import com.intellij.psi.PsiElement; +import com.intellij.refactoring.ui.NameSuggestionsField; +import com.intellij.ui.TitledSeparator; +import com.intellij.util.concurrency.AppExecutorUtil; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.MultiMap; +import kotlin.Unit; +import kotlin.jvm.functions.Function0; +import kotlin.jvm.functions.Function1; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.analysis.api.types.KtType; +import org.jetbrains.kotlin.idea.base.resources.KotlinBundle; +import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.*; +import org.jetbrains.kotlin.idea.refactoring.KotlinCommonRefactoringUtilKt; +import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ui.FirExtractFunctionParameterTablePanel; +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ExtractUtilKt; +import org.jetbrains.kotlin.idea.refactoring.introduce.ui.KotlinSignatureComponent; +import org.jetbrains.kotlin.lexer.KtModifierKeywordToken; +import org.jetbrains.kotlin.lexer.KtTokens; +import org.jetbrains.kotlin.psi.KtElement; +import org.jetbrains.kotlin.psi.KtPsiFactory; +import org.jetbrains.kotlin.psi.KtTypeCodeFragment; +import org.jetbrains.kotlin.psi.psiUtil.KtPsiUtilKt; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class KotlinFirExtractFunctionDialog extends DialogWrapper { + private JPanel contentPane; + private TitledSeparator inputParametersPanel; + private JComboBox visibilityBox; + private KotlinSignatureComponent signaturePreviewField; + private JPanel functionNamePanel; + private NameSuggestionsField functionNameField; + private JLabel functionNameLabel; + private JComboBox returnTypeBox; + private JPanel returnTypePanel; + private FirExtractFunctionParameterTablePanel parameterTablePanel; + + private final Project project; + + private final ExtractableCodeDescriptorWithConflicts originalDescriptor; + + private final Function1 onAccept; + + public KotlinFirExtractFunctionDialog( + @NotNull Project project, + @NotNull ExtractableCodeDescriptorWithConflicts originalDescriptor, + @NotNull Function1 onAccept + ) { + super(project, true); + + this.project = project; + this.originalDescriptor = originalDescriptor; + this.onAccept = onAccept; + + setModal(true); + setTitle(KotlinBundle.message("extract.function")); + init(); + update(); + } + + private void createUIComponents() { + this.signaturePreviewField = new KotlinSignatureComponent("", project); + } + + private boolean isVisibilitySectionAvailable() { + return ExtractUtilKt.isVisibilityApplicable(originalDescriptor.getDescriptor().getExtractionData()); + } + + private String getFunctionName() { + return KtPsiUtilKt.quoteIfNeeded(functionNameField.getEnteredName()); + } + + @Nullable + private KtModifierKeywordToken getVisibility() { + if (!isVisibilitySectionAvailable()) return null; + + KtModifierKeywordToken value = (KtModifierKeywordToken) visibilityBox.getSelectedItem(); + return KtTokens.DEFAULT_VISIBILITY_KEYWORD.equals(value) ? null : value; + } + + private boolean checkNames() { + if (!KtPsiUtilKt.isIdentifier(getFunctionName())) return false; + for (FirExtractFunctionParameterTablePanel.ParameterInfo parameterInfo : parameterTablePanel.getSelectedParameterInfos()) { + if (!KtPsiUtilKt.isIdentifier(parameterInfo.getName())) return false; + } + return true; + } + + private void update() { + setOKActionEnabled(checkNames()); + ReadAction.nonBlocking(() -> + PresentationUtilKt.getSignaturePreview(originalDescriptor.getDescriptor(), + getFunctionName(), + getVisibility(), + parameterTablePanel.getSelectedReceiverInfo(), + parameterTablePanel.getSelectedParameterInfos(), + ((KtTypeCodeFragment) returnTypeBox.getSelectedItem()), + originalDescriptor.getDescriptor().getContext()) + ) + .expireWith(getDisposable()) + .finishOnUiThread(ModalityState.current(), preview -> signaturePreviewField.setText(preview)) + .submit(AppExecutorUtil.getAppExecutorService()); + } + + @Override + protected void init() { + super.init(); + + ExtractableCodeDescriptor extractableCodeDescriptor = originalDescriptor.getDescriptor(); + + functionNameField = new NameSuggestionsField( + //TODO ArrayUtil.toStringArray(extractableCodeDescriptor.getSuggestedNames()), + // without type pointers (see KT-60484), combobox with names fires events which invalidate types precalculated by first phase of refactoring + new String[] { extractableCodeDescriptor.getName() }, + project, + PlainTextFileType.INSTANCE + ); + functionNameField.addDataChangedListener(() -> update()); + functionNamePanel.add(functionNameField, BorderLayout.CENTER); + functionNameLabel.setLabelFor(functionNameField); + + KtElement context = originalDescriptor.getDescriptor().getContext(); + List possibleReturnTypes = ExtractableCodeDescriptorKt.getPossibleReturnTypes(extractableCodeDescriptor.getControlFlow()); + if (!possibleReturnTypes.isEmpty()) { + List fragments = ContainerUtil.map(possibleReturnTypes, + t -> new KtPsiFactory(project).createTypeCodeFragment( + PresentationUtilKt.render(t, context), context)); + DefaultComboBoxModel returnTypeBoxModel = + new DefaultComboBoxModel<>(fragments.toArray(new KtTypeCodeFragment[0])); + returnTypeBox.setModel(returnTypeBoxModel); + returnTypeBox.setRenderer( + new DefaultListCellRenderer() { + @NotNull + @Override + public Component getListCellRendererComponent( + JList list, + Object value, + int index, + boolean isSelected, + boolean cellHasFocus + ) { + @NlsSafe + String text = ((KtTypeCodeFragment) value).getText(); + setText(text); + return this; + } + } + ); + returnTypeBox.addItemListener( + new ItemListener() { + @Override + public void itemStateChanged(@NotNull ItemEvent e) { + update(); + } + } + ); + } else { + returnTypePanel.getParent().remove(returnTypePanel); + } + + visibilityBox.setModel(new DefaultComboBoxModel(KtTokens.VISIBILITY_MODIFIERS.getTypes())); + + boolean enableVisibility = isVisibilitySectionAvailable(); + visibilityBox.setEnabled(enableVisibility); + if (enableVisibility) { + KtModifierKeywordToken defaultVisibility = extractableCodeDescriptor.getVisibility(); + visibilityBox.setSelectedItem(defaultVisibility); + } + visibilityBox.addItemListener( + new ItemListener() { + @Override + public void itemStateChanged(@NotNull ItemEvent e) { + update(); + } + } + ); + + parameterTablePanel = new FirExtractFunctionParameterTablePanel() { + @NotNull + @Override + public KtElement getContext() { + return context; + } + + @Override + protected void updateSignature() { + KotlinFirExtractFunctionDialog.this.update(); + } + + @Override + protected void onEnterAction() { + doOKAction(); + } + + @Override + protected void onCancelAction() { + doCancelAction(); + } + }; + parameterTablePanel.init(extractableCodeDescriptor.getReceiverParameter(), extractableCodeDescriptor.getParameters()); + + inputParametersPanel.setText(KotlinBundle.message("text.parameters")); + inputParametersPanel.setLabelFor(parameterTablePanel.getTable()); + inputParametersPanel.add(parameterTablePanel); + } + + @Override + protected void doOKAction() { + ExtractableCodeDescriptorWithConflicts result = PresentationUtilKt.validate(originalDescriptor.getDescriptor(), + getFunctionName(), + getVisibility(), + parameterTablePanel.getSelectedReceiverInfo(), + parameterTablePanel.getSelectedParameterInfos(), + ((KtTypeCodeFragment) returnTypeBox.getSelectedItem()), + originalDescriptor.getDescriptor().getContext()); + + MultiMap conflicts = result.getConflicts(); + conflicts.values().removeAll(originalDescriptor.getConflicts().values()); + + KotlinCommonRefactoringUtilKt.checkConflictsInteractively( + project, + conflicts, + new Function0<>() { + @Override + public Unit invoke() { + close(OK_EXIT_CODE); + return Unit.INSTANCE; + } + }, + new Function0<>() { + @Override + public Unit invoke() { + KotlinFirExtractFunctionDialog.super.doOKAction(); + return onAccept.invoke(result.getDescriptor()); + } + } + ); + onAccept.invoke(result.getDescriptor()); + close(OK_EXIT_CODE); + } + + @Override + public JComponent getPreferredFocusedComponent() { + return functionNameField.getFocusableComponent(); + } + + @Override + protected JComponent createCenterPanel() { + return contentPane; + } + + @NotNull + @Override + protected JComponent createContentPane() { + return contentPane; + } + + public static ExtractableCodeDescriptor createNewDescriptor( + @NotNull ExtractableCodeDescriptor originalDescriptor, + @NotNull String newName, + @Nullable KtModifierKeywordToken newVisibility, + @Nullable FirExtractFunctionParameterTablePanel.ParameterInfo newReceiverInfo, + @NotNull List newParameterInfos, + @Nullable KtType returnType + ) { + Map oldToNewParameters = new LinkedHashMap<>(); + for (FirExtractFunctionParameterTablePanel.ParameterInfo parameterInfo : newParameterInfos) { + oldToNewParameters.put(parameterInfo.getOriginalParameter(), parameterInfo.toParameter()); + } + Parameter originalReceiver = originalDescriptor.getReceiverParameter(); + Parameter newReceiver = newReceiverInfo != null ? newReceiverInfo.toParameter() : null; + if (originalReceiver != null && newReceiver != null) { + oldToNewParameters.put(originalReceiver, newReceiver); + } + + ExtractionData data = originalDescriptor.getExtractionData(); + return new ExtractableCodeDescriptor(originalDescriptor.getContext(), + data, + List.of(newName), + newVisibility, + oldToNewParameters.values().stream().toList(), + newReceiver, + originalDescriptor.getTypeParameters(), + originalDescriptor.getReplacementMap(), + originalDescriptor.getControlFlow(), + returnType, + originalDescriptor.getModifiers(), + originalDescriptor.getOptInMarkers(), + originalDescriptor.getAnnotations()); + } +} diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/ui/presentationUtil.kt b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/ui/presentationUtil.kt new file mode 100644 index 000000000000..d2637187a8af --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/ui/presentationUtil.kt @@ -0,0 +1,87 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.ui + +import org.jetbrains.kotlin.analysis.api.KtAllowAnalysisOnEdt +import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.analysis.api.lifetime.allowAnalysisOnEdt +import org.jetbrains.kotlin.analysis.api.renderer.types.impl.KtTypeRendererForSource +import org.jetbrains.kotlin.analysis.api.types.KtType +import org.jetbrains.kotlin.idea.base.analysis.api.utils.analyzeInModalWindow +import org.jetbrains.kotlin.idea.base.resources.KotlinBundle +import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.ExtractableCodeDescriptor +import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.ExtractableCodeDescriptorWithConflicts +import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.ExtractionGeneratorConfiguration +import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.ui.KotlinFirExtractFunctionDialog.createNewDescriptor +import org.jetbrains.kotlin.idea.k2.refactoring.introduce.extractionEngine.Generator +import org.jetbrains.kotlin.idea.k2.refactoring.introduce.extractionEngine.validate +import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ui.FirExtractFunctionParameterTablePanel +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ExtractionGeneratorOptions +import org.jetbrains.kotlin.lexer.KtModifierKeywordToken +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtTypeCodeFragment +import org.jetbrains.kotlin.types.Variance + +@OptIn(KtAllowAnalysisOnEdt::class) +internal fun render(type: KtType, context: KtElement): String { + return allowAnalysisOnEdt { + analyze(context) { + type.render(KtTypeRendererForSource.WITH_QUALIFIED_NAMES, Variance.IN_VARIANCE) + } + } +} + +@OptIn(KtAllowAnalysisOnEdt::class) +internal fun getKtType(fragment: KtTypeCodeFragment): KtType? { + return allowAnalysisOnEdt { + analyze(fragment) { + fragment.getContentElement()?.getKtType() + } + } +} + +internal fun validate( + originalDescriptor: ExtractableCodeDescriptor, + newName: String, + newVisibility: KtModifierKeywordToken?, + newReceiverInfo: FirExtractFunctionParameterTablePanel.ParameterInfo?, + newParameterInfos: List, + returnCodeFragment: KtTypeCodeFragment, + context: KtElement +): ExtractableCodeDescriptorWithConflicts { + return analyzeInModalWindow(context, KotlinBundle.message("fix.change.signature.prepare")) { + val newDescriptor = createNewDescriptor( + originalDescriptor, + newName, + newVisibility, + newReceiverInfo, + newParameterInfos, + returnCodeFragment.getContentElement()?.getKtType() + ) + + newDescriptor.validate() + } + +} + +internal fun getSignaturePreview( + originalDescriptor: ExtractableCodeDescriptor, + newName: String, + newVisibility: KtModifierKeywordToken?, + newReceiverInfo: FirExtractFunctionParameterTablePanel.ParameterInfo?, + newParameterInfos: List, + returnCodeFragment: KtTypeCodeFragment, + context: KtElement +): String { + return analyze(context) { + val newDescriptor = createNewDescriptor( + originalDescriptor, + newName, + newVisibility, + newReceiverInfo, + newParameterInfos, + returnCodeFragment.getContentElement()?.getKtType() + ) + + Generator.getSignaturePreview(ExtractionGeneratorConfiguration(newDescriptor, ExtractionGeneratorOptions.DEFAULT)) + } +} diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduce/extractionEngine/ExtractionDataAnalyzer.kt b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduce/extractionEngine/ExtractionDataAnalyzer.kt new file mode 100644 index 000000000000..f69d7ff23706 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduce/extractionEngine/ExtractionDataAnalyzer.kt @@ -0,0 +1,398 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.k2.refactoring.introduce.extractionEngine + +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiNamedElement +import com.intellij.psi.search.searches.ReferencesSearch +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.psi.util.descendantsOfType +import com.intellij.refactoring.util.RefactoringUIUtil +import com.intellij.util.containers.MultiMap +import org.jetbrains.kotlin.analysis.api.KtAnalysisNonPublicApi +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.analysis.api.annotations.KtAnnotationApplicationWithArgumentsInfo +import org.jetbrains.kotlin.analysis.api.annotations.KtArrayAnnotationValue +import org.jetbrains.kotlin.analysis.api.annotations.KtKClassAnnotationValue +import org.jetbrains.kotlin.analysis.api.annotations.annotations +import org.jetbrains.kotlin.analysis.api.components.KtDataFlowExitPointSnapshot +import org.jetbrains.kotlin.analysis.api.components.KtDiagnosticCheckerFilter +import org.jetbrains.kotlin.analysis.api.fir.diagnostics.KtFirDiagnostic +import org.jetbrains.kotlin.analysis.api.symbols.KtAnonymousObjectSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionSymbol +import org.jetbrains.kotlin.analysis.api.symbols.markers.KtAnnotatedSymbol +import org.jetbrains.kotlin.analysis.api.types.KtNonErrorClassType +import org.jetbrains.kotlin.analysis.api.types.KtType +import org.jetbrains.kotlin.idea.base.resources.KotlinBundle +import org.jetbrains.kotlin.idea.base.util.names.FqNames +import org.jetbrains.kotlin.idea.base.util.names.FqNames.OptInFqNames.isRequiresOptInFqName +import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.ExtractableCodeDescriptor +import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.ExtractableCodeDescriptorWithConflicts +import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.ExtractionData +import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.ExtractionGeneratorConfiguration +import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.MutableParameter +import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.inferParametersInfo +import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.targetKey +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.AbstractExtractionDataAnalyzer +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ControlFlow +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ExtractionGeneratorOptions +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.IExtractableCodeDescriptor +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.IExtractionData +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.IReplacement +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputDescriptor +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ParametersInfo +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ResolveResult +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.TypeDescriptor +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.TypeParameter +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.getDefaultVisibility +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.resolveResult +import org.jetbrains.kotlin.idea.references.ReadWriteAccessChecker +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.lexer.KtModifierKeywordToken +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtBlockExpression +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtNamedDeclaration +import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.psi.KtParameter +import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.psi.KtReferenceExpression +import org.jetbrains.kotlin.psi.KtSimpleNameExpression +import org.jetbrains.kotlin.psi.KtThisExpression +import org.jetbrains.kotlin.psi.KtTreeVisitorVoid +import org.jetbrains.kotlin.psi.KtTypeParameter +import org.jetbrains.kotlin.psi.KtUserType +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType + +@OptIn(KtAnalysisNonPublicApi::class) +internal class ExtractionDataAnalyzer(private val extractionData: ExtractionData) : + AbstractExtractionDataAnalyzer(extractionData) { + + override fun hasSyntaxErrors(): Boolean { + return false + } + + override fun getLocalDeclarationsWithNonLocalUsages(): List { + val definedDeclarations = mutableListOf() + extractionData.expressions.forEach { p -> + p.accept(object : KtTreeVisitorVoid() { + override fun visitNamedDeclaration(declaration: KtNamedDeclaration) { + super.visitNamedDeclaration(declaration) + ReferencesSearch.search(declaration).forEach { ref -> + if (extractionData.expressions.none { PsiTreeUtil.isAncestor(it, ref.element, false) }) { + definedDeclarations.add(declaration) + return + } + } + } + }) + } + return definedDeclarations + } + + override fun getVarDescriptorsAccessedAfterwards(): Set { + val accessedLocals = mutableSetOf() + val allProperties = mutableSetOf() + val collector = object : VariableCollector() { + override fun registerModifiedVar(e: KtProperty) { + allProperties.add(e) + } + } + extractionData.expressions.forEach { it.accept(collector) } + for (prop in allProperties) { + val name = prop.name ?: continue + val afterwardsRef = ReferencesSearch.search(prop).firstOrNull { ref -> + extractionData.expressions.none { PsiTreeUtil.isAncestor(it, ref.element, false) } + } + if (afterwardsRef != null) { + accessedLocals.add(name) + } + } + return accessedLocals + } + + override fun getModifiedVars(): Map> { + val map = HashMap>() + val collector = object : VariableCollector() { + override fun acceptProperty(prop: KtProperty): Boolean { + return prop.hasInitializer() + } + + override fun registerModifiedVar(e: KtProperty) { + //TODO register assigned expr for duplicates processing https://youtrack.jetbrains.com/issue/KTIJ-29165 + e.name?.let { key -> map.getOrPut(key) { ArrayList() } } + } + } + extractionData.expressions.forEach { it.accept(collector) } + return map + } + + private abstract inner class VariableCollector : KtTreeVisitorVoid() { + private val accessChecker: ReadWriteAccessChecker = ReadWriteAccessChecker.getInstance(extractionData.project) + + open fun acceptProperty(prop: KtProperty): Boolean = true + + override fun visitDeclaration(declaration: KtDeclaration) { + if (declaration is KtProperty && declaration.isLocal && acceptProperty(declaration)) { + registerModifiedVar(declaration) + } + super.visitDeclaration(declaration) + } + + override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) { + if (accessChecker.readWriteAccessWithFullExpression(expression, true).first.isWrite) { + val target = expression.mainReference.resolve() + if (target is KtProperty && target.isLocal) { + registerModifiedVar(target) + } + super.visitSimpleNameExpression(expression) + } + } + + abstract fun registerModifiedVar(e: KtProperty) + } + + override fun createOutputDescriptor(): OutputDescriptor { + analyze(extractionData.commonParent) { + val exitSnapshot: KtDataFlowExitPointSnapshot = getExitPointSnapshot(extractionData.expressions) + val defaultExpressionInfo = exitSnapshot.defaultExpressionInfo + val typeOfDefaultFlow = defaultExpressionInfo?.type?.takeIf { + //extract as Unit function if the last expression is not used afterward + !extractionData.options.inferUnitTypeForUnusedValues || defaultExpressionInfo.expression.isUsedAsExpression() + } + + fun approximate(type: KtType?): KtType? { + if (type == null) return null + if (!(type is KtNonErrorClassType && type.classSymbol is KtAnonymousObjectSymbol) + && typeDescriptor.isResolvableInScope(type, mutableSetOf()) + ) return type + return type.getAllSuperTypes().firstOrNull { + typeDescriptor.isResolvableInScope(it, mutableSetOf()) + } + } + + return OutputDescriptor( + defaultResultExpression = defaultExpressionInfo?.expression, + typeOfDefaultFlow = approximate(typeOfDefaultFlow) ?: builtinTypes.UNIT, + valuedReturnExpressions = exitSnapshot.valuedReturnExpressions, + returnValueType = approximate(exitSnapshot.returnValueType) ?: builtinTypes.UNIT, + jumpExpressions = exitSnapshot.loopJumpExpressions, + hasSingleTarget = !exitSnapshot.hasMultipleJumpTargets && !exitSnapshot.hasMultipleJumpKinds, + sameExitForDefaultAndJump = !exitSnapshot.hasEscapingJumps + ) + } + } + + override val nameSuggester = KotlinNameSuggester + + override val typeDescriptor: TypeDescriptor = KotlinTypeDescriptor(extractionData) + + override fun inferParametersInfo( + virtualBlock: KtBlockExpression, + modifiedVariables: Set + ): ParametersInfo { + analyze(extractionData.commonParent) { + return extractionData.inferParametersInfo( + virtualBlock, + modifiedVariables, + typeDescriptor + ) + } + } + + override fun createDescriptor( + suggestedFunctionNames: List, + defaultVisibility: KtModifierKeywordToken?, + parameters: List, + receiverParameter: MutableParameter?, + typeParameters: List, + replacementMap: MultiMap>, + flow: ControlFlow, + returnType: KtType + ): IExtractableCodeDescriptor { + val experimentalMarkers = analyze(extractionData.commonParent) { extractionData.getExperimentalMarkers() } + val descriptor = ExtractableCodeDescriptor( + context = extractionData.commonParent, + extractionData = extractionData, + suggestedNames = suggestedFunctionNames, + visibility = extractionData.getDefaultVisibility(), + parameters = parameters, + receiverParameter = receiverParameter, + typeParameters = typeParameters, + replacementMap = replacementMap, + controlFlow = flow, + returnType = returnType, + modifiers = if (hasSuspendReference(extractionData)) listOf(KtTokens.SUSPEND_KEYWORD) else emptyList(), + optInMarkers = experimentalMarkers.optInMarkers, + annotations = experimentalMarkers.propagatingMarkerDescriptors + ) + return descriptor + } + + private fun hasSuspendReference(extractionData: ExtractionData): Boolean { + return extractionData.expressions + .flatMap { it.descendantsOfType() } + .any { nameExpression -> + analyze(nameExpression) { + val symbol = nameExpression.mainReference.resolveToSymbol() + symbol is KtFunctionSymbol && symbol.isSuspend + } + } + } +} + +private data class ExperimentalMarkers( + val propagatingMarkerDescriptors: List, + val optInMarkers: List +) { + companion object { + val empty = ExperimentalMarkers(emptyList(), emptyList()) + } +} + +context(KtAnalysisSession) +private fun IExtractionData.getExperimentalMarkers(): ExperimentalMarkers { + fun KtAnnotationApplicationWithArgumentsInfo.isExperimentalMarker(): Boolean { + val id = classId + if (id == null) return false + val annotations = getClassOrObjectSymbolByClassId(id)?.annotations ?: return false + return annotations.any { isRequiresOptInFqName(it.classId?.asSingleFqName()) } + } + + val container = commonParent.getStrictParentOfType() ?: return ExperimentalMarkers.empty + + val propagatingMarkerDescriptors = mutableListOf() + val optInMarkerNames = mutableListOf() + for (annotationEntry in container.getSymbol().annotations) { + val fqName = annotationEntry.classId?.asSingleFqName() ?: continue + + if (fqName in FqNames.OptInFqNames.OPT_IN_FQ_NAMES) { + for (argument in annotationEntry.arguments) { + val expression = argument.expression + if (expression is KtKClassAnnotationValue.KtNonLocalKClassAnnotationValue) { + optInMarkerNames.add(expression.classId.asSingleFqName()) + } else if (expression is KtArrayAnnotationValue) { + expression.values.filterIsInstance() + .forEach { optInMarkerNames.add(it.classId.asSingleFqName()) } + } + } + } else if (annotationEntry.isExperimentalMarker()) { + propagatingMarkerDescriptors.add(annotationEntry) + } + } + + val requiredMarkers = mutableSetOf() + if (propagatingMarkerDescriptors.isNotEmpty() || optInMarkerNames.isNotEmpty()) { + originalElements.forEach { element -> + element.accept(object : KtTreeVisitorVoid() { + override fun visitReferenceExpression(expression: KtReferenceExpression) { + super.visitReferenceExpression(expression) + val descriptor = expression.mainReference.resolveToSymbol() as? KtAnnotatedSymbol ?: return + + for (ann in descriptor.annotations) { + val fqName = ann.classId?.asSingleFqName() ?: continue + if (ann.isExperimentalMarker()) { + requiredMarkers.add(fqName) + } + } + } + }) + } + } + + return ExperimentalMarkers( + propagatingMarkerDescriptors.filter { + val classId = it.classId + classId != null && classId.asSingleFqName() in requiredMarkers + }, + optInMarkerNames.filter { it in requiredMarkers } + ) +} + +context(KtAnalysisSession) +fun ExtractableCodeDescriptor.validate(): ExtractableCodeDescriptorWithConflicts { + fun getDeclarationMessage(declaration: PsiElement, messageKey: String, capitalize: Boolean = true): String { + val declarationStr = RefactoringUIUtil.getDescription(declaration, true) + val message = KotlinBundle.message(messageKey, declarationStr) + return if (capitalize) message.capitalize() else message + } + + val conflicts = MultiMap() + + val config = ExtractionGeneratorConfiguration( + this, + ExtractionGeneratorOptions( + inTempFile = true, + allowExpressionBody = false + ) + ) + val result = Generator.generateDeclaration(config, null) + + val namedFunction = result.declaration as? KtNamedFunction + val valueParameterList = namedFunction?.valueParameterList + val typeParameterList = namedFunction?.typeParameterList + + fun processReference(currentRefExpr: KtSimpleNameExpression) { + val resolveResult = currentRefExpr.resolveResult as? ResolveResult ?: return + if (currentRefExpr.parent is KtThisExpression) return + + val diagnostics = currentRefExpr.getDiagnostics(KtDiagnosticCheckerFilter.ONLY_COMMON_CHECKERS) + + val currentDescriptor = currentRefExpr.mainReference.resolve() + if (currentDescriptor is KtParameter && currentDescriptor.parent == valueParameterList) return + if (currentDescriptor is KtTypeParameter && currentDescriptor.parent == typeParameterList) return + if (currentDescriptor is KtProperty && currentDescriptor.isLocal + && parameters.any { it.mirrorVarName == currentDescriptor.name } + ) return + + if (diagnostics.any { + it.diagnosticClass == KtFirDiagnostic.UnresolvedReference::class || + it.diagnosticClass == KtFirDiagnostic.UnresolvedReferenceWrongReceiver::class + } + || (currentDescriptor != null + && currentDescriptor != resolveResult.descriptor + && currentDescriptor.getCopyableUserData(targetKey) != resolveResult.descriptor.getCopyableUserData(targetKey))) { + conflicts.putValue( + resolveResult.originalRefExpr, + getDeclarationMessage(resolveResult.declaration, "0.will.no.longer.be.accessible.after.extraction") + ) + return + } + + diagnostics.firstOrNull { + it.diagnosticClass == KtFirDiagnostic.InvisibleReference::class || + it.diagnosticClass == KtFirDiagnostic.InvisibleSetter::class + }?.let { + val message = when (it.diagnosticClass) { + KtFirDiagnostic.InvisibleSetter::class -> + getDeclarationMessage(resolveResult.declaration, "setter.of.0.will.become.invisible.after.extraction", false) + + else -> + getDeclarationMessage(resolveResult.declaration, "0.will.become.invisible.after.extraction") + } + conflicts.putValue(resolveResult.originalRefExpr, message) + } + + } + + result.declaration.accept( + object : KtTreeVisitorVoid() { + override fun visitUserType(userType: KtUserType) { + val refExpr = userType.referenceExpression ?: return + val diagnostics = refExpr.getDiagnostics(KtDiagnosticCheckerFilter.ONLY_COMMON_CHECKERS) + diagnostics.firstOrNull { it.diagnosticClass == KtFirDiagnostic.InvisibleReference::class }?.let { + val declaration = refExpr.mainReference.resolve() as? PsiNamedElement ?: return + conflicts.putValue(declaration, getDeclarationMessage(declaration, "0.will.become.invisible.after.extraction")) + } + } + + override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) { + processReference(expression) + } + } + ) + + return ExtractableCodeDescriptorWithConflicts(this, conflicts) +} \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduce/extractionEngine/ExtractionEngineHelper.kt b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduce/extractionEngine/ExtractionEngineHelper.kt new file mode 100644 index 000000000000..b372f2488304 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduce/extractionEngine/ExtractionEngineHelper.kt @@ -0,0 +1,37 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.k2.refactoring.introduce.extractionEngine + +import com.intellij.openapi.util.NlsContexts +import org.jetbrains.kotlin.analysis.api.KtAllowAnalysisFromWriteAction +import org.jetbrains.kotlin.analysis.api.KtAllowAnalysisOnEdt +import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.analysis.api.lifetime.allowAnalysisFromWriteAction +import org.jetbrains.kotlin.analysis.api.lifetime.allowAnalysisOnEdt +import org.jetbrains.kotlin.analysis.api.types.KtType +import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.ExtractableCodeDescriptor +import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.ExtractableCodeDescriptorWithConflicts +import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.ExtractionData +import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.ExtractionGeneratorConfiguration +import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.ExtractionResult +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.IExtractionEngineHelper + +abstract class ExtractionEngineHelper(@NlsContexts.DialogTitle operationName: String) : + IExtractionEngineHelper( + operationName + ) { + + + @OptIn(KtAllowAnalysisFromWriteAction::class, KtAllowAnalysisOnEdt::class) + override fun generateDeclaration(config: ExtractionGeneratorConfiguration): ExtractionResult { + allowAnalysisOnEdt { + allowAnalysisFromWriteAction { + return Generator.generateDeclaration(config, null) + } + } + } + + override fun validate(descriptor: ExtractableCodeDescriptor): ExtractableCodeDescriptorWithConflicts = + analyze(descriptor.extractionData.commonParent) { + descriptor.validate() + } +} diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduce/extractionEngine/Generator.kt b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduce/extractionEngine/Generator.kt new file mode 100644 index 000000000000..bd1ee75b423f --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduce/extractionEngine/Generator.kt @@ -0,0 +1,54 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.k2.refactoring.introduce.extractionEngine + +import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.analysis.api.types.KtType +import org.jetbrains.kotlin.idea.base.psi.unifier.KotlinPsiRange +import org.jetbrains.kotlin.idea.codeinsight.utils.isAnnotatedDeep +import org.jetbrains.kotlin.idea.codeinsight.utils.isConvertableToExpressionBody +import org.jetbrains.kotlin.idea.codeinsight.utils.replaceWithExpressionBodyPreservingComments +import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.ExtractionGeneratorConfiguration +import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.ExtractionResult +import org.jetbrains.kotlin.idea.k2.refactoring.util.areTypeArgumentsRedundant +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ExtractFunctionGenerator +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.IExtractionData +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.IExtractionGeneratorConfiguration +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.IExtractionNameSuggester +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.TypeDescriptor +import org.jetbrains.kotlin.psi.KtBlockExpression +import org.jetbrains.kotlin.psi.KtDeclarationWithBody +import org.jetbrains.kotlin.psi.KtNamedDeclaration +import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.psi.KtTypeArgumentList + +internal object Generator : ExtractFunctionGenerator() { + override val nameGenerator: IExtractionNameSuggester = KotlinNameSuggester + + override fun createTypeDescriptor(data: IExtractionData): TypeDescriptor { + return KotlinTypeDescriptor(data) + } + + override fun IExtractionGeneratorConfiguration.collapseBody( + blockExpression: KtBlockExpression + ) { + val declarationWithBody = blockExpression.parent as? KtDeclarationWithBody ?: return + + if (declarationWithBody.isConvertableToExpressionBody()) { + declarationWithBody.replaceWithExpressionBodyPreservingComments() + } + } + + override fun resolveNameConflict(property: KtProperty) { + //TODO resolve name conflicts for extract property https://youtrack.jetbrains.com/issue/KTIJ-21139 + } + + override fun checkTypeArgumentsAreRedundant(args: KtTypeArgumentList): Boolean { + return args.arguments.none { it.typeReference?.isAnnotatedDeep() == true } && + analyze(args) { areTypeArgumentsRedundant(args) } + } + + override fun IExtractionGeneratorConfiguration.createExtractionResult( + declaration: KtNamedDeclaration, + duplicatesReplacer: Map Unit> + ): ExtractionResult = ExtractionResult(this as ExtractionGeneratorConfiguration, declaration, duplicatesReplacer) +} \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduce/extractionEngine/KotlinNameSuggester.kt b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduce/extractionEngine/KotlinNameSuggester.kt new file mode 100644 index 000000000000..eebc3f692bbc --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduce/extractionEngine/KotlinNameSuggester.kt @@ -0,0 +1,51 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.k2.refactoring.introduce.extractionEngine + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.analysis.api.types.KtType +import org.jetbrains.kotlin.idea.base.codeInsight.KotlinDeclarationNameValidator +import org.jetbrains.kotlin.idea.base.codeInsight.KotlinNameSuggester +import org.jetbrains.kotlin.idea.base.codeInsight.KotlinNameSuggestionProvider +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.IExtractionNameSuggester +import org.jetbrains.kotlin.psi.KtElement + +object KotlinNameSuggester : IExtractionNameSuggester { + override fun suggestNamesByType( + kotlinType: KtType, + container: KtElement, + validator: (String) -> Boolean, + defaultName: String? + ): List = with(KotlinNameSuggester()) { + analyze(container) { + if (kotlinType.isUnit) emptyList() else suggestTypeNames(kotlinType).filter(validator).toList() + } + } + + override fun createNameValidator( + container: KtElement, + anchor: PsiElement?, + validatorType: KotlinNameSuggestionProvider.ValidatorTarget + ): (String) -> Boolean = { + analyze(container) { + KotlinDeclarationNameValidator( + container, + true, + validatorType, + ).validate(it) + } + } + + override fun suggestNameByName( + name: String, + container: KtElement, + anchor: PsiElement? + ): String = KotlinNameSuggester.suggestNameByName( + name, + createNameValidator( + container, + anchor, + KotlinNameSuggestionProvider.ValidatorTarget.PARAMETER + ) + ) +} \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduce/extractionEngine/KotlinTypeDescriptor.kt b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduce/extractionEngine/KotlinTypeDescriptor.kt new file mode 100644 index 000000000000..1257eaccdc74 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduce/extractionEngine/KotlinTypeDescriptor.kt @@ -0,0 +1,161 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.k2.refactoring.introduce.extractionEngine + +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.psi.util.parentOfType +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.analysis.api.components.buildClassType +import org.jetbrains.kotlin.analysis.api.renderer.declarations.impl.KtDeclarationRendererForSource +import org.jetbrains.kotlin.analysis.api.symbols.KtAnonymousObjectSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol +import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolWithVisibility +import org.jetbrains.kotlin.analysis.api.types.KtErrorType +import org.jetbrains.kotlin.analysis.api.types.KtIntersectionType +import org.jetbrains.kotlin.analysis.api.types.KtNonErrorClassType +import org.jetbrains.kotlin.analysis.api.types.KtType +import org.jetbrains.kotlin.analysis.api.types.KtTypeParameterType +import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.Parameter +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.IExtractionData +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.IParameter +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.TypeDescriptor +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.TypeParameter +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.collectRelevantConstraints +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.name.StandardClassIds +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtNamedDeclaration +import org.jetbrains.kotlin.psi.KtTypeParameter +import org.jetbrains.kotlin.psi.KtTypeParameterListOwner +import org.jetbrains.kotlin.types.Variance + +class KotlinTypeDescriptor(private val data: IExtractionData) : TypeDescriptor { + override fun KtType.isMeaningful(): Boolean = + analyze(data.commonParent) { + !this@isMeaningful.isEqualTo(builtinTypes.UNIT) && !this@isMeaningful.isEqualTo(builtinTypes.NOTHING) + } + + override fun KtType.isError(): Boolean { + return this is KtErrorType + } + + override val booleanType: KtType = analyze(data.commonParent) { builtinTypes.BOOLEAN } + + override val unitType: KtType = analyze(data.commonParent) { builtinTypes.UNIT } + override val nullableAnyType: KtType = analyze(data.commonParent) { builtinTypes.NULLABLE_ANY } + + override fun createListType(argTypes: List): KtType { + return analyze(data.commonParent) { + buildClassType(StandardClassIds.List) { + argument(commonSuperType(argTypes) ?: builtinTypes.NULLABLE_ANY) + } + } + } + + override fun createTuple(outputValues: List>): KtType { + analyze(data.commonParent) { + val boxingClass = when (outputValues.size) { + 1 -> return outputValues.first().valueType + 2 -> getClassOrObjectSymbolByClassId(ClassId(StandardClassIds.BASE_KOTLIN_PACKAGE, Name.identifier("Pair")))!! + 3 -> getClassOrObjectSymbolByClassId(ClassId(StandardClassIds.BASE_KOTLIN_PACKAGE, Name.identifier("Triple")))!! + else -> return builtinTypes.UNIT + } + return buildClassType(boxingClass) { + boxingClass.typeParameters.forEachIndexed { idx, s -> + argument(outputValues[idx].valueType) + } + } + } + } + + override fun returnType(ktNamedDeclaration: KtNamedDeclaration): KtType? = + analyze(data.commonParent) { ktNamedDeclaration.getReturnKtType() } + + override fun renderForMessage(ktNamedDeclaration: KtNamedDeclaration): String { + return analyze(data.commonParent) { + ktNamedDeclaration.getSymbol().render(KtDeclarationRendererForSource.WITH_SHORT_NAMES) + } + } + + override fun renderForMessage(param: IParameter): String { + val descriptor = (param as Parameter).originalDescriptor + return if (descriptor is KtNamedDeclaration) { + renderForMessage(descriptor) + } else descriptor.name ?: descriptor.text + + } + + override fun renderTypeWithoutApproximation(kotlinType: KtType): String { + return analyze(data.commonParent) { + kotlinType.render(position = Variance.INVARIANT) + } + } + + override fun typeArguments(ktType: KtType): List { + analyze(data.commonParent) { + return (ktType as? KtNonErrorClassType)?.ownTypeArguments?.mapNotNull { it.type } ?: emptyList() + } + } + + override fun renderType( + ktType: KtType, isReceiver: Boolean, variance: Variance + ): String = analyze(data.commonParent) { + val renderType = ktType.render(position = variance) + if (ktType.isFunctionType && isReceiver) "($renderType)" else renderType + } + + override fun isResolvableInScope( + typeToCheck: KtType, + typeParameters: MutableSet, + ): Boolean { + val ktElement = data.targetSibling as KtElement + return analyze(ktElement) { + isResolvableInScope(typeToCheck, ktElement, typeParameters) + } + } +} + +/** + * Checks whether a given type is resolvable within a scope. + * + * @return true if [typeToCheck] doesn't contain unresolved components in the scope of [scope] and is "denotable" + */ +context(KtAnalysisSession) +fun isResolvableInScope(typeToCheck: KtType, scope: KtElement, typeParameters: MutableSet): Boolean { + ((typeToCheck as? KtTypeParameterType)?.symbol?.psi as? KtTypeParameter)?.let { typeParameter -> + val typeParameterListOwner = typeParameter.parentOfType() + if (typeParameterListOwner == null || !PsiTreeUtil.isAncestor(typeParameterListOwner, scope, true)) { + typeParameters.add(TypeParameter(typeParameter, typeParameter.collectRelevantConstraints())) + } + return true + } + if (typeToCheck is KtNonErrorClassType) { + + val classSymbol = typeToCheck.classSymbol + if ((classSymbol as? KtAnonymousObjectSymbol)?.superTypes?.all { isResolvableInScope(it, scope, typeParameters) } == true) { + return true + } + + if ((classSymbol as? KtClassOrObjectSymbol)?.classIdIfNonLocal == null) { + //because org.jetbrains.kotlin.fir.FirVisibilityChecker.Default always return true for local classes, + //let's be pessimistic here and prohibit local classes completely + return false + } + + if (classSymbol is KtSymbolWithVisibility && !isVisible(classSymbol, scope.containingKtFile.getFileSymbol(), null, scope)) { + return false + } + typeToCheck.ownTypeArguments.mapNotNull { it.type }.forEach { + if (!isResolvableInScope(it, scope, typeParameters)) return false + } + } + if (typeToCheck is KtErrorType) { + return false + } + if (typeToCheck is KtIntersectionType) { + return false + } + return true +} \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.tests.k2/kotlin.refactorings.tests.k2.iml b/plugins/kotlin/refactorings/kotlin.refactorings.tests.k2/kotlin.refactorings.tests.k2.iml index b07c9f874981..3e9174fa7d59 100644 --- a/plugins/kotlin/refactorings/kotlin.refactorings.tests.k2/kotlin.refactorings.tests.k2.iml +++ b/plugins/kotlin/refactorings/kotlin.refactorings.tests.k2/kotlin.refactorings.tests.k2.iml @@ -22,5 +22,7 @@ + + \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.tests.k2/test/org/jetbrains/kotlin/idea/k2/refactoring/introduceFunction/AbstractK2IntroduceFunctionTest.kt b/plugins/kotlin/refactorings/kotlin.refactorings.tests.k2/test/org/jetbrains/kotlin/idea/k2/refactoring/introduceFunction/AbstractK2IntroduceFunctionTest.kt new file mode 100644 index 000000000000..2a7cd64e533a --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.tests.k2/test/org/jetbrains/kotlin/idea/k2/refactoring/introduceFunction/AbstractK2IntroduceFunctionTest.kt @@ -0,0 +1,63 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.k2.refactoring.introduceFunction + +import com.intellij.openapi.actionSystem.ex.ActionUtil +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.intellij.testFramework.LightProjectDescriptor +import com.intellij.testFramework.common.runAll +import org.jetbrains.kotlin.idea.fir.invalidateCaches +import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.ExtractableCodeDescriptorWithConflicts +import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.ExtractionData +import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.ExtractionGeneratorConfiguration +import org.jetbrains.kotlin.idea.k2.refactoring.extractFunction.ExtractionResult +import org.jetbrains.kotlin.idea.k2.refactoring.introduce.extractFunction.KotlinFirExtractFunctionHandler +import org.jetbrains.kotlin.idea.k2.refactoring.introduce.extractionEngine.ExtractionEngineHelper +import org.jetbrains.kotlin.idea.refactoring.introduce.AbstractExtractionTest +import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.AbstractExtractKotlinFunctionHandler +import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.EXTRACT_FUNCTION +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ExtractionGeneratorOptions +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ExtractionOptions +import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor + +abstract class AbstractK2IntroduceFunctionTest : AbstractExtractionTest() { + override fun isFirPlugin(): Boolean = true + + override fun getExtractFunctionHandler( + explicitPreviousSibling: PsiElement?, + expectedNames: List, + expectedReturnTypes: List, + expectedDescriptors: String, + expectedTypes: String, + extractionOptions: ExtractionOptions + ): AbstractExtractKotlinFunctionHandler { + return KotlinFirExtractFunctionHandler( + helper = object : ExtractionEngineHelper(EXTRACT_FUNCTION) { + override fun adjustExtractionData(data: ExtractionData): ExtractionData { + return ActionUtil.underModalProgress(project, "adjust for tests") { + data.copy(options = extractionOptions) + } + } + + override fun configureAndRun( + project: Project, + editor: Editor, + descriptorWithConflicts: ExtractableCodeDescriptorWithConflicts, + onFinish: (ExtractionResult) -> Unit + ) { + doRefactor(ExtractionGeneratorConfiguration(descriptorWithConflicts.descriptor, ExtractionGeneratorOptions.DEFAULT), onFinish) + } + } + ) + } + + override fun tearDown() { + runAll( + { project.invalidateCaches() }, + { super.tearDown() } + ) + } + + override fun getProjectDescriptor(): LightProjectDescriptor = KotlinWithJdkAndRuntimeLightProjectDescriptor.getInstance() +} \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.tests.k2/test/org/jetbrains/kotlin/idea/k2/refactoring/introduceFunction/K2IntroduceFunctionTestGenerated.java b/plugins/kotlin/refactorings/kotlin.refactorings.tests.k2/test/org/jetbrains/kotlin/idea/k2/refactoring/introduceFunction/K2IntroduceFunctionTestGenerated.java new file mode 100644 index 000000000000..f1e71e7a7821 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.tests.k2/test/org/jetbrains/kotlin/idea/k2/refactoring/introduceFunction/K2IntroduceFunctionTestGenerated.java @@ -0,0 +1,1857 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. + +package org.jetbrains.kotlin.idea.k2.refactoring.introduceFunction; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.idea.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.idea.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.TestMetadata; +import org.jetbrains.kotlin.idea.base.test.TestRoot; +import org.junit.runner.RunWith; + +/** + * This class is generated by {@link org.jetbrains.kotlin.testGenerator.generator.TestGenerator}. + * DO NOT MODIFY MANUALLY. + */ +@SuppressWarnings("all") +@TestRoot("refactorings/kotlin.refactorings.tests.k2") +@TestDataPath("$CONTENT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +@TestMetadata("../../idea/tests/testData/refactoring/extractFunction") +public abstract class K2IntroduceFunctionTestGenerated extends AbstractK2IntroduceFunctionTest { + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/basic") + public static class Basic extends AbstractK2IntroduceFunctionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath); + } + + @TestMetadata("callWithLambdaArgument.kt") + public void testCallWithLambdaArgument() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/callWithLambdaArgument.kt"); + } + + @TestMetadata("callWithPlatformTypeReceiver.kt") + public void testCallWithPlatformTypeReceiver() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/callWithPlatformTypeReceiver.kt"); + } + + @TestMetadata("callableReferenceSelector.kt") + public void testCallableReferenceSelector() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/callableReferenceSelector.kt"); + } + + @TestMetadata("calleeExpression.kt") + public void testCalleeExpression() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/calleeExpression.kt"); + } + + @TestMetadata("classQualifier.kt") + public void testClassQualifier() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/classQualifier.kt"); + } + + @TestMetadata("companionObjectMemberRef.kt") + public void testCompanionObjectMemberRef() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/companionObjectMemberRef.kt"); + } + + @TestMetadata("convertBinaryExpression.kt") + public void testConvertBinaryExpression() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/convertBinaryExpression.kt"); + } + + @TestMetadata("convertInfixExpression.kt") + public void testConvertInfixExpression() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/convertInfixExpression.kt"); + } + + @TestMetadata("delegatingFunction.kt") + public void testDelegatingFunction() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/delegatingFunction.kt"); + } + + @TestMetadata("enumQualifier.kt") + public void testEnumQualifier() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/enumQualifier.kt"); + } + + @TestMetadata("extensionFunForArray.kt") + public void testExtensionFunForArray() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/extensionFunForArray.kt"); + } + + @TestMetadata("extractBlockContent.kt") + public void testExtractBlockContent() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/extractBlockContent.kt"); + } + + @TestMetadata("extractFromPrivateInlineFun.kt") + public void testExtractFromPrivateInlineFun() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/extractFromPrivateInlineFun.kt"); + } + + @TestMetadata("extractFromPublicInlineFun.kt") + public void testExtractFromPublicInlineFun() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/extractFromPublicInlineFun.kt"); + } + + @TestMetadata("extractSuspend.kt") + public void testExtractSuspend() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/extractSuspend.kt"); + } + + @TestMetadata("extractToEnumClassBody.kt") + public void testExtractToEnumClassBody() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/extractToEnumClassBody.kt"); + } + + @TestMetadata("fakeOverride.kt") + public void testFakeOverride() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/fakeOverride.kt"); + } + + @TestMetadata("fragmentWithComment.kt") + public void testFragmentWithComment() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/fragmentWithComment.kt"); + } + + @TestMetadata("fragmentWithMultilineComment.kt") + public void testFragmentWithMultilineComment() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/fragmentWithMultilineComment.kt"); + } + + @TestMetadata("identityEquals.kt") + public void testIdentityEquals() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/identityEquals.kt"); + } + + @TestMetadata("inlineReified.kt") + public void testInlineReified() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/inlineReified.kt"); + } + + @TestMetadata("inlineReified2.kt") + public void testInlineReified2() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/inlineReified2.kt"); + } + + @TestMetadata("interfaceFunction.kt") + public void testInterfaceFunction() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/interfaceFunction.kt"); + } + + @TestMetadata("invisibleType.kt") + public void testInvisibleType() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/invisibleType.kt"); + } + + @TestMetadata("lineWithSemicolon.kt") + public void testLineWithSemicolon() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/lineWithSemicolon.kt"); + } + + @TestMetadata("localClassExtraction.kt") + public void testLocalClassExtraction() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/localClassExtraction.kt"); + } + + @TestMetadata("localClassFunctionRef.kt") + public void testLocalClassFunctionRef() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/localClassFunctionRef.kt"); + } + + @TestMetadata("localClassPropertyRef.kt") + public void testLocalClassPropertyRef() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/localClassPropertyRef.kt"); + } + + @TestMetadata("localExtraction.kt") + public void testLocalExtraction() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/localExtraction.kt"); + } + + @TestMetadata("localFunExtraction.kt") + public void testLocalFunExtraction() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/localFunExtraction.kt"); + } + + @TestMetadata("localFunctionInTheMiddleSimpleControlFlow.kt") + public void testLocalFunctionInTheMiddleSimpleControlFlow() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/localFunctionInTheMiddleSimpleControlFlow.kt"); + } + + @TestMetadata("localFunctionInTheMiddleUnusedVar.kt") + public void testLocalFunctionInTheMiddleUnusedVar() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/localFunctionInTheMiddleUnusedVar.kt"); + } + + @TestMetadata("localFunctionInTheMiddleUsedVar.kt") + public void testLocalFunctionInTheMiddleUsedVar() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/localFunctionInTheMiddleUsedVar.kt"); + } + + @TestMetadata("localFunctionRef.kt") + public void testLocalFunctionRef() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/localFunctionRef.kt"); + } + + @TestMetadata("localObjectRef.kt") + public void testLocalObjectRef() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/localObjectRef.kt"); + } + + @TestMetadata("malformedExpression.kt") + public void testMalformedExpression() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/malformedExpression.kt"); + } + + @TestMetadata("malformedStatements.kt") + public void testMalformedStatements() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/malformedStatements.kt"); + } + + @TestMetadata("memberValUnderSmartCast.kt") + public void testMemberValUnderSmartCast() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/memberValUnderSmartCast.kt"); + } + + @TestMetadata("memberValUnderSmartCastAndImplicitReceiver.kt") + public void testMemberValUnderSmartCastAndImplicitReceiver() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/memberValUnderSmartCastAndImplicitReceiver.kt"); + } + + @TestMetadata("misdirectedRef.kt") + public void testMisdirectedRef() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/misdirectedRef.kt"); + } + + @TestMetadata("noConflictWithInnerFunction.kt") + public void testNoConflictWithInnerFunction() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/noConflictWithInnerFunction.kt"); + } + + @TestMetadata("noConflictWithInnerFunction1.kt") + public void testNoConflictWithInnerFunction1() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/noConflictWithInnerFunction1.kt"); + } + + @TestMetadata("objectQualifier.kt") + public void testObjectQualifier() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/objectQualifier.kt"); + } + + @TestMetadata("packageQualifier.kt") + public void testPackageQualifier() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/packageQualifier.kt"); + } + + @TestMetadata("privateMemberInClassObject.kt") + public void testPrivateMemberInClassObject() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/privateMemberInClassObject.kt"); + } + + @TestMetadata("privateMemberRef.kt") + public void testPrivateMemberRef() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/privateMemberRef.kt"); + } + + @TestMetadata("privateNestedClass.kt") + public void testPrivateNestedClass() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/privateNestedClass.kt"); + } + + @TestMetadata("privateSetter.kt") + public void testPrivateSetter() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/privateSetter.kt"); + } + + @TestMetadata("refInReturn.kt") + public void testRefInReturn() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/refInReturn.kt"); + } + + @TestMetadata("sameParameterNames.kt") + public void testSameParameterNames() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/sameParameterNames.kt"); + } + + @TestMetadata("selectedConstructor.kt") + public void testSelectedConstructor() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/selectedConstructor.kt"); + } + + @TestMetadata("selectedParameter.kt") + public void testSelectedParameter() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/selectedParameter.kt"); + } + + @TestMetadata("suspendCall.kt") + public void testSuspendCall() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/suspendCall.kt"); + } + + @TestMetadata("suspendCallWithExtractedParameter.kt") + public void testSuspendCallWithExtractedParameter() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/suspendCallWithExtractedParameter.kt"); + } + + @TestMetadata("topLevelValUnderSmartCast.kt") + public void testTopLevelValUnderSmartCast() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/topLevelValUnderSmartCast.kt"); + } + + @TestMetadata("unnecessaryNullabilityAnnotation.kt") + public void testUnnecessaryNullabilityAnnotation() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/unnecessaryNullabilityAnnotation.kt"); + } + + @TestMetadata("unresolvedWrongReceiver.kt") + public void testUnresolvedWrongReceiver() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/basic/unresolvedWrongReceiver.kt"); + } + } + + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/controlFlow") + public abstract static class ControlFlow extends AbstractK2IntroduceFunctionTest { + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps") + public static class ConditionalJumps extends AbstractK2IntroduceFunctionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath); + } + + @TestMetadata("conditionalBreakWithIf.kt") + public void testConditionalBreakWithIf() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/conditionalBreakWithIf.kt"); + } + + @TestMetadata("conditionalBreakWithIfAndExtraVars.kt") + public void testConditionalBreakWithIfAndExtraVars() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/conditionalBreakWithIfAndExtraVars.kt"); + } + + @TestMetadata("conditionalBreakWithIfElse.kt") + public void testConditionalBreakWithIfElse() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/conditionalBreakWithIfElse.kt"); + } + + @TestMetadata("conditionalBreakWithWhen.kt") + public void testConditionalBreakWithWhen() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/conditionalBreakWithWhen.kt"); + } + + @TestMetadata("conditionalReturnWithIf.kt") + public void testConditionalReturnWithIf() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/conditionalReturnWithIf.kt"); + } + + @TestMetadata("conditionalReturnWithIfElse.kt") + public void testConditionalReturnWithIfElse() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/conditionalReturnWithIfElse.kt"); + } + + @TestMetadata("conditionalReturnWithWhen.kt") + public void testConditionalReturnWithWhen() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/conditionalReturnWithWhen.kt"); + } + + @TestMetadata("unconditionalBreakWithIf.kt") + public void testUnconditionalBreakWithIf() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/unconditionalBreakWithIf.kt"); + } + + @TestMetadata("unconditionalBreakWithIfElse.kt") + public void testUnconditionalBreakWithIfElse() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/unconditionalBreakWithIfElse.kt"); + } + + @TestMetadata("unconditionalBreakWithWhen.kt") + public void testUnconditionalBreakWithWhen() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/conditionalJumps/unconditionalBreakWithWhen.kt"); + } + } + + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/controlFlow/default") + public static class Default extends AbstractK2IntroduceFunctionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath); + } + + @TestMetadata("defaultCF.kt") + public void testDefaultCF() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/default/defaultCF.kt"); + } + + @TestMetadata("defaultCFWithExtraVars.kt") + public void testDefaultCFWithExtraVars() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/default/defaultCFWithExtraVars.kt"); + } + + @TestMetadata("defaultCFWithJumps.kt") + public void testDefaultCFWithJumps() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/default/defaultCFWithJumps.kt"); + } + + @TestMetadata("ignoredReturnValueWithIf.kt") + public void testIgnoredReturnValueWithIf() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/default/ignoredReturnValueWithIf.kt"); + } + + @TestMetadata("ignoredReturnValueWithIfNoBlocks.kt") + public void testIgnoredReturnValueWithIfNoBlocks() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/default/ignoredReturnValueWithIfNoBlocks.kt"); + } + + @TestMetadata("ignoredReturnValueWithWhen.kt") + public void testIgnoredReturnValueWithWhen() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/default/ignoredReturnValueWithWhen.kt"); + } + + @TestMetadata("ignoredReturnValueWithWhenNoBlocks.kt") + public void testIgnoredReturnValueWithWhenNoBlocks() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/default/ignoredReturnValueWithWhenNoBlocks.kt"); + } + } + + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/controlFlow/definiteReturns") + public static class DefiniteReturns extends AbstractK2IntroduceFunctionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath); + } + + @TestMetadata("definiteReturnWithIf.kt") + public void testDefiniteReturnWithIf() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/definiteReturns/definiteReturnWithIf.kt"); + } + + @TestMetadata("definiteReturnWithWhen.kt") + public void testDefiniteReturnWithWhen() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/definiteReturns/definiteReturnWithWhen.kt"); + } + + @TestMetadata("labeledAndUnlabeledReturn1.kt") + public void testLabeledAndUnlabeledReturn1() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/definiteReturns/labeledAndUnlabeledReturn1.kt"); + } + + @TestMetadata("labeledAndUnlabeledReturn2.kt") + public void testLabeledAndUnlabeledReturn2() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/definiteReturns/labeledAndUnlabeledReturn2.kt"); + } + + @TestMetadata("nonLocalReturn.kt") + public void testNonLocalReturn() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/definiteReturns/nonLocalReturn.kt"); + } + + @TestMetadata("nonLocalReturnWithLabel.kt") + public void testNonLocalReturnWithLabel() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/definiteReturns/nonLocalReturnWithLabel.kt"); + } + + @TestMetadata("returnWithLabel.kt") + public void testReturnWithLabel() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/definiteReturns/returnWithLabel.kt"); + } + + @TestMetadata("singleDefiniteReturn.kt") + public void testSingleDefiniteReturn() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/definiteReturns/singleDefiniteReturn.kt"); + } + } + + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression") + public static class EvaluateExpression extends AbstractK2IntroduceFunctionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath); + } + + @TestMetadata("evalExprInIfCondition.kt") + public void testEvalExprInIfCondition() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInIfCondition.kt"); + } + + @TestMetadata("evalExprInIfElse.kt") + public void testEvalExprInIfElse() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInIfElse.kt"); + } + + @TestMetadata("evalExprInIfThen.kt") + public void testEvalExprInIfThen() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInIfThen.kt"); + } + + @TestMetadata("evalExprInWhenBranch.kt") + public void testEvalExprInWhenBranch() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInWhenBranch.kt"); + } + + @TestMetadata("evalExprInWhenCondition.kt") + public void testEvalExprInWhenCondition() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInWhenCondition.kt"); + } + + @TestMetadata("evalExprInWhenSubject.kt") + public void testEvalExprInWhenSubject() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInWhenSubject.kt"); + } + + @TestMetadata("evalExpressionBodyFunction.kt") + public void testEvalExpressionBodyFunction() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExpressionBodyFunction.kt"); + } + + @TestMetadata("evalIfExpr.kt") + public void testEvalIfExpr() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalIfExpr.kt"); + } + + @TestMetadata("evalWhenExpr.kt") + public void testEvalWhenExpr() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalWhenExpr.kt"); + } + + @TestMetadata("simpleEvalExpr.kt") + public void testSimpleEvalExpr() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/simpleEvalExpr.kt"); + } + + @TestMetadata("trailingLambdaEmptyArgList.kt") + public void testTrailingLambdaEmptyArgList() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/trailingLambdaEmptyArgList.kt"); + } + + @TestMetadata("trailingLambdaNoArgList.kt") + public void testTrailingLambdaNoArgList() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/trailingLambdaNoArgList.kt"); + } + + @TestMetadata("trailingLambdaNonEmptyArgList.kt") + public void testTrailingLambdaNonEmptyArgList() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/trailingLambdaNonEmptyArgList.kt"); + } + + @TestMetadata("trailingLambdaNonEmptyArgListWithNamedArgs.kt") + public void testTrailingLambdaNonEmptyArgListWithNamedArgs() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/evaluateExpression/trailingLambdaNonEmptyArgListWithNamedArgs.kt"); + } + } + + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/controlFlow/exitPointEquivalence") + public static class ExitPointEquivalence extends AbstractK2IntroduceFunctionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath); + } + + @TestMetadata("breakAndReturn.kt") + public void testBreakAndReturn() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/exitPointEquivalence/breakAndReturn.kt"); + } + + @TestMetadata("breakContinueAndDefault.kt") + public void testBreakContinueAndDefault() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/exitPointEquivalence/breakContinueAndDefault.kt"); + } + + @TestMetadata("continueAndReturn.kt") + public void testContinueAndReturn() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/exitPointEquivalence/continueAndReturn.kt"); + } + + @TestMetadata("defaultAndBreak.kt") + public void testDefaultAndBreak() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/exitPointEquivalence/defaultAndBreak.kt"); + } + + @TestMetadata("defaultAndContinue.kt") + public void testDefaultAndContinue() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/exitPointEquivalence/defaultAndContinue.kt"); + } + + @TestMetadata("defaultAndReturn.kt") + public void testDefaultAndReturn() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/exitPointEquivalence/defaultAndReturn.kt"); + } + + @TestMetadata("defaultAndReturnInWhen.kt") + public void testDefaultAndReturnInWhen() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/exitPointEquivalence/defaultAndReturnInWhen.kt"); + } + + @TestMetadata("multipleBreaks.kt") + public void testMultipleBreaks() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/exitPointEquivalence/multipleBreaks.kt"); + } + } + + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/controlFlow/initializer") + public static class Initializer extends AbstractK2IntroduceFunctionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath); + } + + @TestMetadata("propertyWithInitializer.kt") + public void testPropertyWithInitializer() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/initializer/propertyWithInitializer.kt"); + } + + @TestMetadata("propertyWithInitializerAndExtraVars.kt") + public void testPropertyWithInitializerAndExtraVars() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/initializer/propertyWithInitializerAndExtraVars.kt"); + } + + @TestMetadata("propertyWithInitializerAndUnusedVars.kt") + public void testPropertyWithInitializerAndUnusedVars() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/initializer/propertyWithInitializerAndUnusedVars.kt"); + } + + @TestMetadata("propertyWithSeparateInitializer.kt") + public void testPropertyWithSeparateInitializer() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/initializer/propertyWithSeparateInitializer.kt"); + } + + @TestMetadata("valueUsedInAnonymousObject.kt") + public void testValueUsedInAnonymousObject() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/initializer/valueUsedInAnonymousObject.kt"); + } + + @TestMetadata("valueUsedInLambda.kt") + public void testValueUsedInLambda() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/initializer/valueUsedInLambda.kt"); + } + + @TestMetadata("valueUsedInLocalFunction.kt") + public void testValueUsedInLocalFunction() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/initializer/valueUsedInLocalFunction.kt"); + } + } + + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/controlFlow/outputValues") + public static class OutputValues extends AbstractK2IntroduceFunctionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath); + } + + @TestMetadata("genericPair.kt") + public void testGenericPair() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/outputValues/genericPair.kt"); + } + + @TestMetadata("initializersAndUpdate.kt") + public void testInitializersAndUpdate() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/outputValues/initializersAndUpdate.kt"); + } + + @TestMetadata("nestedNonInlinableCall.kt") + public void testNestedNonInlinableCall() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/outputValues/nestedNonInlinableCall.kt"); + } + + @TestMetadata("outputValueWithExpression.kt") + public void testOutputValueWithExpression() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/outputValues/outputValueWithExpression.kt"); + } + + @TestMetadata("outputValueWithReturn.kt") + public void testOutputValueWithReturn() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/outputValues/outputValueWithReturn.kt"); + } + + @TestMetadata("outputValueWithSingleLineExpression.kt") + public void testOutputValueWithSingleLineExpression() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/outputValues/outputValueWithSingleLineExpression.kt"); + } + + @TestMetadata("outputValuesWithExpression.kt") + public void testOutputValuesWithExpression() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/outputValues/outputValuesWithExpression.kt"); + } + + @TestMetadata("pair.kt") + public void testPair() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/outputValues/pair.kt"); + } + + @TestMetadata("pairOfInitalizersWithNonLocalUsages.kt") + public void testPairOfInitalizersWithNonLocalUsages() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/outputValues/pairOfInitalizersWithNonLocalUsages.kt"); + } + + @TestMetadata("pairOfVarInitalizersWithNonLocalUsages.kt") + public void testPairOfVarInitalizersWithNonLocalUsages() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/outputValues/pairOfVarInitalizersWithNonLocalUsages.kt"); + } + + @TestMetadata("pairWithIf.kt") + public void testPairWithIf() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/outputValues/pairWithIf.kt"); + } + + @TestMetadata("pairWithNameClash.kt") + public void testPairWithNameClash() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/outputValues/pairWithNameClash.kt"); + } + + @TestMetadata("pairWithWhen.kt") + public void testPairWithWhen() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/outputValues/pairWithWhen.kt"); + } + + @TestMetadata("singleOutputValue.kt") + public void testSingleOutputValue() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/outputValues/singleOutputValue.kt"); + } + + @TestMetadata("singleOutputValueWithIf.kt") + public void testSingleOutputValueWithIf() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/outputValues/singleOutputValueWithIf.kt"); + } + + @TestMetadata("singleOutputValueWithIfElse.kt") + public void testSingleOutputValueWithIfElse() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/outputValues/singleOutputValueWithIfElse.kt"); + } + + @TestMetadata("singleOutputValueWithWhen.kt") + public void testSingleOutputValueWithWhen() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/outputValues/singleOutputValueWithWhen.kt"); + } + + @TestMetadata("singleOutputValueWithWhenElse.kt") + public void testSingleOutputValueWithWhenElse() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/outputValues/singleOutputValueWithWhenElse.kt"); + } + + @TestMetadata("tooManyOutputValues.kt") + public void testTooManyOutputValues() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/outputValues/tooManyOutputValues.kt"); + } + + @TestMetadata("tooManyOutputValuesAsList.kt") + public void testTooManyOutputValuesAsList() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/outputValues/tooManyOutputValuesAsList.kt"); + } + + @TestMetadata("triple.kt") + public void testTriple() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/outputValues/triple.kt"); + } + + @TestMetadata("usedAndUnusedOutputValues.kt") + public void testUsedAndUnusedOutputValues() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/outputValues/usedAndUnusedOutputValues.kt"); + } + + @TestMetadata("valAndVarInitalizersWithNonLocalUsages.kt") + public void testValAndVarInitalizersWithNonLocalUsages() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/outputValues/valAndVarInitalizersWithNonLocalUsages.kt"); + } + + @TestMetadata("valuesUsedInLambdaOnly.kt") + public void testValuesUsedInLambdaOnly() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/outputValues/valuesUsedInLambdaOnly.kt"); + } + + @TestMetadata("valuesUsedInNestedBlock.kt") + public void testValuesUsedInNestedBlock() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/outputValues/valuesUsedInNestedBlock.kt"); + } + } + + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates") + public static class ReturnTypeCandidates extends AbstractK2IntroduceFunctionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath); + } + + @TestMetadata("javaAnnotatedNotNull.kt") + public void testJavaAnnotatedNotNull() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNotNull.kt"); + } + + @TestMetadata("javaAnnotatedNullable.kt") + public void testJavaAnnotatedNullable() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNullable.kt"); + } + + @TestMetadata("javaNoNullabilityAnnotation.kt") + public void testJavaNoNullabilityAnnotation() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaNoNullabilityAnnotation.kt"); + } + } + + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/controlFlow/throws") + public static class Throws extends AbstractK2IntroduceFunctionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath); + } + + @TestMetadata("breakWithThrow.kt") + public void testBreakWithThrow() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/throws/breakWithThrow.kt"); + } + + @TestMetadata("continueWithThrow.kt") + public void testContinueWithThrow() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/throws/continueWithThrow.kt"); + } + + @TestMetadata("evalExpressionWithThrow.kt") + public void testEvalExpressionWithThrow() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/throws/evalExpressionWithThrow.kt"); + } + + @TestMetadata("implicitThrow.kt") + public void testImplicitThrow() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/throws/implicitThrow.kt"); + } + + @TestMetadata("nonValuedReturnWithThrow.kt") + public void testNonValuedReturnWithThrow() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/throws/nonValuedReturnWithThrow.kt"); + } + + @TestMetadata("outputValueWithThrow.kt") + public void testOutputValueWithThrow() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/throws/outputValueWithThrow.kt"); + } + + @TestMetadata("returnWithThrow.kt") + public void testReturnWithThrow() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/throws/returnWithThrow.kt"); + } + } + + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/controlFlow/unextractable") + public static class Unextractable extends AbstractK2IntroduceFunctionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath); + } + + @TestMetadata("anonymousObject.kt") + public void testAnonymousObject() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/unextractable/anonymousObject.kt"); + } + + @TestMetadata("evalExpressionWithReturn.kt") + public void testEvalExpressionWithReturn() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/unextractable/evalExpressionWithReturn.kt"); + } + + @TestMetadata("jumpsAndReturns.kt") + public void testJumpsAndReturns() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/unextractable/jumpsAndReturns.kt"); + } + + @TestMetadata("multipleJumps.kt") + public void testMultipleJumps() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/unextractable/multipleJumps.kt"); + } + + @TestMetadata("multipleReturnsWithDefault.kt") + public void testMultipleReturnsWithDefault() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/unextractable/multipleReturnsWithDefault.kt"); + } + + @TestMetadata("unmatchedReturnTargets.kt") + public void testUnmatchedReturnTargets() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/unextractable/unmatchedReturnTargets.kt"); + } + + @TestMetadata("variablesOutOfScope.kt") + public void testVariablesOutOfScope() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/controlFlow/unextractable/variablesOutOfScope.kt"); + } + } + } + + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/defaultContainer") + public static class DefaultContainer extends AbstractK2IntroduceFunctionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath); + } + + @TestMetadata("anonymousObject.kt") + public void testAnonymousObject() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/defaultContainer/anonymousObject.kt"); + } + + @TestMetadata("classFunction.kt") + public void testClassFunction() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/defaultContainer/classFunction.kt"); + } + + @TestMetadata("lambda.kt") + public void testLambda() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/defaultContainer/lambda.kt"); + } + + @TestMetadata("localClass.kt") + public void testLocalClass() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/defaultContainer/localClass.kt"); + } + + @TestMetadata("localFunction.kt") + public void testLocalFunction() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/defaultContainer/localFunction.kt"); + } + + @TestMetadata("nestedLambda.kt") + public void testNestedLambda() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/defaultContainer/nestedLambda.kt"); + } + + @TestMetadata("topLevelFunction.kt") + public void testTopLevelFunction() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/defaultContainer/topLevelFunction.kt"); + } + } + + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/delegation") + public static class Delegation extends AbstractK2IntroduceFunctionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath); + } + + @TestMetadata("delegationByExpression.kt") + public void testDelegationByExpression() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/delegation/delegationByExpression.kt"); + } + + @TestMetadata("delegationByExpressionFull.kt") + public void testDelegationByExpressionFull() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/delegation/delegationByExpressionFull.kt"); + } + + @TestMetadata("delegationBySuperCall.kt") + public void testDelegationBySuperCall() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/delegation/delegationBySuperCall.kt"); + } + } + + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/initializers") + public abstract static class Initializers extends AbstractK2IntroduceFunctionTest { + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/initializers/accessors") + public static class Accessors extends AbstractK2IntroduceFunctionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath); + } + + @TestMetadata("memberProperty.kt") + public void testMemberProperty() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/initializers/accessors/memberProperty.kt"); + } + + @TestMetadata("memberPropertyWithLambda.kt") + public void testMemberPropertyWithLambda() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/initializers/accessors/memberPropertyWithLambda.kt"); + } + + @TestMetadata("topLevelProperty.kt") + public void testTopLevelProperty() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/initializers/accessors/topLevelProperty.kt"); + } + + @TestMetadata("topLevelPropertyWithLambda.kt") + public void testTopLevelPropertyWithLambda() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/initializers/accessors/topLevelPropertyWithLambda.kt"); + } + } + + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/initializers/classes") + public static class Classes extends AbstractK2IntroduceFunctionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath); + } + + @TestMetadata("classInitializer.kt") + public void testClassInitializer() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/initializers/classes/classInitializer.kt"); + } + + @TestMetadata("classInitializerWithLambda.kt") + public void testClassInitializerWithLambda() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/initializers/classes/classInitializerWithLambda.kt"); + } + + @TestMetadata("classParameters.kt") + public void testClassParameters() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/initializers/classes/classParameters.kt"); + } + + @TestMetadata("classParametersWithLambda.kt") + public void testClassParametersWithLambda() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/initializers/classes/classParametersWithLambda.kt"); + } + } + + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/initializers/functions") + public static class Functions extends AbstractK2IntroduceFunctionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath); + } + + @TestMetadata("localFunction.kt") + public void testLocalFunction() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/initializers/functions/localFunction.kt"); + } + + @TestMetadata("localFunctionParameters.kt") + public void testLocalFunctionParameters() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/initializers/functions/localFunctionParameters.kt"); + } + + @TestMetadata("localFunctionParametersWithLambda.kt") + public void testLocalFunctionParametersWithLambda() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/initializers/functions/localFunctionParametersWithLambda.kt"); + } + + @TestMetadata("localFunctionWithLambda.kt") + public void testLocalFunctionWithLambda() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/initializers/functions/localFunctionWithLambda.kt"); + } + + @TestMetadata("memberFunction.kt") + public void testMemberFunction() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/initializers/functions/memberFunction.kt"); + } + + @TestMetadata("memberFunctionParameters.kt") + public void testMemberFunctionParameters() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/initializers/functions/memberFunctionParameters.kt"); + } + + @TestMetadata("memberFunctionParametersWithLambda.kt") + public void testMemberFunctionParametersWithLambda() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/initializers/functions/memberFunctionParametersWithLambda.kt"); + } + + @TestMetadata("memberFunctionWithLambda.kt") + public void testMemberFunctionWithLambda() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/initializers/functions/memberFunctionWithLambda.kt"); + } + + @TestMetadata("topLevelFunction.kt") + public void testTopLevelFunction() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/initializers/functions/topLevelFunction.kt"); + } + + @TestMetadata("topLevelFunctionParameters.kt") + public void testTopLevelFunctionParameters() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/initializers/functions/topLevelFunctionParameters.kt"); + } + + @TestMetadata("topLevelFunctionParametersWithLambda.kt") + public void testTopLevelFunctionParametersWithLambda() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/initializers/functions/topLevelFunctionParametersWithLambda.kt"); + } + + @TestMetadata("topLevelFunctionWithLambda.kt") + public void testTopLevelFunctionWithLambda() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/initializers/functions/topLevelFunctionWithLambda.kt"); + } + } + + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/initializers/properties") + public static class Properties extends AbstractK2IntroduceFunctionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath); + } + + @TestMetadata("localProperty.kt") + public void testLocalProperty() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/initializers/properties/localProperty.kt"); + } + + @TestMetadata("localPropertyWithLambda.kt") + public void testLocalPropertyWithLambda() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/initializers/properties/localPropertyWithLambda.kt"); + } + + @TestMetadata("memberProperty.kt") + public void testMemberProperty() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/initializers/properties/memberProperty.kt"); + } + + @TestMetadata("memberPropertyWithLambda.kt") + public void testMemberPropertyWithLambda() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/initializers/properties/memberPropertyWithLambda.kt"); + } + + @TestMetadata("multiDeclaration.kt") + public void testMultiDeclaration() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/initializers/properties/multiDeclaration.kt"); + } + + @TestMetadata("nestedInMultiDeclaration.kt") + public void testNestedInMultiDeclaration() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/initializers/properties/nestedInMultiDeclaration.kt"); + } + + @TestMetadata("topLevelProperty.kt") + public void testTopLevelProperty() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/initializers/properties/topLevelProperty.kt"); + } + + @TestMetadata("topLevelPropertyWithLambda.kt") + public void testTopLevelPropertyWithLambda() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/initializers/properties/topLevelPropertyWithLambda.kt"); + } + } + } + + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/multiline") + public static class Multiline extends AbstractK2IntroduceFunctionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath); + } + + @TestMetadata("multilineBinaryExpression.kt") + public void testMultilineBinaryExpression() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/multiline/multilineBinaryExpression.kt"); + } + + @TestMetadata("multilineBinaryExpressionWithComments.kt") + public void testMultilineBinaryExpressionWithComments() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/multiline/multilineBinaryExpressionWithComments.kt"); + } + + @TestMetadata("multilineNestedBinaryExpression1.kt") + public void testMultilineNestedBinaryExpression1() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/multiline/multilineNestedBinaryExpression1.kt"); + } + + @TestMetadata("multilineNestedBinaryExpression2.kt") + public void testMultilineNestedBinaryExpression2() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/multiline/multilineNestedBinaryExpression2.kt"); + } + } + + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/optIn") + public static class OptIn extends AbstractK2IntroduceFunctionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath); + } + + @TestMetadata("allOptInAnnotations.kt") + public void testAllOptInAnnotations() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/optIn/allOptInAnnotations.kt"); + } + + @TestMetadata("containerOptInAnnotation.kt") + public void testContainerOptInAnnotation() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/optIn/containerOptInAnnotation.kt"); + } + + @TestMetadata("filterOptInAnnotations.kt") + public void testFilterOptInAnnotations() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/optIn/filterOptInAnnotations.kt"); + } + + @TestMetadata("filterPropagatingMarkers.kt") + public void testFilterPropagatingMarkers() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/optIn/filterPropagatingMarkers.kt"); + } + + @TestMetadata("filterPropagatingMarkersWitArgs.kt") + public void testFilterPropagatingMarkersWitArgs() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/optIn/filterPropagatingMarkersWitArgs.kt"); + } + + @TestMetadata("noExperimentalApi.kt") + public void testNoExperimentalApi() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/optIn/noExperimentalApi.kt"); + } + + @TestMetadata("singleOptInAnnotation.kt") + public void testSingleOptInAnnotation() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/optIn/singleOptInAnnotation.kt"); + } + + @TestMetadata("singlePropagatingMarker.kt") + public void testSinglePropagatingMarker() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/optIn/singlePropagatingMarker.kt"); + } + + @TestMetadata("useExperimental16.kt") + public void testUseExperimental16() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/optIn/useExperimental16.kt"); + } + } + + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/parameters") + public abstract static class Parameters extends AbstractK2IntroduceFunctionTest { + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes") + public static class CandidateTypes extends AbstractK2IntroduceFunctionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath); + } + + @TestMetadata("cantLiftAnonymousToSupertype.kt") + public void testCantLiftAnonymousToSupertype() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/cantLiftAnonymousToSupertype.kt"); + } + + @TestMetadata("flexibleTypesWithNull.kt") + public void testFlexibleTypesWithNull() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/flexibleTypesWithNull.kt"); + } + + @TestMetadata("flexibleTypesWithoutNull.kt") + public void testFlexibleTypesWithoutNull() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/flexibleTypesWithoutNull.kt"); + } + + @TestMetadata("liftAnonymousToSupertype1.kt") + public void testLiftAnonymousToSupertype1() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/liftAnonymousToSupertype1.kt"); + } + + @TestMetadata("liftAnonymousToSupertype2.kt") + public void testLiftAnonymousToSupertype2() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/liftAnonymousToSupertype2.kt"); + } + + @TestMetadata("multipleTypes.kt") + public void testMultipleTypes() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes.kt"); + } + + @TestMetadata("multipleTypes2.kt") + public void testMultipleTypes2() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes2.kt"); + } + + @TestMetadata("multipleTypes3.kt") + public void testMultipleTypes3() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes3.kt"); + } + + @TestMetadata("multipleTypes4.kt") + public void testMultipleTypes4() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/multipleTypes4.kt"); + } + + @TestMetadata("mutablyFlexibleType.kt") + public void testMutablyFlexibleType() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/mutablyFlexibleType.kt"); + } + + @TestMetadata("nonNullableTypes.kt") + public void testNonNullableTypes() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/nonNullableTypes.kt"); + } + + @TestMetadata("nullableTypes.kt") + public void testNullableTypes() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/nullableTypes.kt"); + } + + @TestMetadata("receiverCandidateTypes.kt") + public void testReceiverCandidateTypes() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/receiverCandidateTypes.kt"); + } + + @TestMetadata("typeHierarchy1.kt") + public void testTypeHierarchy1() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/typeHierarchy1.kt"); + } + + @TestMetadata("typeHierarchy2.kt") + public void testTypeHierarchy2() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/typeHierarchy2.kt"); + } + + @TestMetadata("typeHierarchy3.kt") + public void testTypeHierarchy3() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/typeHierarchy3.kt"); + } + + @TestMetadata("typeHierarchy4.kt") + public void testTypeHierarchy4() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/candidateTypes/typeHierarchy4.kt"); + } + } + + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/parameters/capturedFunctions") + public static class CapturedFunctions extends AbstractK2IntroduceFunctionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath); + } + + @TestMetadata("deeplyLocalFun.kt") + public void testDeeplyLocalFun() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/capturedFunctions/deeplyLocalFun.kt"); + } + + @TestMetadata("localExtensionFun.kt") + public void testLocalExtensionFun() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/capturedFunctions/localExtensionFun.kt"); + } + + @TestMetadata("localExtensionFunChainedCalls.kt") + public void testLocalExtensionFunChainedCalls() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/capturedFunctions/localExtensionFunChainedCalls.kt"); + } + + @TestMetadata("localExtensionFunInfixCall.kt") + public void testLocalExtensionFunInfixCall() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/capturedFunctions/localExtensionFunInfixCall.kt"); + } + + @TestMetadata("localFun.kt") + public void testLocalFun() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/capturedFunctions/localFun.kt"); + } + } + + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/parameters/extractSuper") + public static class ExtractSuper extends AbstractK2IntroduceFunctionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath); + } + + @TestMetadata("labeledSuperPropertyCall.kt") + public void testLabeledSuperPropertyCall() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/extractSuper/labeledSuperPropertyCall.kt"); + } + + @TestMetadata("superFunctionCall.kt") + public void testSuperFunctionCall() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/extractSuper/superFunctionCall.kt"); + } + + @TestMetadata("superLabeledFunctionCall.kt") + public void testSuperLabeledFunctionCall() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/extractSuper/superLabeledFunctionCall.kt"); + } + + @TestMetadata("superPropertyCall.kt") + public void testSuperPropertyCall() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/extractSuper/superPropertyCall.kt"); + } + } + + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/parameters/extractThis") + public static class ExtractThis extends AbstractK2IntroduceFunctionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath); + } + + @TestMetadata("bothReceivers.kt") + public void testBothReceivers() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/extractThis/bothReceivers.kt"); + } + + @TestMetadata("explicitLabeledThisInMember.kt") + public void testExplicitLabeledThisInMember() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/extractThis/explicitLabeledThisInMember.kt"); + } + + @TestMetadata("explicitThisInExtension.kt") + public void testExplicitThisInExtension() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/extractThis/explicitThisInExtension.kt"); + } + + @TestMetadata("explicitThisInMember.kt") + public void testExplicitThisInMember() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/extractThis/explicitThisInMember.kt"); + } + + @TestMetadata("explicitThisWithSmartCast.kt") + public void testExplicitThisWithSmartCast() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/extractThis/explicitThisWithSmartCast.kt"); + } + + @TestMetadata("implicitAndExplicitLabeledThisInMember.kt") + public void testImplicitAndExplicitLabeledThisInMember() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/extractThis/implicitAndExplicitLabeledThisInMember.kt"); + } + + @TestMetadata("implicitAndExplicitThisInExtension.kt") + public void testImplicitAndExplicitThisInExtension() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/extractThis/implicitAndExplicitThisInExtension.kt"); + } + + @TestMetadata("implicitThisInExtension.kt") + public void testImplicitThisInExtension() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/extractThis/implicitThisInExtension.kt"); + } + + @TestMetadata("implicitThisInMember.kt") + public void testImplicitThisInMember() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/extractThis/implicitThisInMember.kt"); + } + + @TestMetadata("implicitThisWithSmartCast.kt") + public void testImplicitThisWithSmartCast() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/extractThis/implicitThisWithSmartCast.kt"); + } + + @TestMetadata("javaSyntheticProperty.kt") + public void testJavaSyntheticProperty() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/extractThis/javaSyntheticProperty.kt"); + } + + @TestMetadata("javaSyntheticPropertyWithDelegation.kt") + public void testJavaSyntheticPropertyWithDelegation() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/extractThis/javaSyntheticPropertyWithDelegation.kt"); + } + + @TestMetadata("javaSyntheticPropertyWithOverride.kt") + public void testJavaSyntheticPropertyWithOverride() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/extractThis/javaSyntheticPropertyWithOverride.kt"); + } + + @TestMetadata("missingReceiver.kt") + public void testMissingReceiver() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/extractThis/missingReceiver.kt"); + } + + @TestMetadata("paramAsExplicitInvoke.kt") + public void testParamAsExplicitInvoke() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/extractThis/paramAsExplicitInvoke.kt"); + } + + @TestMetadata("paramWithExtensionInvoke.kt") + public void testParamWithExtensionInvoke() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/extractThis/paramWithExtensionInvoke.kt"); + } + + @TestMetadata("propertyWithExplicitInvoke.kt") + public void testPropertyWithExplicitInvoke() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/extractThis/propertyWithExplicitInvoke.kt"); + } + + @TestMetadata("propertyWithImplicitInvoke.kt") + public void testPropertyWithImplicitInvoke() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/extractThis/propertyWithImplicitInvoke.kt"); + } + + @TestMetadata("qualifiedThis.kt") + public void testQualifiedThis() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/extractThis/qualifiedThis.kt"); + } + + @TestMetadata("qualifiedThisAsArgument.kt") + public void testQualifiedThisAsArgument() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/extractThis/qualifiedThisAsArgument.kt"); + } + } + + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/parameters/it") + public static class It extends AbstractK2IntroduceFunctionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath); + } + + @TestMetadata("innerIt.kt") + public void testInnerIt() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/it/innerIt.kt"); + } + + @TestMetadata("lambdaWithIt.kt") + public void testLambdaWithIt() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/it/lambdaWithIt.kt"); + } + + @TestMetadata("outerIt.kt") + public void testOuterIt() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/it/outerIt.kt"); + } + + @TestMetadata("simpleIt.kt") + public void testSimpleIt() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/it/simpleIt.kt"); + } + } + + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/parameters/misc") + public static class Misc extends AbstractK2IntroduceFunctionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath); + } + + @TestMetadata("addPrefixToBackticks.kt") + public void testAddPrefixToBackticks() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/misc/addPrefixToBackticks.kt"); + } + + @TestMetadata("classObject.kt") + public void testClassObject() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/misc/classObject.kt"); + } + + @TestMetadata("extensionLambda.kt") + public void testExtensionLambda() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/misc/extensionLambda.kt"); + } + + @TestMetadata("extensionLambdaAsParameter.kt") + public void testExtensionLambdaAsParameter() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/misc/extensionLambdaAsParameter.kt"); + } + + @TestMetadata("idWithBackticks.kt") + public void testIdWithBackticks() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/misc/idWithBackticks.kt"); + } + + @TestMetadata("idWithRequiredBackticks.kt") + public void testIdWithRequiredBackticks() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/misc/idWithRequiredBackticks.kt"); + } + + @TestMetadata("innerClassObject.kt") + public void testInnerClassObject() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/misc/innerClassObject.kt"); + } + + @TestMetadata("kt5001.kt") + public void testKt5001() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/misc/kt5001.kt"); + } + + @TestMetadata("multiDeclaration.kt") + public void testMultiDeclaration() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/misc/multiDeclaration.kt"); + } + + @TestMetadata("multipleOccurrences.kt") + public void testMultipleOccurrences() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/misc/multipleOccurrences.kt"); + } + + @TestMetadata("namedArguments.kt") + public void testNamedArguments() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/misc/namedArguments.kt"); + } + + @TestMetadata("qualifiedAnnotation.kt") + public void testQualifiedAnnotation() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/misc/qualifiedAnnotation.kt"); + } + + @TestMetadata("qualifiedClassObject.kt") + public void testQualifiedClassObject() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/misc/qualifiedClassObject.kt"); + } + + @TestMetadata("qualifiedEnum.kt") + public void testQualifiedEnum() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/misc/qualifiedEnum.kt"); + } + + @TestMetadata("qualifiedObject.kt") + public void testQualifiedObject() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/misc/qualifiedObject.kt"); + } + + @TestMetadata("qualifiedPackage.kt") + public void testQualifiedPackage() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/misc/qualifiedPackage.kt"); + } + + @TestMetadata("qualifiedTypeArg.kt") + public void testQualifiedTypeArg() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/misc/qualifiedTypeArg.kt"); + } + + @TestMetadata("qualifiedTypeInValueArg.kt") + public void testQualifiedTypeInValueArg() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/misc/qualifiedTypeInValueArg.kt"); + } + + @TestMetadata("qualifiedTypeRef.kt") + public void testQualifiedTypeRef() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/misc/qualifiedTypeRef.kt"); + } + + @TestMetadata("reducedParameterList.kt") + public void testReducedParameterList() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/misc/reducedParameterList.kt"); + } + + @TestMetadata("replaceWithBackticks.kt") + public void testReplaceWithBackticks() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/misc/replaceWithBackticks.kt"); + } + + @TestMetadata("replaceWithBackticks2.kt") + public void testReplaceWithBackticks2() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/misc/replaceWithBackticks2.kt"); + } + + @TestMetadata("thisInObject.kt") + public void testThisInObject() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/misc/thisInObject.kt"); + } + + @TestMetadata("typeRef.kt") + public void testTypeRef() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/misc/typeRef.kt"); + } + + @TestMetadata("usagesInCallArgs.kt") + public void testUsagesInCallArgs() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/misc/usagesInCallArgs.kt"); + } + + @TestMetadata("variableAsFunction.kt") + public void testVariableAsFunction() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/misc/variableAsFunction.kt"); + } + } + + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/parameters/nonDenotableTypes") + public static class NonDenotableTypes extends AbstractK2IntroduceFunctionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath); + } + + @TestMetadata("anonymousObject.kt") + public void testAnonymousObject() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/nonDenotableTypes/anonymousObject.kt"); + } + + @TestMetadata("anonymousObjectWithCall.kt") + public void testAnonymousObjectWithCall() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/nonDenotableTypes/anonymousObjectWithCall.kt"); + } + + @TestMetadata("localClass.kt") + public void testLocalClass() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClass.kt"); + } + + @TestMetadata("localClassWithSuperclass.kt") + public void testLocalClassWithSuperclass() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclass.kt"); + } + + @TestMetadata("localClassWithSuperclassParameter.kt") + public void testLocalClassWithSuperclassParameter() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameter.kt"); + } + + @TestMetadata("localClassWithSuperclassParameterInLocalFun.kt") + public void testLocalClassWithSuperclassParameterInLocalFun() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameterInLocalFun.kt"); + } + + @TestMetadata("localClassWithSuperclassParameterNoApproximation.kt") + public void testLocalClassWithSuperclassParameterNoApproximation() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameterNoApproximation.kt"); + } + } + } + + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/stringTemplates") + public static class StringTemplates extends AbstractK2IntroduceFunctionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath); + } + + @TestMetadata("brokenEntryWithBlockExpr.kt") + public void testBrokenEntryWithBlockExpr() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/stringTemplates/brokenEntryWithBlockExpr.kt"); + } + + @TestMetadata("brokenEntryWithExpr.kt") + public void testBrokenEntryWithExpr() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/stringTemplates/brokenEntryWithExpr.kt"); + } + + @TestMetadata("brokenEscapeEntry.kt") + public void testBrokenEscapeEntry() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/stringTemplates/brokenEscapeEntry.kt"); + } + + @TestMetadata("extractFalse.kt") + public void testExtractFalse() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/stringTemplates/extractFalse.kt"); + } + + @TestMetadata("extractIntegerLiteral.kt") + public void testExtractIntegerLiteral() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/stringTemplates/extractIntegerLiteral.kt"); + } + + @TestMetadata("extractTrue.kt") + public void testExtractTrue() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/stringTemplates/extractTrue.kt"); + } + + @TestMetadata("fullContent.kt") + public void testFullContent() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/stringTemplates/fullContent.kt"); + } + + @TestMetadata("fullEntryWithBlockExpr.kt") + public void testFullEntryWithBlockExpr() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/stringTemplates/fullEntryWithBlockExpr.kt"); + } + + @TestMetadata("fullEntryWithSimpleName.kt") + public void testFullEntryWithSimpleName() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/stringTemplates/fullEntryWithSimpleName.kt"); + } + + @TestMetadata("multipleEntriesWithPrefix.kt") + public void testMultipleEntriesWithPrefix() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/stringTemplates/multipleEntriesWithPrefix.kt"); + } + + @TestMetadata("multipleEntriesWithSubstring.kt") + public void testMultipleEntriesWithSubstring() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/stringTemplates/multipleEntriesWithSubstring.kt"); + } + + @TestMetadata("multipleEntriesWithSuffix.kt") + public void testMultipleEntriesWithSuffix() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/stringTemplates/multipleEntriesWithSuffix.kt"); + } + + @TestMetadata("rawTemplateWithSubstring.kt") + public void testRawTemplateWithSubstring() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/stringTemplates/rawTemplateWithSubstring.kt"); + } + + @TestMetadata("singleEntryPrefix.kt") + public void testSingleEntryPrefix() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/stringTemplates/singleEntryPrefix.kt"); + } + + @TestMetadata("singleEntrySubstring.kt") + public void testSingleEntrySubstring() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/stringTemplates/singleEntrySubstring.kt"); + } + + @TestMetadata("singleEntrySuffix.kt") + public void testSingleEntrySuffix() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/stringTemplates/singleEntrySuffix.kt"); + } + } + + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/typeParameters") + public static class TypeParameters extends AbstractK2IntroduceFunctionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath); + } + + @TestMetadata("localClassInBound.kt") + public void testLocalClassInBound() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/typeParameters/localClassInBound.kt"); + } + + @TestMetadata("localClassInTypeConstraint.kt") + public void testLocalClassInTypeConstraint() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/typeParameters/localClassInTypeConstraint.kt"); + } + + @TestMetadata("noVarianceInFun.kt") + public void testNoVarianceInFun() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/typeParameters/noVarianceInFun.kt"); + } + + @TestMetadata("simpleTypeParameter.kt") + public void testSimpleTypeParameter() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/typeParameters/simpleTypeParameter.kt"); + } + + @TestMetadata("simpleTypeParameterWithConstraint.kt") + public void testSimpleTypeParameterWithConstraint() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/typeParameters/simpleTypeParameterWithConstraint.kt"); + } + + @TestMetadata("typeParamInArgument.kt") + public void testTypeParamInArgument() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/typeParameters/typeParamInArgument.kt"); + } + + @TestMetadata("typeParameterNotResolvableInTargetScope.kt") + public void testTypeParameterNotResolvableInTargetScope() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/typeParameters/typeParameterNotResolvableInTargetScope.kt"); + } + + @TestMetadata("typeParameterRef.kt") + public void testTypeParameterRef() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/typeParameters/typeParameterRef.kt"); + } + + @TestMetadata("typeParameterRefCanOmit.kt") + public void testTypeParameterRefCanOmit() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/typeParameters/typeParameterRefCanOmit.kt"); + } + + @TestMetadata("typeParameterResolvableInTargetScope.kt") + public void testTypeParameterResolvableInTargetScope() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/typeParameters/typeParameterResolvableInTargetScope.kt"); + } + + @TestMetadata("typeParametersAndConstraintsCombined1.kt") + public void testTypeParametersAndConstraintsCombined1() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/typeParameters/typeParametersAndConstraintsCombined1.kt"); + } + + @TestMetadata("typeParametersAndConstraintsCombined2.kt") + public void testTypeParametersAndConstraintsCombined2() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/typeParameters/typeParametersAndConstraintsCombined2.kt"); + } + + @TestMetadata("typeParametersAndConstraintsCombined3.kt") + public void testTypeParametersAndConstraintsCombined3() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/typeParameters/typeParametersAndConstraintsCombined3.kt"); + } + + @TestMetadata("typeParametersCombined1.kt") + public void testTypeParametersCombined1() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/typeParameters/typeParametersCombined1.kt"); + } + + @TestMetadata("typeParametersCombined2.kt") + public void testTypeParametersCombined2() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/typeParameters/typeParametersCombined2.kt"); + } + + @TestMetadata("typeParametersCombined3.kt") + public void testTypeParametersCombined3() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/typeParameters/typeParametersCombined3.kt"); + } + + @TestMetadata("typeParametersCombinedAndThis.kt") + public void testTypeParametersCombinedAndThis() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/typeParameters/typeParametersCombinedAndThis.kt"); + } + } +} diff --git a/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/GenerateK2RefactoringsTests.kt b/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/GenerateK2RefactoringsTests.kt index 0d213ddc1747..015083db4e2e 100644 --- a/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/GenerateK2RefactoringsTests.kt +++ b/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/GenerateK2RefactoringsTests.kt @@ -6,10 +6,11 @@ import org.jetbrains.kotlin.idea.k2.refactoring.bindToElement.AbstractK2BindToFq import org.jetbrains.kotlin.idea.k2.refactoring.inline.AbstractKotlinFirInlineTest import org.jetbrains.kotlin.idea.k2.refactoring.introduce.AbstractK2PsiUnifierTest import org.jetbrains.kotlin.idea.k2.refactoring.introduce.introduceVariable.AbstractK2IntroduceVariableTest +import org.jetbrains.kotlin.idea.k2.refactoring.introduceFunction.AbstractK2IntroduceFunctionTest import org.jetbrains.kotlin.idea.k2.refactoring.move.AbstractK2ChangePackageTest import org.jetbrains.kotlin.idea.k2.refactoring.move.AbstractK2MoveTest -import org.jetbrains.kotlin.idea.k2.refactoring.safeDelete.AbstractK2SafeDeleteTest import org.jetbrains.kotlin.idea.k2.refactoring.safeDelete.AbstractFirMultiModuleSafeDeleteTest +import org.jetbrains.kotlin.idea.k2.refactoring.safeDelete.AbstractK2SafeDeleteTest import org.jetbrains.kotlin.testGenerator.model.* import org.jetbrains.kotlin.testGenerator.model.Patterns.TEST @@ -17,14 +18,14 @@ internal fun MutableTWorkspace.generateK2RefactoringsTests() { testGroup("refactorings/kotlin.refactorings.tests.k2", testDataPath = "../../idea/tests/testData") { testClass { model("refactoring/safeDelete/deleteClass/kotlinClass", testMethodName = "doClassTest") - //todo secondary constructor + //todo secondary constructor //model("refactoring/safeDelete/deleteClass/kotlinClassWithJava", testMethodName = "doClassTestWithJava") model("refactoring/safeDelete/deleteClass/javaClassWithKotlin", pattern = Patterns.JAVA, testMethodName = "doJavaClassTest") model("refactoring/safeDelete/deleteObject/kotlinObject", testMethodName = "doObjectTest") model("refactoring/safeDelete/deleteFunction/kotlinFunction", testMethodName = "doFunctionTest") model( "refactoring/safeDelete/deleteFunction/kotlinFunctionWithJava", - Patterns.forRegex("^(((?!secondary)(?!implement4).)+)\\.kt"),//todo secondary constructor, super method search from java override + Patterns.forRegex("^(((?!secondary)(?!implement4).)+)\\.kt"), //todo secondary constructor, super method search from java override testMethodName = "doFunctionTestWithJava" ) model("refactoring/safeDelete/deleteFunction/javaFunctionWithKotlin", testMethodName = "doJavaMethodTest") @@ -64,6 +65,9 @@ internal fun MutableTWorkspace.generateK2RefactoringsTests() { model("refactoring/inline/anonymousFunction", pattern = Patterns.KT_WITHOUT_DOTS) model("refactoring/inline/lambdaExpression", pattern = Patterns.KT_WITHOUT_DOTS) } + testClass { + model("refactoring/extractFunction", pattern = Patterns.KT_OR_KTS, testMethodName = "doExtractFunctionTest", excludedDirectories = listOf("inplace", "duplicates", "script")) + } } testGroup("refactorings/kotlin.refactorings.move.k2", testDataPath = "../../idea/tests/testData") {