diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 2250b797cf9..9d387285287 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -1104,6 +1104,11 @@ Kotlin + + org.jetbrains.kotlin.idea.intentions.AddJvmOverloadsIntention + Kotlin + + org.jetbrains.kotlin.idea.intentions.RemoveArgumentNameIntention Kotlin diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/AddJvmOverloadsIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/AddJvmOverloadsIntention.kt new file mode 100644 index 00000000000..644877fccce --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/AddJvmOverloadsIntention.kt @@ -0,0 +1,88 @@ +/* + * 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.intentions + +import com.intellij.codeInsight.intention.LowPriorityAction +import com.intellij.openapi.editor.Editor +import org.jetbrains.kotlin.idea.project.ProjectStructureUtil +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.* +import org.jetbrains.kotlin.psi.psiUtil.endOffset +import org.jetbrains.kotlin.psi.psiUtil.startOffset + +private val annotationFqName = FqName("kotlin.jvm.JvmOverloads") + +class AddJvmOverloadsIntention : SelfTargetingIntention( + KtModifierListOwner::class.java, "Add '@JvmOverloads' annotation" +), LowPriorityAction { + + override fun isApplicableTo(element: KtModifierListOwner, caretOffset: Int): Boolean { + val (targetName, parameters) = when (element) { + is KtNamedFunction -> { + val funKeyword = element.funKeyword ?: return false + val valueParameterList = element.valueParameterList ?: return false + if (caretOffset !in funKeyword.startOffset..valueParameterList.endOffset) { + return false + } + + "function '${element.name}'" to valueParameterList.parameters + } + is KtSecondaryConstructor -> { + val constructorKeyword = element.getConstructorKeyword() + val valueParameterList = element.valueParameterList ?: return false + if (caretOffset !in constructorKeyword.startOffset..valueParameterList.endOffset) { + return false + } + + "secondary constructor" to valueParameterList.parameters + } + is KtPrimaryConstructor -> { + val parameters = (element.valueParameterList ?: return false).parameters + + // For primary constructors with all default values, a zero-arg constructor is generated anyway. If there's only one + // parameter and it has a default value, the bytecode with and without @JvmOverloads is exactly the same. + if (parameters.singleOrNull()?.hasDefaultValue() == true) { + return false + } + + "primary constructor" to parameters + } + else -> return false + } + + text = "Add '@JvmOverloads' annotation to $targetName" + + return !ProjectStructureUtil.isJsKotlinModule(element.getContainingKtFile()) + && parameters.any { it.hasDefaultValue() } + && element.findAnnotation(annotationFqName) == null + } + + override fun applyTo(element: KtModifierListOwner, editor: Editor?) { + if (element is KtPrimaryConstructor) { + if (element.getConstructorKeyword() == null) { + element.addBefore(KtPsiFactory(element).createConstructorKeyword(), element.valueParameterList) + } + element.addAnnotation(annotationFqName, whiteSpaceText = " ") + } + else { + element.addAnnotation(annotationFqName) + } + } + +} \ No newline at end of file diff --git a/idea/testData/intentions/addJvmOverloads/.intention b/idea/testData/intentions/addJvmOverloads/.intention new file mode 100644 index 00000000000..638ffe104c1 --- /dev/null +++ b/idea/testData/intentions/addJvmOverloads/.intention @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.intentions.AddJvmOverloadsIntention diff --git a/idea/testData/intentions/addJvmOverloads/alreadyHasAnnotation.kt b/idea/testData/intentions/addJvmOverloads/alreadyHasAnnotation.kt new file mode 100644 index 00000000000..90f68d38c0e --- /dev/null +++ b/idea/testData/intentions/addJvmOverloads/alreadyHasAnnotation.kt @@ -0,0 +1,6 @@ +// WITH_RUNTIME +// IS_APPLICABLE: false + +@kotlin.jvm.JvmOverloads +fun foo(a: String = "", b: Int) { +} \ No newline at end of file diff --git a/idea/testData/intentions/addJvmOverloads/method.kt b/idea/testData/intentions/addJvmOverloads/method.kt new file mode 100644 index 00000000000..7de11fe993e --- /dev/null +++ b/idea/testData/intentions/addJvmOverloads/method.kt @@ -0,0 +1,5 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Add '@JvmOverloads' annotation to function 'foo'" + +fun foo(a: String = "") { +} \ No newline at end of file diff --git a/idea/testData/intentions/addJvmOverloads/method.kt.after b/idea/testData/intentions/addJvmOverloads/method.kt.after new file mode 100644 index 00000000000..c10e1bbbc06 --- /dev/null +++ b/idea/testData/intentions/addJvmOverloads/method.kt.after @@ -0,0 +1,6 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Add '@JvmOverloads' annotation to function 'foo'" + +@JvmOverloads +fun foo(a: String = "") { +} \ No newline at end of file diff --git a/idea/testData/intentions/addJvmOverloads/noDefaultParams.kt b/idea/testData/intentions/addJvmOverloads/noDefaultParams.kt new file mode 100644 index 00000000000..a559475b452 --- /dev/null +++ b/idea/testData/intentions/addJvmOverloads/noDefaultParams.kt @@ -0,0 +1,4 @@ +// IS_APPLICABLE: false + +fun foo(a: String, b: Int) { +} \ No newline at end of file diff --git a/idea/testData/intentions/addJvmOverloads/primaryConstructor.kt b/idea/testData/intentions/addJvmOverloads/primaryConstructor.kt new file mode 100644 index 00000000000..a78b3dff649 --- /dev/null +++ b/idea/testData/intentions/addJvmOverloads/primaryConstructor.kt @@ -0,0 +1,4 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Add '@JvmOverloads' annotation to primary constructor" + +class A(val a: String = "", b: Int = 0) \ No newline at end of file diff --git a/idea/testData/intentions/addJvmOverloads/primaryConstructor.kt.after b/idea/testData/intentions/addJvmOverloads/primaryConstructor.kt.after new file mode 100644 index 00000000000..b63cb544329 --- /dev/null +++ b/idea/testData/intentions/addJvmOverloads/primaryConstructor.kt.after @@ -0,0 +1,4 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Add '@JvmOverloads' annotation to primary constructor" + +class A @JvmOverloads constructor(val a: String = "", b: Int = 0) \ No newline at end of file diff --git a/idea/testData/intentions/addJvmOverloads/primaryConstructorOneWithDefault.kt b/idea/testData/intentions/addJvmOverloads/primaryConstructorOneWithDefault.kt new file mode 100644 index 00000000000..f37b8cf10d0 --- /dev/null +++ b/idea/testData/intentions/addJvmOverloads/primaryConstructorOneWithDefault.kt @@ -0,0 +1,4 @@ +// IS_APPLICABLE: false + +class A(a: String = "") { +} \ No newline at end of file diff --git a/idea/testData/intentions/addJvmOverloads/primaryConstructorWithConstructorKeyword.kt b/idea/testData/intentions/addJvmOverloads/primaryConstructorWithConstructorKeyword.kt new file mode 100644 index 00000000000..b5cc78da576 --- /dev/null +++ b/idea/testData/intentions/addJvmOverloads/primaryConstructorWithConstructorKeyword.kt @@ -0,0 +1,4 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Add '@JvmOverloads' annotation to primary constructor" + +class A constructor(val a: String = "", b: Int) \ No newline at end of file diff --git a/idea/testData/intentions/addJvmOverloads/primaryConstructorWithConstructorKeyword.kt.after b/idea/testData/intentions/addJvmOverloads/primaryConstructorWithConstructorKeyword.kt.after new file mode 100644 index 00000000000..a4fa5c24912 --- /dev/null +++ b/idea/testData/intentions/addJvmOverloads/primaryConstructorWithConstructorKeyword.kt.after @@ -0,0 +1,4 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Add '@JvmOverloads' annotation to primary constructor" + +class A @JvmOverloads constructor(val a: String = "", b: Int) \ No newline at end of file diff --git a/idea/testData/intentions/addJvmOverloads/secondaryConstructor.kt b/idea/testData/intentions/addJvmOverloads/secondaryConstructor.kt new file mode 100644 index 00000000000..4fa3577d782 --- /dev/null +++ b/idea/testData/intentions/addJvmOverloads/secondaryConstructor.kt @@ -0,0 +1,6 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Add '@JvmOverloads' annotation to secondary constructor" + +class A { + constructor(a: String = "", b: Int) +} \ No newline at end of file diff --git a/idea/testData/intentions/addJvmOverloads/secondaryConstructor.kt.after b/idea/testData/intentions/addJvmOverloads/secondaryConstructor.kt.after new file mode 100644 index 00000000000..0d0930e804c --- /dev/null +++ b/idea/testData/intentions/addJvmOverloads/secondaryConstructor.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Add '@JvmOverloads' annotation to secondary constructor" + +class A { + @JvmOverloads + constructor(a: String = "", b: Int) +} \ 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 17caa252133..46b5d53b0e1 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -194,6 +194,57 @@ public class IntentionTestGenerated extends AbstractIntentionTest { } } + @TestMetadata("idea/testData/intentions/addJvmOverloads") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class AddJvmOverloads extends AbstractIntentionTest { + public void testAllFilesPresentInAddJvmOverloads() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/addJvmOverloads"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); + } + + @TestMetadata("alreadyHasAnnotation.kt") + public void testAlreadyHasAnnotation() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/addJvmOverloads/alreadyHasAnnotation.kt"); + doTest(fileName); + } + + @TestMetadata("method.kt") + public void testMethod() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/addJvmOverloads/method.kt"); + doTest(fileName); + } + + @TestMetadata("noDefaultParams.kt") + public void testNoDefaultParams() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/addJvmOverloads/noDefaultParams.kt"); + doTest(fileName); + } + + @TestMetadata("primaryConstructor.kt") + public void testPrimaryConstructor() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/addJvmOverloads/primaryConstructor.kt"); + doTest(fileName); + } + + @TestMetadata("primaryConstructorOneWithDefault.kt") + public void testPrimaryConstructorOneWithDefault() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/addJvmOverloads/primaryConstructorOneWithDefault.kt"); + doTest(fileName); + } + + @TestMetadata("primaryConstructorWithConstructorKeyword.kt") + public void testPrimaryConstructorWithConstructorKeyword() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/addJvmOverloads/primaryConstructorWithConstructorKeyword.kt"); + doTest(fileName); + } + + @TestMetadata("secondaryConstructor.kt") + public void testSecondaryConstructor() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/addJvmOverloads/secondaryConstructor.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/intentions/addNameToArgument") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)