Implemented @native-* annotation migration quickfix for extension functions
As the first part of KT-15270 Quickfix to migrate from @native***
This commit is contained in:
@@ -384,8 +384,8 @@ class QuickFixRegistrar : QuickFixContributor {
|
||||
|
||||
UNRESOLVED_REFERENCE.registerFactory(ReplaceObsoleteLabelSyntaxFix)
|
||||
|
||||
DEPRECATION.registerFactory(DeprecatedSymbolUsageFix, DeprecatedSymbolUsageInWholeProjectFix)
|
||||
DEPRECATION_ERROR.registerFactory(DeprecatedSymbolUsageFix, DeprecatedSymbolUsageInWholeProjectFix)
|
||||
DEPRECATION.registerFactory(DeprecatedSymbolUsageFix, DeprecatedSymbolUsageInWholeProjectFix, MigrateExternalExtensionFix)
|
||||
DEPRECATION_ERROR.registerFactory(DeprecatedSymbolUsageFix, DeprecatedSymbolUsageInWholeProjectFix, MigrateExternalExtensionFix)
|
||||
PROTECTED_CALL_FROM_PUBLIC_INLINE.registerFactory(ReplaceProtectedToPublishedApiCallFix)
|
||||
|
||||
POSITIONED_VALUE_ARGUMENT_FOR_JAVA_ANNOTATION.registerFactory(ReplaceJavaAnnotationPositionedArgumentsFix)
|
||||
|
||||
+94
-21
@@ -19,9 +19,11 @@ package org.jetbrains.kotlin.idea.quickfix.migration
|
||||
import com.intellij.codeInsight.intention.IntentionAction
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.annotations.checkAnnotationName
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.intentions.SpecifyTypeExplicitlyIntention
|
||||
import org.jetbrains.kotlin.idea.project.builtIns
|
||||
@@ -30,6 +32,7 @@ import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction
|
||||
import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory
|
||||
import org.jetbrains.kotlin.idea.util.addAnnotation
|
||||
import org.jetbrains.kotlin.js.PredefinedAnnotation
|
||||
import org.jetbrains.kotlin.js.resolve.diagnostics.ErrorsJs
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
@@ -44,13 +47,24 @@ class MigrateExternalExtensionFix(declaration: KtNamedDeclaration)
|
||||
|
||||
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
|
||||
val declaration = element ?: return
|
||||
if (isExternalMemberDeclaration(declaration)) {
|
||||
fixExtensionMemberDeclaration(declaration, project, editor, file)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
private fun fixExtensionMemberDeclaration(declaration: KtNamedDeclaration, project: Project, editor: Editor?, file: KtFile) {
|
||||
val name = declaration.nameAsSafeName
|
||||
declaration.modifierList?.annotationEntries?.firstOrNull { it.isJsNative() }?.delete()
|
||||
val annotationEntries = declaration.modifierList?.annotationEntries
|
||||
val isGetter = annotationEntries?.any { it.isJsAnnotation(PredefinedAnnotation.NATIVE_GETTER) } ?: false
|
||||
val isSetter = annotationEntries?.any { it.isJsAnnotation(PredefinedAnnotation.NATIVE_SETTER) } ?: false
|
||||
val isInvoke = annotationEntries?.any { it.isJsAnnotation(PredefinedAnnotation.NATIVE_INVOKE) } ?: false
|
||||
annotationEntries?.filter { it.isJsNativeAnnotation() }?.forEach { it.delete() }
|
||||
declaration.addModifier(KtTokens.INLINE_KEYWORD)
|
||||
declaration.removeModifier(KtTokens.EXTERNAL_KEYWORD)
|
||||
if (declaration is KtFunction) {
|
||||
declaration.addAnnotation(KotlinBuiltIns.FQ_NAMES.suppress.toSafe(), "\"NOTHING_TO_INLINE\"")
|
||||
if (!declaration.hasDeclaredReturnType()) {
|
||||
if (!declaration.hasDeclaredReturnType() && !isSetter && !isInvoke) {
|
||||
SpecifyTypeExplicitlyIntention.addTypeAnnotation(editor, declaration, declaration.builtIns.unitType)
|
||||
}
|
||||
}
|
||||
@@ -58,16 +72,42 @@ class MigrateExternalExtensionFix(declaration: KtNamedDeclaration)
|
||||
val ktPsiFactory = KtPsiFactory(project)
|
||||
val body = ktPsiFactory.buildExpression {
|
||||
appendName(Name.identifier("asDynamic"))
|
||||
appendFixedText("().")
|
||||
appendName(name)
|
||||
if (declaration is KtNamedFunction) {
|
||||
appendParameters(declaration)
|
||||
if (isGetter) {
|
||||
appendFixedText("()")
|
||||
if (declaration is KtNamedFunction) {
|
||||
appendParameters(declaration, "[", "]")
|
||||
}
|
||||
} else if (isSetter) {
|
||||
appendFixedText("()")
|
||||
if (declaration is KtNamedFunction) {
|
||||
appendParameters(declaration, "[", "]", skipLast = true)
|
||||
declaration.valueParameters.last().nameAsName?.let {
|
||||
appendFixedText(" = ")
|
||||
appendName(it)
|
||||
}
|
||||
}
|
||||
} else if (isInvoke) {
|
||||
appendFixedText("()")
|
||||
if (declaration is KtNamedFunction) {
|
||||
appendParameters(declaration, "(", ")")
|
||||
}
|
||||
} else {
|
||||
appendFixedText("().")
|
||||
appendName(name)
|
||||
if (declaration is KtNamedFunction) {
|
||||
appendParameters(declaration, "(", ")")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (declaration is KtNamedFunction) {
|
||||
(declaration.bodyExpression as? KtBlockExpression)?.delete()
|
||||
declaration.bodyExpression?.replace(body) ?: run {
|
||||
declaration.bodyExpression?.delete()
|
||||
declaration.equalsToken?.delete()
|
||||
|
||||
if (isSetter || isInvoke) {
|
||||
val blockBody = ktPsiFactory.createSingleStatementBlock(body)
|
||||
declaration.add(blockBody)
|
||||
} else {
|
||||
declaration.add(ktPsiFactory.createEQ())
|
||||
declaration.add(body)
|
||||
}
|
||||
@@ -95,15 +135,9 @@ class MigrateExternalExtensionFix(declaration: KtNamedDeclaration)
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtAnnotationEntry.isJsNative(): Boolean {
|
||||
val bindingContext = analyze(BodyResolveMode.PARTIAL_WITH_DIAGNOSTICS)
|
||||
val annotationDescriptor = bindingContext[BindingContext.ANNOTATION, this]
|
||||
return annotationDescriptor != null && checkAnnotationName(annotationDescriptor, PredefinedAnnotation.NATIVE.fqName)
|
||||
}
|
||||
|
||||
private fun BuilderByPattern<KtExpression>.appendParameters(declaration: KtNamedFunction) {
|
||||
appendFixedText("(")
|
||||
for ((index, param) in declaration.valueParameters.withIndex()) {
|
||||
private fun BuilderByPattern<KtExpression>.appendParameters(declaration: KtNamedFunction, lParenth: String, rParenth: String, skipLast: Boolean = false) {
|
||||
appendFixedText(lParenth)
|
||||
for ((index, param) in declaration.valueParameters.let { if (skipLast) it.take(it.size-1) else it }.withIndex()) {
|
||||
param.nameAsName?.let { paramName ->
|
||||
if (index > 0) {
|
||||
appendFixedText(",")
|
||||
@@ -111,16 +145,55 @@ class MigrateExternalExtensionFix(declaration: KtNamedDeclaration)
|
||||
appendName(paramName)
|
||||
}
|
||||
}
|
||||
appendFixedText(")")
|
||||
appendFixedText(rParenth)
|
||||
}
|
||||
|
||||
companion object : KotlinSingleIntentionActionFactory() {
|
||||
private fun KtAnnotationEntry.isJsAnnotation(vararg predefinedAnnotations: PredefinedAnnotation): Boolean {
|
||||
val bindingContext = analyze(BodyResolveMode.PARTIAL_WITH_DIAGNOSTICS)
|
||||
val annotationDescriptor = bindingContext[BindingContext.ANNOTATION, this]
|
||||
return annotationDescriptor != null && predefinedAnnotations.any { checkAnnotationName(annotationDescriptor, it.fqName) }
|
||||
}
|
||||
|
||||
private fun KtAnnotationEntry.isJsNativeAnnotation(): Boolean {
|
||||
return isJsAnnotation(PredefinedAnnotation.NATIVE, PredefinedAnnotation.NATIVE_GETTER, PredefinedAnnotation.NATIVE_SETTER, PredefinedAnnotation.NATIVE_INVOKE )
|
||||
}
|
||||
|
||||
private fun isExternalMemberDeclaration(psiElement: PsiElement): Boolean {
|
||||
return (psiElement is KtNamedFunction && psiElement.receiverTypeReference != null) ||
|
||||
(psiElement is KtProperty && psiElement.receiverTypeReference != null)
|
||||
}
|
||||
|
||||
private inline fun<reified T: PsiElement> getContainingElement(e: PsiElement): T? {
|
||||
var element: PsiElement? = e
|
||||
while (element != null) {
|
||||
if (element is T)
|
||||
return element
|
||||
|
||||
element = element.parent
|
||||
}
|
||||
return null
|
||||
}
|
||||
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
|
||||
val e = diagnostic.psiElement
|
||||
if ((e is KtNamedFunction && e.receiverTypeReference != null) ||
|
||||
(e is KtProperty && e.receiverTypeReference != null)) {
|
||||
return MigrateExternalExtensionFix(e as KtNamedDeclaration)
|
||||
when (diagnostic.factory) {
|
||||
ErrorsJs.WRONG_EXTERNAL_DECLARATION -> {
|
||||
if (isExternalMemberDeclaration(e)) {
|
||||
return MigrateExternalExtensionFix(e as KtNamedDeclaration)
|
||||
}
|
||||
}
|
||||
Errors.DEPRECATION_ERROR, Errors.DEPRECATION -> {
|
||||
if (getContainingElement<KtAnnotationEntry>(e)?.isJsNativeAnnotation() == true) {
|
||||
getContainingElement<KtNamedDeclaration>(e)?.let {
|
||||
return MigrateExternalExtensionFix(it)
|
||||
}
|
||||
}
|
||||
if ((e as? KtNamedDeclaration)?.modifierList?.annotationEntries?.any { it.isJsNativeAnnotation() } == true) {
|
||||
return MigrateExternalExtensionFix(e as KtNamedDeclaration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// "Fix with 'asDynamic'" "true"
|
||||
// JS
|
||||
|
||||
external class B
|
||||
|
||||
@<caret>nativeGetter
|
||||
fun B.boo(i: Int): B?
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// "Fix with 'asDynamic'" "true"
|
||||
// JS
|
||||
|
||||
external class B
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun B.boo(i: Int): B? = asDynamic()[i]
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// "Fix with 'asDynamic'" "true"
|
||||
// JS
|
||||
|
||||
external class B
|
||||
|
||||
@native<caret>Invoke
|
||||
fun B.baz(a: B)
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// "Fix with 'asDynamic'" "true"
|
||||
// JS
|
||||
|
||||
external class B
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun B.baz(a: B) {
|
||||
asDynamic()(a)
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// "Fix with 'asDynamic'" "true"
|
||||
// JS
|
||||
|
||||
external class B
|
||||
|
||||
@<caret>nativeSetter
|
||||
fun B.boo(i: Int, v: B)
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// "Fix with 'asDynamic'" "true"
|
||||
// JS
|
||||
|
||||
external class B
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun B.boo(i: Int, v: B) {
|
||||
asDynamic()[i] = v
|
||||
}
|
||||
@@ -6444,6 +6444,24 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/migration/jsExternal/nativeExtensionVaslJsRuntime.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("nativeGetterExtensionFunJsRuntime.kt")
|
||||
public void testNativeGetterExtensionFunJsRuntime() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/migration/jsExternal/nativeGetterExtensionFunJsRuntime.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("nativeInvokeExtensionFunJsRuntime.kt")
|
||||
public void testNativeInvokeExtensionFunJsRuntime() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/migration/jsExternal/nativeInvokeExtensionFunJsRuntime.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("nativeSetterExtensionFunJsRuntime.kt")
|
||||
public void testNativeSetterExtensionFunJsRuntime() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/migration/jsExternal/nativeSetterExtensionFunJsRuntime.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/migration/missingConstructorKeyword")
|
||||
|
||||
-1
@@ -53,5 +53,4 @@ public class SourceMapGenerationSmokeTestGenerated extends AbstractSourceMapGene
|
||||
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/sourcemap/methodCallInMethod.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user