Retain javascript name from @native annotation

As the first part of KT-15257 JS: quickfix to migrate from @native to external
This commit is contained in:
qx
2017-04-28 15:30:53 +03:00
parent c364e79557
commit 71de20b9e6
4 changed files with 75 additions and 17 deletions
@@ -34,6 +34,7 @@ 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.js.resolve.diagnostics.ErrorsJs
import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext 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) { private data class JsNativeAnnotations(val annotations: List<KtAnnotationEntry>, val nativeAnnotation: KtAnnotationEntry?, val isGetter: Boolean, val isSetter: Boolean, val isInvoke: Boolean)
val name = declaration.nameAsSafeName
val annotationEntries = declaration.modifierList?.annotationEntries private fun fetchJsNativeAnnotations(declaration: KtNamedDeclaration) : JsNativeAnnotations {
val isGetter = annotationEntries?.any { it.isJsAnnotation(PredefinedAnnotation.NATIVE_GETTER) } ?: false var isGetter: Boolean = false
val isSetter = annotationEntries?.any { it.isJsAnnotation(PredefinedAnnotation.NATIVE_SETTER) } ?: false var isSetter: Boolean = false
val isInvoke = annotationEntries?.any { it.isJsAnnotation(PredefinedAnnotation.NATIVE_INVOKE) } ?: false var isInvoke: Boolean = false
annotationEntries?.filter { it.isJsNativeAnnotation() }?.forEach { it.delete() } var nativeAnnotation: KtAnnotationEntry? = null
declaration.addModifier(KtTokens.INLINE_KEYWORD) val nativeAnnotations = ArrayList<KtAnnotationEntry>()
declaration.removeModifier(KtTokens.EXTERNAL_KEYWORD)
if (declaration is KtFunction) { declaration.modifierList?.annotationEntries?.forEach {
declaration.addAnnotation(KotlinBuiltIns.FQ_NAMES.suppress.toSafe(), "\"NOTHING_TO_INLINE\"") if (it.isJsAnnotation(PredefinedAnnotation.NATIVE_GETTER)) {
if (!declaration.hasDeclaredReturnType() && !isSetter && !isInvoke) { isGetter = true
SpecifyTypeExplicitlyIntention.addTypeAnnotation(editor, declaration, declaration.builtIns.unitType) 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 ktPsiFactory = KtPsiFactory(project)
val body = ktPsiFactory.buildExpression { val body = ktPsiFactory.buildExpression {
appendName(Name.identifier("asDynamic")) appendName(Name.identifier("asDynamic"))
if (isGetter) { if (annotations.isGetter) {
appendFixedText("()") appendFixedText("()")
if (declaration is KtNamedFunction) { if (declaration is KtNamedFunction) {
appendParameters(declaration, "[", "]") appendParameters(declaration, "[", "]")
} }
} else if (isSetter) { } else if (annotations.isSetter) {
appendFixedText("()") appendFixedText("()")
if (declaration is KtNamedFunction) { if (declaration is KtNamedFunction) {
appendParameters(declaration, "[", "]", skipLast = true) appendParameters(declaration, "[", "]", skipLast = true)
@@ -86,7 +104,7 @@ class MigrateExternalExtensionFix(declaration: KtNamedDeclaration)
appendName(it) appendName(it)
} }
} }
} else if (isInvoke) { } else if (annotations.isInvoke) {
appendFixedText("()") appendFixedText("()")
if (declaration is KtNamedFunction) { if (declaration is KtNamedFunction) {
appendParameters(declaration, "(", ")") appendParameters(declaration, "(", ")")
@@ -104,7 +122,7 @@ class MigrateExternalExtensionFix(declaration: KtNamedDeclaration)
declaration.bodyExpression?.delete() declaration.bodyExpression?.delete()
declaration.equalsToken?.delete() declaration.equalsToken?.delete()
if (isSetter || isInvoke) { if (annotations.isSetter || annotations.isInvoke) {
val blockBody = ktPsiFactory.createSingleStatementBlock(body) val blockBody = ktPsiFactory.createSingleStatementBlock(body)
declaration.add(blockBody) declaration.add(blockBody)
} else { } 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<KtExpression>.appendParameters(declaration: KtNamedFunction, lParenth: String, rParenth: String, skipLast: Boolean = false) { private fun BuilderByPattern<KtExpression>.appendParameters(declaration: KtNamedFunction, lParenth: String, rParenth: String, skipLast: Boolean = false) {
appendFixedText(lParenth) appendFixedText(lParenth)
for ((index, param) in declaration.valueParameters.let { if (skipLast) it.take(it.size-1) else it }.withIndex()) { for ((index, param) in declaration.valueParameters.let { if (skipLast) it.take(it.size-1) else it }.withIndex()) {
@@ -0,0 +1,6 @@
// "Fix with 'asDynamic'" "true"
// JS
class A
<caret>@native("aaaa") fun A.foo(): Int
@@ -0,0 +1,8 @@
// "Fix with 'asDynamic'" "true"
// JS
class A
@JsName("aaaa")
@Suppress("NOTHING_TO_INLINE")
inline fun A.foo(): Int = asDynamic().foo()
@@ -6427,6 +6427,12 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
doTest(fileName); 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") @TestMetadata("nativeExtensionFunJsRuntime.kt")
public void testNativeExtensionFunJsRuntime() throws Exception { public void testNativeExtensionFunJsRuntime() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/migration/jsExternal/nativeExtensionFunJsRuntime.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/migration/jsExternal/nativeExtensionFunJsRuntime.kt");