diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index a8ef8024c22..d5c3697236b 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -2480,6 +2480,7 @@ + diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/KotlinCommonModifications.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/KotlinCommonModifications.kt new file mode 100644 index 00000000000..e1684bb35ad --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/KotlinCommonModifications.kt @@ -0,0 +1,55 @@ +/* + * 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.* +import com.intellij.psi.PsiModifier +import org.jetbrains.kotlin.asJava.elements.KtLightElement +import org.jetbrains.kotlin.idea.quickfix.AddModifierFix +import org.jetbrains.kotlin.idea.quickfix.RemoveModifierFix +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.KtModifierListOwner +import org.jetbrains.uast.UDeclaration + + +class KotlinCommonModifications : JvmCommonIntentionActionsFactory() { + override fun createChangeModifierAction(declaration: UDeclaration, modifier: String, shouldPresent: Boolean): IntentionAction? { + val kModifierOwner = (declaration.psi as? KtLightElement<*, *>?)?.kotlinOrigin as? KtModifierListOwner? + ?: throw IllegalArgumentException("$declaration is expected to contain KtLightElement with KtModifierListOwner") + + val (kToken, shouldPresentMapped) = if (PsiModifier.FINAL == modifier) + KtTokens.OPEN_KEYWORD to !shouldPresent + else + javaPsiModifiersMapping[modifier] to shouldPresent + + if (kToken == null) return null + return if (shouldPresentMapped) + AddModifierFix.createIfApplicable(kModifierOwner, kToken) + else + RemoveModifierFix(kModifierOwner, kToken, false) + } + + companion object { + val javaPsiModifiersMapping = mapOf( + PsiModifier.PRIVATE to KtTokens.PRIVATE_KEYWORD, + PsiModifier.PUBLIC to KtTokens.PUBLIC_KEYWORD, + PsiModifier.PROTECTED to KtTokens.PUBLIC_KEYWORD, + PsiModifier.ABSTRACT to KtTokens.ABSTRACT_KEYWORD + ) + } + +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/AddModifierFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/AddModifierFix.kt index 82369aa9eb0..bc3aa95fd37 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/AddModifierFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/AddModifierFix.kt @@ -100,21 +100,24 @@ open class AddModifierFix( return object : KotlinSingleIntentionActionFactory() { public override fun createAction(diagnostic: Diagnostic): IntentionAction? { val modifierListOwner = QuickFixUtil.getParentElementOfType(diagnostic, modifierOwnerClass) ?: return null - - if (modifier == KtTokens.ABSTRACT_KEYWORD) { - if (modifierListOwner is KtObjectDeclaration) return null - if (modifierListOwner is KtEnumEntry) return null - if (modifierListOwner is KtDeclaration && modifierListOwner !is KtClass) { - val parentClassOrObject = modifierListOwner.containingClassOrObject ?: return null - if (parentClassOrObject is KtObjectDeclaration) return null - if (parentClassOrObject is KtEnumEntry) return null - } - } - - return AddModifierFix(modifierListOwner, modifier) + return createIfApplicable(modifierListOwner, modifier) } } } + + fun createIfApplicable(modifierListOwner: KtModifierListOwner, modifier: KtModifierKeywordToken): AddModifierFix? { + if (modifier == ABSTRACT_KEYWORD || modifier == OPEN_KEYWORD) { + if (modifierListOwner is KtObjectDeclaration) return null + if (modifierListOwner is KtEnumEntry) return null + if (modifierListOwner is KtDeclaration && modifierListOwner !is KtClass) { + val parentClassOrObject = modifierListOwner.containingClassOrObject ?: return null + if (parentClassOrObject is KtObjectDeclaration) return null + if (parentClassOrObject is KtEnumEntry) return null + } + } + return AddModifierFix(modifierListOwner, modifier) + } + } object MakeClassOpenFactory : KotlinSingleIntentionActionFactory() { diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/CommonModificationsTest.kt b/idea/tests/org/jetbrains/kotlin/idea/quickfix/CommonModificationsTest.kt new file mode 100644 index 00000000000..44ceba9b303 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/CommonModificationsTest.kt @@ -0,0 +1,98 @@ +/* + * 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.quickfix + +import com.intellij.codeInsight.intention.JvmCommonIntentionActionsFactory +import com.intellij.lang.Language +import com.intellij.openapi.components.ServiceManager +import com.intellij.psi.PsiModifier +import com.intellij.testFramework.fixtures.CodeInsightTestFixture +import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase +import org.jetbrains.uast.UDeclaration +import org.jetbrains.uast.UastContext +import org.jetbrains.uast.convert +import org.junit.Assert + + +class CommonModificationsTest : LightPlatformCodeInsightFixtureTestCase() { + + fun testMakeNotFinal() { + myFixture.configureByText("foo.kt", """ + class Foo { + fun bar(){} + } + """) + + myFixture.launchAction(codeModifications.createChangeModifierAction(uastElementAtCaret(myFixture), PsiModifier.FINAL, false )!!) + myFixture.checkResult(""" + class Foo { + open fun bar(){} + } + """) + } + + fun testMakePrivate() { + myFixture.configureByText("foo.kt", """ + class Foo { + fun bar(){} + } + """) + + myFixture.launchAction(codeModifications.createChangeModifierAction(uastElementAtCaret(myFixture), PsiModifier.PRIVATE, true )!!) + myFixture.checkResult(""" + private class Foo { + fun bar(){} + } + """) + } + + fun testMakeNotPrivate() { + myFixture.configureByText("foo.kt", """ + private class Foo { + fun bar(){} + } + """.trim()) + + myFixture.launchAction(codeModifications.createChangeModifierAction(uastElementAtCaret(myFixture), PsiModifier.PRIVATE, false )!!) + myFixture.checkResult(""" + class Foo { + fun bar(){} + } + """.trim(), true) + } + + fun testDontMakeFunInObjectsOpen() { + myFixture.configureByText("foo.kt", """ + object Foo { + fun bar(){} + } + """.trim()) + Assert.assertNull(codeModifications.createChangeModifierAction(uastElementAtCaret(myFixture), PsiModifier.FINAL, false)) + } + + private fun uastElementAtCaret(myFixture: CodeInsightTestFixture): UDeclaration { + val elementAtCaret = myFixture.elementAtCaret + val uastContext = ServiceManager.getService(elementAtCaret.project, UastContext::class.java) ?: error("UastContext not found") + val uastLanguagePlugin = uastContext.findPlugin(elementAtCaret) ?: error("Language plugin was not found for $this (${this.javaClass.name})") + return uastLanguagePlugin.convert(elementAtCaret, null) + } + + private val codeModifications: JvmCommonIntentionActionsFactory + get() = JvmCommonIntentionActionsFactory.forLanguage(Language.findLanguageByID("kotlin")!!) + +} +