diff --git a/idea/resources/META-INF/plugin-common.xml b/idea/resources/META-INF/plugin-common.xml index 60f2a0a5324..4cc19631437 100644 --- a/idea/resources/META-INF/plugin-common.xml +++ b/idea/resources/META-INF/plugin-common.xml @@ -1644,6 +1644,11 @@ Kotlin + + org.jetbrains.kotlin.idea.intentions.AddMissingClassKeywordIntention + Kotlin + + class E \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/AddMissingClassKeywordIntention/before.kt.template b/idea/resources/intentionDescriptions/AddMissingClassKeywordIntention/before.kt.template new file mode 100644 index 00000000000..f593bae5de2 --- /dev/null +++ b/idea/resources/intentionDescriptions/AddMissingClassKeywordIntention/before.kt.template @@ -0,0 +1 @@ +enum E \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/AddMissingClassKeywordIntention/description.html b/idea/resources/intentionDescriptions/AddMissingClassKeywordIntention/description.html new file mode 100644 index 00000000000..0dbef2ce12a --- /dev/null +++ b/idea/resources/intentionDescriptions/AddMissingClassKeywordIntention/description.html @@ -0,0 +1,5 @@ + + +This intention adds a missing class keyword. + + diff --git a/idea/resources/messages/KotlinBundle.properties b/idea/resources/messages/KotlinBundle.properties index 871c9431ff6..a96693bb826 100644 --- a/idea/resources/messages/KotlinBundle.properties +++ b/idea/resources/messages/KotlinBundle.properties @@ -2195,5 +2195,5 @@ move.refactoring.error.text.cannot.perform.refactoring.since.the.following.files kotlin.script.definitions.model.name.is.enabled=Is Enabled kotlin.script.definitions.model.name.pattern.extension=Pattern/Extension kotlin.script.definitions.model.name.name=Name -codestyle.name.kotlin=Kotlin +add.missing.class.keyword=Add missing 'class' keyword fix.move.typealias.to.top.level=Move typealias to top level \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/AddMissingClassKeywordIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/AddMissingClassKeywordIntention.kt new file mode 100644 index 00000000000..3d642613238 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/AddMissingClassKeywordIntention.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.intentions + +import com.intellij.openapi.editor.Editor +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiErrorElement +import org.jetbrains.kotlin.idea.KotlinBundle +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtModifierList +import org.jetbrains.kotlin.psi.psiUtil.getPrevSiblingIgnoringWhitespaceAndComments +import org.jetbrains.kotlin.psi.psiUtil.startOffset + +class AddMissingClassKeywordIntention : SelfTargetingIntention( + PsiElement::class.java, + KotlinBundle.message("add.missing.class.keyword") +) { + companion object { + private val targetKeywords = listOf( + KtTokens.ANNOTATION_KEYWORD, + KtTokens.DATA_KEYWORD, + KtTokens.SEALED_KEYWORD, + KtTokens.INNER_KEYWORD + ) + } + + override fun isApplicableTo(element: PsiElement, caretOffset: Int): Boolean { + if (element.node?.elementType != KtTokens.IDENTIFIER) return false + val ktClass = element.parent as? KtClass + if (ktClass == null) { + val errorElement = element.parent as? PsiErrorElement ?: return false + val modifierList = errorElement.getPrevSiblingIgnoringWhitespaceAndComments() as? KtModifierList ?: return false + return targetKeywords.any { modifierList.hasModifier(it) } + } else { + return ktClass.isEnum() && ktClass.getClassKeyword() == null + } + } + + override fun applyTo(element: PsiElement, editor: Editor?) { + val document = editor?.document ?: return + val targetElement = (element.parent as? PsiErrorElement) ?: element + document.insertString(targetElement.startOffset, "class ") + PsiDocumentManager.getInstance(element.project).commitDocument(document) + } +} diff --git a/idea/testData/intentions/addMissingClassKeyword/.intention b/idea/testData/intentions/addMissingClassKeyword/.intention new file mode 100644 index 00000000000..12fde8d657f --- /dev/null +++ b/idea/testData/intentions/addMissingClassKeyword/.intention @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.intentions.AddMissingClassKeywordIntention diff --git a/idea/testData/intentions/addMissingClassKeyword/abstract.kt b/idea/testData/intentions/addMissingClassKeyword/abstract.kt new file mode 100644 index 00000000000..4a32fdc5736 --- /dev/null +++ b/idea/testData/intentions/addMissingClassKeyword/abstract.kt @@ -0,0 +1,5 @@ +// IS_APPLICABLE: false +// DISABLE-ERRORS +abstract Foo { + abstract fun foo() +} \ No newline at end of file diff --git a/idea/testData/intentions/addMissingClassKeyword/annottion.kt b/idea/testData/intentions/addMissingClassKeyword/annottion.kt new file mode 100644 index 00000000000..1534add0f45 --- /dev/null +++ b/idea/testData/intentions/addMissingClassKeyword/annottion.kt @@ -0,0 +1 @@ +annotation Foo \ No newline at end of file diff --git a/idea/testData/intentions/addMissingClassKeyword/annottion.kt.after b/idea/testData/intentions/addMissingClassKeyword/annottion.kt.after new file mode 100644 index 00000000000..2e437ae4690 --- /dev/null +++ b/idea/testData/intentions/addMissingClassKeyword/annottion.kt.after @@ -0,0 +1 @@ +annotation class Foo \ No newline at end of file diff --git a/idea/testData/intentions/addMissingClassKeyword/data.kt b/idea/testData/intentions/addMissingClassKeyword/data.kt new file mode 100644 index 00000000000..9533eec96f2 --- /dev/null +++ b/idea/testData/intentions/addMissingClassKeyword/data.kt @@ -0,0 +1,7 @@ +// DISABLE-ERRORS +annotation class Ann + +@Ann +private data Foo(val s: String) { + fun foo() {} +} \ No newline at end of file diff --git a/idea/testData/intentions/addMissingClassKeyword/data.kt.after b/idea/testData/intentions/addMissingClassKeyword/data.kt.after new file mode 100644 index 00000000000..cf3c351c97c --- /dev/null +++ b/idea/testData/intentions/addMissingClassKeyword/data.kt.after @@ -0,0 +1,7 @@ +// DISABLE-ERRORS +annotation class Ann + +@Ann +private data class Foo(val s: String) { + fun foo() {} +} \ No newline at end of file diff --git a/idea/testData/intentions/addMissingClassKeyword/dataHasClassKeyword.kt b/idea/testData/intentions/addMissingClassKeyword/dataHasClassKeyword.kt new file mode 100644 index 00000000000..8447343aba4 --- /dev/null +++ b/idea/testData/intentions/addMissingClassKeyword/dataHasClassKeyword.kt @@ -0,0 +1,2 @@ +// IS_APPLICABLE: false +private data class Foo(val s: String) \ No newline at end of file diff --git a/idea/testData/intentions/addMissingClassKeyword/enum.kt b/idea/testData/intentions/addMissingClassKeyword/enum.kt new file mode 100644 index 00000000000..2868149bdd3 --- /dev/null +++ b/idea/testData/intentions/addMissingClassKeyword/enum.kt @@ -0,0 +1,3 @@ +enum Foo(val s: String) { + A("a"), B("b") +} \ No newline at end of file diff --git a/idea/testData/intentions/addMissingClassKeyword/enum.kt.after b/idea/testData/intentions/addMissingClassKeyword/enum.kt.after new file mode 100644 index 00000000000..e689ade0ad5 --- /dev/null +++ b/idea/testData/intentions/addMissingClassKeyword/enum.kt.after @@ -0,0 +1,3 @@ +enum class Foo(val s: String) { + A("a"), B("b") +} \ No newline at end of file diff --git a/idea/testData/intentions/addMissingClassKeyword/enumHasClassKeyword.kt b/idea/testData/intentions/addMissingClassKeyword/enumHasClassKeyword.kt new file mode 100644 index 00000000000..64198a38a7c --- /dev/null +++ b/idea/testData/intentions/addMissingClassKeyword/enumHasClassKeyword.kt @@ -0,0 +1,4 @@ +// IS_APPLICABLE: false +enum class Foo(val s: String) { + A("a"), B("b") +} \ No newline at end of file diff --git a/idea/testData/intentions/addMissingClassKeyword/inner.kt b/idea/testData/intentions/addMissingClassKeyword/inner.kt new file mode 100644 index 00000000000..8a806a93b4a --- /dev/null +++ b/idea/testData/intentions/addMissingClassKeyword/inner.kt @@ -0,0 +1,3 @@ +class C { + inner Foo +} diff --git a/idea/testData/intentions/addMissingClassKeyword/inner.kt.after b/idea/testData/intentions/addMissingClassKeyword/inner.kt.after new file mode 100644 index 00000000000..abbc07f69bb --- /dev/null +++ b/idea/testData/intentions/addMissingClassKeyword/inner.kt.after @@ -0,0 +1,3 @@ +class C { + inner class Foo +} diff --git a/idea/testData/intentions/addMissingClassKeyword/sealed.kt b/idea/testData/intentions/addMissingClassKeyword/sealed.kt new file mode 100644 index 00000000000..d0dfb094e22 --- /dev/null +++ b/idea/testData/intentions/addMissingClassKeyword/sealed.kt @@ -0,0 +1,4 @@ +// DISABLE-ERRORS +sealed Foo { + abstract fun foo() +} \ No newline at end of file diff --git a/idea/testData/intentions/addMissingClassKeyword/sealed.kt.after b/idea/testData/intentions/addMissingClassKeyword/sealed.kt.after new file mode 100644 index 00000000000..0fe8c7341a8 --- /dev/null +++ b/idea/testData/intentions/addMissingClassKeyword/sealed.kt.after @@ -0,0 +1,4 @@ +// DISABLE-ERRORS +sealed class Foo { + abstract fun foo() +} \ 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 5671edd2a65..b421e904d64 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -1130,6 +1130,59 @@ public class IntentionTestGenerated extends AbstractIntentionTest { } } + @TestMetadata("idea/testData/intentions/addMissingClassKeyword") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class AddMissingClassKeyword extends AbstractIntentionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + @TestMetadata("abstract.kt") + public void testAbstract() throws Exception { + runTest("idea/testData/intentions/addMissingClassKeyword/abstract.kt"); + } + + public void testAllFilesPresentInAddMissingClassKeyword() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/addMissingClassKeyword"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), true); + } + + @TestMetadata("annottion.kt") + public void testAnnottion() throws Exception { + runTest("idea/testData/intentions/addMissingClassKeyword/annottion.kt"); + } + + @TestMetadata("data.kt") + public void testData() throws Exception { + runTest("idea/testData/intentions/addMissingClassKeyword/data.kt"); + } + + @TestMetadata("dataHasClassKeyword.kt") + public void testDataHasClassKeyword() throws Exception { + runTest("idea/testData/intentions/addMissingClassKeyword/dataHasClassKeyword.kt"); + } + + @TestMetadata("enum.kt") + public void testEnum() throws Exception { + runTest("idea/testData/intentions/addMissingClassKeyword/enum.kt"); + } + + @TestMetadata("enumHasClassKeyword.kt") + public void testEnumHasClassKeyword() throws Exception { + runTest("idea/testData/intentions/addMissingClassKeyword/enumHasClassKeyword.kt"); + } + + @TestMetadata("inner.kt") + public void testInner() throws Exception { + runTest("idea/testData/intentions/addMissingClassKeyword/inner.kt"); + } + + @TestMetadata("sealed.kt") + public void testSealed() throws Exception { + runTest("idea/testData/intentions/addMissingClassKeyword/sealed.kt"); + } + } + @TestMetadata("idea/testData/intentions/addMissingDestructuring") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)