diff --git a/idea/resources/inspectionDescriptions/RedundantSemicolon.html b/idea/resources/inspectionDescriptions/RedundantSemicolon.html new file mode 100644 index 00000000000..1150cb1df58 --- /dev/null +++ b/idea/resources/inspectionDescriptions/RedundantSemicolon.html @@ -0,0 +1,5 @@ + + +This inspection reports redundant semicolon (';') token which is not required in Kotlin and may be removed. + + diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index be702947101..62a9134b303 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -1461,6 +1461,14 @@ level="WARNING" /> + + diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantSemicolonInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantSemicolonInspection.kt new file mode 100644 index 00000000000..9c57d106dd9 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantSemicolonInspection.kt @@ -0,0 +1,69 @@ +/* + * 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.inspections + +import com.intellij.codeInspection.* +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiComment +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiElementVisitor +import com.intellij.psi.PsiWhiteSpace +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.KtEnumEntry +import org.jetbrains.kotlin.psi.psiUtil.nextLeaf + +class RedundantSemicolonInspection : AbstractKotlinInspection(), CleanupLocalInspectionTool { + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor { + return object : PsiElementVisitor() { + override fun visitElement(element: PsiElement) { + super.visitElement(element) + + if (element.node.elementType == KtTokens.SEMICOLON && isRedundant(element)) { + holder.registerProblem(element, + "Redundant semicolon", + ProblemHighlightType.LIKE_UNUSED_SYMBOL, + Fix) + } + } + } + } + + private fun isRedundant(semicolon: PsiElement): Boolean { + val nextLeaf = semicolon.nextLeaf { it !is PsiWhiteSpace && it !is PsiComment || it.isLineBreak() } + val isAtEndOfLine = nextLeaf == null || nextLeaf.isLineBreak() + if (!isAtEndOfLine) return false + + if (semicolon.parent is KtEnumEntry) return false + + if (nextLeaf?.nextLeaf { it !is PsiComment }?.node?.elementType == KtTokens.LBRACE) { + return false // case with statement starting with '{' and call on the previous line + } + + return true + } + + private fun PsiElement?.isLineBreak() = this is PsiWhiteSpace && textContains('\n') + + private object Fix : LocalQuickFix { + override fun getName() = "Remove redundant semicolon" + override fun getFamilyName() = name + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + descriptor.psiElement.delete() + } + } +} diff --git a/idea/testData/inspections/redundantSemicolon/Test.kt b/idea/testData/inspections/redundantSemicolon/Test.kt new file mode 100644 index 00000000000..3165ce05ecb --- /dev/null +++ b/idea/testData/inspections/redundantSemicolon/Test.kt @@ -0,0 +1,32 @@ +package p; // redundant + +import java.util.ArrayList; // redundant + +class A { + fun foo() { + print(1); print(2); // redundant + }; // redundant + + fun bar() {} +}; // redundant + +enum class E { + A, B; // let's not consider it to be redundant +} + +fun foo(p: Int) { + if (p > 0) foo(); // not redundant! + + { p: Int -> + print(p) + }.doIt() +} + +fun ((Int) -> Unit).doIt() { + this.invoke(1); // redundant +} + +fun bar() { + a(); // redundant + b() +} \ No newline at end of file diff --git a/idea/testData/inspections/redundantSemicolon/inspectionData/expected.xml b/idea/testData/inspections/redundantSemicolon/inspectionData/expected.xml new file mode 100644 index 00000000000..27fbe1a90b9 --- /dev/null +++ b/idea/testData/inspections/redundantSemicolon/inspectionData/expected.xml @@ -0,0 +1,64 @@ + + + Test.kt + 1 + light_idea_test_case + + Redundant semicolon + Redundant semicolon + + + + Test.kt + 3 + light_idea_test_case + + Redundant semicolon + Redundant semicolon + + + + Test.kt + 7 + light_idea_test_case + + Redundant semicolon + Redundant semicolon + + + + Test.kt + 8 + light_idea_test_case + + Redundant semicolon + Redundant semicolon + + + + Test.kt + 11 + light_idea_test_case + + Redundant semicolon + Redundant semicolon + + + + Test.kt + 26 + light_idea_test_case + + Redundant semicolon + Redundant semicolon + + + + Test.kt + 30 + light_idea_test_case + + Redundant semicolon + Redundant semicolon + + diff --git a/idea/testData/inspections/redundantSemicolon/inspectionData/inspections.test b/idea/testData/inspections/redundantSemicolon/inspectionData/inspections.test new file mode 100644 index 00000000000..3ced7a40ea6 --- /dev/null +++ b/idea/testData/inspections/redundantSemicolon/inspectionData/inspections.test @@ -0,0 +1 @@ +// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.RedundantSemicolonInspection diff --git a/idea/testData/quickfix/redundantSemicolon/.inspection b/idea/testData/quickfix/redundantSemicolon/.inspection new file mode 100644 index 00000000000..6160b7b2659 --- /dev/null +++ b/idea/testData/quickfix/redundantSemicolon/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.RedundantSemicolonInspection \ No newline at end of file diff --git a/idea/testData/quickfix/redundantSemicolon/afterImport.kt b/idea/testData/quickfix/redundantSemicolon/afterImport.kt new file mode 100644 index 00000000000..4c037808b4d --- /dev/null +++ b/idea/testData/quickfix/redundantSemicolon/afterImport.kt @@ -0,0 +1,5 @@ +// "Remove redundant semicolon" "true" +import kotlin.*; + +fun foo() { +} diff --git a/idea/testData/quickfix/redundantSemicolon/afterImport.kt.after b/idea/testData/quickfix/redundantSemicolon/afterImport.kt.after new file mode 100644 index 00000000000..471225144f3 --- /dev/null +++ b/idea/testData/quickfix/redundantSemicolon/afterImport.kt.after @@ -0,0 +1,5 @@ +// "Remove redundant semicolon" "true" +import kotlin.* + +fun foo() { +} diff --git a/idea/testData/quickfix/redundantSemicolon/afterStatement.kt b/idea/testData/quickfix/redundantSemicolon/afterStatement.kt new file mode 100644 index 00000000000..863f6485d94 --- /dev/null +++ b/idea/testData/quickfix/redundantSemicolon/afterStatement.kt @@ -0,0 +1,8 @@ +// "Remove redundant semicolon" "true" +fun foo() { + a(); + b() +} + +fun a(){} +fun b(){} \ No newline at end of file diff --git a/idea/testData/quickfix/redundantSemicolon/afterStatement.kt.after b/idea/testData/quickfix/redundantSemicolon/afterStatement.kt.after new file mode 100644 index 00000000000..3f87dbad534 --- /dev/null +++ b/idea/testData/quickfix/redundantSemicolon/afterStatement.kt.after @@ -0,0 +1,8 @@ +// "Remove redundant semicolon" "true" +fun foo() { + a() + b() +} + +fun a(){} +fun b(){} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java index a1612e28b29..1e808d85295 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java @@ -172,6 +172,12 @@ public class InspectionTestGenerated extends AbstractInspectionTest { doTest(fileName); } + @TestMetadata("redundantSemicolon/inspectionData/inspections.test") + public void testRedundantSemicolon_inspectionData_Inspections_test() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/redundantSemicolon/inspectionData/inspections.test"); + doTest(fileName); + } + @TestMetadata("redundantVisibilityModifier/inspectionData/inspections.test") public void testRedundantVisibilityModifier_inspectionData_Inspections_test() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/redundantVisibilityModifier/inspectionData/inspections.test"); diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index 44f284ee5dd..58e539a5f81 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -6344,6 +6344,27 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { } } + @TestMetadata("idea/testData/quickfix/redundantSemicolon") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class RedundantSemicolon extends AbstractQuickFixTest { + @TestMetadata("afterImport.kt") + public void testAfterImport() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/redundantSemicolon/afterImport.kt"); + doTest(fileName); + } + + @TestMetadata("afterStatement.kt") + public void testAfterStatement() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/redundantSemicolon/afterStatement.kt"); + doTest(fileName); + } + + public void testAllFilesPresentInRedundantSemicolon() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/redundantSemicolon"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); + } + } + @TestMetadata("idea/testData/quickfix/redundantVisibilityModifier") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)