mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[kotlin] k2: introduce type alias
^KTIJ-21140 fixed GitOrigin-RevId: 88a5721101bde2f6b0c808e950e74cfae85dfd3a
This commit is contained in:
committed by
intellij-monorepo-bot
parent
bda500c565
commit
891c58b534
+20
-1
@@ -563,7 +563,26 @@ class KotlinNameSuggester(
|
||||
}
|
||||
}
|
||||
|
||||
val TYPE_PARAMETER_NAMES = listOf(
|
||||
fun suggestNameByMultipleNames(names: Collection<String>, 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<String> {
|
||||
val result = ArrayList<String>()
|
||||
for (i in 0 until count) {
|
||||
result.add(suggestNameByMultipleNames(TYPE_PARAMETER_NAMES, validator))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
val TYPE_PARAMETER_NAMES: List<String> = 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"
|
||||
)
|
||||
|
||||
+19
-13
@@ -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<PsiElement>,
|
||||
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<PsiElement>,
|
||||
targetSibling: PsiElement,
|
||||
descriptorSubstitutor: ((IntroduceTypeAliasDescriptor) -> IntroduceTypeAliasDescriptor)?
|
||||
) {
|
||||
super.doInvoke(project, editor, elements, explicitPreviousSibling ?: targetSibling) {
|
||||
it.copy(name = aliasName ?: it.name, visibility = aliasVisibility ?: it.visibility)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
@@ -98,6 +98,11 @@
|
||||
<add-to-group group-id="IntroduceActionsGroup" anchor="after" relative-to-action="IntroduceField"/>
|
||||
</action>
|
||||
|
||||
<action id="IntroduceTypeAlias" class="org.jetbrains.kotlin.idea.k2.refactoring.introduceTypeAlias.IntroduceTypeAliasAction">
|
||||
<keyboard-shortcut keymap="$default" first-keystroke="control alt shift A"/>
|
||||
<add-to-group group-id="IntroduceActionsGroup" anchor="after" relative-to-action="IntroduceProperty"/>
|
||||
</action>
|
||||
|
||||
</actions>
|
||||
</idea-plugin>
|
||||
|
||||
|
||||
+151
@@ -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<PsiElement>, 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<KtTypeAlias>(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<PsiElement>,
|
||||
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<KtTypeElement>() ?: 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<KtSimpleNameExpression>()
|
||||
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<out PsiElement>, 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)
|
||||
}
|
||||
}
|
||||
+60
@@ -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<KtTypeReference> { it.originalReference = null }
|
||||
}
|
||||
}
|
||||
|
||||
data class TypeParameter(val name: String, val typeReferenceInfos: Collection<KtTypeReference>)
|
||||
|
||||
data class IntroduceTypeAliasDescriptor(
|
||||
val originalData: IntroduceTypeAliasData,
|
||||
val name: String,
|
||||
val visibility: KtModifierKeywordToken?,
|
||||
val typeParameters: List<TypeParameter>
|
||||
)
|
||||
|
||||
data class IntroduceTypeAliasDescriptorWithConflicts(
|
||||
val descriptor: IntroduceTypeAliasDescriptor,
|
||||
val conflicts: MultiMap<PsiElement, String>
|
||||
)
|
||||
+299
@@ -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<Throwable> {
|
||||
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<KtTypeReference, KtTypeReference> {
|
||||
val newReferences = newTypeReference.collectDescendantsOfType<KtTypeReference> { it.originalReference != null }
|
||||
|
||||
val referencesToReplaceWithTypeParameters = MultiMap.createLinked<KtTypeReference, KtTypeReference>()
|
||||
|
||||
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<KtModifierKeywordToken> = 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<PsiElement, String>()
|
||||
|
||||
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<KotlinPsiRange, () -> 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<KtTypeElement>) {
|
||||
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<Pair<KotlinPsiRange, () -> 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<KtTypeElement>
|
||||
val occurrence: KtElement
|
||||
|
||||
val callElement = element.getParentOfTypeAndBranch<KtCallElement> { calleeExpression }
|
||||
if (callElement != null) {
|
||||
occurrence = callElement
|
||||
arguments = callElement.typeArguments.mapNotNull { it.typeReference?.typeElement }
|
||||
} else {
|
||||
val userType = element.getParentOfTypeAndBranch<KtUserType> { 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<KtTypeReference> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
+32
@@ -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<TypeParameter, IntroduceTypeAliasParameterTablePanel.TypeParameterInfo>() {
|
||||
class TypeParameterInfo(
|
||||
originalParameter: TypeParameter
|
||||
) : AbstractParameterTablePanel.AbstractParameterInfo<TypeParameter>(originalParameter) {
|
||||
init {
|
||||
name = originalParameter.name
|
||||
}
|
||||
|
||||
override fun toParameter() = originalParameter.copy(name)
|
||||
}
|
||||
|
||||
fun init(parameters: List<TypeParameter>) {
|
||||
parameterInfos = parameters.mapTo(ArrayList(), ::TypeParameterInfo)
|
||||
super.init()
|
||||
}
|
||||
|
||||
override fun isCheckMarkColumnEditable() = false
|
||||
|
||||
val selectedTypeParameterInfos: List<TypeParameterInfo>
|
||||
get() = parameterInfos.filter { it.isEnabled }
|
||||
|
||||
val selectedTypeParameters: List<TypeParameter>
|
||||
get() = selectedTypeParameterInfos.map { it.toParameter() }
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.kotlin.idea.k2.refactoring.introduceTypeAlias.ui.KotlinIntroduceTypeAliasDialog">
|
||||
<grid id="27dc6" binding="contentPane" layout-manager="GridLayoutManager" row-count="4" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="20" y="20" width="524" height="396"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<grid id="34b30" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<grid id="fa2d1" layout-manager="BorderLayout" hgap="0" vgap="0">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<grid id="2aa47" class="com.intellij.ui.TitledSeparator" binding="inputParametersPanel" layout-manager="BorderLayout" hgap="0" vgap="0">
|
||||
<constraints border-constraint="Center"/>
|
||||
<properties>
|
||||
<text value=""/>
|
||||
</properties>
|
||||
<border type="none" title-resource-bundle="messages/KotlinBundle" title-key="title.parameters"/>
|
||||
<children/>
|
||||
</grid>
|
||||
</children>
|
||||
</grid>
|
||||
</children>
|
||||
</grid>
|
||||
<grid id="b45d1" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="-1" height="200"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<grid id="4266d" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none" title-resource-bundle="messages/KotlinBundle" title-key="signature.preview"/>
|
||||
<children>
|
||||
<component id="7722b" class="org.jetbrains.kotlin.idea.refactoring.introduce.ui.KotlinSignatureComponent" binding="signaturePreviewField" custom-create="true">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false">
|
||||
<minimum-size width="500" height="100"/>
|
||||
<preferred-size width="500" height="100"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value=""/>
|
||||
</properties>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
</children>
|
||||
</grid>
|
||||
<grid id="5fe96" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<enabled value="true"/>
|
||||
<visible value="true"/>
|
||||
</properties>
|
||||
<border type="empty"/>
|
||||
<children>
|
||||
<component id="d1d09" class="javax.swing.JLabel" binding="aliasNameLabel">
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text resource-bundle="messages/KotlinBundle" key="name"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="52aa7" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="114" height="16"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties>
|
||||
<labelFor value="71ee7"/>
|
||||
<text resource-bundle="messages/KotlinBundle" key="visibility"/>
|
||||
<visible value="true"/>
|
||||
</properties>
|
||||
</component>
|
||||
<grid id="d0f58" binding="aliasNamePanel" layout-manager="BorderLayout" hgap="0" vgap="0">
|
||||
<constraints>
|
||||
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="150" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children/>
|
||||
</grid>
|
||||
<component id="71ee7" class="javax.swing.JComboBox" binding="visibilityBox">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="0" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="114" height="26"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties>
|
||||
<visible value="true"/>
|
||||
</properties>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
<vspacer id="cd10f">
|
||||
<constraints>
|
||||
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</vspacer>
|
||||
</children>
|
||||
</grid>
|
||||
</form>
|
||||
+230
@@ -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<KtModifierKeywordToken> 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<KotlinIntroduceTypeAliasDialog, Unit> onAccept;
|
||||
|
||||
public KotlinIntroduceTypeAliasDialog(
|
||||
@NotNull Project project,
|
||||
@NotNull IntroduceTypeAliasDescriptor originalDescriptor,
|
||||
@NotNull Function1<KotlinIntroduceTypeAliasDialog, Unit> 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<KtModifierKeywordToken> 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<PsiElement, String> 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;
|
||||
}
|
||||
}
|
||||
+48
@@ -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<PsiElement>,
|
||||
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()
|
||||
}
|
||||
+177
@@ -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");
|
||||
}
|
||||
}
|
||||
+4
@@ -73,6 +73,10 @@ internal fun MutableTWorkspace.generateK2RefactoringsTests() {
|
||||
model("refactoring/extractFunction", pattern = Patterns.KT_OR_KTS, testMethodName = "doExtractFunctionTest")
|
||||
}
|
||||
|
||||
testClass<AbstractK2IntroduceTypeAliasTest> {
|
||||
model("refactoring/introduceTypeAlias", pattern = Patterns.KT_OR_KTS, testMethodName = "doIntroduceTypeAliasTest")
|
||||
}
|
||||
|
||||
testClass<AbstractK2IntroduceFunctionWithExtractFunctionModifierTest> {
|
||||
model("refactoring/extractFunctionModifier", pattern = Patterns.KT_OR_KTS, testMethodName = "doExtractFunctionTest")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user