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)
|
UNRESOLVED_REFERENCE.registerFactory(ReplaceObsoleteLabelSyntaxFix)
|
||||||
|
|
||||||
DEPRECATION.registerFactory(DeprecatedSymbolUsageFix, DeprecatedSymbolUsageInWholeProjectFix)
|
DEPRECATION.registerFactory(DeprecatedSymbolUsageFix, DeprecatedSymbolUsageInWholeProjectFix, MigrateExternalExtensionFix)
|
||||||
DEPRECATION_ERROR.registerFactory(DeprecatedSymbolUsageFix, DeprecatedSymbolUsageInWholeProjectFix)
|
DEPRECATION_ERROR.registerFactory(DeprecatedSymbolUsageFix, DeprecatedSymbolUsageInWholeProjectFix, MigrateExternalExtensionFix)
|
||||||
PROTECTED_CALL_FROM_PUBLIC_INLINE.registerFactory(ReplaceProtectedToPublishedApiCallFix)
|
PROTECTED_CALL_FROM_PUBLIC_INLINE.registerFactory(ReplaceProtectedToPublishedApiCallFix)
|
||||||
|
|
||||||
POSITIONED_VALUE_ARGUMENT_FOR_JAVA_ANNOTATION.registerFactory(ReplaceJavaAnnotationPositionedArgumentsFix)
|
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.codeInsight.intention.IntentionAction
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||||
import org.jetbrains.kotlin.descriptors.annotations.checkAnnotationName
|
import org.jetbrains.kotlin.descriptors.annotations.checkAnnotationName
|
||||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
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.caches.resolve.analyze
|
||||||
import org.jetbrains.kotlin.idea.intentions.SpecifyTypeExplicitlyIntention
|
import org.jetbrains.kotlin.idea.intentions.SpecifyTypeExplicitlyIntention
|
||||||
import org.jetbrains.kotlin.idea.project.builtIns
|
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.quickfix.KotlinSingleIntentionActionFactory
|
||||||
import org.jetbrains.kotlin.idea.util.addAnnotation
|
import org.jetbrains.kotlin.idea.util.addAnnotation
|
||||||
import org.jetbrains.kotlin.js.PredefinedAnnotation
|
import org.jetbrains.kotlin.js.PredefinedAnnotation
|
||||||
|
import org.jetbrains.kotlin.js.resolve.diagnostics.ErrorsJs
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
@@ -44,13 +47,24 @@ class MigrateExternalExtensionFix(declaration: KtNamedDeclaration)
|
|||||||
|
|
||||||
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
|
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
|
||||||
val declaration = element ?: return
|
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
|
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.addModifier(KtTokens.INLINE_KEYWORD)
|
||||||
declaration.removeModifier(KtTokens.EXTERNAL_KEYWORD)
|
declaration.removeModifier(KtTokens.EXTERNAL_KEYWORD)
|
||||||
if (declaration is KtFunction) {
|
if (declaration is KtFunction) {
|
||||||
declaration.addAnnotation(KotlinBuiltIns.FQ_NAMES.suppress.toSafe(), "\"NOTHING_TO_INLINE\"")
|
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)
|
SpecifyTypeExplicitlyIntention.addTypeAnnotation(editor, declaration, declaration.builtIns.unitType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,16 +72,42 @@ class MigrateExternalExtensionFix(declaration: KtNamedDeclaration)
|
|||||||
val ktPsiFactory = KtPsiFactory(project)
|
val ktPsiFactory = KtPsiFactory(project)
|
||||||
val body = ktPsiFactory.buildExpression {
|
val body = ktPsiFactory.buildExpression {
|
||||||
appendName(Name.identifier("asDynamic"))
|
appendName(Name.identifier("asDynamic"))
|
||||||
appendFixedText("().")
|
if (isGetter) {
|
||||||
appendName(name)
|
appendFixedText("()")
|
||||||
if (declaration is KtNamedFunction) {
|
if (declaration is KtNamedFunction) {
|
||||||
appendParameters(declaration)
|
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) {
|
if (declaration is KtNamedFunction) {
|
||||||
(declaration.bodyExpression as? KtBlockExpression)?.delete()
|
declaration.bodyExpression?.delete()
|
||||||
declaration.bodyExpression?.replace(body) ?: run {
|
declaration.equalsToken?.delete()
|
||||||
|
|
||||||
|
if (isSetter || isInvoke) {
|
||||||
|
val blockBody = ktPsiFactory.createSingleStatementBlock(body)
|
||||||
|
declaration.add(blockBody)
|
||||||
|
} else {
|
||||||
declaration.add(ktPsiFactory.createEQ())
|
declaration.add(ktPsiFactory.createEQ())
|
||||||
declaration.add(body)
|
declaration.add(body)
|
||||||
}
|
}
|
||||||
@@ -95,15 +135,9 @@ class MigrateExternalExtensionFix(declaration: KtNamedDeclaration)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun KtAnnotationEntry.isJsNative(): Boolean {
|
private fun BuilderByPattern<KtExpression>.appendParameters(declaration: KtNamedFunction, lParenth: String, rParenth: String, skipLast: Boolean = false) {
|
||||||
val bindingContext = analyze(BodyResolveMode.PARTIAL_WITH_DIAGNOSTICS)
|
appendFixedText(lParenth)
|
||||||
val annotationDescriptor = bindingContext[BindingContext.ANNOTATION, this]
|
for ((index, param) in declaration.valueParameters.let { if (skipLast) it.take(it.size-1) else it }.withIndex()) {
|
||||||
return annotationDescriptor != null && checkAnnotationName(annotationDescriptor, PredefinedAnnotation.NATIVE.fqName)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun BuilderByPattern<KtExpression>.appendParameters(declaration: KtNamedFunction) {
|
|
||||||
appendFixedText("(")
|
|
||||||
for ((index, param) in declaration.valueParameters.withIndex()) {
|
|
||||||
param.nameAsName?.let { paramName ->
|
param.nameAsName?.let { paramName ->
|
||||||
if (index > 0) {
|
if (index > 0) {
|
||||||
appendFixedText(",")
|
appendFixedText(",")
|
||||||
@@ -111,16 +145,55 @@ class MigrateExternalExtensionFix(declaration: KtNamedDeclaration)
|
|||||||
appendName(paramName)
|
appendName(paramName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
appendFixedText(")")
|
appendFixedText(rParenth)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object : KotlinSingleIntentionActionFactory() {
|
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? {
|
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
|
||||||
val e = diagnostic.psiElement
|
val e = diagnostic.psiElement
|
||||||
if ((e is KtNamedFunction && e.receiverTypeReference != null) ||
|
when (diagnostic.factory) {
|
||||||
(e is KtProperty && e.receiverTypeReference != null)) {
|
ErrorsJs.WRONG_EXTERNAL_DECLARATION -> {
|
||||||
return MigrateExternalExtensionFix(e as KtNamedDeclaration)
|
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
|
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");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/migration/jsExternal/nativeExtensionVaslJsRuntime.kt");
|
||||||
doTest(fileName);
|
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")
|
@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");
|
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/sourcemap/methodCallInMethod.kt");
|
||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user