From c364e795574af0580250f2bb634cecb2655f98a3 Mon Sep 17 00:00:00 2001 From: qx Date: Fri, 28 Apr 2017 10:13:26 +0300 Subject: [PATCH] Implemented @native-* annotation migration quickfix for extension functions As the first part of KT-15270 Quickfix to migrate from @native*** --- .../kotlin/idea/quickfix/QuickFixRegistrar.kt | 4 +- .../migration/MigrateExternalExtensionFix.kt | 115 ++++++++++++++---- .../nativeGetterExtensionFunJsRuntime.kt | 7 ++ ...nativeGetterExtensionFunJsRuntime.kt.after | 7 ++ .../nativeInvokeExtensionFunJsRuntime.kt | 7 ++ ...nativeInvokeExtensionFunJsRuntime.kt.after | 9 ++ .../nativeSetterExtensionFunJsRuntime.kt | 7 ++ ...nativeSetterExtensionFunJsRuntime.kt.after | 9 ++ .../idea/quickfix/QuickFixTestGenerated.java | 18 +++ ...SourceMapGenerationSmokeTestGenerated.java | 1 - 10 files changed, 160 insertions(+), 24 deletions(-) create mode 100644 idea/testData/quickfix/migration/jsExternal/nativeGetterExtensionFunJsRuntime.kt create mode 100644 idea/testData/quickfix/migration/jsExternal/nativeGetterExtensionFunJsRuntime.kt.after create mode 100644 idea/testData/quickfix/migration/jsExternal/nativeInvokeExtensionFunJsRuntime.kt create mode 100644 idea/testData/quickfix/migration/jsExternal/nativeInvokeExtensionFunJsRuntime.kt.after create mode 100644 idea/testData/quickfix/migration/jsExternal/nativeSetterExtensionFunJsRuntime.kt create mode 100644 idea/testData/quickfix/migration/jsExternal/nativeSetterExtensionFunJsRuntime.kt.after diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt index b4a5b45b8af..309ff625f6a 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt @@ -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) diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/migration/MigrateExternalExtensionFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/migration/MigrateExternalExtensionFix.kt index 845d1490959..8788ff17649 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/migration/MigrateExternalExtensionFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/migration/MigrateExternalExtensionFix.kt @@ -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.appendParameters(declaration: KtNamedFunction) { - appendFixedText("(") - for ((index, param) in declaration.valueParameters.withIndex()) { + private fun BuilderByPattern.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 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(e)?.isJsNativeAnnotation() == true) { + getContainingElement(e)?.let { + return MigrateExternalExtensionFix(it) + } + } + if ((e as? KtNamedDeclaration)?.modifierList?.annotationEntries?.any { it.isJsNativeAnnotation() } == true) { + return MigrateExternalExtensionFix(e as KtNamedDeclaration) + } + } } + return null } } diff --git a/idea/testData/quickfix/migration/jsExternal/nativeGetterExtensionFunJsRuntime.kt b/idea/testData/quickfix/migration/jsExternal/nativeGetterExtensionFunJsRuntime.kt new file mode 100644 index 00000000000..36b38314d6f --- /dev/null +++ b/idea/testData/quickfix/migration/jsExternal/nativeGetterExtensionFunJsRuntime.kt @@ -0,0 +1,7 @@ +// "Fix with 'asDynamic'" "true" +// JS + +external class B + +@nativeGetter +fun B.boo(i: Int): B? \ No newline at end of file diff --git a/idea/testData/quickfix/migration/jsExternal/nativeGetterExtensionFunJsRuntime.kt.after b/idea/testData/quickfix/migration/jsExternal/nativeGetterExtensionFunJsRuntime.kt.after new file mode 100644 index 00000000000..a7445ccf5c2 --- /dev/null +++ b/idea/testData/quickfix/migration/jsExternal/nativeGetterExtensionFunJsRuntime.kt.after @@ -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] \ No newline at end of file diff --git a/idea/testData/quickfix/migration/jsExternal/nativeInvokeExtensionFunJsRuntime.kt b/idea/testData/quickfix/migration/jsExternal/nativeInvokeExtensionFunJsRuntime.kt new file mode 100644 index 00000000000..7888dcf971b --- /dev/null +++ b/idea/testData/quickfix/migration/jsExternal/nativeInvokeExtensionFunJsRuntime.kt @@ -0,0 +1,7 @@ +// "Fix with 'asDynamic'" "true" +// JS + +external class B + +@nativeInvoke +fun B.baz(a: B) \ No newline at end of file diff --git a/idea/testData/quickfix/migration/jsExternal/nativeInvokeExtensionFunJsRuntime.kt.after b/idea/testData/quickfix/migration/jsExternal/nativeInvokeExtensionFunJsRuntime.kt.after new file mode 100644 index 00000000000..cdbf22c962c --- /dev/null +++ b/idea/testData/quickfix/migration/jsExternal/nativeInvokeExtensionFunJsRuntime.kt.after @@ -0,0 +1,9 @@ +// "Fix with 'asDynamic'" "true" +// JS + +external class B + +@Suppress("NOTHING_TO_INLINE") +inline fun B.baz(a: B) { + asDynamic()(a) +} \ No newline at end of file diff --git a/idea/testData/quickfix/migration/jsExternal/nativeSetterExtensionFunJsRuntime.kt b/idea/testData/quickfix/migration/jsExternal/nativeSetterExtensionFunJsRuntime.kt new file mode 100644 index 00000000000..b1ef2984b05 --- /dev/null +++ b/idea/testData/quickfix/migration/jsExternal/nativeSetterExtensionFunJsRuntime.kt @@ -0,0 +1,7 @@ +// "Fix with 'asDynamic'" "true" +// JS + +external class B + +@nativeSetter +fun B.boo(i: Int, v: B) \ No newline at end of file diff --git a/idea/testData/quickfix/migration/jsExternal/nativeSetterExtensionFunJsRuntime.kt.after b/idea/testData/quickfix/migration/jsExternal/nativeSetterExtensionFunJsRuntime.kt.after new file mode 100644 index 00000000000..7b2ea9872c9 --- /dev/null +++ b/idea/testData/quickfix/migration/jsExternal/nativeSetterExtensionFunJsRuntime.kt.after @@ -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 +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index 1879bff92c1..2bf6b62e9ea 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -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") diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/SourceMapGenerationSmokeTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/SourceMapGenerationSmokeTestGenerated.java index dccfefd35fb..d9470ebf8c7 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/SourceMapGenerationSmokeTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/SourceMapGenerationSmokeTestGenerated.java @@ -53,5 +53,4 @@ public class SourceMapGenerationSmokeTestGenerated extends AbstractSourceMapGene String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/sourcemap/methodCallInMethod.kt"); doTest(fileName); } - }