Add quickfixes for unnecessary casts and nullability operations to cleanup inspection. Rewrite two quickfixes from Java to Kotlin. Add inspection description. Use marker interface for identifying quickfixes applicable for code cleanup.

This commit is contained in:
Dmitry Jemerov
2015-05-21 17:06:59 +02:00
parent e2dbfebf32
commit 58e4ff0502
22 changed files with 285 additions and 333 deletions
@@ -0,0 +1,7 @@
<html>
<body>
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.
</body>
</html>
+1 -1
View File
@@ -1009,7 +1009,7 @@
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.KotlinCleanupInspection"
shortName="KotlinDeprecation"
displayName="Deprecated language feature"
displayName="Usage of redundant or deprecated syntax"
groupName="Kotlin"
enabledByDefault="true"
cleanupTool="true"
@@ -27,6 +27,7 @@ import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFullyAndGetResult
import org.jetbrains.kotlin.idea.highlighter.JetPsiChecker
import org.jetbrains.kotlin.idea.quickfix.CleanupFix
import org.jetbrains.kotlin.idea.quickfix.JetWholeProjectModalAction
import org.jetbrains.kotlin.idea.quickfix.ReplaceObsoleteLabelSyntaxFix
import org.jetbrains.kotlin.idea.quickfix.looksLikeObsoleteLabel
@@ -39,7 +40,7 @@ import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm
public class KotlinCleanupInspection(): LocalInspectionTool(), CleanupLocalInspectionTool {
// required to simplify the inspection registration in tests
override fun getDisplayName(): String = "Deprecated language feature"
override fun getDisplayName(): String = "Usage of redundant or deprecated syntax"
override fun checkFile(file: PsiFile, manager: InspectionManager, isOnTheFly: Boolean): Array<out ProblemDescriptor>? {
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<JetAnnotationEntry>() ?: 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<*>
}
}
@@ -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
@@ -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<JetAnnotation>(element) {
public class DeprecatedAnnotationSyntaxFix(element: JetAnnotation) : JetIntentionAction<JetAnnotation>(element), CleanupFix {
override fun getFamilyName(): String = "Replace with '@' annotations"
override fun getText(): String = "Replace with '@' annotations"
@@ -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<JetEnumEntry>(element) {
class DeprecatedEnumEntryDelimiterSyntaxFix(element: JetEnumEntry): JetIntentionAction<JetEnumEntry>(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<JetIntentionAction>.isAvailable(project, editor, file) && DeclarationsChecker.enumEntryUsesDeprecatedOrNoDelimiter(element)
companion object : JetSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): IntentionAction? =
@@ -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<JetEnumEntry>(element) {
class DeprecatedEnumEntrySuperConstructorSyntaxFix(element: JetEnumEntry): JetIntentionAction<JetEnumEntry>(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<JetIntentionAction>.isAvailable(project, editor, file) && DeclarationsChecker.enumEntryUsesDeprecatedSuperConstructor(element)
companion object: JetSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): IntentionAction? =
@@ -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<JetFunctionLiteralExpression>(element) {
public class DeprecatedLambdaSyntaxFix(element: JetFunctionLiteralExpression) : JetIntentionAction<JetFunctionLiteralExpression>(element), CleanupFix {
override fun getText() = JetBundle.message("migrate.lambda.syntax")
override fun getFamilyName() = JetBundle.message("migrate.lambda.syntax.family")
@@ -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<PsiElement>(element) {
public class DeprecatedTraitSyntaxFix(element: PsiElement): JetIntentionAction<PsiElement>(element), CleanupFix {
override fun getFamilyName() = "Replace with 'interface'"
override fun getText() = getFamilyName()
@@ -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;
}
}
@@ -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<ExclExclCallFix>.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())
}
}
@@ -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<JetPrimaryConstructor>(element) {
public class MissingConstructorKeywordFix(element: JetPrimaryConstructor) : JetIntentionAction<JetPrimaryConstructor>(element), CleanupFix {
override fun getFamilyName(): String = getText()
override fun getText(): String = "Add 'constructor' keyword"
@@ -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);
@@ -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<JetNamedFunction>(element) {
public class RemoveNameFromFunctionExpressionFix(element: JetNamedFunction) : JetIntentionAction<JetNamedFunction>(element), CleanupFix {
override fun getText(): String = "Remove identifier from function expression"
override fun getFamilyName(): String = getText()
@@ -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<T extends JetExpression> extends JetIntentionAction<T> {
public class RemoveRightPartOfBinaryExpressionFix<T extends JetExpression> extends JetIntentionAction<T> implements CleanupFix {
private final String message;
public RemoveRightPartOfBinaryExpressionFix(@NotNull T element, String message) {
@@ -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());
}
}
@@ -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<out JetQualifiedExpression>
}
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<out JetQualifiedExpression> get() = javaClass<JetDotQualifiedExpression>()
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<out JetQualifiedExpression> get() = javaClass<JetSafeQualifiedExpression>()
companion object : JetSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): IntentionAction
= ReplaceWithDotCallFix(diagnostic.getPsiElement())
}
}
@@ -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<JetAnnotationEntry>(element) {
public class ReplaceObsoleteLabelSyntaxFix(element: JetAnnotationEntry?) : JetIntentionAction<JetAnnotationEntry>(element), CleanupFix {
override fun getFamilyName(): String = "Update obsolete label syntax"
override fun getText(): String = "Replace with label ${element.getCalleeExpression()?.getText() ?: ""}@"
@@ -33,7 +33,7 @@ import java.util.ArrayList
public class ReplaceJavaClassAsAnnotationArgumentFix(
annotationEntry: JetAnnotationEntry
) : JetIntentionAction<JetAnnotationEntry>(annotationEntry) {
) : JetIntentionAction<JetAnnotationEntry>(annotationEntry), CleanupFix {
override fun getText() = JetBundle.message("replace.java.class.argument")
override fun getFamilyName() = JetBundle.message("replace.java.class.argument.family")
@@ -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<JetClass>(annotationClass) {
) : JetIntentionAction<JetClass>(annotationClass), CleanupFix {
override fun getText() = JetBundle.message("replace.java.class.parameter")
override fun getFamilyName() = JetBundle.message("replace.java.class.parameter.family")
@@ -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 ?: ""
@@ -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