diff --git a/idea/resources/inspectionDescriptions/KotlinDeprecation.html b/idea/resources/inspectionDescriptions/KotlinDeprecation.html
new file mode 100644
index 00000000000..8a523b9e534
--- /dev/null
+++ b/idea/resources/inspectionDescriptions/KotlinDeprecation.html
@@ -0,0 +1,7 @@
+
+
+This inspection is used during the code cleanup operation (Analyze | Code Cleanup) to automatically
+replace usages of obsolete language features or unnecessarily verbose code constructs with
+compact and up-to-date syntax.
+
+
diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml
index 38c9a88c3da..9bcf437c988 100644
--- a/idea/src/META-INF/plugin.xml
+++ b/idea/src/META-INF/plugin.xml
@@ -1009,7 +1009,7 @@
? {
if (isOnTheFly || file !is JetFile || !ProjectRootsUtil.isInProjectSource(file)) {
@@ -55,29 +56,32 @@ public class KotlinCleanupInspection(): LocalInspectionTool(), CleanupLocalInspe
file.acceptChildren(object: JetTreeVisitorVoid() {
override fun visitElement(element: PsiElement) {
super.visitElement(element)
- val collection = diagnostics.forElement(element)
- collection.forEach {
- if (it.isCleanup()) {
- problemDescriptors.add(it.toProblemDescriptor(file, manager))
- }
- }
+ diagnostics.forElement(element)
+ .filter { it.isCleanup() }
+ .map { it.toProblemDescriptor(file, manager) }
+ .filterNotNullTo(problemDescriptors)
}
})
return problemDescriptors.toTypedArray()
}
- private fun Diagnostic.isCleanup() = getFactory().isCleanup() || isObsoleteLabel()
+ private fun Diagnostic.isCleanup() = getFactory() in cleanupDiagnosticsFactories || isObsoleteLabel()
- private fun DiagnosticFactory<*>.isCleanup() =
- this == Errors.DEPRECATED_TRAIT_KEYWORD ||
- this == Errors.DEPRECATED_ANNOTATION_SYNTAX ||
- this == Errors.ENUM_ENTRY_USES_DEPRECATED_OR_NO_DELIMITER ||
- this == Errors.ENUM_ENTRY_USES_DEPRECATED_SUPER_CONSTRUCTOR ||
- this == Errors.DEPRECATED_LAMBDA_SYNTAX ||
- this == Errors.MISSING_CONSTRUCTOR_KEYWORD ||
- this == Errors.FUNCTION_EXPRESSION_WITH_NAME ||
- this == Errors.JAVA_LANG_CLASS_PARAMETER_IN_ANNOTATION ||
- this == ErrorsJvm.JAVA_LANG_CLASS_ARGUMENT_IN_ANNOTATION
+ private val cleanupDiagnosticsFactories = setOf(
+ Errors.DEPRECATED_TRAIT_KEYWORD,
+ Errors.DEPRECATED_ANNOTATION_SYNTAX,
+ Errors.ENUM_ENTRY_USES_DEPRECATED_OR_NO_DELIMITER,
+ Errors.ENUM_ENTRY_USES_DEPRECATED_SUPER_CONSTRUCTOR,
+ Errors.DEPRECATED_LAMBDA_SYNTAX,
+ Errors.MISSING_CONSTRUCTOR_KEYWORD,
+ Errors.FUNCTION_EXPRESSION_WITH_NAME,
+ Errors.JAVA_LANG_CLASS_PARAMETER_IN_ANNOTATION,
+ ErrorsJvm.JAVA_LANG_CLASS_ARGUMENT_IN_ANNOTATION,
+ Errors.UNNECESSARY_NOT_NULL_ASSERTION,
+ Errors.UNNECESSARY_SAFE_CALL,
+ Errors.USELESS_CAST,
+ Errors.USELESS_ELVIS
+ )
private fun Diagnostic.isObsoleteLabel(): Boolean {
val annotationEntry = getPsiElement().getNonStrictParentOfType() ?: return false
@@ -86,20 +90,15 @@ public class KotlinCleanupInspection(): LocalInspectionTool(), CleanupLocalInspe
private fun Diagnostic.toProblemDescriptor(file: JetFile, manager: InspectionManager): ProblemDescriptor? {
val quickFixes = JetPsiChecker.createQuickfixes(this)
- .filter { it.isCleanupFix(this) }
+ .filter { it is CleanupFix }
.map { IntentionWrapper(it, file) }
- return manager.createProblemDescriptor(getPsiElement(),
+ if (quickFixes.isEmpty()) return null
+
+ return manager.createProblemDescriptor(getPsiElement(),
DefaultErrorMessages.render(this),
false,
quickFixes.toTypedArray(),
ProblemHighlightType.GENERIC_ERROR_OR_WARNING)
}
-
- private fun IntentionAction.isCleanupFix(diagnostic: Diagnostic): Boolean {
- if (diagnostic.getFactory() == Errors.UNRESOLVED_REFERENCE) {
- return this is ReplaceObsoleteLabelSyntaxFix
- }
- return this !is JetWholeProjectModalAction<*>
- }
}
diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/CleanupFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/CleanupFix.kt
new file mode 100644
index 00000000000..a7f8ff22a00
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/CleanupFix.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2010-2015 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.quickfix
+
+/**
+ * Marker interface for quickfixes that can be used as part of the "Cleanup Code" action. The diagnostics
+ * that produce these quickfixes need to be added to KotlinCleanupInspection.cleanupDiagnosticsFactories.
+ */
+public interface CleanupFix
+// TODO(yole): add isSafeToApply() method here to get rid of filtering by diagnostics factories in
+// KotlinCleanupInspection
diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedAnnotationSyntaxFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedAnnotationSyntaxFix.kt
index d6961d98557..f2de071e753 100644
--- a/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedAnnotationSyntaxFix.kt
+++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedAnnotationSyntaxFix.kt
@@ -30,7 +30,7 @@ import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.psi.JetPrimaryConstructor
import org.jetbrains.kotlin.psi.JetPsiFactory
-public class DeprecatedAnnotationSyntaxFix(element: JetAnnotation) : JetIntentionAction(element) {
+public class DeprecatedAnnotationSyntaxFix(element: JetAnnotation) : JetIntentionAction(element), CleanupFix {
override fun getFamilyName(): String = "Replace with '@' annotations"
override fun getText(): String = "Replace with '@' annotations"
diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedEnumEntryDelimiterSyntaxFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedEnumEntryDelimiterSyntaxFix.kt
index 9e5e834a923..1ab5085abf1 100644
--- a/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedEnumEntryDelimiterSyntaxFix.kt
+++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedEnumEntryDelimiterSyntaxFix.kt
@@ -32,7 +32,7 @@ import org.jetbrains.kotlin.psi.psiUtil.getNextSiblingIgnoringWhitespaceAndComme
import org.jetbrains.kotlin.psi.stubs.elements.JetStubElementTypes
import org.jetbrains.kotlin.resolve.DeclarationsChecker
-class DeprecatedEnumEntryDelimiterSyntaxFix(element: JetEnumEntry): JetIntentionAction(element) {
+class DeprecatedEnumEntryDelimiterSyntaxFix(element: JetEnumEntry): JetIntentionAction(element), CleanupFix {
override fun getFamilyName(): String = getText()
@@ -41,7 +41,7 @@ class DeprecatedEnumEntryDelimiterSyntaxFix(element: JetEnumEntry): JetIntention
override fun invoke(project: Project, editor: Editor?, file: JetFile?) = insertLackingCommaSemicolon(element)
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile): Boolean
- = super.isAvailable(project, editor, file) && DeclarationsChecker.enumEntryUsesDeprecatedOrNoDelimiter(element)
+ = super.isAvailable(project, editor, file) && DeclarationsChecker.enumEntryUsesDeprecatedOrNoDelimiter(element)
companion object : JetSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): IntentionAction? =
diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedEnumEntrySuperConstructorSyntaxFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedEnumEntrySuperConstructorSyntaxFix.kt
index c906db972c1..2bd1a8f3c5c 100644
--- a/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedEnumEntrySuperConstructorSyntaxFix.kt
+++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedEnumEntrySuperConstructorSyntaxFix.kt
@@ -36,7 +36,7 @@ import org.jetbrains.kotlin.psi.psiUtil.getNextSiblingIgnoringWhitespaceAndComme
import org.jetbrains.kotlin.psi.stubs.elements.JetStubElementTypes
import org.jetbrains.kotlin.resolve.DeclarationsChecker
-class DeprecatedEnumEntrySuperConstructorSyntaxFix(element: JetEnumEntry): JetIntentionAction(element) {
+class DeprecatedEnumEntrySuperConstructorSyntaxFix(element: JetEnumEntry): JetIntentionAction(element), CleanupFix {
override fun getFamilyName(): String = getText()
override fun getText(): String = "Change to short enum entry super constructor"
@@ -44,7 +44,7 @@ class DeprecatedEnumEntrySuperConstructorSyntaxFix(element: JetEnumEntry): JetIn
override fun invoke(project: Project, editor: Editor?, file: JetFile?) = changeConstructorToShort(element)
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile): Boolean
- = super.isAvailable(project, editor, file) && DeclarationsChecker.enumEntryUsesDeprecatedSuperConstructor(element)
+ = super.isAvailable(project, editor, file) && DeclarationsChecker.enumEntryUsesDeprecatedSuperConstructor(element)
companion object: JetSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): IntentionAction? =
diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedLambdaSyntaxFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedLambdaSyntaxFix.kt
index f5b4cb43942..1c76dfd043c 100644
--- a/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedLambdaSyntaxFix.kt
+++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedLambdaSyntaxFix.kt
@@ -35,7 +35,7 @@ import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.utils.sure
import java.util.ArrayList
-public class DeprecatedLambdaSyntaxFix(element: JetFunctionLiteralExpression) : JetIntentionAction(element) {
+public class DeprecatedLambdaSyntaxFix(element: JetFunctionLiteralExpression) : JetIntentionAction(element), CleanupFix {
override fun getText() = JetBundle.message("migrate.lambda.syntax")
override fun getFamilyName() = JetBundle.message("migrate.lambda.syntax.family")
diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedTraitSyntaxFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedTraitSyntaxFix.kt
index 3ad5f8cd3cb..7dd3eb1f256 100644
--- a/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedTraitSyntaxFix.kt
+++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedTraitSyntaxFix.kt
@@ -27,7 +27,7 @@ import org.jetbrains.kotlin.psi.JetClass
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.psi.JetPsiFactory
-public class DeprecatedTraitSyntaxFix(element: PsiElement): JetIntentionAction(element) {
+public class DeprecatedTraitSyntaxFix(element: PsiElement): JetIntentionAction(element), CleanupFix {
override fun getFamilyName() = "Replace with 'interface'"
override fun getText() = getFamilyName()
diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/ExclExclCallFix.java b/idea/src/org/jetbrains/kotlin/idea/quickfix/ExclExclCallFix.java
deleted file mode 100644
index 97c3d2aa7f7..00000000000
--- a/idea/src/org/jetbrains/kotlin/idea/quickfix/ExclExclCallFix.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright 2010-2015 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.quickfix;
-
-import com.intellij.codeInsight.FileModificationService;
-import com.intellij.codeInsight.intention.IntentionAction;
-import com.intellij.openapi.editor.Editor;
-import com.intellij.openapi.project.Project;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.impl.source.tree.LeafPsiElement;
-import com.intellij.psi.util.PsiTreeUtil;
-import com.intellij.util.IncorrectOperationException;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.jetbrains.kotlin.idea.JetBundle;
-import org.jetbrains.kotlin.lexer.JetTokens;
-import org.jetbrains.kotlin.psi.JetExpression;
-import org.jetbrains.kotlin.psi.JetFile;
-import org.jetbrains.kotlin.psi.JetPostfixExpression;
-import org.jetbrains.kotlin.psi.JetPsiFactory;
-
-import static org.jetbrains.kotlin.psi.PsiPackage.JetPsiFactory;
-
-@SuppressWarnings("IntentionDescriptionNotFoundInspection")
-public class ExclExclCallFix implements IntentionAction {
-
- private final boolean isRemove;
-
- private ExclExclCallFix(boolean remove) {
- isRemove = remove;
- }
-
- public static ExclExclCallFix removeExclExclCall() {
- return new ExclExclCallFix(true);
- }
-
- public static ExclExclCallFix introduceExclExclCall() {
- return new ExclExclCallFix(false);
- }
-
- @NotNull
- @Override
- public String getText() {
- return isRemove ? JetBundle.message("remove.unnecessary.non.null.assertion") : JetBundle.message("introduce.non.null.assertion");
- }
-
- @NotNull
- @Override
- public String getFamilyName() {
- return getText();
- }
-
- @Override
- public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
- if (file instanceof JetFile) {
- if (!isRemove) {
- return isAvailableForIntroduce(editor, file);
- }
- else {
- return isAvailableForRemove(editor, file);
- }
- }
-
- return false;
- }
-
- @Override
- public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
- if (!FileModificationService.getInstance().prepareFileForWrite(file)) {
- return;
- }
-
- JetPsiFactory psiFactory = JetPsiFactory(project);
- if (!isRemove) {
- JetExpression modifiedExpression = getExpressionForIntroduceCall(editor, file);
- JetExpression exclExclExpression = psiFactory.createExpression(modifiedExpression.getText() + "!!");
- modifiedExpression.replace(exclExclExpression);
- }
- else {
- JetPostfixExpression postfixExpression = getExclExclPostfixExpression(editor, file);
- JetExpression expression = psiFactory.createExpression(postfixExpression.getBaseExpression().getText());
- postfixExpression.replace(expression);
- }
- }
-
- @Override
- public boolean startInWriteAction() {
- return true;
- }
-
- private static boolean isAvailableForIntroduce(Editor editor, PsiFile file) {
- return getExpressionForIntroduceCall(editor, file) != null;
- }
-
- private static boolean isAvailableForRemove(Editor editor, PsiFile file) {
- return getExclExclPostfixExpression(editor, file) != null;
- }
-
- private static PsiElement getExclExclElement(Editor editor, PsiFile file) {
- PsiElement elementAtCaret = file.findElementAt(editor.getCaretModel().getOffset());
- if (isExclExclLeaf(elementAtCaret)) {
- return elementAtCaret;
- }
-
- if (elementAtCaret != null) {
- // Case when caret is placed right after !!
- PsiElement prevLeaf = PsiTreeUtil.prevLeaf(elementAtCaret);
- if (isExclExclLeaf(prevLeaf)) {
- return prevLeaf;
- }
- }
-
- return null;
- }
-
- private static boolean isExclExclLeaf(@Nullable PsiElement element) {
- return (element instanceof LeafPsiElement) && ((LeafPsiElement) element).getElementType() == JetTokens.EXCLEXCL;
- }
-
- private static JetExpression getExpressionForIntroduceCall(Editor editor, PsiFile file) {
- PsiElement elementAtCaret = file.findElementAt(editor.getCaretModel().getOffset());
- if (elementAtCaret != null) {
- JetExpression expression = getExpressionForIntroduceCall(elementAtCaret);
- if (expression != null) {
- return expression;
- }
-
- // Maybe caret is after error element
- expression = getExpressionForIntroduceCall(PsiTreeUtil.prevLeaf(elementAtCaret));
- if (expression != null) {
- return expression;
- }
- }
-
- return null;
- }
-
- private static JetExpression getExpressionForIntroduceCall(PsiElement problemElement) {
- if (problemElement instanceof LeafPsiElement && ((LeafPsiElement) problemElement).getElementType() == JetTokens.DOT) {
- PsiElement sibling = problemElement.getPrevSibling();
- if (sibling instanceof JetExpression) {
- return (JetExpression) sibling;
- }
- }
-
- return null;
- }
-
- private static JetPostfixExpression getExclExclPostfixExpression(Editor editor, PsiFile file) {
- PsiElement exclExclElement = getExclExclElement(editor, file);
-
- if (exclExclElement != null) {
- PsiElement parent = exclExclElement.getParent();
- if (parent != null) {
- PsiElement operationParent = parent.getParent();
- if (operationParent instanceof JetPostfixExpression && ((JetPostfixExpression) operationParent).getBaseExpression() != null) {
- return (JetPostfixExpression) operationParent;
- }
- }
- }
-
- return null;
- }
-}
diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/ExclExlCallFixes.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/ExclExlCallFixes.kt
new file mode 100644
index 00000000000..b94d10f4ab7
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/ExclExlCallFixes.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2010-2015 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.quickfix
+
+import com.intellij.codeInsight.FileModificationService
+import com.intellij.codeInsight.intention.IntentionAction
+import com.intellij.openapi.editor.Editor
+import com.intellij.openapi.project.Project
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiFile
+import com.intellij.psi.impl.source.tree.LeafPsiElement
+import org.jetbrains.kotlin.diagnostics.Diagnostic
+import org.jetbrains.kotlin.idea.JetBundle
+import org.jetbrains.kotlin.lexer.JetTokens
+import org.jetbrains.kotlin.psi.JetExpression
+import org.jetbrains.kotlin.psi.JetFile
+import org.jetbrains.kotlin.psi.JetPostfixExpression
+import org.jetbrains.kotlin.psi.JetPsiFactory
+
+public abstract class ExclExclCallFix : IntentionAction {
+ override fun getFamilyName(): String = getText()
+
+ override fun startInWriteAction(): Boolean = true
+
+ override fun isAvailable(project: Project, editor: Editor?, file: PsiFile) = file is JetFile
+}
+
+public class RemoveExclExclCallFix(val psiElement: PsiElement) : ExclExclCallFix(), CleanupFix {
+ override fun getText(): String = JetBundle.message("remove.unnecessary.non.null.assertion")
+
+ override fun isAvailable(project: Project, editor: Editor?, file: PsiFile): Boolean
+ = super.isAvailable(project, editor, file) &&
+ getExclExclPostfixExpression() != null
+
+ override fun invoke(project: Project, editor: Editor?, file: PsiFile?) {
+ if (!FileModificationService.getInstance().prepareFileForWrite(file)) return
+
+ val postfixExpression = getExclExclPostfixExpression() ?: return
+ val expression = JetPsiFactory(project).createExpression(postfixExpression.getBaseExpression().getText())
+ postfixExpression.replace(expression)
+ }
+
+ private fun getExclExclPostfixExpression(): JetPostfixExpression? {
+ val operationParent = psiElement.getParent()
+ if (operationParent is JetPostfixExpression && operationParent.getBaseExpression() != null) {
+ return operationParent
+ }
+ return null
+ }
+
+ companion object : JetSingleIntentionActionFactory() {
+ override fun createAction(diagnostic: Diagnostic): IntentionAction
+ = RemoveExclExclCallFix(diagnostic.getPsiElement())
+ }
+}
+
+public class AddExclExclCallFix(val psiElement: PsiElement) : ExclExclCallFix() {
+ override fun getText() = JetBundle.message("introduce.non.null.assertion")
+
+ override fun isAvailable(project: Project, editor: Editor?, file: PsiFile): Boolean
+ = super.isAvailable(project, editor, file) &&
+ getExpressionForIntroduceCall() != null
+
+ override fun invoke(project: Project, editor: Editor?, file: PsiFile?) {
+ val modifiedExpression = getExpressionForIntroduceCall() ?: return
+ val exclExclExpression = JetPsiFactory(project).createExpression(modifiedExpression.getText() + "!!")
+ modifiedExpression.replace(exclExclExpression)
+ }
+
+ protected fun getExpressionForIntroduceCall(): JetExpression? {
+ if (psiElement is LeafPsiElement && psiElement.getElementType() == JetTokens.DOT) {
+ val sibling = psiElement.getPrevSibling()
+ if (sibling is JetExpression) {
+ return sibling
+ }
+ }
+
+ return null
+ }
+
+ companion object : JetSingleIntentionActionFactory() {
+ override fun createAction(diagnostic: Diagnostic): IntentionAction
+ = AddExclExclCallFix(diagnostic.getPsiElement())
+ }
+}
diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/MissingConstructorKeywordFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/MissingConstructorKeywordFix.kt
index b06e40aabac..81576bece2f 100644
--- a/idea/src/org/jetbrains/kotlin/idea/quickfix/MissingConstructorKeywordFix.kt
+++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/MissingConstructorKeywordFix.kt
@@ -26,7 +26,7 @@ import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.psi.JetPrimaryConstructor
import org.jetbrains.kotlin.idea.quickfix.quickfixUtil.addConstructorKeyword
-public class MissingConstructorKeywordFix(element: JetPrimaryConstructor) : JetIntentionAction(element) {
+public class MissingConstructorKeywordFix(element: JetPrimaryConstructor) : JetIntentionAction(element), CleanupFix {
override fun getFamilyName(): String = getText()
override fun getText(): String = "Add 'constructor' keyword"
diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.java b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.java
index aa82d5bf88b..62971ea8b8a 100644
--- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.java
+++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.java
@@ -173,11 +173,11 @@ public class QuickFixRegistrar {
QuickFixes.factories.put(UNUSED_VARIABLE, RemovePsiElementSimpleFix.createRemoveVariableFactory());
- QuickFixes.actions.put(UNNECESSARY_SAFE_CALL, ReplaceCallFix.toDotCallFromSafeCall());
- QuickFixes.actions.put(UNSAFE_CALL, ReplaceCallFix.toSafeCall());
+ QuickFixes.factories.put(UNNECESSARY_SAFE_CALL, ReplaceWithDotCallFix.Companion);
+ QuickFixes.factories.put(UNSAFE_CALL, ReplaceWithSafeCallFix.Companion);
- QuickFixes.actions.put(UNSAFE_CALL, ExclExclCallFix.introduceExclExclCall());
- QuickFixes.actions.put(UNNECESSARY_NOT_NULL_ASSERTION, ExclExclCallFix.removeExclExclCall());
+ QuickFixes.factories.put(UNSAFE_CALL, AddExclExclCallFix.Companion);
+ QuickFixes.factories.put(UNNECESSARY_NOT_NULL_ASSERTION, RemoveExclExclCallFix.Companion);
QuickFixes.factories.put(UNSAFE_INFIX_CALL, ReplaceInfixCallFix.createFactory());
JetSingleIntentionActionFactory removeProtectedModifierFactory = RemoveModifierFix.createRemoveModifierFromListOwnerFactory(PROTECTED_KEYWORD);
diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/RemoveNameFromFunctionExpressionFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/RemoveNameFromFunctionExpressionFix.kt
index 18bd5f19594..b8dd55a8093 100644
--- a/idea/src/org/jetbrains/kotlin/idea/quickfix/RemoveNameFromFunctionExpressionFix.kt
+++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/RemoveNameFromFunctionExpressionFix.kt
@@ -30,7 +30,7 @@ import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantsOfType
import org.jetbrains.kotlin.psi.psiUtil.getNextSiblingIgnoringWhitespaceAndComments
import org.jetbrains.kotlin.resolve.BindingContext
-public class RemoveNameFromFunctionExpressionFix(element: JetNamedFunction) : JetIntentionAction(element) {
+public class RemoveNameFromFunctionExpressionFix(element: JetNamedFunction) : JetIntentionAction(element), CleanupFix {
override fun getText(): String = "Remove identifier from function expression"
override fun getFamilyName(): String = getText()
diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/RemoveRightPartOfBinaryExpressionFix.java b/idea/src/org/jetbrains/kotlin/idea/quickfix/RemoveRightPartOfBinaryExpressionFix.java
index ff3c2b355f7..175c34e2a3b 100644
--- a/idea/src/org/jetbrains/kotlin/idea/quickfix/RemoveRightPartOfBinaryExpressionFix.java
+++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/RemoveRightPartOfBinaryExpressionFix.java
@@ -26,7 +26,7 @@ import org.jetbrains.kotlin.idea.JetBundle;
import org.jetbrains.kotlin.idea.core.quickfix.QuickFixUtil;
import org.jetbrains.kotlin.psi.*;
-public class RemoveRightPartOfBinaryExpressionFix extends JetIntentionAction {
+public class RemoveRightPartOfBinaryExpressionFix extends JetIntentionAction implements CleanupFix {
private final String message;
public RemoveRightPartOfBinaryExpressionFix(@NotNull T element, String message) {
diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/ReplaceCallFix.java b/idea/src/org/jetbrains/kotlin/idea/quickfix/ReplaceCallFix.java
deleted file mode 100644
index 038a4c95f4f..00000000000
--- a/idea/src/org/jetbrains/kotlin/idea/quickfix/ReplaceCallFix.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright 2010-2015 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.quickfix;
-
-import com.intellij.codeInsight.intention.IntentionAction;
-import com.intellij.openapi.editor.Editor;
-import com.intellij.openapi.project.Project;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.util.PsiTreeUtil;
-import com.intellij.util.IncorrectOperationException;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.kotlin.idea.JetBundle;
-import org.jetbrains.kotlin.psi.*;
-
-import static org.jetbrains.kotlin.psi.PsiPackage.JetPsiFactory;
-
-public class ReplaceCallFix implements IntentionAction {
- private final boolean toSafe;
-
- private ReplaceCallFix(boolean safe) {
- this.toSafe = safe;
- }
-
- /**
- * @return quickfix for replacing dot call with toSafe (?.) call
- */
- public static ReplaceCallFix toSafeCall() {
- return new ReplaceCallFix(true);
- }
-
- /**
- * @return quickfix for replacing unnecessary toSafe (?.) call with dot call
- */
- public static ReplaceCallFix toDotCallFromSafeCall() {
- return new ReplaceCallFix(false);
- }
-
- @NotNull
- @Override
- public String getText() {
- return toSafe ? JetBundle.message("replace.with.safe.call") : JetBundle.message("replace.with.dot.call");
- }
-
- @NotNull
- @Override
- public String getFamilyName() {
- return getText();
- }
-
- @Override
- public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
- if (file instanceof JetFile) {
- return getCallExpression(editor, (JetFile) file) != null;
- }
- return false;
- }
-
- @Override
- public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
- JetQualifiedExpression callExpression = getCallExpression(editor, (JetFile) file);
- assert callExpression != null;
-
- JetExpression selector = callExpression.getSelectorExpression();
- if (selector != null) {
- JetQualifiedExpression newElement = (JetQualifiedExpression) JetPsiFactory(callExpression).createExpression(
- callExpression.getReceiverExpression().getText() + (toSafe ? "?." : ".") + selector.getText()
- );
-
- callExpression.replace(newElement);
- }
- }
-
- @Override
- public boolean startInWriteAction() {
- return true;
- }
-
- private JetQualifiedExpression getCallExpression(@NotNull Editor editor, @NotNull JetFile file) {
- PsiElement elementAtCaret = getElementAtCaret(editor, file);
- return PsiTreeUtil.getParentOfType(elementAtCaret, toSafe ? JetDotQualifiedExpression.class : JetSafeQualifiedExpression.class);
- }
-
- private static PsiElement getElementAtCaret(Editor editor, PsiFile file) {
- return file.findElementAt(editor.getCaretModel().getOffset());
- }
-}
diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/ReplaceCallFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/ReplaceCallFix.kt
new file mode 100644
index 00000000000..9f166884ff4
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/ReplaceCallFix.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2010-2015 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.quickfix
+
+import com.intellij.codeInsight.intention.IntentionAction
+import com.intellij.extapi.psi.ASTDelegatePsiElement
+import com.intellij.openapi.editor.Editor
+import com.intellij.openapi.project.Project
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiFile
+import com.intellij.psi.util.PsiTreeUtil
+import org.jetbrains.kotlin.diagnostics.Diagnostic
+import org.jetbrains.kotlin.idea.JetBundle
+import org.jetbrains.kotlin.psi.*
+
+public abstract class ReplaceCallFix protected (val psiElement: PsiElement) : IntentionAction {
+
+ override fun getFamilyName(): String = getText()
+
+ override fun isAvailable(project: Project, editor: Editor?, file: PsiFile): Boolean {
+ if (file is JetFile) {
+ return getCallExpression() != null
+ }
+ return false
+ }
+
+ override fun invoke(project: Project, editor: Editor?, file: PsiFile) {
+ val callExpression = getCallExpression() ?: return
+
+ val selector = callExpression.getSelectorExpression()
+ if (selector != null) {
+ val newElement = JetPsiFactory(callExpression).createExpression(
+ callExpression.getReceiverExpression().getText() + operation + selector.getText()) as JetQualifiedExpression
+
+ callExpression.replace(newElement)
+ }
+ }
+
+ override fun startInWriteAction(): Boolean = true
+
+ private fun getCallExpression(): JetQualifiedExpression? {
+ return PsiTreeUtil.getParentOfType(psiElement, classToReplace)
+ }
+
+ abstract val operation: String
+ abstract val classToReplace: Class
+}
+
+public class ReplaceWithSafeCallFix(psiElement: PsiElement): ReplaceCallFix(psiElement) {
+ override fun getText(): String = JetBundle.message("replace.with.safe.call")
+ override val operation: String get() = "?."
+ override val classToReplace: Class get() = javaClass()
+
+ companion object : JetSingleIntentionActionFactory() {
+ override fun createAction(diagnostic: Diagnostic): IntentionAction
+ = ReplaceWithSafeCallFix(diagnostic.getPsiElement())
+ }
+}
+
+public class ReplaceWithDotCallFix(psiElement: PsiElement): ReplaceCallFix(psiElement), CleanupFix {
+ override fun getText(): String = JetBundle.message("replace.with.dot.call")
+ override val operation: String get() = "."
+ override val classToReplace: Class get() = javaClass()
+
+ companion object : JetSingleIntentionActionFactory() {
+ override fun createAction(diagnostic: Diagnostic): IntentionAction
+ = ReplaceWithDotCallFix(diagnostic.getPsiElement())
+ }
+}
diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/ReplaceObsoleteLabelSyntaxFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/ReplaceObsoleteLabelSyntaxFix.kt
index fdda73e9a46..1c64b66948b 100644
--- a/idea/src/org/jetbrains/kotlin/idea/quickfix/ReplaceObsoleteLabelSyntaxFix.kt
+++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/ReplaceObsoleteLabelSyntaxFix.kt
@@ -30,7 +30,7 @@ import org.jetbrains.kotlin.psi.psiUtil.getNextSiblingIgnoringWhitespaceAndComme
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.resolve.BindingContext
-public class ReplaceObsoleteLabelSyntaxFix(element: JetAnnotationEntry?) : JetIntentionAction(element) {
+public class ReplaceObsoleteLabelSyntaxFix(element: JetAnnotationEntry?) : JetIntentionAction(element), CleanupFix {
override fun getFamilyName(): String = "Update obsolete label syntax"
override fun getText(): String = "Replace with label ${element.getCalleeExpression()?.getText() ?: ""}@"
diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceJavaClass/ReplaceJavaClassAsAnnotationArgumentFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceJavaClass/ReplaceJavaClassAsAnnotationArgumentFix.kt
index e8bcdcdca46..3b21f88168c 100644
--- a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceJavaClass/ReplaceJavaClassAsAnnotationArgumentFix.kt
+++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceJavaClass/ReplaceJavaClassAsAnnotationArgumentFix.kt
@@ -33,7 +33,7 @@ import java.util.ArrayList
public class ReplaceJavaClassAsAnnotationArgumentFix(
annotationEntry: JetAnnotationEntry
-) : JetIntentionAction(annotationEntry) {
+) : JetIntentionAction(annotationEntry), CleanupFix {
override fun getText() = JetBundle.message("replace.java.class.argument")
override fun getFamilyName() = JetBundle.message("replace.java.class.argument.family")
diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceJavaClass/ReplaceJavaClassAsAnnotationParameterFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceJavaClass/ReplaceJavaClassAsAnnotationParameterFix.kt
index 125e8585853..720e8ad9ffa 100644
--- a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceJavaClass/ReplaceJavaClassAsAnnotationParameterFix.kt
+++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceJavaClass/ReplaceJavaClassAsAnnotationParameterFix.kt
@@ -16,27 +16,22 @@
package org.jetbrains.kotlin.idea.quickfix.replaceJavaClass
-import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
-import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.idea.JetBundle
+import org.jetbrains.kotlin.idea.quickfix.CleanupFix
import org.jetbrains.kotlin.idea.quickfix.JetIntentionAction
import org.jetbrains.kotlin.idea.quickfix.JetSingleIntentionActionFactory
import org.jetbrains.kotlin.idea.quickfix.JetWholeProjectForEachElementOfTypeFix
-import org.jetbrains.kotlin.idea.quickfix.JetWholeProjectModalAction
import org.jetbrains.kotlin.idea.quickfix.quickfixUtil.createIntentionFactory
import org.jetbrains.kotlin.idea.quickfix.quickfixUtil.createIntentionForFirstParentOfType
-import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
-import org.jetbrains.kotlin.idea.util.ShortenReferences
-import org.jetbrains.kotlin.psi.*
-import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
-import java.util.ArrayList
+import org.jetbrains.kotlin.psi.JetClass
+import org.jetbrains.kotlin.psi.JetFile
public class ReplaceJavaClassAsAnnotationParameterFix(
annotationClass: JetClass
-) : JetIntentionAction(annotationClass) {
+) : JetIntentionAction(annotationClass), CleanupFix {
override fun getText() = JetBundle.message("replace.java.class.parameter")
override fun getFamilyName() = JetBundle.message("replace.java.class.parameter.family")
diff --git a/idea/testData/inspections/cleanup/cleanup.kt b/idea/testData/inspections/cleanup/cleanup.kt
index 9cef11cddd8..9f8535b26c5 100644
--- a/idea/testData/inspections/cleanup/cleanup.kt
+++ b/idea/testData/inspections/cleanup/cleanup.kt
@@ -30,3 +30,15 @@ fun foo() {
continue@loop
}
}
+
+fun unnecessarySafeCall(x: String) {
+ x?.length()
+}
+
+fun unnecessaryExclExcl(x: String) {
+ x!!.length()
+}
+
+fun unnecessaryCast(x: String) = x as String
+
+fun unnecessaryElvis(x: String) = x ?: ""
diff --git a/idea/testData/inspections/cleanup/cleanup.kt.after b/idea/testData/inspections/cleanup/cleanup.kt.after
index 4ae57e05f26..b1ae42ea09d 100644
--- a/idea/testData/inspections/cleanup/cleanup.kt.after
+++ b/idea/testData/inspections/cleanup/cleanup.kt.after
@@ -32,3 +32,15 @@ fun foo() {
continue@loop
}
}
+
+fun unnecessarySafeCall(x: String) {
+ x.length()
+}
+
+fun unnecessaryExclExcl(x: String) {
+ x.length()
+}
+
+fun unnecessaryCast(x: String) = x
+
+fun unnecessaryElvis(x: String) = x