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)