diff --git a/java/java-analysis-impl/src/META-INF/JavaAnalysisPlugin.xml b/java/java-analysis-impl/src/META-INF/JavaAnalysisPlugin.xml index c16e1b7026c5..e5e7ca3890e2 100644 --- a/java/java-analysis-impl/src/META-INF/JavaAnalysisPlugin.xml +++ b/java/java-analysis-impl/src/META-INF/JavaAnalysisPlugin.xml @@ -37,6 +37,7 @@ + diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightFixUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightFixUtil.java index 28b782cecff8..5c8504521311 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightFixUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightFixUtil.java @@ -176,26 +176,29 @@ public final class HighlightFixUtil { } } - static void registerUnhandledExceptionFixes(@NotNull PsiElement element, @Nullable HighlightInfo.Builder info) { - IntentionAction action4 = QuickFixFactory.getInstance().createAddExceptionFromFieldInitializerToConstructorThrowsFix(element); + static void registerUnhandledExceptionFixes(@NotNull PsiElement element, @Nullable HighlightInfo.Builder info, + @NotNull List unhandledExceptions) { if (info != null) { + final QuickFixFactory quickFixFactory = QuickFixFactory.getInstance(); + + IntentionAction action4 = quickFixFactory.createAddExceptionFromFieldInitializerToConstructorThrowsFix(element); info.registerFix(action4, null, null, null, null); - } - IntentionAction action3 = QuickFixFactory.getInstance().createAddExceptionToCatchFix(); - if (info != null) { + + IntentionAction action3 = quickFixFactory.createAddExceptionToCatchFix(); info.registerFix(action3, null, null, null, null); - } - IntentionAction action2 = QuickFixFactory.getInstance().createAddExceptionToExistingCatch(element); - if (info != null) { + + IntentionAction action2 = quickFixFactory.createAddExceptionToExistingCatch(element); info.registerFix(action2, null, null, null, null); - } - IntentionAction action1 = QuickFixFactory.getInstance().createAddExceptionToThrowsFix(element); - if (info != null) { + + IntentionAction action1 = quickFixFactory.createAddExceptionToThrowsFix(element); info.registerFix(action1, null, null, null, null); - } - IntentionAction action = QuickFixFactory.getInstance().createSurroundWithTryCatchFix(element); - if (info != null) { + + IntentionAction action = quickFixFactory.createSurroundWithTryCatchFix(element); info.registerFix(action, null, null, null, null); + + for (UnhandledExceptionFixProvider provider : UnhandledExceptionFixProvider.EP_NAME.getExtensionList()) { + provider.registerUnhandledExceptionFixes(info, element, unhandledExceptions); + } } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java index 66c4b23e1ba8..000d3bc6f9ef 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java @@ -925,7 +925,7 @@ public final class HighlightUtil { TextRange textRange = computeRange(element); String description = getUnhandledExceptionsDescriptor(unhandled); HighlightInfo.Builder info = HighlightInfo.newHighlightInfo(highlightType).range(textRange).descriptionAndTooltip(description); - HighlightFixUtil.registerUnhandledExceptionFixes(element, info); + HighlightFixUtil.registerUnhandledExceptionFixes(element, info, unhandled); return info; } @@ -958,7 +958,7 @@ public final class HighlightUtil { String description = JavaErrorBundle.message("unhandled.close.exceptions", formatTypes(unhandled), unhandled.size(), JavaErrorBundle.message("auto.closeable.resource")); HighlightInfo.Builder highlight = HighlightInfo.newHighlightInfo(highlightType).range(resource).descriptionAndTooltip(description); - HighlightFixUtil.registerUnhandledExceptionFixes(resource, highlight); + HighlightFixUtil.registerUnhandledExceptionFixes(resource, highlight, unhandled); return highlight; } diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/UnhandledExceptionFixProvider.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/UnhandledExceptionFixProvider.java new file mode 100644 index 000000000000..bc2578c728e2 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/UnhandledExceptionFixProvider.java @@ -0,0 +1,29 @@ +// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.codeInsight.daemon.impl.analysis; + +import com.intellij.codeInsight.daemon.impl.HighlightInfo; +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.psi.PsiClassType; +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + * The UnhandledExceptionFixProvider interface provides a way to register fixes for unhandled exceptions. + */ +public interface UnhandledExceptionFixProvider { + ExtensionPointName EP_NAME = new ExtensionPointName<>("com.intellij.unhandledExceptionFixProvider"); + + /** + * Registers fixes for unhandled exceptions. + *

+ * This method is responsible for registering fixes for unhandled exceptions in the provided {@link HighlightInfo.Builder} instance. + * + * @param info the {@link HighlightInfo.Builder} instance to register the fixes into + * @param element the {@link PsiElement} representing the location of the unhandled exception + * @param unhandledExceptions the list of unhandled exception types + */ + void registerUnhandledExceptionFixes(@NotNull HighlightInfo.Builder info, @NotNull PsiElement element, + @NotNull List unhandledExceptions); +} diff --git a/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/extension/LombokHighlightErrorFilter.java b/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/extension/LombokHighlightErrorFilter.java index 49292f768685..19fe3d843f34 100644 --- a/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/extension/LombokHighlightErrorFilter.java +++ b/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/extension/LombokHighlightErrorFilter.java @@ -1,41 +1,18 @@ package de.plushnikov.intellij.plugin.extension; -import com.intellij.codeInsight.daemon.JavaErrorBundle; import com.intellij.codeInsight.daemon.impl.HighlightInfo; import com.intellij.codeInsight.daemon.impl.HighlightInfoFilter; -import com.intellij.codeInsight.intention.AddAnnotationFix; import com.intellij.lang.annotation.HighlightSeverity; -import com.intellij.openapi.editor.colors.CodeInsightColors; -import com.intellij.openapi.editor.colors.TextAttributesKey; import com.intellij.openapi.project.Project; -import com.intellij.psi.*; -import com.intellij.psi.util.PsiTreeUtil; -import de.plushnikov.intellij.plugin.LombokClassNames; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; import de.plushnikov.intellij.plugin.handler.OnXAnnotationHandler; -import de.plushnikov.intellij.plugin.quickfix.PsiQuickFixFactory; import de.plushnikov.intellij.plugin.util.LombokLibraryUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; -import java.util.regex.Pattern; - public class LombokHighlightErrorFilter implements HighlightInfoFilter { - private static final class Holder { - static final Map>> registeredHooks; - - static { - registeredHooks = new HashMap<>(); - - for (LombokHighlightFixHook highlightFixHook : LombokHighlightFixHook.values()) { - registeredHooks.computeIfAbsent(highlightFixHook.severity, s -> new HashMap<>()) - .computeIfAbsent(highlightFixHook.key, k -> new ArrayList<>()) - .add(highlightFixHook); - } - } - } - public LombokHighlightErrorFilter() { } @@ -63,59 +40,6 @@ public class LombokHighlightErrorFilter implements HighlightInfoFilter { } } - // register different quick fix for highlight - Holder.registeredHooks - .getOrDefault(highlightInfo.getSeverity(), Collections.emptyMap()) - .getOrDefault(highlightInfo.type.getAttributesKey(), Collections.emptyList()) - .stream() - .filter(filter -> filter.descriptionCheck(highlightInfo.getDescription())) - .forEach(filter -> filter.processHook(highlightedElement, highlightInfo)); - return true; } - - private enum LombokHighlightFixHook { - - UNHANDLED_EXCEPTION(HighlightSeverity.ERROR, CodeInsightColors.ERRORS_ATTRIBUTES) { - private final Pattern pattern = preparePattern(1); - private final Pattern pattern2 = preparePattern(2); - - @NotNull - private static Pattern preparePattern(int count) { - return Pattern.compile(JavaErrorBundle.message("unhandled.exceptions", ".*", count)); - } - - @Override - public boolean descriptionCheck(@Nullable String description) { - return description != null && (pattern.matcher(description).matches() || pattern2.matcher(description).matches()); - } - - @Override - public void processHook(@NotNull PsiElement highlightedElement, @NotNull HighlightInfo highlightInfo) { - PsiElement importantParent = PsiTreeUtil.getParentOfType(highlightedElement, - PsiMethod.class, PsiLambdaExpression.class, - PsiMethodReferenceExpression.class, PsiClassInitializer.class - ); - - // applicable only for methods - if (importantParent instanceof PsiMethod) { - AddAnnotationFix fix = - PsiQuickFixFactory.createAddAnnotationFix(LombokClassNames.SNEAKY_THROWS, (PsiModifierListOwner)importantParent); - highlightInfo.registerFix(fix, null, null, null, null); - } - } - }; - - private final HighlightSeverity severity; - private final TextAttributesKey key; - - LombokHighlightFixHook(@NotNull HighlightSeverity severity, @Nullable TextAttributesKey key) { - this.severity = severity; - this.key = key; - } - - abstract public boolean descriptionCheck(@Nullable String description); - - abstract public void processHook(@NotNull PsiElement highlightedElement, @NotNull HighlightInfo highlightInfo); - } } diff --git a/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/handler/SneakyThrowsExceptionHandler.java b/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/handler/SneakyThrowsExceptionHandler.java index ff442c36c902..582b3a88f7c5 100644 --- a/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/handler/SneakyThrowsExceptionHandler.java +++ b/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/handler/SneakyThrowsExceptionHandler.java @@ -23,7 +23,7 @@ public class SneakyThrowsExceptionHandler extends CustomExceptionHandler { @Override public boolean isHandled(@Nullable PsiElement element, @NotNull PsiClassType exceptionType, PsiElement topElement) { final PsiCodeBlock containingCodeBlock = PsiTreeUtil.getParentOfType(element, PsiCodeBlock.class, false); - if (isCodeBlockWithExceptionInConstructorCall(containingCodeBlock, exceptionType)) { + if (isCodeBlockWithExceptionInConstructorCall(containingCodeBlock, Collections.singleton(exceptionType))) { // call to a sibling or super constructor is excluded from the @SneakyThrows treatment return false; } @@ -50,20 +50,25 @@ public class SneakyThrowsExceptionHandler extends CustomExceptionHandler { } private static boolean isCodeBlockWithExceptionInConstructorCall(@Nullable PsiCodeBlock codeBlock, - @NotNull PsiClassType exceptionType) { + @NotNull Collection exceptionTypes) { final PsiMethod containingMethod = PsiTreeUtil.getParentOfType(codeBlock, PsiMethod.class); if (null != containingMethod) { final PsiMethodCallExpression thisOrSuperCallInConstructor = JavaPsiConstructorUtil.findThisOrSuperCallInConstructor(containingMethod); if (null != thisOrSuperCallInConstructor) { - ExceptionTypesCollector visitor = new ExceptionTypesCollector(); - thisOrSuperCallInConstructor.accept(visitor); - return visitor.exceptionTypes.contains(exceptionType); + return throwsExceptionsTypes(thisOrSuperCallInConstructor, exceptionTypes); } } return false; } + static boolean throwsExceptionsTypes(@NotNull PsiMethodCallExpression thisOrSuperCallInConstructor, + @NotNull Collection exceptionTypes) { + ExceptionTypesCollector visitor = new ExceptionTypesCollector(); + thisOrSuperCallInConstructor.accept(visitor); + return ContainerUtil.intersects(visitor.exceptionTypes, exceptionTypes); + } + private static class ExceptionTypesCollector extends JavaRecursiveElementWalkingVisitor { private final Collection exceptionTypes = new HashSet<>(); diff --git a/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/handler/SneakyThrowsUnhandledExceptionFixProvider.java b/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/handler/SneakyThrowsUnhandledExceptionFixProvider.java new file mode 100644 index 000000000000..dbf28a50baeb --- /dev/null +++ b/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/handler/SneakyThrowsUnhandledExceptionFixProvider.java @@ -0,0 +1,37 @@ +package de.plushnikov.intellij.plugin.handler; + +import com.intellij.codeInsight.daemon.impl.HighlightInfo; +import com.intellij.codeInsight.daemon.impl.analysis.UnhandledExceptionFixProvider; +import com.intellij.codeInsight.intention.AddAnnotationFix; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.JavaPsiConstructorUtil; +import de.plushnikov.intellij.plugin.LombokClassNames; +import de.plushnikov.intellij.plugin.quickfix.PsiQuickFixFactory; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + * Adds @SneakyThrows Annotation as Fix to handle unhandled exceptions + */ +public class SneakyThrowsUnhandledExceptionFixProvider implements UnhandledExceptionFixProvider { + @Override + public void registerUnhandledExceptionFixes(@NotNull HighlightInfo.Builder info, + @NotNull PsiElement element, + @NotNull List unhandledExceptions) { + PsiElement importantParent = PsiTreeUtil.getParentOfType(element, PsiMethod.class, PsiLambdaExpression.class, + PsiMethodReferenceExpression.class, PsiClassInitializer.class); + + // applicable only for methods + if (importantParent instanceof PsiMethod psiMethod) { + final PsiMethodCallExpression thisOrSuperCallInConstructor = JavaPsiConstructorUtil.findThisOrSuperCallInConstructor(psiMethod); + if (null == thisOrSuperCallInConstructor || + !SneakyThrowsExceptionHandler.throwsExceptionsTypes(thisOrSuperCallInConstructor, unhandledExceptions)) { + + AddAnnotationFix fix = PsiQuickFixFactory.createAddAnnotationFix(LombokClassNames.SNEAKY_THROWS, psiMethod); + info.registerFix(fix, null, null, null, null); + } + } + } +} diff --git a/plugins/lombok/src/main/resources/META-INF/plugin.xml b/plugins/lombok/src/main/resources/META-INF/plugin.xml index 36e086ced0f8..3a1b8c3e2f39 100644 --- a/plugins/lombok/src/main/resources/META-INF/plugin.xml +++ b/plugins/lombok/src/main/resources/META-INF/plugin.xml @@ -83,6 +83,7 @@ + diff --git a/plugins/lombok/src/test/java/de/plushnikov/intellij/plugin/highlights/SneakyThrowsQuickFixTest.java b/plugins/lombok/src/test/java/de/plushnikov/intellij/plugin/highlights/SneakyThrowsQuickFixTest.java new file mode 100644 index 000000000000..8975b4298300 --- /dev/null +++ b/plugins/lombok/src/test/java/de/plushnikov/intellij/plugin/highlights/SneakyThrowsQuickFixTest.java @@ -0,0 +1,133 @@ +package de.plushnikov.intellij.plugin.highlights; + +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.ide.highlighter.JavaFileType; +import de.plushnikov.intellij.plugin.AbstractLombokLightCodeInsightTestCase; +import de.plushnikov.intellij.plugin.inspection.LombokInspection; +import org.intellij.lang.annotations.Language; + +import java.util.List; + +public class SneakyThrowsQuickFixTest extends AbstractLombokLightCodeInsightTestCase { + + public void testSneakyThrowsInvalidQuickFix() { + myFixture.enableInspections(LombokInspection.class); + + @Language("JAVA") final String text = """ + import lombok.SneakyThrows; + import java.io.IOException; + + public class TestSneakyThrow { + + @SneakyThrows + public TestSneakyThrow(String s) { + this(throwException()); + } + + public TestSneakyThrow(int i) { + } + + private static int throwException() throws IOException { + throw new IOException(); + } + } + """; + myFixture.configureByText(JavaFileType.INSTANCE, text); + + myFixture.launchAction("Remove annotation"); + + @Language("JAVA") final String expectedText = """ + import java.io.IOException; + + public class TestSneakyThrow { + + public TestSneakyThrow(String s) { + this(throwException()); + } + + public TestSneakyThrow(int i) { + } + + private static int throwException() throws IOException { + throw new IOException(); + } + } + """; + myFixture.checkResult(expectedText, true); + } + + public void testSneakyThrowsUnhandledExceptionWithoutQuickFix() { + @Language("JAVA") final String text = """ + import java.io.IOException; + + public class TestSneakyThrow { + + public TestSneakyThrow(String s) { + this(throwException()); + } + + public TestSneakyThrow(int i) { + } + + private static int throwException() throws IOException { + throw new IOException(); + } + } + """; + myFixture.configureByText(JavaFileType.INSTANCE, text); + + List actions = myFixture.filterAvailableIntentions("Annotate constructor 'TestSneakyThrow()' as '@SneakyThrows'"); + assertEmpty(actions); + } + + public void testSneakyThrowsUnhandledExceptionWithQuickFix() { + @Language("JAVA") final String text = """ + import java.io.IOException; + + public class TestSneakyThrow { + + public TestSneakyThrow(String s) { + this(throwException()); + if (1 == 1) { + throw new Exception("123"); + } + } + + public TestSneakyThrow(int i) { + } + + private static int throwException() throws IOException { + throw new IOException(); + } + } + """; + myFixture.configureByText(JavaFileType.INSTANCE, text); + + myFixture.launchAction("Annotate constructor 'TestSneakyThrow()' as '@SneakyThrows'"); + + @Language("JAVA") final String expectedText = """ + import lombok.SneakyThrows; + + import java.io.IOException; + + public class TestSneakyThrow { + + @SneakyThrows + public TestSneakyThrow(String s) { + this(throwException()); + if (1 == 1) { + throw new Exception("123"); + } + } + + public TestSneakyThrow(int i) { + } + + private static int throwException() throws IOException { + throw new IOException(); + } + } + """; + myFixture.checkResult(expectedText, true); + } +}