diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/DescriptorResolver.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/DescriptorResolver.java
index d42ae7149d1..02018afa188 100644
--- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/DescriptorResolver.java
+++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/DescriptorResolver.java
@@ -758,8 +758,7 @@ public class DescriptorResolver {
return propertyDescriptor;
}
- /*package*/
- static boolean hasBody(KtProperty property) {
+ public static boolean hasBody(KtProperty property) {
boolean hasBody = property.hasDelegateExpressionOrInitializer();
if (!hasBody) {
KtPropertyAccessor getter = property.getGetter();
diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/psiModificationUtils.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/psiModificationUtils.kt
index c224c710abf..a7219e3a1d2 100644
--- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/psiModificationUtils.kt
+++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/psiModificationUtils.kt
@@ -26,9 +26,11 @@ import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
+import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.getLambdaArgumentName
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierType
import org.jetbrains.kotlin.resolve.BindingContext
+import org.jetbrains.kotlin.resolve.DescriptorResolver
import org.jetbrains.kotlin.resolve.OverridingUtil
import org.jetbrains.kotlin.resolve.calls.callUtil.getValueArgumentsInParentheses
@@ -188,6 +190,27 @@ fun KtDeclaration.implicitVisibility(): KtModifierKeywordToken? {
return defaultVisibilityKeyword
}
+fun KtDeclaration.implicitModality(): KtModifierKeywordToken {
+ if (this is KtClassOrObject) return KtTokens.FINAL_KEYWORD
+ val klass = containingClassOrObject ?: return KtTokens.FINAL_KEYWORD
+ if (hasModifier(KtTokens.OVERRIDE_KEYWORD)) {
+ if (klass.hasModifier(KtTokens.ABSTRACT_KEYWORD) ||
+ klass.hasModifier(KtTokens.OPEN_KEYWORD) ||
+ klass.hasModifier(KtTokens.SEALED_KEYWORD)) {
+ return KtTokens.OPEN_KEYWORD
+ }
+ }
+ if (klass is KtClass && klass.isInterface() && !hasModifier(KtTokens.PRIVATE_KEYWORD)) {
+ val hasBody = when (this) {
+ is KtProperty -> DescriptorResolver.hasBody(this)
+ is KtFunction -> hasBody()
+ else -> false
+ }
+ return if (hasBody) KtTokens.OPEN_KEYWORD else KtTokens.ABSTRACT_KEYWORD
+ }
+ return KtTokens.FINAL_KEYWORD
+}
+
fun KtSecondaryConstructor.getOrCreateBody(): KtBlockExpression {
bodyExpression?.let { return it }
diff --git a/idea/resources/inspectionDescriptions/RedundantModalityModifier.html b/idea/resources/inspectionDescriptions/RedundantModalityModifier.html
new file mode 100644
index 00000000000..90feef56a14
--- /dev/null
+++ b/idea/resources/inspectionDescriptions/RedundantModalityModifier.html
@@ -0,0 +1,6 @@
+
+
+This inspection reports modality modifiers which match the default modality of an element
+(final for most elements, open for members with override).
+
+
diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml
index 15d31cd620c..0482789f132 100644
--- a/idea/src/META-INF/plugin.xml
+++ b/idea/src/META-INF/plugin.xml
@@ -1443,6 +1443,14 @@
level="WARNING"
/>
+
+
diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantModalityModifierInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantModalityModifierInspection.kt
new file mode 100644
index 00000000000..30a3cf6a122
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantModalityModifierInspection.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.psi.PsiElementVisitor
+import org.jetbrains.kotlin.idea.core.implicitModality
+import org.jetbrains.kotlin.psi.KtDeclaration
+import org.jetbrains.kotlin.psi.KtVisitorVoid
+import org.jetbrains.kotlin.psi.psiUtil.modalityModifier
+
+class RedundantModalityModifierInspection : AbstractKotlinInspection(), CleanupLocalInspectionTool {
+ override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor {
+ return object : KtVisitorVoid() {
+ override fun visitDeclaration(declaration: KtDeclaration) {
+ val modalityModifier = declaration.modalityModifier() ?: return
+ if (modalityModifier.node.elementType == declaration.implicitModality()) {
+ holder.registerProblem(modalityModifier,
+ "Redundant modality modifier",
+ ProblemHighlightType.LIKE_UNUSED_SYMBOL,
+ RemoveModifierFix("Remove redundant modality modifier"))
+ }
+ }
+ }
+ }
+}
diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantVisibilityModifierInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantVisibilityModifierInspection.kt
index c906d73777f..39ac3fc0c61 100644
--- a/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantVisibilityModifierInspection.kt
+++ b/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantVisibilityModifierInspection.kt
@@ -17,43 +17,24 @@
package org.jetbrains.kotlin.idea.inspections
import com.intellij.codeInspection.*
-import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElementVisitor
import org.jetbrains.kotlin.idea.core.implicitVisibility
-import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
import org.jetbrains.kotlin.psi.KtDeclaration
-import org.jetbrains.kotlin.psi.KtModifierList
-import org.jetbrains.kotlin.psi.KtModifierListOwner
import org.jetbrains.kotlin.psi.KtVisitorVoid
-import org.jetbrains.kotlin.psi.addRemoveModifier.removeModifier
-import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifier
class RedundantVisibilityModifierInspection : AbstractKotlinInspection(), CleanupLocalInspectionTool {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor {
return object : KtVisitorVoid() {
- override fun visitDeclaration(dcl: KtDeclaration) {
- val visibilityModifier = dcl.visibilityModifier() ?: return
- if (visibilityModifier.node.elementType == dcl.implicitVisibility()) {
+ override fun visitDeclaration(declaration: KtDeclaration) {
+ val visibilityModifier = declaration.visibilityModifier() ?: return
+ if (visibilityModifier.node.elementType == declaration.implicitVisibility()) {
holder.registerProblem(visibilityModifier,
"Redundant visibility modifier",
ProblemHighlightType.LIKE_UNUSED_SYMBOL,
- RemoveVisibilityModifierFix())
+ RemoveModifierFix("Remove redundant visibility modifier"))
}
}
}
}
-
- class RemoveVisibilityModifierFix : LocalQuickFix {
- override fun getName(): String = "Remove redundant visibility modifier"
-
- override fun getFamilyName(): String = name
-
- override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
- val modifierKeyword = descriptor.psiElement.node.elementType as KtModifierKeywordToken
- val modifierListOwner = descriptor.psiElement.getParentOfType(true)
- ?: throw IllegalStateException("Can't find modifier list owner for modifier")
- removeModifier(modifierListOwner, modifierKeyword)
- }
- }
}
diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/RemoveModifierFix.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/RemoveModifierFix.kt
new file mode 100644
index 00000000000..7d26da5d011
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/inspections/RemoveModifierFix.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.LocalQuickFix
+import com.intellij.codeInspection.ProblemDescriptor
+import com.intellij.openapi.project.Project
+import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
+import org.jetbrains.kotlin.psi.KtModifierListOwner
+import org.jetbrains.kotlin.psi.addRemoveModifier.removeModifier
+import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
+
+class RemoveModifierFix(val text: String) : LocalQuickFix {
+ override fun getName(): String = text
+
+ override fun getFamilyName(): String = text
+
+ override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
+ val modifierKeyword = descriptor.psiElement.node.elementType as KtModifierKeywordToken
+ val modifierListOwner = descriptor.psiElement.getParentOfType(true)
+ ?: throw IllegalStateException("Can't find modifier list owner for modifier")
+ removeModifier(modifierListOwner, modifierKeyword)
+ }
+}
diff --git a/idea/testData/inspections/redundantModalityModifier/inspectionData/expected.xml b/idea/testData/inspections/redundantModalityModifier/inspectionData/expected.xml
new file mode 100644
index 00000000000..5f6e52271a1
--- /dev/null
+++ b/idea/testData/inspections/redundantModalityModifier/inspectionData/expected.xml
@@ -0,0 +1,74 @@
+
+
+ redundantModalityModifier.kt
+ 4
+ light_idea_test_case
+
+ Redundant modality modifier
+ Redundant modality modifier
+
+
+ redundantModalityModifier.kt
+ 13
+ light_idea_test_case
+
+ Redundant modality modifier
+ Redundant modality modifier
+
+
+ redundantModalityModifier.kt
+ 22
+ light_idea_test_case
+
+ Redundant modality modifier
+ Redundant modality modifier
+
+
+ redundantModalityModifier.kt
+ 25
+ light_idea_test_case
+
+ Redundant modality modifier
+ Redundant modality modifier
+
+
+ redundantModalityModifier.kt
+ 29
+ light_idea_test_case
+
+ Redundant modality modifier
+ Redundant modality modifier
+
+
+ redundantModalityModifier.kt
+ 31
+ light_idea_test_case
+
+ Redundant modality modifier
+ Redundant modality modifier
+
+
+ redundantModalityModifier.kt
+ 33
+ light_idea_test_case
+
+ Redundant modality modifier
+ Redundant modality modifier
+
+
+ redundantModalityModifier.kt
+ 39
+ light_idea_test_case
+
+ Redundant modality modifier
+ Redundant modality modifier
+
+
+ redundantModalityModifier.kt
+ 41
+ light_idea_test_case
+
+ Redundant modality modifier
+ Redundant modality modifier
+
+
\ No newline at end of file
diff --git a/idea/testData/inspections/redundantModalityModifier/inspectionData/inspections.test b/idea/testData/inspections/redundantModalityModifier/inspectionData/inspections.test
new file mode 100644
index 00000000000..be84d245e96
--- /dev/null
+++ b/idea/testData/inspections/redundantModalityModifier/inspectionData/inspections.test
@@ -0,0 +1 @@
+// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.RedundantModalityModifierInspection
diff --git a/idea/testData/inspections/redundantModalityModifier/redundantModalityModifier.kt b/idea/testData/inspections/redundantModalityModifier/redundantModalityModifier.kt
new file mode 100644
index 00000000000..513d79936c0
--- /dev/null
+++ b/idea/testData/inspections/redundantModalityModifier/redundantModalityModifier.kt
@@ -0,0 +1,42 @@
+// Abstract
+abstract class Base {
+ // Redundant final
+ final fun foo() {}
+ // Abstract
+ abstract fun bar()
+ // Open
+ open val gav = 42
+}
+
+class FinalDerived : Base() {
+ // Redundant final
+ override final fun bar() {}
+ // Non-final member in final class
+ override open val gav = 13
+}
+// Open
+open class OpenDerived : Base() {
+ // Final
+ override final fun bar() {}
+ // Redundant open
+ override open val gav = 13
+}
+// Redundant final
+final class Final
+// Interface
+interface Interface {
+ // Redundant
+ abstract fun foo()
+ // Redundant
+ private final fun bar() {}
+ // Redundant
+ open val gav: Int
+ get() = 42
+}
+// Derived interface
+interface Derived : Interface {
+ // Redundant
+ override open fun foo() {}
+ // Redundant
+ final class Nested
+}
diff --git a/idea/testData/quickfix/redundantModalityModifier/.inspection b/idea/testData/quickfix/redundantModalityModifier/.inspection
new file mode 100644
index 00000000000..822c3c7b76c
--- /dev/null
+++ b/idea/testData/quickfix/redundantModalityModifier/.inspection
@@ -0,0 +1 @@
+org.jetbrains.kotlin.idea.inspections.RedundantModalityModifierInspection
\ No newline at end of file
diff --git a/idea/testData/quickfix/redundantModalityModifier/simple.kt b/idea/testData/quickfix/redundantModalityModifier/simple.kt
new file mode 100644
index 00000000000..a9d7acbcbbc
--- /dev/null
+++ b/idea/testData/quickfix/redundantModalityModifier/simple.kt
@@ -0,0 +1,4 @@
+// "Remove redundant modality modifier" "true"
+open class C {
+ final fun foo(){}
+}
\ No newline at end of file
diff --git a/idea/testData/quickfix/redundantModalityModifier/simple.kt.after b/idea/testData/quickfix/redundantModalityModifier/simple.kt.after
new file mode 100644
index 00000000000..64da278dfe2
--- /dev/null
+++ b/idea/testData/quickfix/redundantModalityModifier/simple.kt.after
@@ -0,0 +1,4 @@
+// "Remove redundant modality modifier" "true"
+open class C {
+ fun foo(){}
+}
\ 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 5c276aa1acb..a1612e28b29 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java
@@ -160,6 +160,12 @@ public class InspectionTestGenerated extends AbstractInspectionTest {
doTest(fileName);
}
+ @TestMetadata("redundantModalityModifier/inspectionData/inspections.test")
+ public void testRedundantModalityModifier_inspectionData_Inspections_test() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/redundantModalityModifier/inspectionData/inspections.test");
+ doTest(fileName);
+ }
+
@TestMetadata("redundantSamConstructor/inspectionData/inspections.test")
public void testRedundantSamConstructor_inspectionData_Inspections_test() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/redundantSamConstructor/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 060b2dc1a4b..c354ef13098 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java
@@ -6128,6 +6128,21 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
}
}
+ @TestMetadata("idea/testData/quickfix/redundantModalityModifier")
+ @TestDataPath("$PROJECT_ROOT")
+ @RunWith(JUnit3RunnerWithInners.class)
+ public static class RedundantModalityModifier extends AbstractQuickFixTest {
+ public void testAllFilesPresentInRedundantModalityModifier() throws Exception {
+ KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/redundantModalityModifier"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
+ }
+
+ @TestMetadata("simple.kt")
+ public void testSimple() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/redundantModalityModifier/simple.kt");
+ doTest(fileName);
+ }
+ }
+
@TestMetadata("idea/testData/quickfix/redundantVisibilityModifier")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)