From 366b9d1d796f9ea44c98149fdc2c8c28719e82e2 Mon Sep 17 00:00:00 2001 From: Toshiaki Kameyama Date: Wed, 19 Jul 2017 20:37:12 +0900 Subject: [PATCH] KT-12195 Quickfix @JvmStatic on main() method in an object (#1192) * Quickfix @JvmStatic on main() method in an object #KT-12195 Fixed * Fixed #KT-12195 --- .../kotlin/idea/MainFunctionDetector.kt | 11 ++-- .../AddJvmStaticIntention/after.kt.template | 5 ++ .../AddJvmStaticIntention/before.kt.template | 4 ++ .../AddJvmStaticIntention/description.html | 5 ++ idea/src/META-INF/plugin.xml | 5 ++ .../idea/intentions/AddJvmStaticIntention.kt | 48 ++++++++++++++ .../intentions/addJvmStatic/.intention | 1 + .../intentions/addJvmStatic/hasJvmStatic.kt | 8 +++ .../addJvmStatic/inAnonymousObject.kt | 6 ++ .../intentions/addJvmStatic/inClass.kt | 6 ++ .../addJvmStatic/inCompanionObject.kt | 8 +++ .../addJvmStatic/inCompanionObject.kt.after | 9 +++ .../intentions/addJvmStatic/inObjedct.kt | 6 ++ .../addJvmStatic/inObjedct.kt.after | 7 +++ .../intentions/addJvmStatic/inTopLevel.kt | 4 ++ .../intentions/addJvmStatic/mainJvmName.kt | 7 +++ .../addJvmStatic/mainJvmName.kt.after | 8 +++ .../intentions/addJvmStatic/notMainJvmName.kt | 8 +++ .../intentions/addJvmStatic/notMainMethod.kt | 6 ++ .../intentions/IntentionTestGenerated.java | 63 +++++++++++++++++++ 20 files changed, 220 insertions(+), 5 deletions(-) create mode 100644 idea/resources/intentionDescriptions/AddJvmStaticIntention/after.kt.template create mode 100644 idea/resources/intentionDescriptions/AddJvmStaticIntention/before.kt.template create mode 100644 idea/resources/intentionDescriptions/AddJvmStaticIntention/description.html create mode 100644 idea/src/org/jetbrains/kotlin/idea/intentions/AddJvmStaticIntention.kt create mode 100644 idea/testData/intentions/addJvmStatic/.intention create mode 100644 idea/testData/intentions/addJvmStatic/hasJvmStatic.kt create mode 100644 idea/testData/intentions/addJvmStatic/inAnonymousObject.kt create mode 100644 idea/testData/intentions/addJvmStatic/inClass.kt create mode 100644 idea/testData/intentions/addJvmStatic/inCompanionObject.kt create mode 100644 idea/testData/intentions/addJvmStatic/inCompanionObject.kt.after create mode 100644 idea/testData/intentions/addJvmStatic/inObjedct.kt create mode 100644 idea/testData/intentions/addJvmStatic/inObjedct.kt.after create mode 100644 idea/testData/intentions/addJvmStatic/inTopLevel.kt create mode 100644 idea/testData/intentions/addJvmStatic/mainJvmName.kt create mode 100644 idea/testData/intentions/addJvmStatic/mainJvmName.kt.after create mode 100644 idea/testData/intentions/addJvmStatic/notMainJvmName.kt create mode 100644 idea/testData/intentions/addJvmStatic/notMainMethod.kt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/idea/MainFunctionDetector.kt b/compiler/frontend/src/org/jetbrains/kotlin/idea/MainFunctionDetector.kt index e0a0e251fda..87e36868a6a 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/idea/MainFunctionDetector.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/idea/MainFunctionDetector.kt @@ -46,7 +46,8 @@ class MainFunctionDetector { return findMainFunction(declarations) != null } - fun isMain(function: KtNamedFunction): Boolean { + @JvmOverloads + fun isMain(function: KtNamedFunction, checkJvmStaticAnnotation: Boolean = true): Boolean { if (function.isLocal) { return false } @@ -61,11 +62,11 @@ class MainFunctionDetector { } /* Psi only check for kotlin.jvm.jvmStatic annotation */ - if (!function.isTopLevel && !hasAnnotationWithExactNumberOfArguments(function, 0)) { + if (checkJvmStaticAnnotation && !function.isTopLevel && !hasAnnotationWithExactNumberOfArguments(function, 0)) { return false } - return isMain(getFunctionDescriptor(function)) + return isMain(getFunctionDescriptor(function), checkJvmStaticAnnotation) } fun getMainFunction(module: ModuleDescriptor): FunctionDescriptor? = getMainFunction(module, module.getPackage(FqName.ROOT)) @@ -90,7 +91,7 @@ class MainFunctionDetector { companion object { - fun isMain(descriptor: DeclarationDescriptor): Boolean { + fun isMain(descriptor: DeclarationDescriptor, checkJvmStaticAnnotation: Boolean = true): Boolean { if (descriptor !is FunctionDescriptor) return false if (getJVMFunctionName(descriptor) != "main") { @@ -123,7 +124,7 @@ class MainFunctionDetector { val containingDeclaration = descriptor.containingDeclaration return containingDeclaration is ClassDescriptor && containingDeclaration.kind.isSingleton - && descriptor.hasJvmStaticAnnotation() + && (descriptor.hasJvmStaticAnnotation() || !checkJvmStaticAnnotation) } private fun getJVMFunctionName(functionDescriptor: FunctionDescriptor): String { diff --git a/idea/resources/intentionDescriptions/AddJvmStaticIntention/after.kt.template b/idea/resources/intentionDescriptions/AddJvmStaticIntention/after.kt.template new file mode 100644 index 00000000000..9fe19d07997 --- /dev/null +++ b/idea/resources/intentionDescriptions/AddJvmStaticIntention/after.kt.template @@ -0,0 +1,5 @@ +object Test { + @JvmStatic + fun main(args: Array) { + } +} \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/AddJvmStaticIntention/before.kt.template b/idea/resources/intentionDescriptions/AddJvmStaticIntention/before.kt.template new file mode 100644 index 00000000000..238796eef0e --- /dev/null +++ b/idea/resources/intentionDescriptions/AddJvmStaticIntention/before.kt.template @@ -0,0 +1,4 @@ +object Test { + fun main(args: Array) { + } +} \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/AddJvmStaticIntention/description.html b/idea/resources/intentionDescriptions/AddJvmStaticIntention/description.html new file mode 100644 index 00000000000..837b11d23a8 --- /dev/null +++ b/idea/resources/intentionDescriptions/AddJvmStaticIntention/description.html @@ -0,0 +1,5 @@ + + +This intention adds @JvmStatic annotation to main() method in an object. + + diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index ffb28e9e267..7744a836b5b 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -1288,6 +1288,11 @@ Kotlin + + org.jetbrains.kotlin.idea.intentions.AddJvmStaticIntention + Kotlin + + org.jetbrains.kotlin.idea.intentions.RemoveArgumentNameIntention Kotlin diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/AddJvmStaticIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/AddJvmStaticIntention.kt new file mode 100644 index 00000000000..95b3de7552f --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/AddJvmStaticIntention.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2010-2017 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.intentions + +import com.intellij.codeInsight.intention.LowPriorityAction +import com.intellij.openapi.editor.Editor +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.idea.MainFunctionDetector +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor +import org.jetbrains.kotlin.idea.util.addAnnotation +import org.jetbrains.kotlin.idea.util.findAnnotation +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtNamedFunction + +class AddJvmStaticIntention : SelfTargetingIntention( + KtNamedFunction::class.java, + "Add '@JvmStatic' annotation" +), LowPriorityAction { + + private val annotationFqName = FqName("kotlin.jvm.JvmStatic") + + override fun isApplicableTo(element: KtNamedFunction, caretOffset: Int): Boolean { + if (element.findAnnotation(annotationFqName) != null) return false + if (element.isTopLevel) return false + val detector = MainFunctionDetector { function -> + function.resolveToDescriptor() as FunctionDescriptor + } + return detector.isMain(element, false) + } + + override fun applyTo(element: KtNamedFunction, editor: Editor?) { + element.addAnnotation(annotationFqName) + } +} diff --git a/idea/testData/intentions/addJvmStatic/.intention b/idea/testData/intentions/addJvmStatic/.intention new file mode 100644 index 00000000000..dad9d3eacc4 --- /dev/null +++ b/idea/testData/intentions/addJvmStatic/.intention @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.intentions.AddJvmStaticIntention diff --git a/idea/testData/intentions/addJvmStatic/hasJvmStatic.kt b/idea/testData/intentions/addJvmStatic/hasJvmStatic.kt new file mode 100644 index 00000000000..f537bb8e504 --- /dev/null +++ b/idea/testData/intentions/addJvmStatic/hasJvmStatic.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME +// IS_APPLICABLE: false + +object Test { + @JvmStatic + fun main(args: Array) { + } +} \ No newline at end of file diff --git a/idea/testData/intentions/addJvmStatic/inAnonymousObject.kt b/idea/testData/intentions/addJvmStatic/inAnonymousObject.kt new file mode 100644 index 00000000000..916b5952659 --- /dev/null +++ b/idea/testData/intentions/addJvmStatic/inAnonymousObject.kt @@ -0,0 +1,6 @@ +// IS_APPLICABLE: false + +private fun test() = object { + fun main(args: Array) { + } +} \ No newline at end of file diff --git a/idea/testData/intentions/addJvmStatic/inClass.kt b/idea/testData/intentions/addJvmStatic/inClass.kt new file mode 100644 index 00000000000..226a56497b8 --- /dev/null +++ b/idea/testData/intentions/addJvmStatic/inClass.kt @@ -0,0 +1,6 @@ +// IS_APPLICABLE: false + +class Test { + fun main(args: Array) { + } +} \ No newline at end of file diff --git a/idea/testData/intentions/addJvmStatic/inCompanionObject.kt b/idea/testData/intentions/addJvmStatic/inCompanionObject.kt new file mode 100644 index 00000000000..81ddf128729 --- /dev/null +++ b/idea/testData/intentions/addJvmStatic/inCompanionObject.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME + +class Test { + companion object { + fun main(args: Array) { + } + } +} \ No newline at end of file diff --git a/idea/testData/intentions/addJvmStatic/inCompanionObject.kt.after b/idea/testData/intentions/addJvmStatic/inCompanionObject.kt.after new file mode 100644 index 00000000000..7b14edcab2d --- /dev/null +++ b/idea/testData/intentions/addJvmStatic/inCompanionObject.kt.after @@ -0,0 +1,9 @@ +// WITH_RUNTIME + +class Test { + companion object { + @JvmStatic + fun main(args: Array) { + } + } +} \ No newline at end of file diff --git a/idea/testData/intentions/addJvmStatic/inObjedct.kt b/idea/testData/intentions/addJvmStatic/inObjedct.kt new file mode 100644 index 00000000000..a0b87f27b85 --- /dev/null +++ b/idea/testData/intentions/addJvmStatic/inObjedct.kt @@ -0,0 +1,6 @@ +// WITH_RUNTIME + +object Test { + fun main(args: Array) { + } +} \ No newline at end of file diff --git a/idea/testData/intentions/addJvmStatic/inObjedct.kt.after b/idea/testData/intentions/addJvmStatic/inObjedct.kt.after new file mode 100644 index 00000000000..41077952c1f --- /dev/null +++ b/idea/testData/intentions/addJvmStatic/inObjedct.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +object Test { + @JvmStatic + fun main(args: Array) { + } +} \ No newline at end of file diff --git a/idea/testData/intentions/addJvmStatic/inTopLevel.kt b/idea/testData/intentions/addJvmStatic/inTopLevel.kt new file mode 100644 index 00000000000..df6ec2b953a --- /dev/null +++ b/idea/testData/intentions/addJvmStatic/inTopLevel.kt @@ -0,0 +1,4 @@ +// IS_APPLICABLE: false + +fun main(args: Array) { +} \ No newline at end of file diff --git a/idea/testData/intentions/addJvmStatic/mainJvmName.kt b/idea/testData/intentions/addJvmStatic/mainJvmName.kt new file mode 100644 index 00000000000..ebd68374e7d --- /dev/null +++ b/idea/testData/intentions/addJvmStatic/mainJvmName.kt @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +object Test { + @JvmName("main") + fun test(args: Array) { + } +} \ No newline at end of file diff --git a/idea/testData/intentions/addJvmStatic/mainJvmName.kt.after b/idea/testData/intentions/addJvmStatic/mainJvmName.kt.after new file mode 100644 index 00000000000..06e0126fbfd --- /dev/null +++ b/idea/testData/intentions/addJvmStatic/mainJvmName.kt.after @@ -0,0 +1,8 @@ +// WITH_RUNTIME + +object Test { + @JvmStatic + @JvmName("main") + fun test(args: Array) { + } +} \ No newline at end of file diff --git a/idea/testData/intentions/addJvmStatic/notMainJvmName.kt b/idea/testData/intentions/addJvmStatic/notMainJvmName.kt new file mode 100644 index 00000000000..f2b31b2ad06 --- /dev/null +++ b/idea/testData/intentions/addJvmStatic/notMainJvmName.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME +// IS_APPLICABLE: false + +object Test { + @JvmName("test") + fun main(args: Array) { + } +} \ No newline at end of file diff --git a/idea/testData/intentions/addJvmStatic/notMainMethod.kt b/idea/testData/intentions/addJvmStatic/notMainMethod.kt new file mode 100644 index 00000000000..f118e5af40b --- /dev/null +++ b/idea/testData/intentions/addJvmStatic/notMainMethod.kt @@ -0,0 +1,6 @@ +// IS_APPLICABLE: false + +object Test { + fun test(args: Array) { + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index 1751978403f..713262a2170 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -288,6 +288,69 @@ public class IntentionTestGenerated extends AbstractIntentionTest { } } + @TestMetadata("idea/testData/intentions/addJvmStatic") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class AddJvmStatic extends AbstractIntentionTest { + public void testAllFilesPresentInAddJvmStatic() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/addJvmStatic"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("hasJvmStatic.kt") + public void testHasJvmStatic() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/addJvmStatic/hasJvmStatic.kt"); + doTest(fileName); + } + + @TestMetadata("inAnonymousObject.kt") + public void testInAnonymousObject() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/addJvmStatic/inAnonymousObject.kt"); + doTest(fileName); + } + + @TestMetadata("inClass.kt") + public void testInClass() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/addJvmStatic/inClass.kt"); + doTest(fileName); + } + + @TestMetadata("inCompanionObject.kt") + public void testInCompanionObject() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/addJvmStatic/inCompanionObject.kt"); + doTest(fileName); + } + + @TestMetadata("inObjedct.kt") + public void testInObjedct() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/addJvmStatic/inObjedct.kt"); + doTest(fileName); + } + + @TestMetadata("inTopLevel.kt") + public void testInTopLevel() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/addJvmStatic/inTopLevel.kt"); + doTest(fileName); + } + + @TestMetadata("mainJvmName.kt") + public void testMainJvmName() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/addJvmStatic/mainJvmName.kt"); + doTest(fileName); + } + + @TestMetadata("notMainJvmName.kt") + public void testNotMainJvmName() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/addJvmStatic/notMainJvmName.kt"); + doTest(fileName); + } + + @TestMetadata("notMainMethod.kt") + public void testNotMainMethod() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/addJvmStatic/notMainMethod.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/intentions/addMissingDestructuring") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)