From 891c58b5342a318e4a3e1e132380b29a135b0214 Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Tue, 29 Jul 2025 20:46:03 +0200 Subject: [PATCH] [kotlin] k2: introduce type alias ^KTIJ-21140 fixed GitOrigin-RevId: 88a5721101bde2f6b0c808e950e74cfae85dfd3a --- .../base/codeInsight/KotlinNameSuggester.kt | 21 +- .../introduce/AbstractExtractionTest.kt | 32 +- .../resources/kotlin.refactorings.k2.xml | 5 + .../IntroduceTypeAliasHandler.kt | 151 +++++++++ .../introduceTypeAlias/extractionModel.kt | 60 ++++ .../introduceTypeAliasImpl.kt | 299 ++++++++++++++++++ .../IntroduceTypeAliasParameterTablePanel.kt | 32 ++ .../ui/KotlinIntroduceTypeAliasDialog.form | 131 ++++++++ .../ui/KotlinIntroduceTypeAliasDialog.java | 230 ++++++++++++++ .../AbstractK2IntroduceTypeAliasTest.kt | 48 +++ .../K2IntroduceTypeAliasTestGenerated.java | 177 +++++++++++ .../GenerateK2RefactoringsTests.kt | 4 + 12 files changed, 1176 insertions(+), 14 deletions(-) create mode 100644 plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduceTypeAlias/IntroduceTypeAliasHandler.kt create mode 100644 plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduceTypeAlias/extractionModel.kt create mode 100644 plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduceTypeAlias/introduceTypeAliasImpl.kt create mode 100644 plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduceTypeAlias/ui/IntroduceTypeAliasParameterTablePanel.kt create mode 100644 plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduceTypeAlias/ui/KotlinIntroduceTypeAliasDialog.form create mode 100644 plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduceTypeAlias/ui/KotlinIntroduceTypeAliasDialog.java create mode 100644 plugins/kotlin/refactorings/kotlin.refactorings.tests.k2/test/org/jetbrains/kotlin/idea/k2/refactoring/introduce/AbstractK2IntroduceTypeAliasTest.kt create mode 100644 plugins/kotlin/refactorings/kotlin.refactorings.tests.k2/test/org/jetbrains/kotlin/idea/k2/refactoring/introduce/K2IntroduceTypeAliasTestGenerated.java diff --git a/plugins/kotlin/base/code-insight/src/org/jetbrains/kotlin/idea/base/codeInsight/KotlinNameSuggester.kt b/plugins/kotlin/base/code-insight/src/org/jetbrains/kotlin/idea/base/codeInsight/KotlinNameSuggester.kt index be720bc3dd4e..a2af94f8ae85 100644 --- a/plugins/kotlin/base/code-insight/src/org/jetbrains/kotlin/idea/base/codeInsight/KotlinNameSuggester.kt +++ b/plugins/kotlin/base/code-insight/src/org/jetbrains/kotlin/idea/base/codeInsight/KotlinNameSuggester.kt @@ -563,7 +563,26 @@ class KotlinNameSuggester( } } - val TYPE_PARAMETER_NAMES = listOf( + fun suggestNameByMultipleNames(names: Collection, validator: (String) -> Boolean): String { + var i = 0 + while (true) { + for (name in names) { + val candidate = if (i > 0) name + i else name + if (validator(candidate)) return candidate + } + i++ + } + } + + fun suggestNamesForTypeParameters(count: Int, validator: (String) -> Boolean): List { + val result = ArrayList() + for (i in 0 until count) { + result.add(suggestNameByMultipleNames(TYPE_PARAMETER_NAMES, validator)) + } + return result + } + + val TYPE_PARAMETER_NAMES: List = listOf( "T", "U", "V", "W", "X", "Y", "Z", "A", "B", "C", "D", "E", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "R", "S" ) 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 f7cba0e343ac..3d6f13e00eee 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 @@ -379,19 +379,25 @@ abstract class AbstractExtractionTest : KotlinLightCodeInsightFixtureTestCase() KtPsiFactory(project).createModifierList(it).firstChild.node.elementType as KtModifierKeywordToken } val editor = fixture.editor - object : KotlinIntroduceTypeAliasHandler() { - override fun doInvoke( - project: Project, - editor: Editor, - elements: List, - targetSibling: PsiElement, - descriptorSubstitutor: ((IntroduceTypeAliasDescriptor) -> IntroduceTypeAliasDescriptor)? - ) { - super.doInvoke(project, editor, elements, explicitPreviousSibling ?: targetSibling) { - it.copy(name = aliasName ?: it.name, visibility = aliasVisibility ?: it.visibility) - } - } - }.invoke(project, editor, file, null) + getIntroduceTypeAliasHandler(explicitPreviousSibling, aliasName, aliasVisibility).invoke(project, editor, file, null) + } + } + + protected open fun getIntroduceTypeAliasHandler( + explicitPreviousSibling: PsiElement?, + aliasName: String?, + aliasVisibility: KtModifierKeywordToken? + ): RefactoringActionHandler = object : KotlinIntroduceTypeAliasHandler() { + override fun doInvoke( + project: Project, + editor: Editor, + elements: List, + targetSibling: PsiElement, + descriptorSubstitutor: ((IntroduceTypeAliasDescriptor) -> IntroduceTypeAliasDescriptor)? + ) { + super.doInvoke(project, editor, elements, explicitPreviousSibling ?: targetSibling) { + it.copy(name = aliasName ?: it.name, visibility = aliasVisibility ?: it.visibility) + } } } 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 82115d0019d7..108705fc95ec 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 @@ -98,6 +98,11 @@ + + + + + diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduceTypeAlias/IntroduceTypeAliasHandler.kt b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduceTypeAlias/IntroduceTypeAliasHandler.kt new file mode 100644 index 000000000000..51c869ac6a71 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduceTypeAlias/IntroduceTypeAliasHandler.kt @@ -0,0 +1,151 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. + +package org.jetbrains.kotlin.idea.k2.refactoring.introduceTypeAlias + +import com.intellij.lang.refactoring.RefactoringSupportProvider +import com.intellij.openapi.actionSystem.DataContext +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.module.ModuleUtil +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.refactoring.RefactoringActionHandler +import com.intellij.refactoring.actions.BasePlatformRefactoringAction +import org.jetbrains.annotations.Nls +import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.idea.base.analysis.api.utils.analyzeInModalWindow +import org.jetbrains.kotlin.idea.base.projectStructure.languageVersionSettings +import org.jetbrains.kotlin.idea.base.resources.KotlinBundle +import org.jetbrains.kotlin.idea.k2.refactoring.KotlinFirRefactoringSupportProvider +import org.jetbrains.kotlin.idea.k2.refactoring.introduceTypeAlias.ui.KotlinIntroduceTypeAliasDialog +import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.processDuplicates +import org.jetbrains.kotlin.idea.refactoring.introduce.selectElementsWithTargetSibling +import org.jetbrains.kotlin.idea.refactoring.introduce.showErrorHint +import org.jetbrains.kotlin.idea.util.ElementKind.TYPE_CONSTRUCTOR +import org.jetbrains.kotlin.idea.util.ElementKind.TYPE_ELEMENT +import org.jetbrains.kotlin.idea.util.application.executeWriteCommand +import org.jetbrains.kotlin.idea.util.application.isUnitTestMode +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.* + +open class KotlinIntroduceTypeAliasHandler : RefactoringActionHandler { + companion object { + @JvmStatic + val REFACTORING_NAME + @Nls + get() = KotlinBundle.message("name.introduce.type.alias") + + val INSTANCE = KotlinIntroduceTypeAliasHandler() + } + + private fun selectElements( + editor: Editor, + file: KtFile, + continuation: (elements: List, targetSibling: PsiElement) -> Unit + ) { + selectElementsWithTargetSibling( + REFACTORING_NAME, + editor, + file, + KotlinBundle.message("title.select.target.code.block"), + listOf(TYPE_ELEMENT, TYPE_CONSTRUCTOR), + { null }, + { _, parent -> listOf(parent.containingFile) }, + continuation + ) + } + + private fun runRefactoring(descriptor: IntroduceTypeAliasDescriptor, project: Project, editor: Editor) { + val typeAlias = project.executeWriteCommand(REFACTORING_NAME) { descriptor.generateTypeAlias() } + + val duplicateReplacers = analyzeInModalWindow(typeAlias, KotlinBundle.message("text.process.duplicates")) { + findDuplicates(typeAlias) + } + if (duplicateReplacers.isNotEmpty()) { + processDuplicates(duplicateReplacers, project, editor) + } + } + + open fun doInvoke( + project: Project, + editor: Editor, + elements: List, + targetSibling: KtElement, + descriptorSubstitutor: ((IntroduceTypeAliasDescriptor) -> IntroduceTypeAliasDescriptor)? = null + ) { + val elementToExtract = elements.singleOrNull() + + val errorMessage = when (elementToExtract) { + is KtSimpleNameExpression -> { + if (!(isTypeConstructorReference(elementToExtract) || isDoubleColonReceiver(elementToExtract))) KotlinBundle.message("error.text.type.reference.is.expected" + ) else null + } + !is KtTypeElement -> KotlinBundle.message("error.text.no.type.to.refactor") + else -> null + } + if (errorMessage != null) return showErrorHint(project, editor, errorMessage, REFACTORING_NAME) + + val introduceData = when (elementToExtract) { + is KtTypeElement -> IntroduceTypeAliasData(elementToExtract, targetSibling) + else -> IntroduceTypeAliasData( + elementToExtract!!.getStrictParentOfType() ?: elementToExtract as KtElement, + targetSibling, + true + ) + } + when (val analysisResult = analyzeResult(introduceData)) { + is IntroduceTypeAliasAnalysisResult.Error -> { + return showErrorHint(project, editor, analysisResult.message, REFACTORING_NAME) + } + + is IntroduceTypeAliasAnalysisResult.Success -> { + val originalDescriptor = analysisResult.descriptor + if (isUnitTestMode()) { + val (descriptor, conflicts) = descriptorSubstitutor!!(originalDescriptor).validate() + project.checkConflictsInteractively(conflicts) { runRefactoring(descriptor, project, editor) } + } else { + KotlinIntroduceTypeAliasDialog(project, originalDescriptor) { + runRefactoring( + it.currentDescriptor, + project, + editor + ) + }.show() + } + } + } + } + + override fun invoke(project: Project, editor: Editor, file: PsiFile, dataContext: DataContext?) { + if (file !is KtFile) return + + val offset = if (editor.selectionModel.hasSelection()) editor.selectionModel.selectionStart else editor.caretModel.offset + + val refExpression = file.findElementAt(offset)?.getNonStrictParentOfType() + if (refExpression != null && isDoubleColonReceiver(refExpression)) { + return doInvoke(project, editor, listOf(refExpression), refExpression.getOutermostParentContainedIn(file) as KtElement) + } + + selectElements(editor, file) { elements, targetSibling -> doInvoke(project, editor, elements, targetSibling as KtElement) } + } + + override fun invoke(project: Project, elements: Array, dataContext: DataContext?) { + throw AssertionError("$REFACTORING_NAME can only be invoked from editor") + } +} + +class IntroduceTypeAliasAction : BasePlatformRefactoringAction() { + override fun getRefactoringHandler(provider: RefactoringSupportProvider): RefactoringActionHandler? { + return if (provider is KotlinFirRefactoringSupportProvider) KotlinIntroduceTypeAliasHandler.INSTANCE else null + } + + override fun isAvailableInEditorOnly(): Boolean { + return true + } + + override fun isAvailableOnElementInEditorAndFile(element: PsiElement, editor: Editor, file: PsiFile, context: DataContext): Boolean { + return super.isAvailableOnElementInEditorAndFile(element, editor, file, context) && + (ModuleUtil.findModuleForPsiElement(file)?.languageVersionSettings?.supportsFeature(LanguageFeature.TypeAliases) ?: false) + } +} \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduceTypeAlias/extractionModel.kt b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduceTypeAlias/extractionModel.kt new file mode 100644 index 000000000000..2b8163cbba54 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduceTypeAlias/extractionModel.kt @@ -0,0 +1,60 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. + +package org.jetbrains.kotlin.idea.k2.refactoring.introduceTypeAlias + +import com.intellij.openapi.Disposable +import com.intellij.openapi.util.Key +import com.intellij.psi.PsiElement +import com.intellij.util.containers.MultiMap +import org.jetbrains.kotlin.lexer.KtModifierKeywordToken +import org.jetbrains.kotlin.psi.CopyablePsiUserDataProperty +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtTreeVisitorVoid +import org.jetbrains.kotlin.psi.KtTypeReference +import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType + + +internal var KtTypeReference.originalReference: KtTypeReference? by CopyablePsiUserDataProperty(Key.create("RESOLVE_INFO")) + +class IntroduceTypeAliasData( + val originalTypeElement: KtElement, + val targetSibling: KtElement, + val extractTypeConstructor: Boolean = false +) : Disposable { + + init { + markReferences() + } + + private fun markReferences() { + val visitor = object : KtTreeVisitorVoid() { + override fun visitTypeReference(typeReference: KtTypeReference) { + val typeElement = typeReference.typeElement ?: return + + typeReference.originalReference = typeReference + + typeElement.typeArgumentsAsTypes.forEach { it?.accept(this) } + } + } + (originalTypeElement.parent as? KtTypeReference ?: originalTypeElement).accept(visitor) + } + + override fun dispose() { + if (!originalTypeElement.isValid) return + originalTypeElement.forEachDescendantOfType { it.originalReference = null } + } +} + +data class TypeParameter(val name: String, val typeReferenceInfos: Collection) + +data class IntroduceTypeAliasDescriptor( + val originalData: IntroduceTypeAliasData, + val name: String, + val visibility: KtModifierKeywordToken?, + val typeParameters: List +) + +data class IntroduceTypeAliasDescriptorWithConflicts( + val descriptor: IntroduceTypeAliasDescriptor, + val conflicts: MultiMap +) \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduceTypeAlias/introduceTypeAliasImpl.kt b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduceTypeAlias/introduceTypeAliasImpl.kt new file mode 100644 index 000000000000..2183f1450292 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduceTypeAlias/introduceTypeAliasImpl.kt @@ -0,0 +1,299 @@ +// Copyright 2000-2025 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.introduceTypeAlias + +import com.intellij.openapi.command.WriteCommandAction +import com.intellij.openapi.util.Key +import com.intellij.openapi.util.NlsContexts +import com.intellij.psi.PsiElement +import com.intellij.psi.search.LocalSearchScope +import com.intellij.psi.search.searches.ReferencesSearch +import com.intellij.util.containers.MultiMap +import org.jetbrains.kotlin.analysis.api.KaSession +import org.jetbrains.kotlin.analysis.api.symbols.KaClassifierSymbol +import org.jetbrains.kotlin.analysis.api.types.KaClassType +import org.jetbrains.kotlin.idea.base.analysis.api.utils.analyzeInModalWindow +import org.jetbrains.kotlin.idea.base.codeInsight.KotlinNameSuggester +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.toRange +import org.jetbrains.kotlin.idea.base.resources.KotlinBundle +import org.jetbrains.kotlin.idea.core.CollectingNameValidator +import org.jetbrains.kotlin.idea.k2.refactoring.introduce.K2SemanticMatcher +import org.jetbrains.kotlin.idea.k2.refactoring.introduce.K2SemanticMatcher.matchRanges +import org.jetbrains.kotlin.idea.refactoring.introduce.insertDeclaration +import org.jetbrains.kotlin.lexer.KtModifierKeywordToken +import org.jetbrains.kotlin.lexer.KtTokens.* +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.* + +sealed class IntroduceTypeAliasAnalysisResult { + class Error(@NlsContexts.DialogMessage val message: String) : IntroduceTypeAliasAnalysisResult() + class Success(val descriptor: IntroduceTypeAliasDescriptor) : IntroduceTypeAliasAnalysisResult() +} + +fun analyzeResult(aliasData: IntroduceTypeAliasData): IntroduceTypeAliasAnalysisResult { + val psiFactory = KtPsiFactory(aliasData.originalTypeElement.project) + + val dummyVar = + (psiFactory.createBlockCodeFragment("val a: Int", aliasData.targetSibling.parent).getContentElement().children[0] as KtProperty).apply { + WriteCommandAction.writeCommandAction(project).run { + typeReference!!.replace( + aliasData.originalTypeElement.parent as? KtTypeReference + ?: if (aliasData.originalTypeElement is KtTypeElement) psiFactory.createType( + aliasData.originalTypeElement + ) else psiFactory.createType(aliasData.originalTypeElement.text) + ) + } + } + + val newTypeReference = dummyVar.typeReference!! + return analyzeInModalWindow(newTypeReference, KotlinBundle.message("fix.change.signature.prepare")) { + + val referencesToReplaceWithTypeParameters = findReferencesToReplaceWithTypeParameters(aliasData, newTypeReference) + + val typeParameterNameValidator = CollectingNameValidator() + val brokenReferences = + referencesToReplaceWithTypeParameters.keySet().filter { referencesToReplaceWithTypeParameters[it].isNotEmpty() } + val typeParameterNames = KotlinNameSuggester.suggestNamesForTypeParameters(brokenReferences.size, typeParameterNameValidator) + val typeParameters = + (typeParameterNames zip brokenReferences).map { TypeParameter(it.first, referencesToReplaceWithTypeParameters[it.second]) } + + if (typeParameters.any { it.typeReferenceInfos.any { info -> info.typeElement == aliasData.originalTypeElement } }) { + return@analyzeInModalWindow IntroduceTypeAliasAnalysisResult.Error(KotlinBundle.message("text.type.alias.cannot.refer.to.types.which.aren.t.accessible.in.the.scope.where.it.s.defined")) + } + + val descriptor = IntroduceTypeAliasDescriptor(aliasData, "Dummy", null, typeParameters) + + val initialName = + KotlinNameSuggester.suggestTypeAliasNameByPsi(descriptor.generateTypeAlias(true).getTypeReference()!!.typeElement!!) { + findClassifierByName(aliasData.targetSibling, descriptor) == null + } + + return@analyzeInModalWindow IntroduceTypeAliasAnalysisResult.Success(descriptor.copy(name = initialName)) + } +} + +context(KaSession) +private fun findReferencesToReplaceWithTypeParameters( + aliasData: IntroduceTypeAliasData, + newTypeReference: KtTypeReference, +): MultiMap { + val newReferences = newTypeReference.collectDescendantsOfType { it.originalReference != null } + + val referencesToReplaceWithTypeParameters = MultiMap.createLinked() + + val forcedCandidates = if (aliasData.extractTypeConstructor) newTypeReference.typeElement!!.typeArgumentsAsTypes else emptyList() + + for (newReference in newReferences) { + val originalReference = newReference.originalReference!! + + if (newReference !in forcedCandidates) { //skip for resolved references + val originalSymbol = (originalReference.type as? KaClassType)?.symbol + val newSymbol = (newReference.type as? KaClassType)?.symbol + if (originalSymbol == newSymbol) continue + } + + with(K2SemanticMatcher) { + val firstEquivalentReference = + referencesToReplaceWithTypeParameters.keySet().firstOrNull { it.isSemanticMatch(originalReference) } + if (firstEquivalentReference != null) { + referencesToReplaceWithTypeParameters.putValue(firstEquivalentReference, originalReference) + } else { + referencesToReplaceWithTypeParameters.putValue(originalReference, originalReference) + } + } + + // skip references under other references + val referencesToExtractIterator = referencesToReplaceWithTypeParameters.values().iterator() + while (referencesToExtractIterator.hasNext()) { + val referenceToExtract = referencesToExtractIterator.next() + if (originalReference.isAncestor(referenceToExtract, true)) { + referencesToExtractIterator.remove() + } + } + } + return referencesToReplaceWithTypeParameters +} + +fun IntroduceTypeAliasData.getApplicableVisibilities(): List = when (targetSibling.parent) { + is KtClassBody -> listOf(PRIVATE_KEYWORD, PUBLIC_KEYWORD, INTERNAL_KEYWORD, PROTECTED_KEYWORD) + is KtFile -> listOf(PRIVATE_KEYWORD, PUBLIC_KEYWORD, INTERNAL_KEYWORD) + else -> emptyList() +} + +fun IntroduceTypeAliasDescriptor.validate(): IntroduceTypeAliasDescriptorWithConflicts { + val conflicts = MultiMap() + + val originalType = originalData.originalTypeElement + when { + name.isEmpty() -> conflicts.putValue(originalType, KotlinBundle.message("text.no.name.provided.for.type.alias")) + + !name.isIdentifier() -> conflicts.putValue( + originalType, + KotlinBundle.message("text.type.alias.name.must.be.a.valid.identifier.0", name) + ) + + analyzeInModalWindow(originalData.targetSibling, KotlinBundle.message("progress.title.check.for.conflicts")) { + findClassifierByName(originalData.targetSibling, this@validate) != null + } -> conflicts.putValue(originalType, KotlinBundle.message("text.type.already.exists.in.the.target.scope", name)) + } + + if (typeParameters.distinctBy { it.name }.size != typeParameters.size) { + conflicts.putValue(originalType, KotlinBundle.message("text.type.parameter.names.must.be.distinct")) + } + + if (visibility != null && visibility !in originalData.getApplicableVisibilities()) { + conflicts.putValue(originalType, KotlinBundle.message("text.0.is.not.allowed.in.the.target.context", visibility)) + } + + return IntroduceTypeAliasDescriptorWithConflicts(this, conflicts) +} + +context(KaSession) +private fun findClassifierByName( + targetSibling: KtElement, descriptor: IntroduceTypeAliasDescriptor +): KaClassifierSymbol? { + val ktFile = targetSibling.containingKtFile + return ktFile.scopeContext(targetSibling).compositeScope().classifiers { it.asString() == descriptor.name }.firstOrNull() +} + +context(KaSession) +fun findDuplicates(typeAlias: KtTypeAlias): Map Unit> { + val aliasName = typeAlias.name?.quoteIfNeeded() ?: return emptyMap() + val aliasRange = typeAlias.textRange + val typeAliasSymbol = typeAlias.symbol + + val psiFactory = KtPsiFactory(typeAlias.project) + + fun replaceTypeReference(occurrence: KtTypeReference, typeArgumentsText: String) { + occurrence.replace(psiFactory.createType("$aliasName$typeArgumentsText")) + } + + fun replaceOccurrence(occurrence: PsiElement, arguments: List) { + val typeArgumentsText = if (arguments.isNotEmpty()) "<${arguments.joinToString { it.text }}>" else "" + when (occurrence) { + is KtTypeReference -> { + replaceTypeReference(occurrence, typeArgumentsText) + } + + is KtSuperTypeCallEntry -> { + occurrence.calleeExpression.typeReference?.let { replaceTypeReference(it, typeArgumentsText) } + } + + is KtCallElement -> { + val qualifiedExpression = occurrence.parent as? KtQualifiedExpression + val callExpression = if (qualifiedExpression != null && qualifiedExpression.selectorExpression == occurrence) { + qualifiedExpression.replaced(occurrence) + } else occurrence + val typeArgumentList = callExpression.typeArgumentList + if (arguments.isNotEmpty()) { + val newTypeArgumentList = psiFactory.createTypeArguments(typeArgumentsText) + typeArgumentList?.replace(newTypeArgumentList) ?: callExpression.addAfter( + newTypeArgumentList, callExpression.calleeExpression + ) + } else { + typeArgumentList?.delete() + } + callExpression.calleeExpression?.replace(psiFactory.createExpression(aliasName)) + } + + is KtExpression -> occurrence.replace(psiFactory.createExpression(aliasName)) + } + } + + val rangesWithReplacers = ArrayList Unit>>() + + val originalTypePsi = (typeAliasSymbol.expandedType as? KaClassType)?.symbol?.psi + if (originalTypePsi != null) { + for (reference in ReferencesSearch.search(originalTypePsi, LocalSearchScope(typeAlias.parent)).asIterable()) { + val element = reference.element as? KtSimpleNameExpression ?: continue + if ((element.textRange.intersects(aliasRange))) continue + + val arguments: List + val occurrence: KtElement + + val callElement = element.getParentOfTypeAndBranch { calleeExpression } + if (callElement != null) { + occurrence = callElement + arguments = callElement.typeArguments.mapNotNull { it.typeReference?.typeElement } + } else { + val userType = element.getParentOfTypeAndBranch { referenceExpression } + if (userType != null) { + occurrence = userType + arguments = userType.typeArgumentsAsTypes.mapNotNull { it.typeElement } + } else continue + } + if (arguments.size != typeAliasSymbol.typeParameters.size) continue + + if ((typeAliasSymbol.expandedType as? KaClassType)?.isMarkedNullable == true && occurrence is KtUserType && occurrence.parent !is KtNullableType) continue + rangesWithReplacers += occurrence.toRange() to { replaceOccurrence(occurrence, arguments) } + } + } + + typeAlias.getTypeReference().toRange() + .match(typeAlias.parent) { targetRange, patternRange -> matchRanges(targetRange, patternRange, typeAlias.typeParameters) } + .asSequence() + .filter { !(it.range.textRange.intersects(aliasRange)) } + .mapNotNullTo(rangesWithReplacers) { match -> + val occurrence = match.range.elements.singleOrNull() as? KtTypeReference ?: return@mapNotNullTo null + val arguments = typeAlias.typeParameters.mapNotNull { (match.substitution[it] as? KtTypeReference)?.typeElement } + if (arguments.size != typeAlias.typeParameters.size) return@mapNotNullTo null + match.range to { replaceOccurrence(occurrence, arguments) } + } + return rangesWithReplacers.toMap() +} + +private var KtTypeReference.typeParameterInfo: TypeParameter? by CopyablePsiUserDataProperty(Key.create("TYPE_PARAMETER_INFO")) + +fun IntroduceTypeAliasDescriptor.generateTypeAlias(previewOnly: Boolean = false): KtTypeAlias { + val originalElement = originalData.originalTypeElement + val psiFactory = KtPsiFactory(originalElement.project) + + for (typeParameter in typeParameters) for (it in typeParameter.typeReferenceInfos) { + it.typeParameterInfo = typeParameter + } + + val typeParameterNames = typeParameters.map { it.name } + val typeAlias = if (originalElement is KtTypeElement) { + psiFactory.createTypeAlias(name, typeParameterNames, originalElement) + } else { + psiFactory.createTypeAlias(name, typeParameterNames, originalElement.text) + } + if (visibility != null && visibility != DEFAULT_VISIBILITY_KEYWORD) { + typeAlias.addModifier(visibility) + } + + for (typeParameter in typeParameters) for (it in typeParameter.typeReferenceInfos) { + it.typeParameterInfo = null + } + + fun replaceUsage() { + val aliasInstanceText = if (typeParameters.isNotEmpty()) { + "$name<${typeParameters.joinToString { it.typeReferenceInfos.first().text }}>" + } else { + name + } + when (originalElement) { + is KtTypeElement -> originalElement.replace(psiFactory.createType(aliasInstanceText).typeElement!!) + is KtExpression -> originalElement.replace(psiFactory.createExpression(aliasInstanceText)) + } + } + + fun introduceTypeParameters() { + typeAlias.getTypeReference()!!.forEachDescendantOfType { + val typeParameter = it.typeParameterInfo ?: return@forEachDescendantOfType + val typeParameterReference = psiFactory.createType(typeParameter.name) + it.replace(typeParameterReference) + } + } + + return if (previewOnly) { + introduceTypeParameters() + typeAlias + } else { + replaceUsage() + introduceTypeParameters() + insertDeclaration(typeAlias, originalData.targetSibling) + } +} \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduceTypeAlias/ui/IntroduceTypeAliasParameterTablePanel.kt b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduceTypeAlias/ui/IntroduceTypeAliasParameterTablePanel.kt new file mode 100644 index 000000000000..bcf62686c8c5 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduceTypeAlias/ui/IntroduceTypeAliasParameterTablePanel.kt @@ -0,0 +1,32 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. + +package org.jetbrains.kotlin.idea.k2.refactoring.introduceTypeAlias.ui + +import org.jetbrains.kotlin.idea.k2.refactoring.introduceTypeAlias.TypeParameter +import org.jetbrains.kotlin.idea.refactoring.introduce.ui.AbstractParameterTablePanel + +open class IntroduceTypeAliasParameterTablePanel : + AbstractParameterTablePanel() { + class TypeParameterInfo( + originalParameter: TypeParameter + ) : AbstractParameterTablePanel.AbstractParameterInfo(originalParameter) { + init { + name = originalParameter.name + } + + override fun toParameter() = originalParameter.copy(name) + } + + fun init(parameters: List) { + parameterInfos = parameters.mapTo(ArrayList(), ::TypeParameterInfo) + super.init() + } + + override fun isCheckMarkColumnEditable() = false + + val selectedTypeParameterInfos: List + get() = parameterInfos.filter { it.isEnabled } + + val selectedTypeParameters: List + get() = selectedTypeParameterInfos.map { it.toParameter() } +} \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduceTypeAlias/ui/KotlinIntroduceTypeAliasDialog.form b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduceTypeAlias/ui/KotlinIntroduceTypeAliasDialog.form new file mode 100644 index 000000000000..cd4a5e0680ba --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduceTypeAlias/ui/KotlinIntroduceTypeAliasDialog.form @@ -0,0 +1,131 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduceTypeAlias/ui/KotlinIntroduceTypeAliasDialog.java b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduceTypeAlias/ui/KotlinIntroduceTypeAliasDialog.java new file mode 100644 index 000000000000..7386cffd27c0 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduceTypeAlias/ui/KotlinIntroduceTypeAliasDialog.java @@ -0,0 +1,230 @@ +// 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.introduceTypeAlias.ui; + +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.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.idea.KotlinFileType; +import org.jetbrains.kotlin.idea.base.resources.KotlinBundle; +import org.jetbrains.kotlin.idea.k2.refactoring.introduceTypeAlias.IntroduceTypeAliasDescriptor; +import org.jetbrains.kotlin.idea.k2.refactoring.introduceTypeAlias.IntroduceTypeAliasImplKt; +import org.jetbrains.kotlin.idea.k2.refactoring.introduceTypeAlias.KotlinIntroduceTypeAliasHandler; +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.psiUtil.KtPsiUtilKt; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ItemEvent; +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; + private JComboBox visibilityBox; + private KotlinSignatureComponent signaturePreviewField; + private JPanel aliasNamePanel; + private NameSuggestionsField aliasNameField; + private JLabel aliasNameLabel; + private IntroduceTypeAliasParameterTablePanel parameterTablePanel; + + private final Project project; + + private final IntroduceTypeAliasDescriptor originalDescriptor; + private IntroduceTypeAliasDescriptor currentDescriptor; + + private final Function1 onAccept; + + public KotlinIntroduceTypeAliasDialog( + @NotNull Project project, + @NotNull IntroduceTypeAliasDescriptor originalDescriptor, + @NotNull Function1 onAccept) { + super(project, true); + + this.project = project; + this.originalDescriptor = originalDescriptor; + this.currentDescriptor = originalDescriptor; + this.onAccept = onAccept; + + setModal(true); + setTitle(KotlinIntroduceTypeAliasHandler.getREFACTORING_NAME()); + init(); + update(); + } + + private void createUIComponents() { + this.signaturePreviewField = new KotlinSignatureComponent("", project); + } + + private boolean isVisibilitySectionAvailable() { + return !getApplicableVisibilities().isEmpty(); + } + + private @NotNull List getApplicableVisibilities() { + return IntroduceTypeAliasImplKt.getApplicableVisibilities(originalDescriptor.getOriginalData()); + } + + private String getAliasName() { + return KtPsiUtilKt.quoteIfNeeded(aliasNameField.getEnteredName()); + } + + private @Nullable KtModifierKeywordToken getVisibility() { + if (!isVisibilitySectionAvailable()) return null; + return (KtModifierKeywordToken) visibilityBox.getSelectedItem(); + } + + private boolean checkNames() { + if (!KtPsiUtilKt.isIdentifier(getAliasName())) return false; + if (parameterTablePanel != null) { + for (IntroduceTypeAliasParameterTablePanel.TypeParameterInfo parameterInfo : parameterTablePanel.getSelectedTypeParameterInfos()) { + if (!KtPsiUtilKt.isIdentifier(parameterInfo.getName())) return false; + } + } + return true; + } + + private void update() { + this.currentDescriptor = createDescriptor(); + + setOKActionEnabled(checkNames()); + signaturePreviewField.setText(IntroduceTypeAliasImplKt.generateTypeAlias(currentDescriptor, true).getText()); + } + + @Override + protected void init() { + super.init(); + + visibilityBox.setModel(new DefaultComboBoxModel<>(getApplicableVisibilities().toArray(new KtModifierKeywordToken[0]))); + visibilityBox.setRenderer( + new DefaultListCellRenderer() { + @Override + public Component getListCellRendererComponent( + JList list, + Object value, + int index, + boolean isSelected, + boolean cellHasFocus + ) { + @NlsSafe String tokenValue = value != null ? ((KtModifierKeywordToken) value).getValue() : null; + return super.getListCellRendererComponent(list, tokenValue, index, isSelected, cellHasFocus); + } + } + ); + + aliasNameField = new NameSuggestionsField(new String[]{originalDescriptor.getName()}, project, KotlinFileType.INSTANCE); + aliasNameField.addDataChangedListener(() -> update()); + aliasNamePanel.add(aliasNameField, BorderLayout.CENTER); + aliasNameLabel.setLabelFor(aliasNameField); + + boolean enableVisibility = isVisibilitySectionAvailable(); + visibilityBox.setEnabled(enableVisibility); + if (enableVisibility) { + KtModifierKeywordToken defaultVisibility = originalDescriptor.getVisibility(); + if (defaultVisibility == null) { + defaultVisibility = KtTokens.PUBLIC_KEYWORD; + } + visibilityBox.setSelectedItem(defaultVisibility); + } + visibilityBox.addItemListener( + new ItemListener() { + @Override + public void itemStateChanged(@NotNull ItemEvent e) { + update(); + } + } + ); + + if (!originalDescriptor.getTypeParameters().isEmpty()) { + parameterTablePanel = new IntroduceTypeAliasParameterTablePanel() { + @Override + protected void updateSignature() { + KotlinIntroduceTypeAliasDialog.this.update(); + } + + @Override + protected void onEnterAction() { + doOKAction(); + } + + @Override + protected void onCancelAction() { + doCancelAction(); + } + }; + parameterTablePanel.init(originalDescriptor.getTypeParameters()); + + inputParametersPanel.setText(KotlinBundle.message("text.type.parameters")); + inputParametersPanel.setLabelFor(parameterTablePanel.getTable()); + inputParametersPanel.add(parameterTablePanel); + } + else { + inputParametersPanel.setVisible(false); + } + } + + @Override + protected void doOKAction() { + MultiMap conflicts = IntroduceTypeAliasImplKt.validate(currentDescriptor).getConflicts(); + checkConflictsInteractively( + project, + conflicts, + new Function0<>() { + @Override + public Unit invoke() { + close(OK_EXIT_CODE); + return Unit.INSTANCE; + } + }, + new Function0<>() { + @Override + public Unit invoke() { + KotlinIntroduceTypeAliasDialog.super.doOKAction(); + return onAccept.invoke(KotlinIntroduceTypeAliasDialog.this); + } + } + ); + } + + @Override + public JComponent getPreferredFocusedComponent() { + return aliasNameField; + } + + @Override + protected JComponent createCenterPanel() { + return contentPane; + } + + @Override + protected @NotNull JComponent createContentPane() { + return contentPane; + } + + private @NotNull IntroduceTypeAliasDescriptor createDescriptor() { + return originalDescriptor.copy( + originalDescriptor.getOriginalData(), + getAliasName(), + getVisibility(), + parameterTablePanel != null ? parameterTablePanel.getSelectedTypeParameters() : Collections.emptyList() + ); + } + + public IntroduceTypeAliasDescriptor getCurrentDescriptor() { + return currentDescriptor; + } +} diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.tests.k2/test/org/jetbrains/kotlin/idea/k2/refactoring/introduce/AbstractK2IntroduceTypeAliasTest.kt b/plugins/kotlin/refactorings/kotlin.refactorings.tests.k2/test/org/jetbrains/kotlin/idea/k2/refactoring/introduce/AbstractK2IntroduceTypeAliasTest.kt new file mode 100644 index 000000000000..0258b693c6ae --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.tests.k2/test/org/jetbrains/kotlin/idea/k2/refactoring/introduce/AbstractK2IntroduceTypeAliasTest.kt @@ -0,0 +1,48 @@ +// 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 + +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.intellij.refactoring.RefactoringActionHandler +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.introduceTypeAlias.IntroduceTypeAliasDescriptor +import org.jetbrains.kotlin.idea.k2.refactoring.introduceTypeAlias.KotlinIntroduceTypeAliasHandler +import org.jetbrains.kotlin.idea.refactoring.introduce.AbstractExtractionTest +import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor +import org.jetbrains.kotlin.lexer.KtModifierKeywordToken +import org.jetbrains.kotlin.psi.KtElement + +abstract class AbstractK2IntroduceTypeAliasTest : AbstractExtractionTest() { + + override fun getIntroduceTypeAliasHandler( + explicitPreviousSibling: PsiElement?, + aliasName: String?, + aliasVisibility: KtModifierKeywordToken? + ): RefactoringActionHandler { + return object : KotlinIntroduceTypeAliasHandler() { + override fun doInvoke( + project: Project, + editor: Editor, + elements: List, + targetSibling: KtElement, + descriptorSubstitutor: ((IntroduceTypeAliasDescriptor) -> IntroduceTypeAliasDescriptor)? + ) { + super.doInvoke(project, editor, elements, (explicitPreviousSibling ?: targetSibling) as KtElement) { + it.copy(name = aliasName ?: it.name, visibility = aliasVisibility ?: it.visibility) + } + } + } + } + + 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/introduce/K2IntroduceTypeAliasTestGenerated.java b/plugins/kotlin/refactorings/kotlin.refactorings.tests.k2/test/org/jetbrains/kotlin/idea/k2/refactoring/introduce/K2IntroduceTypeAliasTestGenerated.java new file mode 100644 index 000000000000..ccf086562d13 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.tests.k2/test/org/jetbrains/kotlin/idea/k2/refactoring/introduce/K2IntroduceTypeAliasTestGenerated.java @@ -0,0 +1,177 @@ +// Copyright 2000-2025 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; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.idea.base.plugin.KotlinPluginMode; +import org.jetbrains.kotlin.idea.base.test.TestRoot; +import org.jetbrains.kotlin.idea.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.idea.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.TestMetadata; +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/introduceTypeAlias") +public class K2IntroduceTypeAliasTestGenerated extends AbstractK2IntroduceTypeAliasTest { + @java.lang.Override + @org.jetbrains.annotations.NotNull + public final KotlinPluginMode getPluginMode() { + return KotlinPluginMode.K2; + } + + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doIntroduceTypeAliasTest, this, testDataFilePath); + } + + @TestMetadata("callableReference.kt") + public void testCallableReference() throws Exception { + runTest("../../idea/tests/testData/refactoring/introduceTypeAlias/callableReference.kt"); + } + + @TestMetadata("callableReferenceSelector.kt") + public void testCallableReferenceSelector() throws Exception { + runTest("../../idea/tests/testData/refactoring/introduceTypeAlias/callableReferenceSelector.kt"); + } + + @TestMetadata("classLiteral.kt") + public void testClassLiteral() throws Exception { + runTest("../../idea/tests/testData/refactoring/introduceTypeAlias/classLiteral.kt"); + } + + @TestMetadata("constructorCalls.kt") + public void testConstructorCalls() throws Exception { + runTest("../../idea/tests/testData/refactoring/introduceTypeAlias/constructorCalls.kt"); + } + + @TestMetadata("deeplyNestedClass.kt") + public void testDeeplyNestedClass() throws Exception { + runTest("../../idea/tests/testData/refactoring/introduceTypeAlias/deeplyNestedClass.kt"); + } + + @TestMetadata("emptyName.kt") + public void testEmptyName() throws Exception { + runTest("../../idea/tests/testData/refactoring/introduceTypeAlias/emptyName.kt"); + } + + @TestMetadata("entireTypeExtracted.kt") + public void testEntireTypeExtracted() throws Exception { + runTest("../../idea/tests/testData/refactoring/introduceTypeAlias/entireTypeExtracted.kt"); + } + + @TestMetadata("equivalentNestedTypeElements.kt") + public void testEquivalentNestedTypeElements() throws Exception { + runTest("../../idea/tests/testData/refactoring/introduceTypeAlias/equivalentNestedTypeElements.kt"); + } + + @TestMetadata("existingTypeClash.kt") + public void testExistingTypeClash() throws Exception { + runTest("../../idea/tests/testData/refactoring/introduceTypeAlias/existingTypeClash.kt"); + } + + @TestMetadata("functionTypeDuplicatesNoTypeParameters.kt") + public void testFunctionTypeDuplicatesNoTypeParameters() throws Exception { + runTest("../../idea/tests/testData/refactoring/introduceTypeAlias/functionTypeDuplicatesNoTypeParameters.kt"); + } + + @TestMetadata("functionTypeDuplicatesWithTypeParameters.kt") + public void testFunctionTypeDuplicatesWithTypeParameters() throws Exception { + runTest("../../idea/tests/testData/refactoring/introduceTypeAlias/functionTypeDuplicatesWithTypeParameters.kt"); + } + + @TestMetadata("functionalType.kt") + public void testFunctionalType() throws Exception { + runTest("../../idea/tests/testData/refactoring/introduceTypeAlias/functionalType.kt"); + } + + @TestMetadata("localTypeExtracted.kt") + public void testLocalTypeExtracted() throws Exception { + runTest("../../idea/tests/testData/refactoring/introduceTypeAlias/localTypeExtracted.kt"); + } + + @TestMetadata("localWithVisibility.kt") + public void testLocalWithVisibility() throws Exception { + runTest("../../idea/tests/testData/refactoring/introduceTypeAlias/localWithVisibility.kt"); + } + + @TestMetadata("nestedClass.kt") + public void testNestedClass() throws Exception { + runTest("../../idea/tests/testData/refactoring/introduceTypeAlias/nestedClass.kt"); + } + + @TestMetadata("nestedTypesExtracted.kt") + public void testNestedTypesExtracted() throws Exception { + runTest("../../idea/tests/testData/refactoring/introduceTypeAlias/nestedTypesExtracted.kt"); + } + + @TestMetadata("noTypeElement.kt") + public void testNoTypeElement() throws Exception { + runTest("../../idea/tests/testData/refactoring/introduceTypeAlias/noTypeElement.kt"); + } + + @TestMetadata("nonIdentifierName.kt") + public void testNonIdentifierName() throws Exception { + runTest("../../idea/tests/testData/refactoring/introduceTypeAlias/nonIdentifierName.kt"); + } + + @TestMetadata("nullableType.kt") + public void testNullableType() throws Exception { + runTest("../../idea/tests/testData/refactoring/introduceTypeAlias/nullableType.kt"); + } + + @TestMetadata("privateTypeAlias.kt") + public void testPrivateTypeAlias() throws Exception { + runTest("../../idea/tests/testData/refactoring/introduceTypeAlias/privateTypeAlias.kt"); + } + + @TestMetadata("protectedInFile.kt") + public void testProtectedInFile() throws Exception { + runTest("../../idea/tests/testData/refactoring/introduceTypeAlias/protectedInFile.kt"); + } + + @TestMetadata("skipQualifiers.kt") + public void testSkipQualifiers() throws Exception { + runTest("../../idea/tests/testData/refactoring/introduceTypeAlias/skipQualifiers.kt"); + } + + @TestMetadata("suggestedName.kt") + public void testSuggestedName() throws Exception { + runTest("../../idea/tests/testData/refactoring/introduceTypeAlias/suggestedName.kt"); + } + + @TestMetadata("topLevelTypeAlias.kt") + public void testTopLevelTypeAlias() throws Exception { + runTest("../../idea/tests/testData/refactoring/introduceTypeAlias/topLevelTypeAlias.kt"); + } + + @TestMetadata("typeConstructor.kt") + public void testTypeConstructor() throws Exception { + runTest("../../idea/tests/testData/refactoring/introduceTypeAlias/typeConstructor.kt"); + } + + @TestMetadata("typesExtractedWithFunctionalType.kt") + public void testTypesExtractedWithFunctionalType() throws Exception { + runTest("../../idea/tests/testData/refactoring/introduceTypeAlias/typesExtractedWithFunctionalType.kt"); + } + + @TestMetadata("unmatchedNullability.kt") + public void testUnmatchedNullability() throws Exception { + runTest("../../idea/tests/testData/refactoring/introduceTypeAlias/unmatchedNullability.kt"); + } + + @TestMetadata("userTypeDuplicatesNoTypeParameters.kt") + public void testUserTypeDuplicatesNoTypeParameters() throws Exception { + runTest("../../idea/tests/testData/refactoring/introduceTypeAlias/userTypeDuplicatesNoTypeParameters.kt"); + } + + @TestMetadata("userTypeDuplicatesWithTypeParameters.kt") + public void testUserTypeDuplicatesWithTypeParameters() throws Exception { + runTest("../../idea/tests/testData/refactoring/introduceTypeAlias/userTypeDuplicatesWithTypeParameters.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 529ef7b70b1d..43e4253a3301 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 @@ -73,6 +73,10 @@ internal fun MutableTWorkspace.generateK2RefactoringsTests() { model("refactoring/extractFunction", pattern = Patterns.KT_OR_KTS, testMethodName = "doExtractFunctionTest") } + testClass { + model("refactoring/introduceTypeAlias", pattern = Patterns.KT_OR_KTS, testMethodName = "doIntroduceTypeAliasTest") + } + testClass { model("refactoring/extractFunctionModifier", pattern = Patterns.KT_OR_KTS, testMethodName = "doExtractFunctionTest") }