From 63785b5f665e4665746b6d3b4feb3f2a899c7d74 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Thu, 15 Dec 2016 20:32:14 +0100 Subject: [PATCH] Migration for JS extension functions and properties marked with @native/external #KT-15269 Fixed --- .../inspections/KotlinCleanupInspection.kt | 4 +- .../kotlin/idea/quickfix/QuickFixRegistrar.kt | 4 + .../migration/MigrateExternalExtensionFix.kt | 127 ++++++++++++++++++ .../externalExtensionFunJsRuntime.kt | 5 + .../externalExtensionFunJsRuntime.kt.after | 6 + .../externalExtensionFunParamsJsRuntime.kt | 6 + ...ternalExtensionFunParamsJsRuntime.kt.after | 7 + .../externalExtensionVarJsRuntime.kt | 5 + .../externalExtensionVarJsRuntime.kt.after | 9 ++ .../nativeExtensionFunBlockBodyJsRuntime.kt | 8 ++ ...iveExtensionFunBlockBodyJsRuntime.kt.after | 6 + .../jsExternal/nativeExtensionFunJsRuntime.kt | 5 + .../nativeExtensionFunJsRuntime.kt.after | 6 + .../nativeExtensionFunParamsJsRuntime.kt | 5 + ...nativeExtensionFunParamsJsRuntime.kt.after | 6 + .../nativeExtensionVaslJsRuntime.kt | 5 + .../nativeExtensionVaslJsRuntime.kt.after | 6 + .../idea/quickfix/QuickFixTestGenerated.java | 51 +++++++ 18 files changed, 270 insertions(+), 1 deletion(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/migration/MigrateExternalExtensionFix.kt create mode 100644 idea/testData/quickfix/migration/jsExternal/externalExtensionFunJsRuntime.kt create mode 100644 idea/testData/quickfix/migration/jsExternal/externalExtensionFunJsRuntime.kt.after create mode 100644 idea/testData/quickfix/migration/jsExternal/externalExtensionFunParamsJsRuntime.kt create mode 100644 idea/testData/quickfix/migration/jsExternal/externalExtensionFunParamsJsRuntime.kt.after create mode 100644 idea/testData/quickfix/migration/jsExternal/externalExtensionVarJsRuntime.kt create mode 100644 idea/testData/quickfix/migration/jsExternal/externalExtensionVarJsRuntime.kt.after create mode 100644 idea/testData/quickfix/migration/jsExternal/nativeExtensionFunBlockBodyJsRuntime.kt create mode 100644 idea/testData/quickfix/migration/jsExternal/nativeExtensionFunBlockBodyJsRuntime.kt.after create mode 100644 idea/testData/quickfix/migration/jsExternal/nativeExtensionFunJsRuntime.kt create mode 100644 idea/testData/quickfix/migration/jsExternal/nativeExtensionFunJsRuntime.kt.after create mode 100644 idea/testData/quickfix/migration/jsExternal/nativeExtensionFunParamsJsRuntime.kt create mode 100644 idea/testData/quickfix/migration/jsExternal/nativeExtensionFunParamsJsRuntime.kt.after create mode 100644 idea/testData/quickfix/migration/jsExternal/nativeExtensionVaslJsRuntime.kt create mode 100644 idea/testData/quickfix/migration/jsExternal/nativeExtensionVaslJsRuntime.kt.after diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/KotlinCleanupInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/KotlinCleanupInspection.kt index 487d1d5ef5c..125ba84ebc2 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/KotlinCleanupInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/KotlinCleanupInspection.kt @@ -33,6 +33,7 @@ import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction import org.jetbrains.kotlin.idea.quickfix.ReplaceObsoleteLabelSyntaxFix import org.jetbrains.kotlin.idea.quickfix.replaceWith.DeprecatedSymbolUsageFix import org.jetbrains.kotlin.idea.util.ProjectRootsUtil +import org.jetbrains.kotlin.js.resolve.diagnostics.ErrorsJs import org.jetbrains.kotlin.psi.KtAnnotationEntry import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtImportDirective @@ -96,7 +97,8 @@ class KotlinCleanupInspection : LocalInspectionTool(), CleanupLocalInspectionToo Errors.CALLABLE_REFERENCE_TO_MEMBER_OR_EXTENSION_WITH_EMPTY_LHS, Errors.DEPRECATED_TYPE_PARAMETER_SYNTAX, Errors.MISPLACED_TYPE_PARAMETER_CONSTRAINTS, - Errors.COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT + Errors.COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT, + ErrorsJs.WRONG_EXTERNAL_DECLARATION ) private fun Diagnostic.isObsoleteLabel(): Boolean { diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt index 621aa87da82..45f26aea827 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt @@ -34,10 +34,12 @@ import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createTypeParameter.Cr import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createVariable.CreateLocalVariableActionFactory import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createVariable.CreateParameterByNamedArgumentActionFactory import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createVariable.CreateParameterByRefActionFactory +import org.jetbrains.kotlin.idea.quickfix.migration.MigrateExternalExtensionFix import org.jetbrains.kotlin.idea.quickfix.migration.MigrateTypeParameterListFix import org.jetbrains.kotlin.idea.quickfix.replaceWith.DeprecatedSymbolUsageFix import org.jetbrains.kotlin.idea.quickfix.replaceWith.DeprecatedSymbolUsageInWholeProjectFix import org.jetbrains.kotlin.idea.quickfix.replaceWith.ReplaceProtectedToPublishedApiCallFix +import org.jetbrains.kotlin.js.resolve.diagnostics.ErrorsJs import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.lexer.KtTokens.* import org.jetbrains.kotlin.psi.KtClass @@ -458,5 +460,7 @@ class QuickFixRegistrar : QuickFixContributor { OVERLOADS_PRIVATE.registerFactory(RemoveAnnotationFix.JvmOverloads) OVERLOADS_LOCAL.registerFactory(RemoveAnnotationFix.JvmOverloads) OVERLOADS_WITHOUT_DEFAULT_ARGUMENTS.registerFactory(RemoveAnnotationFix.JvmOverloads) + + ErrorsJs.WRONG_EXTERNAL_DECLARATION.registerFactory(MigrateExternalExtensionFix) } } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/migration/MigrateExternalExtensionFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/migration/MigrateExternalExtensionFix.kt new file mode 100644 index 00000000000..89782b4752f --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/migration/MigrateExternalExtensionFix.kt @@ -0,0 +1,127 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +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 org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.annotations.checkAnnotationName +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.intentions.SpecifyTypeExplicitlyIntention +import org.jetbrains.kotlin.idea.project.builtIns +import org.jetbrains.kotlin.idea.quickfix.CleanupFix +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.lexer.KtTokens +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode + +class MigrateExternalExtensionFix(declaration: KtNamedDeclaration) + : KotlinQuickFixAction(declaration), CleanupFix { + + override fun getText() = "Fix with 'asDynamic'" + override fun getFamilyName() = getText() + + override fun invoke(project: Project, editor: Editor?, file: KtFile) { + val declaration = element ?: return + val name = declaration.nameAsSafeName + declaration.modifierList?.annotationEntries?.firstOrNull { it.isJsNative() }?.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()) { + SpecifyTypeExplicitlyIntention.addTypeAnnotation(editor, declaration, declaration.builtIns.unitType) + } + } + + val ktPsiFactory = KtPsiFactory(project) + val body = ktPsiFactory.buildExpression { + appendName(Name.identifier("asDynamic")) + appendFixedText("().") + appendName(name) + if (declaration is KtNamedFunction) { + appendParameters(declaration) + } + } + + if (declaration is KtNamedFunction) { + (declaration.bodyExpression as? KtBlockExpression)?.delete() + declaration.bodyExpression?.replace(body) ?: run { + declaration.add(ktPsiFactory.createEQ()) + declaration.add(body) + } + } + else if (declaration is KtProperty) { + declaration.setter?.delete() + declaration.getter?.delete() + val getter = ktPsiFactory.createPropertyGetter(body) + declaration.add(getter) + + if (declaration.isVar) { + val setterBody = ktPsiFactory.buildExpression { + appendName(Name.identifier("asDynamic")) + appendFixedText("().") + appendName(name) + appendFixedText(" = ") + appendName(Name.identifier("value")) + } + + val setterStubProperty = ktPsiFactory.createProperty("val x: Unit set(value) { Unit }") + val block = setterStubProperty.setter!!.bodyExpression as KtBlockExpression + block.statements.single().replace(setterBody) + declaration.add(setterStubProperty.setter!!) + } + } + } + + 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()) { + param.nameAsName?.let { paramName -> + if (index > 0) { + appendFixedText(",") + } + appendName(paramName) + } + } + appendFixedText(")") + } + + companion object : KotlinSingleIntentionActionFactory() { + 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) + } + return null + } + } +} diff --git a/idea/testData/quickfix/migration/jsExternal/externalExtensionFunJsRuntime.kt b/idea/testData/quickfix/migration/jsExternal/externalExtensionFunJsRuntime.kt new file mode 100644 index 00000000000..84abab04983 --- /dev/null +++ b/idea/testData/quickfix/migration/jsExternal/externalExtensionFunJsRuntime.kt @@ -0,0 +1,5 @@ +// "Fix with 'asDynamic'" "true" + +class A + +external fun A.bar(): Unit = noImpl diff --git a/idea/testData/quickfix/migration/jsExternal/externalExtensionFunJsRuntime.kt.after b/idea/testData/quickfix/migration/jsExternal/externalExtensionFunJsRuntime.kt.after new file mode 100644 index 00000000000..985aaff3bc4 --- /dev/null +++ b/idea/testData/quickfix/migration/jsExternal/externalExtensionFunJsRuntime.kt.after @@ -0,0 +1,6 @@ +// "Fix with 'asDynamic'" "true" + +class A + +@Suppress("NOTHING_TO_INLINE") +inline fun A.bar(): Unit = asDynamic().bar() diff --git a/idea/testData/quickfix/migration/jsExternal/externalExtensionFunParamsJsRuntime.kt b/idea/testData/quickfix/migration/jsExternal/externalExtensionFunParamsJsRuntime.kt new file mode 100644 index 00000000000..803069c4e42 --- /dev/null +++ b/idea/testData/quickfix/migration/jsExternal/externalExtensionFunParamsJsRuntime.kt @@ -0,0 +1,6 @@ +// "Fix with 'asDynamic'" "true" + +class A + +external fun A.bar(a: Int, b: String) + diff --git a/idea/testData/quickfix/migration/jsExternal/externalExtensionFunParamsJsRuntime.kt.after b/idea/testData/quickfix/migration/jsExternal/externalExtensionFunParamsJsRuntime.kt.after new file mode 100644 index 00000000000..faadf79ea37 --- /dev/null +++ b/idea/testData/quickfix/migration/jsExternal/externalExtensionFunParamsJsRuntime.kt.after @@ -0,0 +1,7 @@ +// "Fix with 'asDynamic'" "true" + +class A + +@Suppress("NOTHING_TO_INLINE") +inline fun A.bar(a: Int, b: String): Unit = asDynamic().bar(a, b) + diff --git a/idea/testData/quickfix/migration/jsExternal/externalExtensionVarJsRuntime.kt b/idea/testData/quickfix/migration/jsExternal/externalExtensionVarJsRuntime.kt new file mode 100644 index 00000000000..6f6db25faa6 --- /dev/null +++ b/idea/testData/quickfix/migration/jsExternal/externalExtensionVarJsRuntime.kt @@ -0,0 +1,5 @@ +// "Fix with 'asDynamic'" "true" + +class A + +external var A.baz: String diff --git a/idea/testData/quickfix/migration/jsExternal/externalExtensionVarJsRuntime.kt.after b/idea/testData/quickfix/migration/jsExternal/externalExtensionVarJsRuntime.kt.after new file mode 100644 index 00000000000..2a971ad90db --- /dev/null +++ b/idea/testData/quickfix/migration/jsExternal/externalExtensionVarJsRuntime.kt.after @@ -0,0 +1,9 @@ +// "Fix with 'asDynamic'" "true" + +class A + +inline var A.baz: String + get() = asDynamic().baz + set(value) { + asDynamic().baz = value + } diff --git a/idea/testData/quickfix/migration/jsExternal/nativeExtensionFunBlockBodyJsRuntime.kt b/idea/testData/quickfix/migration/jsExternal/nativeExtensionFunBlockBodyJsRuntime.kt new file mode 100644 index 00000000000..b05478e9669 --- /dev/null +++ b/idea/testData/quickfix/migration/jsExternal/nativeExtensionFunBlockBodyJsRuntime.kt @@ -0,0 +1,8 @@ +// "Fix with 'asDynamic'" "true" + +class TS + +@native +fun TS.normalizePath(path: String): String { + noImpl +} diff --git a/idea/testData/quickfix/migration/jsExternal/nativeExtensionFunBlockBodyJsRuntime.kt.after b/idea/testData/quickfix/migration/jsExternal/nativeExtensionFunBlockBodyJsRuntime.kt.after new file mode 100644 index 00000000000..ea83f762241 --- /dev/null +++ b/idea/testData/quickfix/migration/jsExternal/nativeExtensionFunBlockBodyJsRuntime.kt.after @@ -0,0 +1,6 @@ +// "Fix with 'asDynamic'" "true" + +class TS + +@Suppress("NOTHING_TO_INLINE") +inline fun TS.normalizePath(path: String): String = asDynamic().normalizePath(path) diff --git a/idea/testData/quickfix/migration/jsExternal/nativeExtensionFunJsRuntime.kt b/idea/testData/quickfix/migration/jsExternal/nativeExtensionFunJsRuntime.kt new file mode 100644 index 00000000000..2c4392b2a39 --- /dev/null +++ b/idea/testData/quickfix/migration/jsExternal/nativeExtensionFunJsRuntime.kt @@ -0,0 +1,5 @@ +// "Fix with 'asDynamic'" "true" + +class A + +@native fun A.foo(): Int = noImpl diff --git a/idea/testData/quickfix/migration/jsExternal/nativeExtensionFunJsRuntime.kt.after b/idea/testData/quickfix/migration/jsExternal/nativeExtensionFunJsRuntime.kt.after new file mode 100644 index 00000000000..454c3323678 --- /dev/null +++ b/idea/testData/quickfix/migration/jsExternal/nativeExtensionFunJsRuntime.kt.after @@ -0,0 +1,6 @@ +// "Fix with 'asDynamic'" "true" + +class A + +@Suppress("NOTHING_TO_INLINE") +inline fun A.foo(): Int = asDynamic().foo() diff --git a/idea/testData/quickfix/migration/jsExternal/nativeExtensionFunParamsJsRuntime.kt b/idea/testData/quickfix/migration/jsExternal/nativeExtensionFunParamsJsRuntime.kt new file mode 100644 index 00000000000..90aed5856d6 --- /dev/null +++ b/idea/testData/quickfix/migration/jsExternal/nativeExtensionFunParamsJsRuntime.kt @@ -0,0 +1,5 @@ +// "Fix with 'asDynamic'" "true" + +class A + +@native fun A.foo(a: Int, b: String): Int diff --git a/idea/testData/quickfix/migration/jsExternal/nativeExtensionFunParamsJsRuntime.kt.after b/idea/testData/quickfix/migration/jsExternal/nativeExtensionFunParamsJsRuntime.kt.after new file mode 100644 index 00000000000..51b370e62f8 --- /dev/null +++ b/idea/testData/quickfix/migration/jsExternal/nativeExtensionFunParamsJsRuntime.kt.after @@ -0,0 +1,6 @@ +// "Fix with 'asDynamic'" "true" + +class A + +@Suppress("NOTHING_TO_INLINE") +inline fun A.foo(a: Int, b: String): Int = asDynamic().foo(a, b) diff --git a/idea/testData/quickfix/migration/jsExternal/nativeExtensionVaslJsRuntime.kt b/idea/testData/quickfix/migration/jsExternal/nativeExtensionVaslJsRuntime.kt new file mode 100644 index 00000000000..e1e872a77a3 --- /dev/null +++ b/idea/testData/quickfix/migration/jsExternal/nativeExtensionVaslJsRuntime.kt @@ -0,0 +1,5 @@ +// "Fix with 'asDynamic'" "true" + +class A + +@native val A.boo: Int diff --git a/idea/testData/quickfix/migration/jsExternal/nativeExtensionVaslJsRuntime.kt.after b/idea/testData/quickfix/migration/jsExternal/nativeExtensionVaslJsRuntime.kt.after new file mode 100644 index 00000000000..b264bbcfac3 --- /dev/null +++ b/idea/testData/quickfix/migration/jsExternal/nativeExtensionVaslJsRuntime.kt.after @@ -0,0 +1,6 @@ +// "Fix with 'asDynamic'" "true" + +class A + +inline val A.boo: Int + get() = asDynamic().boo diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index 27a7b83a134..8f76a861d74 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -6086,6 +6086,57 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { } } + @TestMetadata("idea/testData/quickfix/migration/jsExternal") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class JsExternal extends AbstractQuickFixTest { + public void testAllFilesPresentInJsExternal() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/migration/jsExternal"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("externalExtensionFunJsRuntime.kt") + public void testExternalExtensionFunJsRuntime() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/migration/jsExternal/externalExtensionFunJsRuntime.kt"); + doTest(fileName); + } + + @TestMetadata("externalExtensionFunParamsJsRuntime.kt") + public void testExternalExtensionFunParamsJsRuntime() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/migration/jsExternal/externalExtensionFunParamsJsRuntime.kt"); + doTest(fileName); + } + + @TestMetadata("externalExtensionVarJsRuntime.kt") + public void testExternalExtensionVarJsRuntime() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/migration/jsExternal/externalExtensionVarJsRuntime.kt"); + doTest(fileName); + } + + @TestMetadata("nativeExtensionFunBlockBodyJsRuntime.kt") + public void testNativeExtensionFunBlockBodyJsRuntime() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/migration/jsExternal/nativeExtensionFunBlockBodyJsRuntime.kt"); + doTest(fileName); + } + + @TestMetadata("nativeExtensionFunJsRuntime.kt") + public void testNativeExtensionFunJsRuntime() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/migration/jsExternal/nativeExtensionFunJsRuntime.kt"); + doTest(fileName); + } + + @TestMetadata("nativeExtensionFunParamsJsRuntime.kt") + public void testNativeExtensionFunParamsJsRuntime() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/migration/jsExternal/nativeExtensionFunParamsJsRuntime.kt"); + doTest(fileName); + } + + @TestMetadata("nativeExtensionVaslJsRuntime.kt") + public void testNativeExtensionVaslJsRuntime() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/migration/jsExternal/nativeExtensionVaslJsRuntime.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/quickfix/migration/missingConstructorKeyword") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)