Introduce Type Alias
#KT-12902 Fixed
This commit is contained in:
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
###### New features
|
###### New features
|
||||||
- [`KT-12903`](https://youtrack.jetbrains.com/issue/KT-12903) Implement "Inline type alias" refactoring
|
- [`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)
|
## 1.1-M01 (EAP-1)
|
||||||
|
|
||||||
|
|||||||
@@ -98,11 +98,19 @@ class KtPsiFactory(private val project: Project) {
|
|||||||
return typeReference
|
return typeReference
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createType(typeElement: KtTypeElement) = createType("X").apply { this.typeElement!!.replace(typeElement) }
|
||||||
|
|
||||||
fun createTypeIfPossible(type: String): KtTypeReference? {
|
fun createTypeIfPossible(type: String): KtTypeReference? {
|
||||||
val typeReference = createProperty("val x : $type").typeReference
|
val typeReference = createProperty("val x : $type").typeReference
|
||||||
return if (typeReference?.text == type) typeReference else null
|
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 {
|
fun createStar(): PsiElement {
|
||||||
return createType("List<*>").findElementAt(5)!!
|
return createType("List<*>").findElementAt(5)!!
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,4 +41,9 @@ public class KtTypeArgumentList extends KtElementImplStub<KotlinPlaceHolderStub<
|
|||||||
public List<KtTypeProjection> getArguments() {
|
public List<KtTypeProjection> getArguments() {
|
||||||
return getStubOrPsiChildrenAsList(KtStubElementTypes.TYPE_PROJECTION);
|
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()
|
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.nextSiblingOfSameType() = PsiTreeUtil.getNextSiblingOfType(this, T::class.java)
|
||||||
|
|
||||||
inline fun <reified T : PsiElement> T.prevSiblingOfSameType() = PsiTreeUtil.getPrevSiblingOfType(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/introduceParameter", pattern = kotlinFileOrScript, testMethod = "doIntroduceSimpleParameterTest")
|
||||||
model("refactoring/introduceLambdaParameter", pattern = kotlinFileOrScript, testMethod = "doIntroduceLambdaParameterTest")
|
model("refactoring/introduceLambdaParameter", pattern = kotlinFileOrScript, testMethod = "doIntroduceLambdaParameterTest")
|
||||||
model("refactoring/introduceJavaParameter", extension = "java", testMethod = "doIntroduceJavaParameterTest")
|
model("refactoring/introduceJavaParameter", extension = "java", testMethod = "doIntroduceJavaParameterTest")
|
||||||
|
model("refactoring/introduceTypeAlias", pattern = kotlinFileOrScript, testMethod = "doIntroduceTypeAliasTest")
|
||||||
}
|
}
|
||||||
|
|
||||||
testClass<AbstractPullUpTest>() {
|
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.PsiElement
|
||||||
import com.intellij.psi.impl.source.tree.LeafPsiElement
|
import com.intellij.psi.impl.source.tree.LeafPsiElement
|
||||||
import com.intellij.util.containers.MultiMap
|
import com.intellij.util.containers.MultiMap
|
||||||
|
import org.jetbrains.kotlin.builtins.isExtensionFunctionType
|
||||||
import org.jetbrains.kotlin.descriptors.*
|
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.refactoring.introduce.extractableSubstringInfo
|
import org.jetbrains.kotlin.idea.refactoring.introduce.extractableSubstringInfo
|
||||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
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.KtToken
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||||
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
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.ImplicitReceiver
|
||||||
import org.jetbrains.kotlin.resolve.scopes.receivers.Receiver
|
import org.jetbrains.kotlin.resolve.scopes.receivers.Receiver
|
||||||
import org.jetbrains.kotlin.resolve.source.getPsi
|
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||||
import org.jetbrains.kotlin.types.ErrorUtils
|
import org.jetbrains.kotlin.types.*
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
|
||||||
import org.jetbrains.kotlin.types.TypeUtils
|
|
||||||
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
|
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
|
||||||
import org.jetbrains.kotlin.types.expressions.OperatorConventions
|
import org.jetbrains.kotlin.types.expressions.OperatorConventions
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -71,18 +71,18 @@ interface UnificationResult {
|
|||||||
|
|
||||||
interface Matched: UnificationResult {
|
interface Matched: UnificationResult {
|
||||||
val range: KotlinPsiRange
|
val range: KotlinPsiRange
|
||||||
val substitution: Map<UnifierParameter, KtExpression>
|
val substitution: Map<UnifierParameter, KtElement>
|
||||||
override val status: Status get() = MATCHED
|
override val status: Status get() = MATCHED
|
||||||
}
|
}
|
||||||
|
|
||||||
class StronglyMatched(
|
class StronglyMatched(
|
||||||
override val range: KotlinPsiRange,
|
override val range: KotlinPsiRange,
|
||||||
override val substitution: Map<UnifierParameter, KtExpression>
|
override val substitution: Map<UnifierParameter, KtElement>
|
||||||
): Matched
|
): Matched
|
||||||
|
|
||||||
class WeaklyMatched(
|
class WeaklyMatched(
|
||||||
override val range: KotlinPsiRange,
|
override val range: KotlinPsiRange,
|
||||||
override val substitution: Map<UnifierParameter, KtExpression>,
|
override val substitution: Map<UnifierParameter, KtElement>,
|
||||||
val weakMatches: Map<KtElement, KtElement>
|
val weakMatches: Map<KtElement, KtElement>
|
||||||
): Matched
|
): Matched
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ interface UnificationResult {
|
|||||||
|
|
||||||
class UnifierParameter(
|
class UnifierParameter(
|
||||||
val descriptor: DeclarationDescriptor,
|
val descriptor: DeclarationDescriptor,
|
||||||
val expectedType: KotlinType
|
val expectedType: KotlinType?
|
||||||
)
|
)
|
||||||
|
|
||||||
class KotlinPsiUnifier(
|
class KotlinPsiUnifier(
|
||||||
@@ -109,7 +109,7 @@ class KotlinPsiUnifier(
|
|||||||
) {
|
) {
|
||||||
val patternContext: BindingContext = originalPattern.getBindingContext()
|
val patternContext: BindingContext = originalPattern.getBindingContext()
|
||||||
val targetContext: BindingContext = originalTarget.getBindingContext()
|
val targetContext: BindingContext = originalTarget.getBindingContext()
|
||||||
val substitution = HashMap<UnifierParameter, KtExpression>()
|
val substitution = HashMap<UnifierParameter, KtElement>()
|
||||||
val declarationPatternsToTargets = MultiMap<DeclarationDescriptor, DeclarationDescriptor>()
|
val declarationPatternsToTargets = MultiMap<DeclarationDescriptor, DeclarationDescriptor>()
|
||||||
val weakMatches = HashMap<KtElement, KtElement>()
|
val weakMatches = HashMap<KtElement, KtElement>()
|
||||||
var checkEquivalence: Boolean = false
|
var checkEquivalence: Boolean = false
|
||||||
@@ -118,7 +118,7 @@ class KotlinPsiUnifier(
|
|||||||
private fun KotlinPsiRange.getBindingContext(): BindingContext {
|
private fun KotlinPsiRange.getBindingContext(): BindingContext {
|
||||||
val element = (this as? KotlinPsiRange.ListRange)?.startElement as? KtElement
|
val element = (this as? KotlinPsiRange.ListRange)?.startElement as? KtElement
|
||||||
if ((element?.containingFile as? KtFile)?.doNotAnalyze != null) return BindingContext.EMPTY
|
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 {
|
private fun matchDescriptors(d1: DeclarationDescriptor?, d2: DeclarationDescriptor?): Boolean {
|
||||||
@@ -322,13 +322,20 @@ class KotlinPsiUnifier(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun KtTypeReference.getType(): KotlinType? {
|
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
|
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 != null && type2 != null) {
|
||||||
if (type1.isError || type2.isError) return 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 (TypeUtils.equalTypes(type1, type2)) return MATCHED
|
||||||
|
|
||||||
if (type1.isMarkedNullable != type2.isMarkedNullable) return UNMATCHED
|
if (type1.isMarkedNullable != type2.isMarkedNullable) return UNMATCHED
|
||||||
@@ -339,9 +346,11 @@ class KotlinPsiUnifier(
|
|||||||
val args1 = type1.arguments
|
val args1 = type1.arguments
|
||||||
val args2 = type2.arguments
|
val args2 = type2.arguments
|
||||||
if (args1.size != args2.size) return UNMATCHED
|
if (args1.size != args2.size) return UNMATCHED
|
||||||
if (!args1.zip(args2).all {
|
if (!args1.withIndex().all { p ->
|
||||||
it.first.projectionKind == it.second.projectionKind && matchTypes(it.first.type, it.second.type) == MATCHED }
|
val (i, arg1) = p
|
||||||
) return UNMATCHED
|
val arg2 = args2[i]
|
||||||
|
matchTypeArguments(i, arg1, arg2, typeRef1, typeRef2)
|
||||||
|
}) return UNMATCHED
|
||||||
|
|
||||||
return MATCHED
|
return MATCHED
|
||||||
}
|
}
|
||||||
@@ -349,6 +358,32 @@ class KotlinPsiUnifier(
|
|||||||
return if (type1 == null && type2 == null) null else UNMATCHED
|
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 {
|
private fun matchTypes(types1: Collection<KotlinType>, types2: Collection<KotlinType>): Boolean {
|
||||||
fun sortTypes(types: Collection<KotlinType>) = types.sortedBy { DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(it) }
|
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 KtBinaryExpression -> operationReference.getReferencedNameElementType() == KtTokens.ELVIS
|
||||||
is KtThisExpression -> true
|
is KtThisExpression -> true
|
||||||
|
is KtSimpleNameExpression -> getStrictParentOfType<KtTypeElement>() != null
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -641,7 +677,14 @@ class KotlinPsiUnifier(
|
|||||||
desc2: DeclarationDescriptor?): Status? {
|
desc2: DeclarationDescriptor?): Status? {
|
||||||
if (decl1.javaClass != decl2.javaClass) return UNMATCHED
|
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
|
if (desc1.javaClass != desc2.javaClass) return UNMATCHED
|
||||||
|
|
||||||
declarationPatternsToTargets.putValue(desc1, desc2)
|
declarationPatternsToTargets.putValue(desc1, desc2)
|
||||||
@@ -682,8 +725,11 @@ class KotlinPsiUnifier(
|
|||||||
e2 is KtDeclaration ->
|
e2 is KtDeclaration ->
|
||||||
e2.matchDeclarations(e1)
|
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 ->
|
e1 is KtTypeReference && e2 is KtTypeReference ->
|
||||||
matchTypes(e1.getType(), e2.getType())
|
matchTypes(e1.getType(), e2.getType(), e1, e2)
|
||||||
|
|
||||||
KtPsiUtil.isAssignment(e1) ->
|
KtPsiUtil.isAssignment(e1) ->
|
||||||
(e1 as KtBinaryExpression).matchAssignment(e2)
|
(e1 as KtBinaryExpression).matchAssignment(e2)
|
||||||
@@ -708,8 +754,9 @@ class KotlinPsiUnifier(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun PsiElement.checkType(parameter: UnifierParameter): Boolean {
|
private fun PsiElement.checkType(parameter: UnifierParameter): Boolean {
|
||||||
|
val expectedType = parameter.expectedType ?: return true
|
||||||
val targetElementType = (this as? KtExpression)?.let { it.bindingContext.getType(it) }
|
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 {
|
private fun doUnifyStringTemplateFragments(target: KtStringTemplateExpression, pattern: ExtractableSubstringInfo): Status {
|
||||||
@@ -820,6 +867,23 @@ class KotlinPsiUnifier(
|
|||||||
return status
|
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(
|
fun doUnify(
|
||||||
targetElement: PsiElement?,
|
targetElement: PsiElement?,
|
||||||
patternElement: PsiElement?
|
patternElement: PsiElement?
|
||||||
@@ -831,29 +895,26 @@ class KotlinPsiUnifier(
|
|||||||
if (targetElementUnwrapped == null || patternElementUnwrapped == null) return UNMATCHED
|
if (targetElementUnwrapped == null || patternElementUnwrapped == null) return UNMATCHED
|
||||||
|
|
||||||
if (!checkEquivalence && targetElementUnwrapped !is KtBlockExpression) {
|
if (!checkEquivalence && targetElementUnwrapped !is KtBlockExpression) {
|
||||||
val referencedPatternDescriptor = (patternElementUnwrapped as? KtReferenceExpression)?.let {
|
val referencedPatternDescriptor = when (patternElementUnwrapped) {
|
||||||
it.bindingContext[BindingContext.REFERENCE_TARGET, it]
|
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 referencedPatternDeclaration = (referencedPatternDescriptor as? DeclarationDescriptorWithSource)?.source?.getPsi()
|
||||||
val parameter = descriptorToParameter[referencedPatternDeclaration]
|
val parameter = descriptorToParameter[referencedPatternDeclaration]
|
||||||
if (referencedPatternDeclaration != null && parameter != null) {
|
if (referencedPatternDeclaration != null && parameter != null) {
|
||||||
if (targetElementUnwrapped !is KtExpression) return UNMATCHED
|
if (targetElementUnwrapped is KtExpression) {
|
||||||
if (!targetElementUnwrapped.checkType(parameter)) 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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"/>
|
<add-to-group group-id="IntroduceActionsGroup" anchor="after" relative-to-action="ExtractFunction"/>
|
||||||
</action>
|
</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-->
|
<!-- Kotlin Console REPL-->
|
||||||
<action id="KotlinConsoleREPL" class="org.jetbrains.kotlin.console.actions.RunKotlinConsoleAction"
|
<action id="KotlinConsoleREPL" class="org.jetbrains.kotlin.console.actions.RunKotlinConsoleAction"
|
||||||
text="Kotlin REPL"
|
text="Kotlin REPL"
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ introduce.property=Introduce Property
|
|||||||
introduce.parameter=Introduce Parameter
|
introduce.parameter=Introduce Parameter
|
||||||
cannot.refactor.no.container=Cannot refactor in this place
|
cannot.refactor.no.container=Cannot refactor in this place
|
||||||
cannot.refactor.no.expression=Cannot perform refactoring without an expression
|
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.syntax.errors=Cannot refactor due to erroneous code
|
||||||
cannot.refactor.expression.has.unit.type=Cannot introduce expression of unit type
|
cannot.refactor.expression.has.unit.type=Cannot introduce expression of unit type
|
||||||
cannot.refactor.package.expression=Cannot introduce package reference
|
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 com.intellij.psi.util.PsiTreeUtil
|
||||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
|
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.caches.resolve.getResolutionFacade
|
||||||
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
|
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
|
||||||
import org.jetbrains.kotlin.idea.codeInsight.KotlinFileReferencesResolver
|
import org.jetbrains.kotlin.idea.codeInsight.KotlinFileReferencesResolver
|
||||||
import org.jetbrains.kotlin.idea.core.compareDescriptors
|
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.extractableSubstringInfo
|
import org.jetbrains.kotlin.idea.refactoring.introduce.extractableSubstringInfo
|
||||||
import org.jetbrains.kotlin.idea.refactoring.introduce.substringContextOrThis
|
import org.jetbrains.kotlin.idea.refactoring.introduce.substringContextOrThis
|
||||||
@@ -114,7 +114,7 @@ data class ExtractionData(
|
|||||||
|
|
||||||
val commonParent = PsiTreeUtil.findCommonParent(physicalElements) as KtElement
|
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 itFakeDeclaration by lazy { KtPsiFactory(originalFile).createParameter("it: Any?") }
|
||||||
private val synthesizedInvokeDeclaration by lazy { KtPsiFactory(originalFile).createFunction("fun invoke() {}") }
|
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)
|
val elements = CodeInsightUtils.findElements(file, startOffset, endOffset, elementKind)
|
||||||
if (elements.isEmpty()) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,4 +216,4 @@ fun KtExpression.mustBeParenthesizedInInitializerPosition(): Boolean {
|
|||||||
|
|
||||||
if (left?.mustBeParenthesizedInInitializerPosition() ?: false) return true
|
if (left?.mustBeParenthesizedInInitializerPosition() ?: false) return true
|
||||||
return PsiChildRange(left, operationReference).any { (it is PsiWhiteSpace) && it.textContains('\n') }
|
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
|
(n: Int) -> kotlin.String
|
||||||
|
|
||||||
Int.() -> String
|
(m: Int) -> kotlin.String
|
||||||
|
|
||||||
(m: Int) -> kotlin.String
|
|
||||||
|
|
||||||
@ExtensionFunctionType Function1<Int, String>
|
|
||||||
@@ -6,8 +6,4 @@ kotlin.Function2<kotlin.Any, kotlin.Int, kotlin.String>
|
|||||||
|
|
||||||
(a: Any, n: Int) -> kotlin.String
|
(a: Any, n: Int) -> kotlin.String
|
||||||
|
|
||||||
Any.(Int) -> String
|
(t: Any, u: Int) -> kotlin.String
|
||||||
|
|
||||||
(t: Any, u: Int) -> kotlin.String
|
|
||||||
|
|
||||||
@ExtensionFunctionType Function2<Any, Int, 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.introduceParameter.*
|
||||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceProperty.INTRODUCE_PROPERTY
|
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.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.refactoring.introduce.introduceVariable.KotlinIntroduceVariableHandler
|
||||||
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
|
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
|
||||||
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
|
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
|
||||||
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
|
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
|
||||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||||
|
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
|
||||||
import org.jetbrains.kotlin.psi.KtExpression
|
import org.jetbrains.kotlin.psi.KtExpression
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import org.jetbrains.kotlin.psi.KtNamedDeclaration
|
import org.jetbrains.kotlin.psi.KtNamedDeclaration
|
||||||
|
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||||
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
||||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
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) {
|
protected fun doTest(path: String, checkAdditionalAfterdata: Boolean = false, action: (PsiFile) -> Unit) {
|
||||||
val mainFile = File(path)
|
val mainFile = File(path)
|
||||||
val afterFile = File("$path.after")
|
val afterFile = File("$path.after")
|
||||||
|
|||||||
+135
@@ -3961,4 +3961,139 @@ public class ExtractionTestGenerated extends AbstractExtractionTest {
|
|||||||
doIntroduceJavaParameterTest(fileName);
|
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