From 71de20b9e6a80ab2117513bf47c2ec8e42775b75 Mon Sep 17 00:00:00 2001 From: qx Date: Fri, 28 Apr 2017 15:30:53 +0300 Subject: [PATCH] Retain javascript name from @native annotation As the first part of KT-15257 JS: quickfix to migrate from @native to external --- .../migration/MigrateExternalExtensionFix.kt | 72 ++++++++++++++----- .../jsExternal/nativeExtensionFunJsName.kt | 6 ++ .../nativeExtensionFunJsName.kt.after | 8 +++ .../idea/quickfix/QuickFixTestGenerated.java | 6 ++ 4 files changed, 75 insertions(+), 17 deletions(-) create mode 100644 idea/testData/quickfix/migration/jsExternal/nativeExtensionFunJsName.kt create mode 100644 idea/testData/quickfix/migration/jsExternal/nativeExtensionFunJsName.kt.after 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 8788ff17649..8a07d3855af 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/migration/MigrateExternalExtensionFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/migration/MigrateExternalExtensionFix.kt @@ -34,6 +34,7 @@ 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.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.BindingContext @@ -53,31 +54,48 @@ class MigrateExternalExtensionFix(declaration: KtNamedDeclaration) } } - private fun fixExtensionMemberDeclaration(declaration: KtNamedDeclaration, project: Project, editor: Editor?, file: KtFile) { - val name = declaration.nameAsSafeName - 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() && !isSetter && !isInvoke) { - SpecifyTypeExplicitlyIntention.addTypeAnnotation(editor, declaration, declaration.builtIns.unitType) + private data class JsNativeAnnotations(val annotations: List, val nativeAnnotation: KtAnnotationEntry?, val isGetter: Boolean, val isSetter: Boolean, val isInvoke: Boolean) + + private fun fetchJsNativeAnnotations(declaration: KtNamedDeclaration) : JsNativeAnnotations { + var isGetter: Boolean = false + var isSetter: Boolean = false + var isInvoke: Boolean = false + var nativeAnnotation: KtAnnotationEntry? = null + val nativeAnnotations = ArrayList() + + declaration.modifierList?.annotationEntries?.forEach { + if (it.isJsAnnotation(PredefinedAnnotation.NATIVE_GETTER)) { + isGetter = true + nativeAnnotations.add(it) + } else if (it.isJsAnnotation(PredefinedAnnotation.NATIVE_SETTER)) { + isSetter = true + nativeAnnotations.add(it) + } else if (it.isJsAnnotation(PredefinedAnnotation.NATIVE_INVOKE)) { + isInvoke = true + nativeAnnotations.add(it) + } else if (it.isJsAnnotation(PredefinedAnnotation.NATIVE)) { + nativeAnnotations.add(it) + nativeAnnotation = it + } } + return JsNativeAnnotations(nativeAnnotations, nativeAnnotation, isGetter, isSetter, isInvoke) + } + + private fun fixExtensionMemberDeclaration(declaration: KtNamedDeclaration, project: Project, editor: Editor?, file: KtFile) { + val name = declaration.nameAsSafeName + val annotations = fetchJsNativeAnnotations(declaration) + fixAnnotations(declaration, annotations, editor) val ktPsiFactory = KtPsiFactory(project) val body = ktPsiFactory.buildExpression { appendName(Name.identifier("asDynamic")) - if (isGetter) { + if (annotations.isGetter) { appendFixedText("()") if (declaration is KtNamedFunction) { appendParameters(declaration, "[", "]") } - } else if (isSetter) { + } else if (annotations.isSetter) { appendFixedText("()") if (declaration is KtNamedFunction) { appendParameters(declaration, "[", "]", skipLast = true) @@ -86,7 +104,7 @@ class MigrateExternalExtensionFix(declaration: KtNamedDeclaration) appendName(it) } } - } else if (isInvoke) { + } else if (annotations.isInvoke) { appendFixedText("()") if (declaration is KtNamedFunction) { appendParameters(declaration, "(", ")") @@ -104,7 +122,7 @@ class MigrateExternalExtensionFix(declaration: KtNamedDeclaration) declaration.bodyExpression?.delete() declaration.equalsToken?.delete() - if (isSetter || isInvoke) { + if (annotations.isSetter || annotations.isInvoke) { val blockBody = ktPsiFactory.createSingleStatementBlock(body) declaration.add(blockBody) } else { @@ -135,6 +153,26 @@ class MigrateExternalExtensionFix(declaration: KtNamedDeclaration) } } + private fun fixAnnotations(declaration: KtNamedDeclaration, annotations: JsNativeAnnotations, editor: Editor?) { + annotations.annotations.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 (annotations.nativeAnnotation != null && annotations.nativeAnnotation.valueArguments.isNotEmpty()) { + declaration.addAnnotation(FqName("JsName"), annotations.nativeAnnotation.valueArguments.joinToString { it.asElement().text}) + } + + if (declaration is KtFunction && !declaration.hasDeclaredReturnType() && !annotations.isSetter && !annotations.isInvoke) { + SpecifyTypeExplicitlyIntention.addTypeAnnotation(editor, declaration, declaration.builtIns.unitType) + } + } + 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()) { diff --git a/idea/testData/quickfix/migration/jsExternal/nativeExtensionFunJsName.kt b/idea/testData/quickfix/migration/jsExternal/nativeExtensionFunJsName.kt new file mode 100644 index 00000000000..ff0e571e33c --- /dev/null +++ b/idea/testData/quickfix/migration/jsExternal/nativeExtensionFunJsName.kt @@ -0,0 +1,6 @@ +// "Fix with 'asDynamic'" "true" +// JS + +class A + +@native("aaaa") fun A.foo(): Int diff --git a/idea/testData/quickfix/migration/jsExternal/nativeExtensionFunJsName.kt.after b/idea/testData/quickfix/migration/jsExternal/nativeExtensionFunJsName.kt.after new file mode 100644 index 00000000000..9d8474fe9bb --- /dev/null +++ b/idea/testData/quickfix/migration/jsExternal/nativeExtensionFunJsName.kt.after @@ -0,0 +1,8 @@ +// "Fix with 'asDynamic'" "true" +// JS + +class A + +@JsName("aaaa") +@Suppress("NOTHING_TO_INLINE") +inline fun A.foo(): Int = asDynamic().foo() diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index 2bf6b62e9ea..50ea6e4937b 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -6427,6 +6427,12 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { doTest(fileName); } + @TestMetadata("nativeExtensionFunJsName.kt") + public void testNativeExtensionFunJsName() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/migration/jsExternal/nativeExtensionFunJsName.kt"); + doTest(fileName); + } + @TestMetadata("nativeExtensionFunJsRuntime.kt") public void testNativeExtensionFunJsRuntime() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/migration/jsExternal/nativeExtensionFunJsRuntime.kt");