diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/addRemoveModifier.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/addRemoveModifier.kt index b2b013c03f5..ac53aeae11c 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/addRemoveModifier.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/addRemoveModifier.kt @@ -121,6 +121,13 @@ fun removeModifier(owner: KtModifierListOwner, modifier: KtModifierKeywordToken) } } +fun sortModifiers(modifiers: List): List { + return modifiers.sortedBy { + val index = MODIFIERS_ORDER.indexOf(it) + if (index == -1) Int.MAX_VALUE else index + } +} + private val MODIFIERS_TO_REPLACE = mapOf( OVERRIDE_KEYWORD to listOf(OPEN_KEYWORD), ABSTRACT_KEYWORD to listOf(OPEN_KEYWORD, FINAL_KEYWORD), diff --git a/idea/resources/inspectionDescriptions/SortModifiers.html b/idea/resources/inspectionDescriptions/SortModifiers.html new file mode 100644 index 00000000000..fff0ff38728 --- /dev/null +++ b/idea/resources/inspectionDescriptions/SortModifiers.html @@ -0,0 +1,5 @@ + + +This inspection reports to sort the modifiers in canonical order + + diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 562b81ca492..285dada3b19 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -2701,6 +2701,16 @@ language="kotlin" /> + + + diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/SortModifiersInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/SortModifiersInspection.kt new file mode 100644 index 00000000000..efe640df6e0 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/SortModifiersInspection.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2010-2017 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.PsiElementVisitor +import org.jetbrains.kotlin.lexer.KtModifierKeywordToken +import org.jetbrains.kotlin.psi.KtModifierList +import org.jetbrains.kotlin.psi.KtModifierListOwner +import org.jetbrains.kotlin.psi.KtVisitorVoid +import org.jetbrains.kotlin.psi.addRemoveModifier.sortModifiers +import org.jetbrains.kotlin.psi.psiUtil.allChildren + +class SortModifiersInspection : AbstractKotlinInspection() { + + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor { + return object : KtVisitorVoid() { + override fun visitModifierList(list: KtModifierList) { + super.visitModifierList(list) + + val modifiers = list.allChildren.toList().mapNotNull { it.node.elementType as? KtModifierKeywordToken }.toList() + if (modifiers.isEmpty()) return + + val sortedModifiers = sortModifiers(modifiers) + if (modifiers == sortedModifiers) return + + holder.registerProblem(list, + "Sort modifiers", + ProblemHighlightType.WEAK_WARNING, + SortModifiersFix(sortedModifiers) + ) + } + } + } +} + +private class SortModifiersFix(private val modifiers: List) : LocalQuickFix { + override fun getName() = "Sort modifiers" + + override fun getFamilyName() = name + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val list = descriptor.psiElement as? KtModifierList ?: return + val owner = list.parent as? KtModifierListOwner ?: return + + modifiers.forEach { owner.removeModifier(it) } + modifiers.forEach { owner.addModifier(it) } + } +} + diff --git a/idea/testData/inspectionsLocal/sortModifiers/.inspection b/idea/testData/inspectionsLocal/sortModifiers/.inspection new file mode 100644 index 00000000000..ae9d868c828 --- /dev/null +++ b/idea/testData/inspectionsLocal/sortModifiers/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.SortModifiersInspection \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/sortModifiers/simple.kt b/idea/testData/inspectionsLocal/sortModifiers/simple.kt new file mode 100644 index 00000000000..92bd0c9ffe6 --- /dev/null +++ b/idea/testData/inspectionsLocal/sortModifiers/simple.kt @@ -0,0 +1 @@ +abstract public class Test diff --git a/idea/testData/inspectionsLocal/sortModifiers/simple.kt.after b/idea/testData/inspectionsLocal/sortModifiers/simple.kt.after new file mode 100644 index 00000000000..f50992fcb8e --- /dev/null +++ b/idea/testData/inspectionsLocal/sortModifiers/simple.kt.after @@ -0,0 +1 @@ +public abstract class Test diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index b298ceb9ff0..9d9831d1912 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -2562,6 +2562,21 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { } } + @TestMetadata("idea/testData/inspectionsLocal/sortModifiers") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class SortModifiers extends AbstractLocalInspectionTest { + public void testAllFilesPresentInSortModifiers() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/sortModifiers"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/sortModifiers/simple.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/inspectionsLocal/unnecessaryVariable") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)