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