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)