Introduce Type Alias
#KT-12902 Fixed
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
|
||||
###### New features
|
||||
- [`KT-12903`](https://youtrack.jetbrains.com/issue/KT-12903) Implement "Inline type alias" refactoring
|
||||
- [`KT-12902`](https://youtrack.jetbrains.com/issue/KT-12902) Implement "Introduce type alias" refactoring
|
||||
|
||||
## 1.1-M01 (EAP-1)
|
||||
|
||||
|
||||
@@ -98,11 +98,19 @@ class KtPsiFactory(private val project: Project) {
|
||||
return typeReference
|
||||
}
|
||||
|
||||
fun createType(typeElement: KtTypeElement) = createType("X").apply { this.typeElement!!.replace(typeElement) }
|
||||
|
||||
fun createTypeIfPossible(type: String): KtTypeReference? {
|
||||
val typeReference = createProperty("val x : $type").typeReference
|
||||
return if (typeReference?.text == type) typeReference else null
|
||||
}
|
||||
|
||||
fun createTypeAlias(name: String, typeParameters: List<String>, typeElement: KtTypeElement): KtTypeAlias {
|
||||
val typeParametersText = if (typeParameters.isNotEmpty()) typeParameters.joinToString(prefix = "<", postfix = ">") else ""
|
||||
return createDeclaration<KtTypeAlias>("typealias $name$typeParametersText = X")
|
||||
.apply { getTypeReference()!!.replace(createType(typeElement)) }
|
||||
}
|
||||
|
||||
fun createStar(): PsiElement {
|
||||
return createType("List<*>").findElementAt(5)!!
|
||||
}
|
||||
|
||||
@@ -41,4 +41,9 @@ public class KtTypeArgumentList extends KtElementImplStub<KotlinPlaceHolderStub<
|
||||
public List<KtTypeProjection> getArguments() {
|
||||
return getStubOrPsiChildrenAsList(KtStubElementTypes.TYPE_PROJECTION);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public KtTypeProjection addArgument(@NotNull KtTypeProjection argument) {
|
||||
return EditCommaSeparatedListHelper.INSTANCE.addItem(this, getArguments(), argument);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +140,10 @@ fun PsiElement.getNextSiblingIgnoringWhitespaceAndComments(): PsiElement? {
|
||||
return siblings(withItself = false).filter { it !is PsiWhiteSpace && it !is PsiComment }.firstOrNull()
|
||||
}
|
||||
|
||||
fun PsiElement.getPrevSiblingIgnoringWhitespaceAndComments(): PsiElement? {
|
||||
return siblings(withItself = false, forward = false).filter { it !is PsiWhiteSpace && it !is PsiComment }.firstOrNull()
|
||||
}
|
||||
|
||||
inline fun <reified T : PsiElement> T.nextSiblingOfSameType() = PsiTreeUtil.getNextSiblingOfType(this, T::class.java)
|
||||
|
||||
inline fun <reified T : PsiElement> T.prevSiblingOfSameType() = PsiTreeUtil.getPrevSiblingOfType(this, T::class.java)
|
||||
|
||||
@@ -766,6 +766,7 @@ fun main(args: Array<String>) {
|
||||
model("refactoring/introduceParameter", pattern = kotlinFileOrScript, testMethod = "doIntroduceSimpleParameterTest")
|
||||
model("refactoring/introduceLambdaParameter", pattern = kotlinFileOrScript, testMethod = "doIntroduceLambdaParameterTest")
|
||||
model("refactoring/introduceJavaParameter", extension = "java", testMethod = "doIntroduceJavaParameterTest")
|
||||
model("refactoring/introduceTypeAlias", pattern = kotlinFileOrScript, testMethod = "doIntroduceTypeAliasTest")
|
||||
}
|
||||
|
||||
testClass<AbstractPullUpTest>() {
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.refactoring
|
||||
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
|
||||
fun KtElement.getContextForContainingDeclarationBody(): BindingContext? {
|
||||
val enclosingDeclaration = getStrictParentOfType<KtDeclaration>()
|
||||
val bodyElement = when (enclosingDeclaration) {
|
||||
is KtDeclarationWithBody -> enclosingDeclaration.getBodyExpression()
|
||||
is KtWithExpressionInitializer -> enclosingDeclaration.getInitializer()
|
||||
is KtDestructuringDeclaration -> enclosingDeclaration.getInitializer()
|
||||
is KtParameter -> enclosingDeclaration.getDefaultValue()
|
||||
is KtAnonymousInitializer -> enclosingDeclaration.body
|
||||
is KtClass -> {
|
||||
val delegationSpecifierList = enclosingDeclaration.getSuperTypeList()
|
||||
if (delegationSpecifierList.isAncestor(this)) this else null
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
return bodyElement?.let { it.analyze() }
|
||||
}
|
||||
+97
-36
@@ -20,8 +20,9 @@ import com.intellij.lang.ASTNode
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.impl.source.tree.LeafPsiElement
|
||||
import com.intellij.util.containers.MultiMap
|
||||
import org.jetbrains.kotlin.builtins.isExtensionFunctionType
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.idea.refactoring.getContextForContainingDeclarationBody
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.ExtractableSubstringInfo
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractableSubstringInfo
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
@@ -32,6 +33,7 @@ import org.jetbrains.kotlin.idea.util.psi.patternMatching.UnificationResult.Stat
|
||||
import org.jetbrains.kotlin.lexer.KtToken
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
||||
@@ -45,9 +47,7 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.Receiver
|
||||
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||
import org.jetbrains.kotlin.types.ErrorUtils
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
import org.jetbrains.kotlin.types.*
|
||||
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
|
||||
import org.jetbrains.kotlin.types.expressions.OperatorConventions
|
||||
import java.util.*
|
||||
@@ -71,18 +71,18 @@ interface UnificationResult {
|
||||
|
||||
interface Matched: UnificationResult {
|
||||
val range: KotlinPsiRange
|
||||
val substitution: Map<UnifierParameter, KtExpression>
|
||||
val substitution: Map<UnifierParameter, KtElement>
|
||||
override val status: Status get() = MATCHED
|
||||
}
|
||||
|
||||
class StronglyMatched(
|
||||
override val range: KotlinPsiRange,
|
||||
override val substitution: Map<UnifierParameter, KtExpression>
|
||||
override val substitution: Map<UnifierParameter, KtElement>
|
||||
): Matched
|
||||
|
||||
class WeaklyMatched(
|
||||
override val range: KotlinPsiRange,
|
||||
override val substitution: Map<UnifierParameter, KtExpression>,
|
||||
override val substitution: Map<UnifierParameter, KtElement>,
|
||||
val weakMatches: Map<KtElement, KtElement>
|
||||
): Matched
|
||||
|
||||
@@ -92,7 +92,7 @@ interface UnificationResult {
|
||||
|
||||
class UnifierParameter(
|
||||
val descriptor: DeclarationDescriptor,
|
||||
val expectedType: KotlinType
|
||||
val expectedType: KotlinType?
|
||||
)
|
||||
|
||||
class KotlinPsiUnifier(
|
||||
@@ -109,7 +109,7 @@ class KotlinPsiUnifier(
|
||||
) {
|
||||
val patternContext: BindingContext = originalPattern.getBindingContext()
|
||||
val targetContext: BindingContext = originalTarget.getBindingContext()
|
||||
val substitution = HashMap<UnifierParameter, KtExpression>()
|
||||
val substitution = HashMap<UnifierParameter, KtElement>()
|
||||
val declarationPatternsToTargets = MultiMap<DeclarationDescriptor, DeclarationDescriptor>()
|
||||
val weakMatches = HashMap<KtElement, KtElement>()
|
||||
var checkEquivalence: Boolean = false
|
||||
@@ -118,7 +118,7 @@ class KotlinPsiUnifier(
|
||||
private fun KotlinPsiRange.getBindingContext(): BindingContext {
|
||||
val element = (this as? KotlinPsiRange.ListRange)?.startElement as? KtElement
|
||||
if ((element?.containingFile as? KtFile)?.doNotAnalyze != null) return BindingContext.EMPTY
|
||||
return element?.getContextForContainingDeclarationBody() ?: BindingContext.EMPTY
|
||||
return element?.analyze() ?: BindingContext.EMPTY
|
||||
}
|
||||
|
||||
private fun matchDescriptors(d1: DeclarationDescriptor?, d2: DeclarationDescriptor?): Boolean {
|
||||
@@ -322,13 +322,20 @@ class KotlinPsiUnifier(
|
||||
}
|
||||
|
||||
private fun KtTypeReference.getType(): KotlinType? {
|
||||
val t = bindingContext[BindingContext.TYPE, this]
|
||||
val t = bindingContext.let { it[BindingContext.ABBREVIATED_TYPE, this] ?: it[BindingContext.TYPE, this] }
|
||||
return if (t == null || t.isError) null else t
|
||||
}
|
||||
|
||||
private fun matchTypes(type1: KotlinType?, type2: KotlinType?): Status? {
|
||||
private fun matchTypes(
|
||||
type1: KotlinType?,
|
||||
type2: KotlinType?,
|
||||
typeRef1: KtTypeReference? = null,
|
||||
typeRef2: KtTypeReference? = null
|
||||
): Status? {
|
||||
if (type1 != null && type2 != null) {
|
||||
if (type1.isError || type2.isError) return null
|
||||
if (type1 is AbbreviatedType != type2 is AbbreviatedType) return UNMATCHED
|
||||
if (type1.isExtensionFunctionType != type2.isExtensionFunctionType) return UNMATCHED
|
||||
if (TypeUtils.equalTypes(type1, type2)) return MATCHED
|
||||
|
||||
if (type1.isMarkedNullable != type2.isMarkedNullable) return UNMATCHED
|
||||
@@ -339,9 +346,11 @@ class KotlinPsiUnifier(
|
||||
val args1 = type1.arguments
|
||||
val args2 = type2.arguments
|
||||
if (args1.size != args2.size) return UNMATCHED
|
||||
if (!args1.zip(args2).all {
|
||||
it.first.projectionKind == it.second.projectionKind && matchTypes(it.first.type, it.second.type) == MATCHED }
|
||||
) return UNMATCHED
|
||||
if (!args1.withIndex().all { p ->
|
||||
val (i, arg1) = p
|
||||
val arg2 = args2[i]
|
||||
matchTypeArguments(i, arg1, arg2, typeRef1, typeRef2)
|
||||
}) return UNMATCHED
|
||||
|
||||
return MATCHED
|
||||
}
|
||||
@@ -349,6 +358,32 @@ class KotlinPsiUnifier(
|
||||
return if (type1 == null && type2 == null) null else UNMATCHED
|
||||
}
|
||||
|
||||
private fun matchTypeArguments(
|
||||
argIndex: Int,
|
||||
arg1: TypeProjection,
|
||||
arg2: TypeProjection,
|
||||
typeRef1: KtTypeReference?,
|
||||
typeRef2: KtTypeReference?
|
||||
): Boolean {
|
||||
val typeArgRef1 = typeRef1?.typeElement?.typeArgumentsAsTypes?.getOrNull(argIndex)
|
||||
val typeArgRef2 = typeRef2?.typeElement?.typeArgumentsAsTypes?.getOrNull(argIndex)
|
||||
|
||||
if (arg1.projectionKind != arg2.projectionKind) return false
|
||||
val argType1 = arg1.type
|
||||
val argType2 = arg2.type
|
||||
// Substitution attempt using either arg1, or arg2 as a pattern type. Falls back to exact matching if substitution is not possible
|
||||
val status = if (!checkEquivalence && typeRef1 != null && typeRef2 != null) {
|
||||
val typePsi1 = argType1.constructor.declarationDescriptor?.source?.getPsi()
|
||||
val typePsi2 = argType2.constructor.declarationDescriptor?.source?.getPsi()
|
||||
descriptorToParameter[typePsi1]?.let { substitute(it, typeArgRef2) } ?:
|
||||
descriptorToParameter[typePsi2]?.let { substitute(it, typeArgRef1) } ?:
|
||||
matchTypes(argType1, argType2, typeArgRef1, typeArgRef2)
|
||||
}
|
||||
else matchTypes(argType1, argType2, typeArgRef1, typeArgRef2)
|
||||
|
||||
return status == MATCHED
|
||||
}
|
||||
|
||||
private fun matchTypes(types1: Collection<KotlinType>, types2: Collection<KotlinType>): Boolean {
|
||||
fun sortTypes(types: Collection<KotlinType>) = types.sortedBy { DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(it) }
|
||||
|
||||
@@ -367,6 +402,7 @@ class KotlinPsiUnifier(
|
||||
}
|
||||
is KtBinaryExpression -> operationReference.getReferencedNameElementType() == KtTokens.ELVIS
|
||||
is KtThisExpression -> true
|
||||
is KtSimpleNameExpression -> getStrictParentOfType<KtTypeElement>() != null
|
||||
else -> false
|
||||
}
|
||||
|
||||
@@ -641,7 +677,14 @@ class KotlinPsiUnifier(
|
||||
desc2: DeclarationDescriptor?): Status? {
|
||||
if (decl1.javaClass != decl2.javaClass) return UNMATCHED
|
||||
|
||||
if (desc1 == null || desc2 == null || ErrorUtils.isError(desc1) || ErrorUtils.isError(desc2)) return UNMATCHED
|
||||
if (desc1 == null || desc2 == null) {
|
||||
if (decl1 is KtParameter
|
||||
&& decl2 is KtParameter
|
||||
&& decl1.getStrictParentOfType<KtTypeElement>() != null
|
||||
&& decl2.getStrictParentOfType<KtTypeElement>() != null) return null
|
||||
return UNMATCHED
|
||||
}
|
||||
if (ErrorUtils.isError(desc1) || ErrorUtils.isError(desc2)) return UNMATCHED
|
||||
if (desc1.javaClass != desc2.javaClass) return UNMATCHED
|
||||
|
||||
declarationPatternsToTargets.putValue(desc1, desc2)
|
||||
@@ -682,8 +725,11 @@ class KotlinPsiUnifier(
|
||||
e2 is KtDeclaration ->
|
||||
e2.matchDeclarations(e1)
|
||||
|
||||
e1 is KtTypeElement && e2 is KtTypeElement && e1.parent is KtTypeReference && e2.parent is KtTypeReference ->
|
||||
matchResolvedInfo(e1.parent, e2.parent)
|
||||
|
||||
e1 is KtTypeReference && e2 is KtTypeReference ->
|
||||
matchTypes(e1.getType(), e2.getType())
|
||||
matchTypes(e1.getType(), e2.getType(), e1, e2)
|
||||
|
||||
KtPsiUtil.isAssignment(e1) ->
|
||||
(e1 as KtBinaryExpression).matchAssignment(e2)
|
||||
@@ -708,8 +754,9 @@ class KotlinPsiUnifier(
|
||||
}
|
||||
|
||||
private fun PsiElement.checkType(parameter: UnifierParameter): Boolean {
|
||||
val expectedType = parameter.expectedType ?: return true
|
||||
val targetElementType = (this as? KtExpression)?.let { it.bindingContext.getType(it) }
|
||||
return targetElementType != null && KotlinTypeChecker.DEFAULT.isSubtypeOf(targetElementType, parameter.expectedType)
|
||||
return targetElementType != null && KotlinTypeChecker.DEFAULT.isSubtypeOf(targetElementType, expectedType)
|
||||
}
|
||||
|
||||
private fun doUnifyStringTemplateFragments(target: KtStringTemplateExpression, pattern: ExtractableSubstringInfo): Status {
|
||||
@@ -820,6 +867,23 @@ class KotlinPsiUnifier(
|
||||
return status
|
||||
}
|
||||
|
||||
private fun substitute(parameter: UnifierParameter, targetElement: PsiElement?): Status {
|
||||
val existingArgument = substitution[parameter]
|
||||
return when {
|
||||
existingArgument == null -> {
|
||||
substitution[parameter] = targetElement as KtElement
|
||||
MATCHED
|
||||
}
|
||||
else -> {
|
||||
checkEquivalence = true
|
||||
val status = doUnify(existingArgument, targetElement)
|
||||
checkEquivalence = false
|
||||
|
||||
status
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun doUnify(
|
||||
targetElement: PsiElement?,
|
||||
patternElement: PsiElement?
|
||||
@@ -831,29 +895,26 @@ class KotlinPsiUnifier(
|
||||
if (targetElementUnwrapped == null || patternElementUnwrapped == null) return UNMATCHED
|
||||
|
||||
if (!checkEquivalence && targetElementUnwrapped !is KtBlockExpression) {
|
||||
val referencedPatternDescriptor = (patternElementUnwrapped as? KtReferenceExpression)?.let {
|
||||
it.bindingContext[BindingContext.REFERENCE_TARGET, it]
|
||||
val referencedPatternDescriptor = when (patternElementUnwrapped) {
|
||||
is KtReferenceExpression -> {
|
||||
if (targetElementUnwrapped !is KtExpression) return UNMATCHED
|
||||
patternElementUnwrapped.bindingContext[BindingContext.REFERENCE_TARGET, patternElementUnwrapped]
|
||||
}
|
||||
is KtUserType -> {
|
||||
if (targetElementUnwrapped !is KtUserType) return UNMATCHED
|
||||
patternElementUnwrapped.bindingContext[BindingContext.REFERENCE_TARGET, patternElementUnwrapped.referenceExpression]
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
val referencedPatternDeclaration = (referencedPatternDescriptor as? DeclarationDescriptorWithSource)?.source?.getPsi()
|
||||
val parameter = descriptorToParameter[referencedPatternDeclaration]
|
||||
if (referencedPatternDeclaration != null && parameter != null) {
|
||||
if (targetElementUnwrapped !is KtExpression) return UNMATCHED
|
||||
if (!targetElementUnwrapped.checkType(parameter)) return UNMATCHED
|
||||
|
||||
val existingArgument = substitution[parameter]
|
||||
return when {
|
||||
existingArgument == null -> {
|
||||
substitution[parameter] = targetElementUnwrapped
|
||||
MATCHED
|
||||
}
|
||||
else -> {
|
||||
checkEquivalence = true
|
||||
val status = doUnify(existingArgument, targetElementUnwrapped)
|
||||
checkEquivalence = false
|
||||
|
||||
status
|
||||
}
|
||||
if (targetElementUnwrapped is KtExpression) {
|
||||
if (!targetElementUnwrapped.checkType(parameter)) return UNMATCHED
|
||||
}
|
||||
else if (targetElementUnwrapped !is KtUserType) return UNMATCHED
|
||||
|
||||
return substitute(parameter, targetElementUnwrapped)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -157,6 +157,12 @@
|
||||
<add-to-group group-id="IntroduceActionsGroup" anchor="after" relative-to-action="ExtractFunction"/>
|
||||
</action>
|
||||
|
||||
<action id="IntroduceTypeAlias" class="org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.IntroduceTypeAliasAction"
|
||||
text="Type _Alias...">
|
||||
<keyboard-shortcut keymap="$default" first-keystroke="control alt shift A"/>
|
||||
<add-to-group group-id="IntroduceActionsGroup" anchor="after" relative-to-action="ExtractFunctionToScope"/>
|
||||
</action>
|
||||
|
||||
<!-- Kotlin Console REPL-->
|
||||
<action id="KotlinConsoleREPL" class="org.jetbrains.kotlin.console.actions.RunKotlinConsoleAction"
|
||||
text="Kotlin REPL"
|
||||
|
||||
@@ -6,6 +6,7 @@ introduce.property=Introduce Property
|
||||
introduce.parameter=Introduce Parameter
|
||||
cannot.refactor.no.container=Cannot refactor in this place
|
||||
cannot.refactor.no.expression=Cannot perform refactoring without an expression
|
||||
cannot.refactor.no.type=Cannot perform refactoring without a type
|
||||
cannot.refactor.syntax.errors=Cannot refactor due to erroneous code
|
||||
cannot.refactor.expression.has.unit.type=Cannot introduce expression of unit type
|
||||
cannot.refactor.package.expression=Cannot introduce package reference
|
||||
|
||||
+2
-2
@@ -24,11 +24,11 @@ import com.intellij.psi.PsiNameIdentifierOwner
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
|
||||
import org.jetbrains.kotlin.idea.codeInsight.KotlinFileReferencesResolver
|
||||
import org.jetbrains.kotlin.idea.core.compareDescriptors
|
||||
import org.jetbrains.kotlin.idea.refactoring.getContextForContainingDeclarationBody
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.ExtractableSubstringInfo
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractableSubstringInfo
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.substringContextOrThis
|
||||
@@ -114,7 +114,7 @@ data class ExtractionData(
|
||||
|
||||
val commonParent = PsiTreeUtil.findCommonParent(physicalElements) as KtElement
|
||||
|
||||
val bindingContext: BindingContext? by lazy { commonParent.getContextForContainingDeclarationBody() }
|
||||
val bindingContext: BindingContext? by lazy { commonParent.analyze() }
|
||||
|
||||
private val itFakeDeclaration by lazy { KtPsiFactory(originalFile).createParameter("it: Any?") }
|
||||
private val synthesizedInvokeDeclaration by lazy { KtPsiFactory(originalFile).createFunction("fun invoke() {}") }
|
||||
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias
|
||||
|
||||
import com.intellij.lang.refactoring.RefactoringSupportProvider
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.refactoring.RefactoringActionHandler
|
||||
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
|
||||
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
|
||||
import org.jetbrains.kotlin.idea.refactoring.getExtractionContainers
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.AbstractIntroduceAction
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.processDuplicates
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.ui.KotlinIntroduceTypeAliasDialog
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.selectElementsWithTargetSibling
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.showErrorHint
|
||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtTypeAlias
|
||||
import org.jetbrains.kotlin.psi.KtTypeElement
|
||||
|
||||
object KotlinIntroduceTypeAliasHandler : RefactoringActionHandler {
|
||||
@JvmField
|
||||
val REFACTORING_NAME = "Introduce Type Alias"
|
||||
|
||||
fun selectElements(editor: Editor, file: KtFile, continuation: (elements: List<PsiElement>, targetSibling: PsiElement) -> Unit) {
|
||||
selectElementsWithTargetSibling(
|
||||
REFACTORING_NAME,
|
||||
editor,
|
||||
file,
|
||||
"Select target code block",
|
||||
CodeInsightUtils.ElementKind.TYPE_ELEMENT,
|
||||
{ elements, parent -> parent.getExtractionContainers(strict = true, includeAll = true) },
|
||||
continuation
|
||||
)
|
||||
}
|
||||
|
||||
private fun runRefactoring(descriptor: IntroduceTypeAliasDescriptor, project: Project, editor: Editor) {
|
||||
val typeAlias = project.executeWriteCommand<KtTypeAlias>(REFACTORING_NAME) { descriptor.generateTypeAlias() }
|
||||
|
||||
val duplicateReplacers = findDuplicates(typeAlias)
|
||||
if (duplicateReplacers.isNotEmpty()) {
|
||||
processDuplicates(duplicateReplacers, project, editor)
|
||||
}
|
||||
}
|
||||
|
||||
fun doInvoke(
|
||||
project: Project,
|
||||
editor: Editor,
|
||||
elements: List<PsiElement>,
|
||||
targetSibling: PsiElement,
|
||||
descriptorSubstitutor: ((IntroduceTypeAliasDescriptor) -> IntroduceTypeAliasDescriptor)? = null
|
||||
) {
|
||||
val typeElement = elements.singleOrNull() as? KtTypeElement
|
||||
?: return showErrorHint(project, editor, "No type to refactor", REFACTORING_NAME)
|
||||
val introduceData = IntroduceTypeAliasData(typeElement, targetSibling)
|
||||
val analysisResult = introduceData.analyze()
|
||||
when (analysisResult) {
|
||||
is IntroduceTypeAliasAnalysisResult.Error -> {
|
||||
return showErrorHint(project, editor, analysisResult.message, REFACTORING_NAME)
|
||||
}
|
||||
|
||||
is IntroduceTypeAliasAnalysisResult.Success -> {
|
||||
val originalDescriptor = analysisResult.descriptor
|
||||
if (ApplicationManager.getApplication().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
|
||||
selectElements(editor, file) { elements, targetSibling -> doInvoke(project, editor, elements, targetSibling) }
|
||||
}
|
||||
|
||||
override fun invoke(project: Project, elements: Array<out PsiElement>, dataContext: DataContext?) {
|
||||
throw AssertionError("$REFACTORING_NAME can only be invoked from editor")
|
||||
}
|
||||
}
|
||||
|
||||
class IntroduceTypeAliasAction : AbstractIntroduceAction() {
|
||||
override fun getRefactoringHandler(provider: RefactoringSupportProvider) = KotlinIntroduceTypeAliasHandler
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.refactoring.introduce.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.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
|
||||
class TypeReferenceInfo(val reference: KtTypeReference, val type: KotlinType)
|
||||
|
||||
internal var KtTypeReference.resolveInfo : TypeReferenceInfo? by CopyableUserDataProperty(Key.create("RESOLVE_INFO"))
|
||||
|
||||
class IntroduceTypeAliasData(
|
||||
val originalType: KtTypeElement,
|
||||
val targetSibling: PsiElement
|
||||
) : Disposable {
|
||||
val resolutionFacade = originalType.getResolutionFacade()
|
||||
val bindingContext = resolutionFacade.analyze(originalType, BodyResolveMode.PARTIAL)
|
||||
|
||||
init {
|
||||
markReferences()
|
||||
}
|
||||
|
||||
private fun markReferences() {
|
||||
val visitor = object : KtTreeVisitorVoid() {
|
||||
override fun visitTypeReference(typeReference: KtTypeReference) {
|
||||
val typeElement = typeReference.typeElement ?: return
|
||||
|
||||
val kotlinType = bindingContext[BindingContext.ABBREVIATED_TYPE, typeReference] ?:
|
||||
bindingContext[BindingContext.TYPE, typeReference] ?:
|
||||
return
|
||||
typeReference.resolveInfo = TypeReferenceInfo(typeReference, kotlinType)
|
||||
|
||||
typeElement.typeArgumentsAsTypes.forEach { it.accept(this) }
|
||||
}
|
||||
}
|
||||
(originalType.parent as? KtTypeReference ?: originalType).accept(visitor)
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
if (!originalType.isValid) return
|
||||
originalType.forEachDescendantOfType<KtTypeReference> { it.resolveInfo = null }
|
||||
}
|
||||
}
|
||||
|
||||
data class TypeParameter(val name: String, val typeReferenceInfos: Collection<TypeReferenceInfo>)
|
||||
|
||||
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>
|
||||
)
|
||||
+235
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias
|
||||
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.util.containers.LinkedMultiMap
|
||||
import com.intellij.util.containers.MultiMap
|
||||
import org.jetbrains.kotlin.descriptors.TypeAliasDescriptor
|
||||
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.core.CollectingNameValidator
|
||||
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
|
||||
import org.jetbrains.kotlin.idea.core.compareDescriptors
|
||||
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
||||
import org.jetbrains.kotlin.idea.util.psi.patternMatching.KotlinPsiRange
|
||||
import org.jetbrains.kotlin.idea.util.psi.patternMatching.KotlinPsiUnifier
|
||||
import org.jetbrains.kotlin.idea.util.psi.patternMatching.UnifierParameter
|
||||
import org.jetbrains.kotlin.idea.util.psi.patternMatching.toRange
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.lexer.KtTokens.*
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier
|
||||
import org.jetbrains.kotlin.utils.SmartList
|
||||
|
||||
sealed class IntroduceTypeAliasAnalysisResult {
|
||||
class Error(val message: String) : IntroduceTypeAliasAnalysisResult()
|
||||
class Success(val descriptor: IntroduceTypeAliasDescriptor) : IntroduceTypeAliasAnalysisResult()
|
||||
}
|
||||
|
||||
private fun IntroduceTypeAliasData.getTargetScope() = targetSibling.getResolutionScope(bindingContext, resolutionFacade)
|
||||
|
||||
fun IntroduceTypeAliasData.analyze(): IntroduceTypeAliasAnalysisResult {
|
||||
val psiFactory = KtPsiFactory(originalType)
|
||||
|
||||
val contextExpression = originalType.getStrictParentOfType<KtExpression>()!!
|
||||
val targetScope = getTargetScope()
|
||||
|
||||
val dummyVar = psiFactory.createProperty("val a: Int").apply {
|
||||
typeReference!!.replace(originalType.parent as? KtTypeReference ?: psiFactory.createType(originalType))
|
||||
}
|
||||
val newReferences = dummyVar.typeReference!!.collectDescendantsOfType<KtTypeReference> { it.resolveInfo != null }
|
||||
val newContext = dummyVar.analyzeInContext(targetScope, contextExpression)
|
||||
val project = originalType.project
|
||||
|
||||
val unifier = KotlinPsiUnifier.DEFAULT
|
||||
val groupedBrokenReferences = LinkedMultiMap<TypeReferenceInfo, TypeReferenceInfo>()
|
||||
for (newReference in newReferences) {
|
||||
val resolveInfo = newReference.resolveInfo!!
|
||||
|
||||
val originalDescriptor = resolveInfo.type.constructor.declarationDescriptor
|
||||
val newDescriptor = newContext[BindingContext.TYPE, newReference]?.constructor?.declarationDescriptor
|
||||
if (compareDescriptors(project, originalDescriptor, newDescriptor)) continue
|
||||
|
||||
val equivalenceRepresentative = groupedBrokenReferences
|
||||
.keySet()
|
||||
.firstOrNull { unifier.unify(it.reference, resolveInfo.reference).matched }
|
||||
if (equivalenceRepresentative != null) {
|
||||
groupedBrokenReferences.putValue(equivalenceRepresentative, resolveInfo)
|
||||
}
|
||||
else {
|
||||
groupedBrokenReferences.putValue(resolveInfo, resolveInfo)
|
||||
}
|
||||
|
||||
val brokenReferenceInfoIterator = groupedBrokenReferences.values().iterator()
|
||||
while (brokenReferenceInfoIterator.hasNext()) {
|
||||
val brokenReferenceInfo = brokenReferenceInfoIterator.next()
|
||||
if (resolveInfo.reference.isAncestor(brokenReferenceInfo.reference, true)) {
|
||||
brokenReferenceInfoIterator.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val typeParameterNameValidator = CollectingNameValidator()
|
||||
val brokenReferences = groupedBrokenReferences.keySet().filter { groupedBrokenReferences[it].isNotEmpty() }
|
||||
val typeParameterNames = KotlinNameSuggester.suggestNamesForTypeParameters(brokenReferences.size, typeParameterNameValidator)
|
||||
val typeParameters = (typeParameterNames zip brokenReferences).map { TypeParameter(it.first, groupedBrokenReferences[it.second]) }
|
||||
|
||||
if (typeParameters.any { it.typeReferenceInfos.any { it.reference.typeElement == originalType } }) {
|
||||
return IntroduceTypeAliasAnalysisResult.Error("Type alias cannot refer to types which aren't accessible in the scope where it's defined")
|
||||
}
|
||||
|
||||
return IntroduceTypeAliasAnalysisResult.Success(IntroduceTypeAliasDescriptor(this, "", null, typeParameters))
|
||||
}
|
||||
|
||||
fun IntroduceTypeAliasData.getApplicableVisibilities(): List<KtModifierKeywordToken>{
|
||||
val parent = targetSibling.parent
|
||||
return when (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.originalType
|
||||
if (name.isEmpty()) {
|
||||
conflicts.putValue(originalType, "No name provided for type alias")
|
||||
}
|
||||
else if (!KotlinNameSuggester.isIdentifier(name)) {
|
||||
conflicts.putValue(originalType, "Type alias name must be a valid identifier: $name")
|
||||
}
|
||||
else if (originalData.getTargetScope().findClassifier(Name.identifier(name), NoLookupLocation.FROM_IDE) != null) {
|
||||
conflicts.putValue(originalType, "Type $name already exists in the target scope")
|
||||
}
|
||||
|
||||
if (typeParameters.distinctBy { it.name }.size != typeParameters.size) {
|
||||
conflicts.putValue(originalType, "Type parameter names must be distinct")
|
||||
}
|
||||
|
||||
if (visibility != null && visibility !in originalData.getApplicableVisibilities()) {
|
||||
conflicts.putValue(originalType, "'$visibility' is not allowed in the target context")
|
||||
}
|
||||
|
||||
return IntroduceTypeAliasDescriptorWithConflicts(this, conflicts)
|
||||
}
|
||||
|
||||
fun findDuplicates(typeAlias: KtTypeAlias): Map<KotlinPsiRange, () -> Unit> {
|
||||
val aliasName = typeAlias.name ?: return emptyMap()
|
||||
val typeAliasDescriptor = typeAlias.resolveToDescriptor() as TypeAliasDescriptor
|
||||
|
||||
val unifierParameters = typeAliasDescriptor.declaredTypeParameters.map { UnifierParameter(it, null) }
|
||||
val unifier = KotlinPsiUnifier(unifierParameters)
|
||||
|
||||
val psiFactory = KtPsiFactory(typeAlias)
|
||||
|
||||
fun replaceOccurrence(occurrence: KtTypeElement, arguments: List<KtTypeElement>) {
|
||||
val typeText = if (arguments.isNotEmpty()) "$aliasName<${arguments.joinToString { it.text }}>" else aliasName
|
||||
occurrence.replace(psiFactory.createType(typeText).typeElement!!)
|
||||
}
|
||||
|
||||
val aliasRange = typeAlias.textRange
|
||||
return typeAlias
|
||||
.getTypeReference()
|
||||
?.typeElement
|
||||
.toRange()
|
||||
.match(typeAlias.parent, unifier)
|
||||
.asSequence()
|
||||
.filter { !(it.range.getTextRange().intersects(aliasRange)) }
|
||||
.mapNotNull { match ->
|
||||
val occurrence = match.range.elements.singleOrNull() as? KtTypeElement ?: return@mapNotNull null
|
||||
val arguments = unifierParameters.mapNotNull { (match.substitution[it] as? KtTypeReference)?.typeElement }
|
||||
if (arguments.size != unifierParameters.size) return@mapNotNull null
|
||||
match.range to { replaceOccurrence(occurrence, arguments) }
|
||||
}
|
||||
.toMap()
|
||||
}
|
||||
|
||||
private var KtTypeReference.typeParameterInfo : TypeParameter? by CopyableUserDataProperty(Key.create("TYPE_PARAMETER_INFO"))
|
||||
|
||||
fun IntroduceTypeAliasDescriptor.generateTypeAlias(previewOnly: Boolean = false): KtTypeAlias {
|
||||
val originalType = originalData.originalType
|
||||
val targetSibling = originalData.targetSibling
|
||||
val psiFactory = KtPsiFactory(originalType)
|
||||
|
||||
for (typeParameter in typeParameters)
|
||||
for (it in typeParameter.typeReferenceInfos) {
|
||||
it.reference.typeParameterInfo = typeParameter
|
||||
}
|
||||
|
||||
val typeAlias = psiFactory.createTypeAlias(name, typeParameters.map { it.name }, originalType)
|
||||
if (visibility != null && visibility != KtTokens.DEFAULT_VISIBILITY_KEYWORD) {
|
||||
typeAlias.addModifier(visibility)
|
||||
}
|
||||
|
||||
for (typeParameter in typeParameters)
|
||||
for (it in typeParameter.typeReferenceInfos) {
|
||||
it.reference.typeParameterInfo = null
|
||||
}
|
||||
|
||||
fun replaceUsage() {
|
||||
val aliasInstanceText = if (typeParameters.isNotEmpty()) {
|
||||
"$name<${typeParameters.joinToString { it.typeReferenceInfos.first().reference.text }}>"
|
||||
}
|
||||
else {
|
||||
name
|
||||
}
|
||||
originalType.replace(psiFactory.createType(aliasInstanceText).typeElement!!)
|
||||
}
|
||||
|
||||
fun introduceTypeParameters() {
|
||||
typeAlias.getTypeReference()!!.forEachDescendantOfType<KtTypeReference> {
|
||||
val typeParameter = it.typeParameterInfo ?: return@forEachDescendantOfType
|
||||
val typeParameterReference = psiFactory.createType(typeParameter.name)
|
||||
it.replace(typeParameterReference)
|
||||
}
|
||||
}
|
||||
|
||||
fun insertDeclaration(): KtTypeAlias {
|
||||
val targetParent = originalData.targetSibling.parent
|
||||
|
||||
val anchorCandidates = SmartList<PsiElement>()
|
||||
anchorCandidates.add(targetSibling)
|
||||
if (targetSibling is KtEnumEntry) {
|
||||
anchorCandidates.add(targetSibling.siblings().last { it is KtEnumEntry })
|
||||
}
|
||||
|
||||
val anchor = anchorCandidates.minBy { it.startOffset }!!.parentsWithSelf.first { it.parent == targetParent }
|
||||
val targetContainer = anchor.parent!!
|
||||
return (targetContainer.addBefore(typeAlias, anchor) as KtTypeAlias).apply {
|
||||
targetContainer.addBefore(psiFactory.createWhiteSpace("\n\n"), anchor)
|
||||
}
|
||||
}
|
||||
|
||||
return if (previewOnly) {
|
||||
introduceTypeParameters()
|
||||
typeAlias
|
||||
}
|
||||
else {
|
||||
replaceUsage()
|
||||
introduceTypeParameters()
|
||||
insertDeclaration()
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.ui
|
||||
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.TypeParameter
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.ui.AbstractParameterTablePanel
|
||||
import java.util.*
|
||||
|
||||
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(it) }
|
||||
super.init()
|
||||
}
|
||||
|
||||
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.refactoring.introduce.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="522" 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="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="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 value="&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 value="&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>
|
||||
+256
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.ui;
|
||||
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
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.core.KotlinNameSuggester;
|
||||
import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtilKt;
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.IntroduceTypeAliasDescriptor;
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.IntroduceTypeAliasImplKt;
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.KotlinIntroduceTypeAliasHandler;
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.TypeParameter;
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.ui.KotlinSignatureComponent;
|
||||
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken;
|
||||
import org.jetbrains.kotlin.lexer.KtTokens;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.event.ItemListener;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
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<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.REFACTORING_NAME);
|
||||
init();
|
||||
update();
|
||||
}
|
||||
|
||||
private void createUIComponents() {
|
||||
this.signaturePreviewField = new KotlinSignatureComponent("", project);
|
||||
}
|
||||
|
||||
private boolean isVisibilitySectionAvailable() {
|
||||
return !getApplicableVisibilities().isEmpty();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<KtModifierKeywordToken> getApplicableVisibilities() {
|
||||
return IntroduceTypeAliasImplKt.getApplicableVisibilities(originalDescriptor.getOriginalData());
|
||||
}
|
||||
|
||||
private String getAliasName() {
|
||||
return aliasNameField.getEnteredName();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private KtModifierKeywordToken getVisibility() {
|
||||
if (!isVisibilitySectionAvailable()) return null;
|
||||
return (KtModifierKeywordToken) visibilityBox.getSelectedItem();
|
||||
}
|
||||
|
||||
private boolean checkNames() {
|
||||
if (!KotlinNameSuggester.INSTANCE.isIdentifier(getAliasName())) return false;
|
||||
if (parameterTablePanel != null) {
|
||||
for (IntroduceTypeAliasParameterTablePanel.TypeParameterInfo parameterInfo : parameterTablePanel.getSelectedTypeParameterInfos()) {
|
||||
if (!KotlinNameSuggester.INSTANCE.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();
|
||||
|
||||
//noinspection unchecked
|
||||
visibilityBox.setModel(new DefaultComboBoxModel(getApplicableVisibilities().toArray()));
|
||||
//noinspection unchecked
|
||||
visibilityBox.setRenderer(
|
||||
new DefaultListCellRenderer() {
|
||||
@Override
|
||||
public Component getListCellRendererComponent(
|
||||
JList list,
|
||||
Object value,
|
||||
int index,
|
||||
boolean isSelected,
|
||||
boolean cellHasFocus
|
||||
) {
|
||||
String tokenValue = ((KtModifierKeywordToken) value).getValue();
|
||||
return super.getListCellRendererComponent(list, tokenValue, index, isSelected, cellHasFocus);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
aliasNameField = new NameSuggestionsField(ArrayUtil.EMPTY_STRING_ARRAY, project, KotlinFileType.INSTANCE);
|
||||
aliasNameField.addDataChangedListener(
|
||||
new NameSuggestionsField.DataChanged() {
|
||||
@Override
|
||||
public void dataChanged() {
|
||||
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("Type &Parameters");
|
||||
inputParametersPanel.setLabelFor(parameterTablePanel.getTable());
|
||||
inputParametersPanel.add(parameterTablePanel);
|
||||
}
|
||||
else {
|
||||
inputParametersPanel.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("SuspiciousMethodCalls")
|
||||
@Override
|
||||
protected void doOKAction() {
|
||||
MultiMap<PsiElement, String> conflicts = IntroduceTypeAliasImplKt.validate(currentDescriptor).getConflicts();
|
||||
KotlinRefactoringUtilKt.checkConflictsInteractively(
|
||||
project,
|
||||
conflicts,
|
||||
new Function0<Unit>() {
|
||||
@Override
|
||||
public Unit invoke() {
|
||||
close(OK_EXIT_CODE);
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
},
|
||||
new Function0<Unit>() {
|
||||
@Override
|
||||
public Unit invoke() {
|
||||
KotlinIntroduceTypeAliasDialog.super.doOKAction();
|
||||
return onAccept.invoke(KotlinIntroduceTypeAliasDialog.this);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getPreferredFocusedComponent() {
|
||||
return aliasNameField;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JComponent createCenterPanel() {
|
||||
return contentPane;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected JComponent createContentPane() {
|
||||
return contentPane;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private IntroduceTypeAliasDescriptor createDescriptor() {
|
||||
return originalDescriptor.copy(
|
||||
originalDescriptor.getOriginalData(),
|
||||
getAliasName(),
|
||||
getVisibility(),
|
||||
parameterTablePanel != null ? parameterTablePanel.getSelectedTypeParameters() : Collections.<TypeParameter>emptyList()
|
||||
);
|
||||
}
|
||||
|
||||
public IntroduceTypeAliasDescriptor getCurrentDescriptor() {
|
||||
return currentDescriptor;
|
||||
}
|
||||
}
|
||||
@@ -111,7 +111,11 @@ fun selectElementsWithTargetParent(
|
||||
|
||||
val elements = CodeInsightUtils.findElements(file, startOffset, endOffset, elementKind)
|
||||
if (elements.isEmpty()) {
|
||||
showErrorHintByKey("cannot.refactor.no.expression")
|
||||
val messageKey = when (elementKind) {
|
||||
CodeInsightUtils.ElementKind.EXPRESSION -> "cannot.refactor.no.expression"
|
||||
CodeInsightUtils.ElementKind.TYPE_ELEMENT -> "cannot.refactor.no.type"
|
||||
}
|
||||
showErrorHintByKey(messageKey)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -212,4 +216,4 @@ fun KtExpression.mustBeParenthesizedInInitializerPosition(): Boolean {
|
||||
|
||||
if (left?.mustBeParenthesizedInInitializerPosition() ?: false) return true
|
||||
return PsiChildRange(left, operationReference).any { (it is PsiWhiteSpace) && it.textContains('\n') }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
// NAME:
|
||||
class A<X, Y>
|
||||
|
||||
// SIBLING:
|
||||
fun foo() {
|
||||
val a: <caret>A<Int, String>
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
No name provided for type alias
|
||||
@@ -0,0 +1,8 @@
|
||||
// NAME: C
|
||||
|
||||
// SIBLING:
|
||||
fun foo() {
|
||||
class A<X, Y>
|
||||
|
||||
val a: <caret>A<Int, String>
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Type alias cannot refer to types which aren't accessible in the scope where it's defined
|
||||
@@ -0,0 +1,9 @@
|
||||
// NAME: C
|
||||
class B<X>
|
||||
|
||||
// SIBLING:
|
||||
fun foo() {
|
||||
class A<X>
|
||||
|
||||
val a: <caret>(B<A<Int>>, A<Int>, A<String>) -> A<Int>
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// NAME: C
|
||||
class B<X>
|
||||
|
||||
typealias C<T, U> = (B<T>, T, U) -> T
|
||||
|
||||
// SIBLING:
|
||||
fun foo() {
|
||||
class A<X>
|
||||
|
||||
val a: C<A<Int>, A<String>>
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// NAME: B
|
||||
class A<X, Y>
|
||||
|
||||
class B
|
||||
|
||||
// SIBLING:
|
||||
fun foo() {
|
||||
val a: <caret>A<Int, String>
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Type B already exists in the target scope
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// NAME: S
|
||||
// SIBLING:
|
||||
fun foo() {
|
||||
val t1: <caret>(Int) -> Boolean
|
||||
val t2: (Int) -> Boolean
|
||||
val t3: Int.() -> Boolean
|
||||
val t4: (Int, Int) -> Boolean
|
||||
val t5: (Boolean) -> Int
|
||||
val t6: Function1<Int, Boolean>
|
||||
}
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
typealias S = (Int) -> Boolean
|
||||
|
||||
// NAME: S
|
||||
// SIBLING:
|
||||
fun foo() {
|
||||
val t1: S
|
||||
val t2: S
|
||||
val t3: Int.() -> Boolean
|
||||
val t4: (Int, Int) -> Boolean
|
||||
val t5: (Boolean) -> Int
|
||||
val t6: S
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// NAME: S
|
||||
|
||||
class A<X>
|
||||
|
||||
// SIBLING:
|
||||
fun foo() {
|
||||
class B<T>
|
||||
|
||||
val t1: <caret>(B<Int>) -> ((B<Int>) -> Boolean)
|
||||
val t2: (B<Int>) -> ((B<Int>) -> Boolean)
|
||||
val t3: ((B<Int>) -> B<Int>) -> Boolean
|
||||
val t4: Function1<B<Int>, Function1<B<Int>, Boolean>>
|
||||
val t5: (String) -> ((String) -> Boolean)
|
||||
val t6: (Int) -> ((String) -> Boolean)
|
||||
val t7: (A<B<String>>) -> ((A<B<String>>) -> Boolean)
|
||||
}
|
||||
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
// NAME: S
|
||||
|
||||
class A<X>
|
||||
|
||||
typealias S<T> = (T) -> ((T) -> Boolean)
|
||||
|
||||
// SIBLING:
|
||||
fun foo() {
|
||||
class B<T>
|
||||
|
||||
val t1: S<B<Int>>
|
||||
val t2: S<B<Int>>
|
||||
val t3: ((B<Int>) -> B<Int>) -> Boolean
|
||||
val t4: S<B<Int>>
|
||||
val t5: S<String>
|
||||
val t6: (Int) -> ((String) -> Boolean)
|
||||
val t7: S<A<B<String>>>
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// NAME: F
|
||||
class A<X, Y>
|
||||
|
||||
// SIBLING:
|
||||
fun foo() {
|
||||
val a: A<<caret>(Int) -> String, String>
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// NAME: F
|
||||
class A<X, Y>
|
||||
|
||||
typealias F = (Int) -> String
|
||||
|
||||
// SIBLING:
|
||||
fun foo() {
|
||||
val a: A<F, String>
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// NAME: B
|
||||
class A<X, Y>
|
||||
|
||||
fun foo() {
|
||||
// SIBLING:
|
||||
val a: <caret>A<Int, String>
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// NAME: B
|
||||
class A<X, Y>
|
||||
|
||||
fun foo() {
|
||||
// SIBLING:
|
||||
typealias B = A<Int, String>
|
||||
|
||||
val a: B
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// NAME: C
|
||||
class A<X, Y>
|
||||
|
||||
// SIBLING:
|
||||
fun foo() {
|
||||
class B<X, Y>
|
||||
|
||||
val a: <caret>A<B<Int, String>, B<String, Int>>
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// NAME: C
|
||||
class A<X, Y>
|
||||
|
||||
typealias C<T, U> = A<T, U>
|
||||
|
||||
// SIBLING:
|
||||
fun foo() {
|
||||
class B<X, Y>
|
||||
|
||||
val a: C<B<Int, String>, B<String, Int>>
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// NAME: B
|
||||
// VISIBILITY: private
|
||||
class A<X, Y>
|
||||
|
||||
fun foo() {
|
||||
// SIBLING:
|
||||
val a: <caret>A<Int, String>
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
'private' is not allowed in the target context
|
||||
@@ -0,0 +1,7 @@
|
||||
// NAME: B
|
||||
class A<X, Y>
|
||||
|
||||
class Test {
|
||||
// SIBLING
|
||||
val a: <caret>A<Int, String>
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// NAME: B
|
||||
class A<X, Y>
|
||||
|
||||
class Test {
|
||||
typealias B = A<Int, String>
|
||||
|
||||
// SIBLING
|
||||
val a: B
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// NAME: C
|
||||
class A<X, Y>
|
||||
|
||||
// SIBLING:
|
||||
fun foo() {
|
||||
class B<X, Y>
|
||||
|
||||
val a: <caret>A<B<B<Int, String>, String>, B<String, B<Int, String>>>
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// NAME: C
|
||||
class A<X, Y>
|
||||
|
||||
typealias C<T, U> = A<T, U>
|
||||
|
||||
// SIBLING:
|
||||
fun foo() {
|
||||
class B<X, Y>
|
||||
|
||||
val a: C<B<B<Int, String>, String>, B<String, B<Int, String>>>
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// NAME: A
|
||||
fun foo() {
|
||||
val x = <caret>1 + 2
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Cannot perform refactoring without a type
|
||||
@@ -0,0 +1,7 @@
|
||||
// NAME: 12
|
||||
class A<X, Y>
|
||||
|
||||
// SIBLING:
|
||||
fun foo() {
|
||||
val a: <caret>A<Int, String>
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Type alias name must be a valid identifier: 12
|
||||
@@ -0,0 +1,7 @@
|
||||
// NAME: AN
|
||||
class A<X, Y>
|
||||
|
||||
// SIBLING:
|
||||
fun foo() {
|
||||
val a: A<Int, String>?<caret>
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// NAME: AN
|
||||
class A<X, Y>
|
||||
|
||||
typealias AN = A<Int, String>?
|
||||
|
||||
// SIBLING:
|
||||
fun foo() {
|
||||
val a: AN
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// NAME: B
|
||||
// VISIBILITY: private
|
||||
class A<X, Y>
|
||||
|
||||
// SIBLING:
|
||||
fun foo() {
|
||||
val a: <caret>A<Int, String>
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// NAME: B
|
||||
// VISIBILITY: private
|
||||
class A<X, Y>
|
||||
|
||||
private typealias B = A<Int, String>
|
||||
|
||||
// SIBLING:
|
||||
fun foo() {
|
||||
val a: B
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// NAME: B
|
||||
// VISIBILITY: protected
|
||||
class A<X, Y>
|
||||
|
||||
// SIBLING:
|
||||
fun foo() {
|
||||
val a: <caret>A<Int, String>
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
'protected' is not allowed in the target context
|
||||
@@ -0,0 +1,7 @@
|
||||
// NAME: B
|
||||
class A<X, Y>
|
||||
|
||||
// SIBLING:
|
||||
fun foo() {
|
||||
val a: <caret>A<Int, String>
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// NAME: B
|
||||
class A<X, Y>
|
||||
|
||||
typealias B = A<Int, String>
|
||||
|
||||
// SIBLING:
|
||||
fun foo() {
|
||||
val a: B
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// NAME: F
|
||||
class A<X, Y>
|
||||
|
||||
// SIBLING:
|
||||
fun foo() {
|
||||
class B<X>
|
||||
|
||||
val a: A<<caret>(B<Int>) -> B<String>, String>
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// NAME: F
|
||||
class A<X, Y>
|
||||
|
||||
typealias F<T, U> = (T) -> U
|
||||
|
||||
// SIBLING:
|
||||
fun foo() {
|
||||
class B<X>
|
||||
|
||||
val a: A<F<B<Int>, B<String>>, String>
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// NAME: S
|
||||
class A<X, Y>
|
||||
|
||||
// SIBLING:
|
||||
fun foo() {
|
||||
val t1: <caret>A<Int, Boolean>
|
||||
val t2: A<Int, Boolean>
|
||||
val t3: A<Boolean, Int>
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// NAME: S
|
||||
class A<X, Y>
|
||||
|
||||
typealias S = A<Int, Boolean>
|
||||
|
||||
// SIBLING:
|
||||
fun foo() {
|
||||
val t1: S
|
||||
val t2: S
|
||||
val t3: A<Boolean, Int>
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// NAME: S
|
||||
class A<X, Y>
|
||||
|
||||
// SIBLING:
|
||||
fun foo() {
|
||||
class B<T>
|
||||
|
||||
val t1: <caret>A<B<Int>, A<B<Int>, Boolean>>
|
||||
val t2: A<B<Int>, A<B<Int>, Boolean>>
|
||||
val t3: A<String, A<String, Boolean>>
|
||||
val t4: A<Int, A<String, Boolean>>
|
||||
val t5: A<(Int) -> Int, A<() -> Int, Boolean>>
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// NAME: S
|
||||
class A<X, Y>
|
||||
|
||||
typealias S<T> = A<T, A<T, Boolean>>
|
||||
|
||||
// SIBLING:
|
||||
fun foo() {
|
||||
class B<T>
|
||||
|
||||
val t1: S<B<Int>>
|
||||
val t2: S<B<Int>>
|
||||
val t3: S<String>
|
||||
val t4: A<Int, A<String, Boolean>>
|
||||
val t5: A<(Int) -> Int, A<() -> Int, Boolean>>
|
||||
}
|
||||
@@ -6,8 +6,4 @@ kotlin.Function1<kotlin.Int, kotlin.String>
|
||||
|
||||
(n: Int) -> kotlin.String
|
||||
|
||||
Int.() -> String
|
||||
|
||||
(m: Int) -> kotlin.String
|
||||
|
||||
@ExtensionFunctionType Function1<Int, String>
|
||||
(m: Int) -> kotlin.String
|
||||
@@ -6,8 +6,4 @@ kotlin.Function2<kotlin.Any, kotlin.Int, kotlin.String>
|
||||
|
||||
(a: Any, n: Int) -> kotlin.String
|
||||
|
||||
Any.(Int) -> String
|
||||
|
||||
(t: Any, u: Int) -> kotlin.String
|
||||
|
||||
@ExtensionFunctionType Function2<Any, Int, String>
|
||||
(t: Any, u: Int) -> kotlin.String
|
||||
@@ -44,14 +44,17 @@ import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.*
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceParameter.*
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceProperty.INTRODUCE_PROPERTY
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceProperty.KotlinIntroducePropertyHandler
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.KotlinIntroduceTypeAliasHandler
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.KotlinIntroduceVariableHandler
|
||||
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
|
||||
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
|
||||
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtNamedDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
@@ -309,6 +312,25 @@ abstract class AbstractExtractionTest() : KotlinLightCodeInsightFixtureTestCase(
|
||||
}
|
||||
}
|
||||
|
||||
protected fun doIntroduceTypeAliasTest(path: String) {
|
||||
doTest(path) { file ->
|
||||
file as KtFile
|
||||
|
||||
val explicitPreviousSibling = file.findElementByCommentPrefix("// SIBLING:")
|
||||
val fileText = file.text
|
||||
val aliasName = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// NAME:")!!
|
||||
val aliasVisibility = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// VISIBILITY:")?.let {
|
||||
KtPsiFactory(project).createModifierList(it).firstChild.node.elementType as KtModifierKeywordToken
|
||||
}
|
||||
val editor = fixture.editor
|
||||
KotlinIntroduceTypeAliasHandler.selectElements(editor, file) { elements, previousSibling ->
|
||||
KotlinIntroduceTypeAliasHandler.doInvoke(project, editor, elements, explicitPreviousSibling ?: previousSibling) {
|
||||
it.copy(name = aliasName, visibility = aliasVisibility ?: it.visibility)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun doTest(path: String, checkAdditionalAfterdata: Boolean = false, action: (PsiFile) -> Unit) {
|
||||
val mainFile = File(path)
|
||||
val afterFile = File("$path.after")
|
||||
|
||||
+135
@@ -3961,4 +3961,139 @@ public class ExtractionTestGenerated extends AbstractExtractionTest {
|
||||
doIntroduceJavaParameterTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/refactoring/introduceTypeAlias")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class IntroduceTypeAlias extends AbstractExtractionTest {
|
||||
public void testAllFilesPresentInIntroduceTypeAlias() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/refactoring/introduceTypeAlias"), Pattern.compile("^(.+)\\.(kt|kts)$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("emptyName.kt")
|
||||
public void testEmptyName() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/emptyName.kt");
|
||||
doIntroduceTypeAliasTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("entireTypeExtracted.kt")
|
||||
public void testEntireTypeExtracted() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/entireTypeExtracted.kt");
|
||||
doIntroduceTypeAliasTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("equivalentNestedTypeElements.kt")
|
||||
public void testEquivalentNestedTypeElements() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/equivalentNestedTypeElements.kt");
|
||||
doIntroduceTypeAliasTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("existingTypeClash.kt")
|
||||
public void testExistingTypeClash() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/existingTypeClash.kt");
|
||||
doIntroduceTypeAliasTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("functionTypeDuplicatesNoTypeParameters.kt")
|
||||
public void testFunctionTypeDuplicatesNoTypeParameters() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/functionTypeDuplicatesNoTypeParameters.kt");
|
||||
doIntroduceTypeAliasTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("functionTypeDuplicatesWithTypeParameters.kt")
|
||||
public void testFunctionTypeDuplicatesWithTypeParameters() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/functionTypeDuplicatesWithTypeParameters.kt");
|
||||
doIntroduceTypeAliasTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("functionalType.kt")
|
||||
public void testFunctionalType() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/functionalType.kt");
|
||||
doIntroduceTypeAliasTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("localTypeAlias.kt")
|
||||
public void testLocalTypeAlias() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/localTypeAlias.kt");
|
||||
doIntroduceTypeAliasTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("localTypeExtracted.kt")
|
||||
public void testLocalTypeExtracted() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/localTypeExtracted.kt");
|
||||
doIntroduceTypeAliasTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("localWithVisibility.kt")
|
||||
public void testLocalWithVisibility() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/localWithVisibility.kt");
|
||||
doIntroduceTypeAliasTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("memberTypeAlias.kt")
|
||||
public void testMemberTypeAlias() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/memberTypeAlias.kt");
|
||||
doIntroduceTypeAliasTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("nestedTypesExtracted.kt")
|
||||
public void testNestedTypesExtracted() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/nestedTypesExtracted.kt");
|
||||
doIntroduceTypeAliasTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("noTypeElement.kt")
|
||||
public void testNoTypeElement() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/noTypeElement.kt");
|
||||
doIntroduceTypeAliasTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("nonIdentifierName.kt")
|
||||
public void testNonIdentifierName() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/nonIdentifierName.kt");
|
||||
doIntroduceTypeAliasTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("nullableType.kt")
|
||||
public void testNullableType() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/nullableType.kt");
|
||||
doIntroduceTypeAliasTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("privateTypeAlias.kt")
|
||||
public void testPrivateTypeAlias() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/privateTypeAlias.kt");
|
||||
doIntroduceTypeAliasTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("protectedInFile.kt")
|
||||
public void testProtectedInFile() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/protectedInFile.kt");
|
||||
doIntroduceTypeAliasTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("topLevelTypeAlias.kt")
|
||||
public void testTopLevelTypeAlias() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/topLevelTypeAlias.kt");
|
||||
doIntroduceTypeAliasTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("typesExtractedWithFunctionalType.kt")
|
||||
public void testTypesExtractedWithFunctionalType() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/typesExtractedWithFunctionalType.kt");
|
||||
doIntroduceTypeAliasTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("userTypeDuplicatesNoTypeParameters.kt")
|
||||
public void testUserTypeDuplicatesNoTypeParameters() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/userTypeDuplicatesNoTypeParameters.kt");
|
||||
doIntroduceTypeAliasTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("userTypeDuplicatesWithTypeParameters.kt")
|
||||
public void testUserTypeDuplicatesWithTypeParameters() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeAlias/userTypeDuplicatesWithTypeParameters.kt");
|
||||
doIntroduceTypeAliasTest(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user