From df6793fb4903feb993f4d8c6c641be402d96afee Mon Sep 17 00:00:00 2001 From: UNV Date: Thu, 11 Dec 2025 00:24:06 +0300 Subject: [PATCH] Java inspections and refactoring processors refactoring (part 1). --- .../AnonymousCanBeLambdaInspection.java | 1028 +++++++++-------- ...onymousCanBeMethodReferenceInspection.java | 279 +++-- .../EnhancedSwitchMigrationInspection.java | 432 ++++--- .../deprecation/DeprecationInspection.java | 194 ++-- .../equalsAndHashcode/EqualsAndHashcode.java | 204 ++-- .../com/siyeh/ig/BaseInspectionVisitor.java | 413 +++---- ...ProtectedMemberInFinalClassInspection.java | 41 +- .../ConvertInterfaceToClassIntention.java | 134 ++- .../impl/ipp/modifiers/ModifierIntention.java | 75 +- .../JavaChangeSignatureUsageProcessor.java | 97 +- 10 files changed, 1592 insertions(+), 1305 deletions(-) diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/AnonymousCanBeLambdaInspection.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/AnonymousCanBeLambdaInspection.java index dcdff107ea..f92d7503fc 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/AnonymousCanBeLambdaInspection.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/AnonymousCanBeLambdaInspection.java @@ -29,6 +29,8 @@ import com.intellij.java.language.psi.util.PsiTypesUtil; import com.intellij.java.language.psi.util.PsiUtil; import com.intellij.java.language.psi.util.RedundantCastUtil; +import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; import consulo.annotation.component.ExtensionImpl; import consulo.component.util.text.UniqueNameGenerator; import consulo.document.util.TextRange; @@ -58,556 +60,622 @@ */ @ExtensionImpl public class AnonymousCanBeLambdaInspection extends BaseJavaBatchLocalInspectionTool { - public static final Logger LOG = Logger.getInstance(AnonymousCanBeLambdaInspection.class); - - @Nonnull - @Override - public LocalizeValue getGroupDisplayName() { - return InspectionLocalize.groupNamesLanguageLevelSpecificIssuesAndMigrationAids(); - } - - @Nonnull - @Override - public LocalizeValue getDisplayName() { - return LocalizeValue.localizeTODO("Anonymous type can be replaced with lambda"); - } - - @Override - public boolean isEnabledByDefault() { - return true; - } - - @Nonnull - @Override - public String getShortName() { - return "Convert2Lambda"; - } - - @Nonnull - @Override - public HighlightDisplayLevel getDefaultLevel() { - return HighlightDisplayLevel.WARNING; - } - - @Nonnull - @Override - public AnonymousCanBeLambdaInspectionState createStateProvider() { - return new AnonymousCanBeLambdaInspectionState(); - } - - @Nonnull - @Override - public PsiElementVisitor buildVisitorImpl(@Nonnull final ProblemsHolder holder, - boolean isOnTheFly, - LocalInspectionToolSession session, - AnonymousCanBeLambdaInspectionState state) { - return new JavaElementVisitor() { - @Override - public void visitAnonymousClass(final PsiAnonymousClass aClass) { - super.visitAnonymousClass(aClass); - final PsiElement parent = aClass.getParent(); - final PsiElement lambdaContext = parent != null ? parent.getParent() : null; - if (lambdaContext != null && - (LambdaUtil.isValidLambdaContext(lambdaContext) || !(lambdaContext instanceof PsiExpressionStatement)) && - canBeConvertedToLambda(aClass, false, isOnTheFly || state.reportNotAnnotatedInterfaces, Collections.emptySet())) { - final PsiElement lBrace = aClass.getLBrace(); - LOG.assertTrue(lBrace != null); - final TextRange rangeInElement = new TextRange(0, aClass.getStartOffsetInParent() + lBrace.getStartOffsetInParent()); - ProblemHighlightType problemHighlightType = ProblemHighlightType.LIKE_UNUSED_SYMBOL; - if (isOnTheFly && !state.reportNotAnnotatedInterfaces) { - final PsiClass baseClass = aClass.getBaseClassType().resolve(); - LOG.assertTrue(baseClass != null); - if (!AnnotationUtil.isAnnotated(baseClass, CommonClassNames.JAVA_LANG_FUNCTIONAL_INTERFACE, false, false)) { - problemHighlightType = ProblemHighlightType.INFORMATION; - } - } - holder.registerProblem(parent, "Anonymous #ref #loc can be replaced with lambda", problemHighlightType, rangeInElement, new ReplaceWithLambdaFix()); - } - } - }; - } - - static boolean hasRuntimeAnnotations(PsiMethod method, @Nonnull Set runtimeAnnotationsToIgnore) { - PsiAnnotation[] annotations = method.getModifierList().getAnnotations(); - for (PsiAnnotation annotation : annotations) { - PsiJavaCodeReferenceElement ref = annotation.getNameReferenceElement(); - PsiElement target = ref != null ? ref.resolve() : null; - if (target instanceof PsiClass) { - if (runtimeAnnotationsToIgnore.contains(((PsiClass) target).getQualifiedName())) { - continue; - } - final PsiAnnotation retentionAnno = AnnotationUtil.findAnnotation((PsiClass) target, Retention.class.getName()); - if (retentionAnno != null) { - PsiAnnotationMemberValue value = retentionAnno.findAttributeValue("value"); - if (value instanceof PsiReferenceExpression) { - final PsiElement resolved = ((PsiReferenceExpression) value).resolve(); - if (resolved instanceof PsiField && RetentionPolicy.RUNTIME.name().equals(((PsiField) resolved).getName())) { - final PsiClass containingClass = ((PsiField) resolved).getContainingClass(); - if (containingClass != null && RetentionPolicy.class.getName().equals(containingClass.getQualifiedName())) { - return true; - } - } - } - } - } - } - return false; - } - - public static boolean hasForbiddenRefsInsideBody(PsiMethod method, PsiAnonymousClass aClass) { - final ForbiddenRefsChecker checker = new ForbiddenRefsChecker(method, aClass); - final PsiCodeBlock body = method.getBody(); - LOG.assertTrue(body != null); - body.accept(checker); - return checker.hasForbiddenRefs(); - } - - private static PsiType getInferredType(PsiAnonymousClass aClass, PsiMethod method) { - final PsiExpression expression = (PsiExpression) aClass.getParent(); - final PsiType psiType = PsiTypesUtil.getExpectedTypeByParent(expression); - if (psiType != null) { - return psiType; + public static final Logger LOG = Logger.getInstance(AnonymousCanBeLambdaInspection.class); + + @Nonnull + @Override + public LocalizeValue getGroupDisplayName() { + return InspectionLocalize.groupNamesLanguageLevelSpecificIssuesAndMigrationAids(); } - PsiExpression topExpr = expression; - while (topExpr.getParent() instanceof PsiParenthesizedExpression) { - topExpr = (PsiExpression) topExpr.getParent(); + @Nonnull + @Override + public LocalizeValue getDisplayName() { + return LocalizeValue.localizeTODO("Anonymous type can be replaced with lambda"); } - final PsiCall call = LambdaUtil.treeWalkUp(topExpr); - if (call != null && call.resolveMethod() != null) { - final int offsetInTopCall = aClass.getTextRange().getStartOffset() - call.getTextRange().getStartOffset(); - PsiCall copyCall = LambdaUtil.copyTopLevelCall(call); - if (copyCall == null) { - return null; - } - final PsiAnonymousClass classArg = PsiTreeUtil.getParentOfType(copyCall.findElementAt(offsetInTopCall), PsiAnonymousClass.class); - if (classArg != null) { - PsiExpression lambda = JavaPsiFacade.getElementFactory(aClass.getProject()).createExpressionFromText(ReplaceWithLambdaFix.composeLambdaText(method), expression); - lambda = (PsiExpression) classArg.getParent().replace(lambda); - ((PsiLambdaExpression) lambda).getBody().replace(method.getBody()); - final PsiType interfaceType; - if (copyCall.resolveMethod() == null) { - return PsiType.NULL; - } else { - interfaceType = ((PsiLambdaExpression) lambda).getFunctionalInterfaceType(); - } + @Override + public boolean isEnabledByDefault() { + return true; + } - return interfaceType; - } + @Nonnull + @Override + public String getShortName() { + return "Convert2Lambda"; } - return PsiType.NULL; - } + @Nonnull + @Override + public HighlightDisplayLevel getDefaultLevel() { + return HighlightDisplayLevel.WARNING; + } - public static boolean canBeConvertedToLambda(PsiAnonymousClass aClass, boolean acceptParameterizedFunctionTypes, @Nonnull Set ignoredRuntimeAnnotations) { - return canBeConvertedToLambda(aClass, acceptParameterizedFunctionTypes, true, ignoredRuntimeAnnotations); - } + @Nonnull + @Override + public AnonymousCanBeLambdaInspectionState createStateProvider() { + return new AnonymousCanBeLambdaInspectionState(); + } - public static boolean isLambdaForm(PsiAnonymousClass aClass, Set ignoredRuntimeAnnotations) { - PsiMethod[] methods = aClass.getMethods(); - if (methods.length != 1) { - return false; + @Nonnull + @Override + public PsiElementVisitor buildVisitorImpl( + @Nonnull final ProblemsHolder holder, + boolean isOnTheFly, + LocalInspectionToolSession session, + AnonymousCanBeLambdaInspectionState state + ) { + return new JavaElementVisitor() { + @Override + @RequiredReadAction + public void visitAnonymousClass(@Nonnull PsiAnonymousClass aClass) { + super.visitAnonymousClass(aClass); + PsiElement parent = aClass.getParent(); + PsiElement lambdaContext = parent != null ? parent.getParent() : null; + if (lambdaContext != null + && (LambdaUtil.isValidLambdaContext(lambdaContext) || !(lambdaContext instanceof PsiExpressionStatement)) + && canBeConvertedToLambda(aClass, false, isOnTheFly || state.reportNotAnnotatedInterfaces, Collections.emptySet())) { + PsiElement lBrace = aClass.getLBrace(); + LOG.assertTrue(lBrace != null); + TextRange rangeInElement = new TextRange(0, aClass.getStartOffsetInParent() + lBrace.getStartOffsetInParent()); + ProblemHighlightType problemHighlightType = ProblemHighlightType.LIKE_UNUSED_SYMBOL; + if (isOnTheFly && !state.reportNotAnnotatedInterfaces) { + PsiClass baseClass = aClass.getBaseClassType().resolve(); + LOG.assertTrue(baseClass != null); + if (!AnnotationUtil.isAnnotated(baseClass, CommonClassNames.JAVA_LANG_FUNCTIONAL_INTERFACE, false, false)) { + problemHighlightType = ProblemHighlightType.INFORMATION; + } + } + holder.newProblem(LocalizeValue.localizeTODO("Anonymous #ref #loc can be replaced with lambda")) + .range(parent, rangeInElement) + .highlightType(problemHighlightType) + .withFix(new ReplaceWithLambdaFix()) + .create(); + } + } + }; } - PsiMethod method = methods[0]; - return aClass.getFields().length == 0 && - aClass.getInnerClasses().length == 0 && - aClass.getInitializers().length == 0 && - method.getBody() != null && - method.getDocComment() == null && - !hasRuntimeAnnotations(method, ignoredRuntimeAnnotations) && - !method.hasModifierProperty(PsiModifier.SYNCHRONIZED) && - !hasForbiddenRefsInsideBody(method, aClass); - } - - public static boolean canBeConvertedToLambda(PsiAnonymousClass aClass, - boolean acceptParameterizedFunctionTypes, - boolean reportNotAnnotatedInterfaces, - @Nonnull Set ignoredRuntimeAnnotations) { - if (PsiUtil.getLanguageLevel(aClass).isAtLeast(LanguageLevel.JDK_1_8)) { - final PsiClassType baseClassType = aClass.getBaseClassType(); - final PsiClassType.ClassResolveResult resolveResult = baseClassType.resolveGenerics(); - final PsiClass baseClass = resolveResult.getElement(); - if (baseClass == null || !reportNotAnnotatedInterfaces && !AnnotationUtil.isAnnotated(baseClass, CommonClassNames.JAVA_LANG_FUNCTIONAL_INTERFACE, false, false)) { - return false; - } - final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(resolveResult); - if (interfaceMethod != null && (acceptParameterizedFunctionTypes || !interfaceMethod.hasTypeParameters())) { - if (isLambdaForm(aClass, ignoredRuntimeAnnotations)) { - final PsiMethod method = aClass.getMethods()[0]; - return getInferredType(aClass, method) != null; + + @RequiredReadAction + static boolean hasRuntimeAnnotations(PsiMethod method, @Nonnull Set runtimeAnnotationsToIgnore) { + PsiAnnotation[] annotations = method.getModifierList().getAnnotations(); + for (PsiAnnotation annotation : annotations) { + PsiJavaCodeReferenceElement ref = annotation.getNameReferenceElement(); + if (ref != null && ref.resolve() instanceof PsiClass targetClass) { + if (runtimeAnnotationsToIgnore.contains(targetClass.getQualifiedName())) { + continue; + } + PsiAnnotation retentionAnno = AnnotationUtil.findAnnotation(targetClass, Retention.class.getName()); + if (retentionAnno != null + && retentionAnno.findAttributeValue("value") instanceof PsiReferenceExpression refExpr + && refExpr.resolve() instanceof PsiField field + && RetentionPolicy.RUNTIME.name().equals(field.getName())) { + PsiClass containingClass = field.getContainingClass(); + if (containingClass != null && RetentionPolicy.class.getName().equals(containingClass.getQualifiedName())) { + return true; + } + } + } } - } + return false; } - return false; - } - - public static PsiExpression replaceAnonymousWithLambda(@Nonnull PsiElement anonymousClass, PsiType expectedType) { - PsiNewExpression newArrayExpression = (PsiNewExpression) JavaPsiFacade.getElementFactory(anonymousClass.getProject()).createExpressionFromText("new " + expectedType.getCanonicalText() + - "[]{" + anonymousClass.getText() + "}", anonymousClass); - PsiArrayInitializerExpression initializer = newArrayExpression.getArrayInitializer(); - LOG.assertTrue(initializer != null); - return replacePsiElementWithLambda(initializer.getInitializers()[0], true, false); - } - - public static PsiExpression replacePsiElementWithLambda(@Nonnull PsiElement element, final boolean ignoreEqualsMethod, boolean forceIgnoreTypeCast) { - if (!(element instanceof PsiNewExpression)) { - return null; + + public static boolean hasForbiddenRefsInsideBody(PsiMethod method, PsiAnonymousClass aClass) { + ForbiddenRefsChecker checker = new ForbiddenRefsChecker(method, aClass); + PsiCodeBlock body = method.getBody(); + LOG.assertTrue(body != null); + body.accept(checker); + return checker.hasForbiddenRefs(); } - final PsiNewExpression newExpression = (PsiNewExpression) element; - final PsiAnonymousClass anonymousClass = newExpression.getAnonymousClass(); + @RequiredReadAction + private static PsiType getInferredType(PsiAnonymousClass aClass, PsiMethod method) { + PsiExpression expression = (PsiExpression) aClass.getParent(); + PsiType psiType = PsiTypesUtil.getExpectedTypeByParent(expression); + if (psiType != null) { + return psiType; + } - if (anonymousClass == null) { - return null; - } + PsiExpression topExpr = expression; + while (topExpr.getParent() instanceof PsiParenthesizedExpression parenthesized) { + topExpr = parenthesized; + } - final PsiMethod method; - if (ignoreEqualsMethod) { - final List methods = ContainerUtil.filter(anonymousClass.getMethods(), method1 -> !"equals".equals(method1.getName())); - method = methods.get(0); - } else { - method = anonymousClass.getMethods()[0]; - } - if (method == null || method.getBody() == null) { - return null; - } + PsiCall call = LambdaUtil.treeWalkUp(topExpr); + if (call != null && call.resolveMethod() != null) { + int offsetInTopCall = aClass.getTextRange().getStartOffset() - call.getTextRange().getStartOffset(); + PsiCall copyCall = LambdaUtil.copyTopLevelCall(call); + if (copyCall == null) { + return null; + } + PsiAnonymousClass classArg = + PsiTreeUtil.getParentOfType(copyCall.findElementAt(offsetInTopCall), PsiAnonymousClass.class); + if (classArg != null) { + PsiExpression lambda = JavaPsiFacade.getElementFactory(aClass.getProject()) + .createExpressionFromText(ReplaceWithLambdaFix.composeLambdaText(method), expression); + lambda = (PsiExpression) classArg.getParent().replace(lambda); + ((PsiLambdaExpression) lambda).getBody().replace(method.getBody()); + PsiType interfaceType; + if (copyCall.resolveMethod() == null) { + return PsiType.NULL; + } + else { + interfaceType = ((PsiLambdaExpression) lambda).getFunctionalInterfaceType(); + } - return generateLambdaByMethod(anonymousClass, method, lambda -> (PsiLambdaExpression) newExpression.replace(lambda), forceIgnoreTypeCast); - } + return interfaceType; + } + } - /** - * Try convert given method of given anonymous class into lambda and replace given element. - * - * @param anonymousClass physical anonymous class containing method - * @param method physical method to convert with non-empty body - * @param replacer an operator which actually inserts a lambda into the file (possibly removing anonymous class) - * and returns an inserted physical lambda - * @param forceIgnoreTypeCast if false, type cast might be added if necessary - * @return newly-generated lambda expression (possibly with typecast) - */ - @Nonnull - static PsiExpression generateLambdaByMethod(PsiAnonymousClass anonymousClass, PsiMethod method, UnaryOperator replacer, boolean forceIgnoreTypeCast) { - ChangeContextUtil.encodeContextInfo(anonymousClass, true); - final String canonicalText = anonymousClass.getBaseClassType().getCanonicalText(); + return PsiType.NULL; + } - final PsiCodeBlock body = method.getBody(); - LOG.assertTrue(body != null); + @RequiredReadAction + public static boolean canBeConvertedToLambda( + PsiAnonymousClass aClass, + boolean acceptParameterizedFunctionTypes, + @Nonnull Set ignoredRuntimeAnnotations + ) { + return canBeConvertedToLambda(aClass, acceptParameterizedFunctionTypes, true, ignoredRuntimeAnnotations); + } - final Collection comments = collectCommentsOutsideMethodBody(anonymousClass, body); - final Project project = anonymousClass.getProject(); - final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project); + @RequiredReadAction + public static boolean isLambdaForm(PsiAnonymousClass aClass, Set ignoredRuntimeAnnotations) { + PsiMethod[] methods = aClass.getMethods(); + if (methods.length != 1) { + return false; + } + PsiMethod method = methods[0]; + return aClass.getFields().length == 0 + && aClass.getInnerClasses().length == 0 + && aClass.getInitializers().length == 0 + && method.getBody() != null + && method.getDocComment() == null + && !hasRuntimeAnnotations(method, ignoredRuntimeAnnotations) + && !method.hasModifierProperty(PsiModifier.SYNCHRONIZED) + && !hasForbiddenRefsInsideBody(method, aClass); + } - final String withoutTypesDeclared = ReplaceWithLambdaFix.composeLambdaText(method); + @RequiredReadAction + public static boolean canBeConvertedToLambda( + PsiAnonymousClass aClass, + boolean acceptParameterizedFunctionTypes, + boolean reportNotAnnotatedInterfaces, + @Nonnull Set ignoredRuntimeAnnotations + ) { + if (PsiUtil.getLanguageLevel(aClass).isAtLeast(LanguageLevel.JDK_1_8)) { + PsiClassType baseClassType = aClass.getBaseClassType(); + PsiClassType.ClassResolveResult resolveResult = baseClassType.resolveGenerics(); + PsiClass baseClass = resolveResult.getElement(); + if (baseClass == null || !reportNotAnnotatedInterfaces + && !AnnotationUtil.isAnnotated(baseClass, CommonClassNames.JAVA_LANG_FUNCTIONAL_INTERFACE, false, false)) { + return false; + } + PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(resolveResult); + if (interfaceMethod != null && (acceptParameterizedFunctionTypes || !interfaceMethod.hasTypeParameters())) { + if (isLambdaForm(aClass, ignoredRuntimeAnnotations)) { + PsiMethod method = aClass.getMethods()[0]; + return getInferredType(aClass, method) != null; + } + } + } + return false; + } - PsiLambdaExpression lambdaExpression = (PsiLambdaExpression) elementFactory.createExpressionFromText(withoutTypesDeclared, anonymousClass); + @RequiredWriteAction + public static PsiExpression replaceAnonymousWithLambda(@Nonnull PsiElement anonymousClass, PsiType expectedType) { + PsiNewExpression newArrayExpression = (PsiNewExpression) JavaPsiFacade.getElementFactory(anonymousClass.getProject()) + .createExpressionFromText("new " + expectedType.getCanonicalText() + "[]{" + anonymousClass.getText() + "}", anonymousClass); + PsiArrayInitializerExpression initializer = newArrayExpression.getArrayInitializer(); + LOG.assertTrue(initializer != null); + return replacePsiElementWithLambda(initializer.getInitializers()[0], true, false); + } - PsiElement lambdaBody = lambdaExpression.getBody(); - LOG.assertTrue(lambdaBody != null); - lambdaBody.replace(body); - lambdaExpression = replacer.apply(lambdaExpression); + @RequiredWriteAction + public static PsiExpression replacePsiElementWithLambda( + @Nonnull PsiElement element, + boolean ignoreEqualsMethod, + boolean forceIgnoreTypeCast + ) { + if (!(element instanceof PsiNewExpression newExpression)) { + return null; + } - final Set variables = new HashSet<>(); - final Set usedLocalNames = new HashSet<>(); + PsiAnonymousClass anonymousClass = newExpression.getAnonymousClass(); - collectLocalVariablesDefinedInsideLambda(lambdaExpression, variables, usedLocalNames); + if (anonymousClass == null) { + return null; + } - ReplaceWithLambdaFix.giveUniqueNames(project, elementFactory, lambdaExpression, usedLocalNames, variables.toArray(new PsiVariable[variables.size()])); + PsiMethod method; + if (ignoreEqualsMethod) { + List methods = + ContainerUtil.filter(anonymousClass.getMethods(), method1 -> !"equals".equals(method1.getName())); + method = methods.get(0); + } + else { + method = anonymousClass.getMethods()[0]; + } + if (method == null || method.getBody() == null) { + return null; + } - final PsiExpression singleExpr = RedundantLambdaCodeBlockInspection.isCodeBlockRedundant(lambdaExpression.getBody()); - if (singleExpr != null) { - lambdaExpression.getBody().replace(singleExpr); + return generateLambdaByMethod( + anonymousClass, + method, + lambda -> (PsiLambdaExpression) newExpression.replace(lambda), + forceIgnoreTypeCast + ); } - ChangeContextUtil.decodeContextInfo(lambdaExpression, null, null); - restoreComments(comments, lambdaExpression); - final JavaCodeStyleManager javaCodeStyleManager = JavaCodeStyleManager.getInstance(project); - if (forceIgnoreTypeCast) { - return (PsiExpression) javaCodeStyleManager.shortenClassReferences(lambdaExpression); - } + /** + * Try convert given method of given anonymous class into lambda and replace given element. + * + * @param anonymousClass physical anonymous class containing method + * @param method physical method to convert with non-empty body + * @param replacer an operator which actually inserts a lambda into the file (possibly removing anonymous class) + * and returns an inserted physical lambda + * @param forceIgnoreTypeCast if false, type cast might be added if necessary + * @return newly-generated lambda expression (possibly with typecast) + */ + @RequiredWriteAction + @Nonnull + static PsiExpression generateLambdaByMethod( + PsiAnonymousClass anonymousClass, + PsiMethod method, + UnaryOperator replacer, + boolean forceIgnoreTypeCast + ) { + ChangeContextUtil.encodeContextInfo(anonymousClass, true); + String canonicalText = anonymousClass.getBaseClassType().getCanonicalText(); + + PsiCodeBlock body = method.getBody(); + LOG.assertTrue(body != null); + + Collection comments = collectCommentsOutsideMethodBody(anonymousClass, body); + Project project = anonymousClass.getProject(); + PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project); + + String withoutTypesDeclared = ReplaceWithLambdaFix.composeLambdaText(method); + + PsiLambdaExpression lambdaExpression = + (PsiLambdaExpression) elementFactory.createExpressionFromText(withoutTypesDeclared, anonymousClass); + + PsiElement lambdaBody = lambdaExpression.getBody(); + LOG.assertTrue(lambdaBody != null); + lambdaBody.replace(body); + lambdaExpression = replacer.apply(lambdaExpression); + + Set variables = new HashSet<>(); + Set usedLocalNames = new HashSet<>(); + + collectLocalVariablesDefinedInsideLambda(lambdaExpression, variables, usedLocalNames); + + ReplaceWithLambdaFix.giveUniqueNames( + project, + elementFactory, + lambdaExpression, + usedLocalNames, + variables.toArray(new PsiVariable[variables.size()]) + ); + + PsiExpression singleExpr = RedundantLambdaCodeBlockInspection.isCodeBlockRedundant(lambdaExpression.getBody()); + if (singleExpr != null) { + lambdaExpression.getBody().replace(singleExpr); + } + ChangeContextUtil.decodeContextInfo(lambdaExpression, null, null); + restoreComments(comments, lambdaExpression); - PsiTypeCastExpression typeCast = (PsiTypeCastExpression) elementFactory.createExpressionFromText("(" + canonicalText + ")" + withoutTypesDeclared, lambdaExpression); - final PsiExpression typeCastOperand = typeCast.getOperand(); - LOG.assertTrue(typeCastOperand instanceof PsiLambdaExpression); - final PsiElement fromText = ((PsiLambdaExpression) typeCastOperand).getBody(); - LOG.assertTrue(fromText != null); - lambdaBody = lambdaExpression.getBody(); - LOG.assertTrue(lambdaBody != null); - fromText.replace(lambdaBody); - ((PsiLambdaExpression) typeCastOperand).getParameterList().replace(lambdaExpression.getParameterList()); - typeCast = (PsiTypeCastExpression) lambdaExpression.replace(typeCast); - if (RedundantCastUtil.isCastRedundant(typeCast)) { - final PsiExpression operand = typeCast.getOperand(); - LOG.assertTrue(operand != null); - return (PsiExpression) typeCast.replace(operand); - } - return (PsiExpression) javaCodeStyleManager.shortenClassReferences(typeCast); - } - - @Nonnull - static Collection collectCommentsOutsideMethodBody(PsiAnonymousClass anonymousClass, PsiCodeBlock body) { - final Collection psiComments = PsiTreeUtil.findChildrenOfType(anonymousClass, PsiComment.class); - psiComments.removeIf(comment -> PsiTreeUtil.isAncestor(body, comment, false)); - return ContainerUtil.map(psiComments, (comment) -> (PsiComment) comment.copy()); - } - - private static void collectLocalVariablesDefinedInsideLambda(PsiLambdaExpression lambdaExpression, final Set variables, Set namesOfVariablesInTheBlock) { - PsiElement block = PsiUtil.getTopLevelEnclosingCodeBlock(lambdaExpression, null); - if (block == null) { - block = lambdaExpression; - } + JavaCodeStyleManager javaCodeStyleManager = JavaCodeStyleManager.getInstance(project); + if (forceIgnoreTypeCast) { + return (PsiExpression) javaCodeStyleManager.shortenClassReferences(lambdaExpression); + } - block.accept(new JavaRecursiveElementWalkingVisitor() { - @Override - public void visitVariable(PsiVariable variable) { - super.visitVariable(variable); - if (!(variable instanceof PsiField)) { - variables.add(variable); + PsiTypeCastExpression typeCast = (PsiTypeCastExpression) elementFactory.createExpressionFromText( + "(" + canonicalText + ")" + withoutTypesDeclared, + lambdaExpression + ); + PsiExpression typeCastOperand = typeCast.getOperand(); + LOG.assertTrue(typeCastOperand instanceof PsiLambdaExpression); + PsiElement fromText = ((PsiLambdaExpression) typeCastOperand).getBody(); + LOG.assertTrue(fromText != null); + lambdaBody = lambdaExpression.getBody(); + LOG.assertTrue(lambdaBody != null); + fromText.replace(lambdaBody); + ((PsiLambdaExpression) typeCastOperand).getParameterList().replace(lambdaExpression.getParameterList()); + typeCast = (PsiTypeCastExpression) lambdaExpression.replace(typeCast); + if (RedundantCastUtil.isCastRedundant(typeCast)) { + PsiExpression operand = typeCast.getOperand(); + LOG.assertTrue(operand != null); + return (PsiExpression) typeCast.replace(operand); } - } - }); - - final PsiResolveHelper helper = PsiResolveHelper.getInstance(lambdaExpression.getProject()); - for (Iterator iterator = variables.iterator(); iterator.hasNext(); ) { - PsiVariable local = iterator.next(); - final String localName = local.getName(); - if (localName == null || - shadowingResolve(localName, lambdaExpression, helper) || - !PsiTreeUtil.isAncestor(lambdaExpression, local, false)) { - iterator.remove(); - namesOfVariablesInTheBlock.add(localName); - } + return (PsiExpression) javaCodeStyleManager.shortenClassReferences(typeCast); } - } - - private static boolean shadowingResolve(String localName, PsiLambdaExpression lambdaExpression, PsiResolveHelper helper) { - final PsiVariable variable = helper.resolveReferencedVariable(localName, lambdaExpression); - return variable == null || variable instanceof PsiField; - } - private static class ReplaceWithLambdaFix implements LocalQuickFix, HighPriorityAction { @Nonnull - @Override - public LocalizeValue getName() { - return LocalizeValue.localizeTODO("Replace with lambda"); + @RequiredReadAction + static Collection collectCommentsOutsideMethodBody(PsiAnonymousClass anonymousClass, PsiCodeBlock body) { + Collection psiComments = PsiTreeUtil.findChildrenOfType(anonymousClass, PsiComment.class); + psiComments.removeIf(comment -> PsiTreeUtil.isAncestor(body, comment, false)); + return ContainerUtil.map(psiComments, (comment) -> (PsiComment) comment.copy()); } - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { - final PsiElement element = descriptor.getPsiElement(); - if (element != null) { - replacePsiElementWithLambda(element, false, false); - } - } + @RequiredReadAction + private static void collectLocalVariablesDefinedInsideLambda( + PsiLambdaExpression lambdaExpression, + final Set variables, + Set namesOfVariablesInTheBlock + ) { + PsiElement block = PsiUtil.getTopLevelEnclosingCodeBlock(lambdaExpression, null); + if (block == null) { + block = lambdaExpression; + } - private static void giveUniqueNames(Project project, final PsiElementFactory elementFactory, PsiElement body, Set usedLocalNames, PsiVariable[] parameters) { - final JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(project); - final Map names = new HashMap<>(); - for (PsiVariable parameter : parameters) { - String parameterName = parameter.getName(); - String uniqueVariableName = UniqueNameGenerator.generateUniqueName(codeStyleManager.suggestUniqueVariableName(parameterName, parameter.getParent(), false), usedLocalNames); - if (!Comparing.equal(parameterName, uniqueVariableName)) { - names.put(parameter, uniqueVariableName); + block.accept(new JavaRecursiveElementWalkingVisitor() { + @Override + public void visitVariable(@Nonnull PsiVariable variable) { + super.visitVariable(variable); + if (!(variable instanceof PsiField)) { + variables.add(variable); + } + } + }); + + PsiResolveHelper helper = PsiResolveHelper.getInstance(lambdaExpression.getProject()); + for (Iterator iterator = variables.iterator(); iterator.hasNext(); ) { + PsiVariable local = iterator.next(); + String localName = local.getName(); + if (localName == null || + shadowingResolve(localName, lambdaExpression, helper) || + !PsiTreeUtil.isAncestor(lambdaExpression, local, false)) { + iterator.remove(); + namesOfVariablesInTheBlock.add(localName); + } } - } + } - if (names.isEmpty()) { - return; - } + private static boolean shadowingResolve(String localName, PsiLambdaExpression lambdaExpression, PsiResolveHelper helper) { + PsiVariable variable = helper.resolveReferencedVariable(localName, lambdaExpression); + return variable == null || variable instanceof PsiField; + } - final Map replacements = new LinkedHashMap<>(); - body.accept(new JavaRecursiveElementWalkingVisitor() { + private static class ReplaceWithLambdaFix implements LocalQuickFix, HighPriorityAction { + @Nonnull @Override - public void visitVariable(PsiVariable variable) { - super.visitVariable(variable); - final String newName = names.get(variable); - if (newName != null) { - replacements.put(variable.getNameIdentifier(), elementFactory.createIdentifier(newName)); - } + public LocalizeValue getName() { + return LocalizeValue.localizeTODO("Replace with lambda"); } @Override - public void visitReferenceExpression(PsiReferenceExpression expression) { - super.visitReferenceExpression(expression); - final PsiElement resolve = expression.resolve(); - if (resolve instanceof PsiVariable) { - final String newName = names.get(resolve); - if (newName != null) { - replacements.put(expression, elementFactory.createExpressionFromText(newName, expression)); + @RequiredWriteAction + public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + PsiElement element = descriptor.getPsiElement(); + if (element != null) { + replacePsiElementWithLambda(element, false, false); } - } } - }); - for (PsiElement psiElement : replacements.keySet()) { - psiElement.replace(replacements.get(psiElement)); - } - } + @RequiredWriteAction + private static void giveUniqueNames( + Project project, + final PsiElementFactory elementFactory, + PsiElement body, + Set usedLocalNames, + PsiVariable[] parameters + ) { + JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(project); + final Map names = new HashMap<>(); + for (PsiVariable parameter : parameters) { + String parameterName = parameter.getName(); + String uniqueVariableName = UniqueNameGenerator.generateUniqueName( + codeStyleManager.suggestUniqueVariableName(parameterName, parameter.getParent(), false), + usedLocalNames + ); + if (!Comparing.equal(parameterName, uniqueVariableName)) { + names.put(parameter, uniqueVariableName); + } + } - private static String composeLambdaText(PsiMethod method) { - final StringBuilder buf = new StringBuilder(); - final PsiParameter[] parameters = method.getParameterList().getParameters(); - if (parameters.length != 1) { - buf.append("("); - } - buf.append(StringUtil.join(parameters, ReplaceWithLambdaFix::composeParameter, ",")); - if (parameters.length != 1) { - buf.append(")"); - } - buf.append("-> {}"); - return buf.toString(); - } + if (names.isEmpty()) { + return; + } - private static String composeParameter(PsiParameter parameter) { - String parameterName = parameter.getName(); - if (parameterName == null) { - parameterName = ""; - } - return parameterName; - } - } + final Map replacements = new LinkedHashMap<>(); + body.accept(new JavaRecursiveElementWalkingVisitor() { + @Override + public void visitVariable(@Nonnull PsiVariable variable) { + super.visitVariable(variable); + String newName = names.get(variable); + if (newName != null) { + replacements.put(variable.getNameIdentifier(), elementFactory.createIdentifier(newName)); + } + } - public static boolean functionalInterfaceMethodReferenced(PsiMethod psiMethod, PsiAnonymousClass anonymClass, PsiCallExpression callExpression) { - if (psiMethod != null && !psiMethod.hasModifierProperty(PsiModifier.STATIC)) { - final PsiClass containingClass = psiMethod.getContainingClass(); - if (containingClass != null && CommonClassNames.JAVA_LANG_OBJECT.equals(containingClass.getQualifiedName())) { - return false; - } + @Override + @RequiredReadAction + public void visitReferenceExpression(PsiReferenceExpression expression) { + super.visitReferenceExpression(expression); + if (expression.resolve() instanceof PsiVariable variable) { + String newName = names.get(variable); + if (newName != null) { + replacements.put(expression, elementFactory.createExpressionFromText(newName, expression)); + } + } + } + }); - if (callExpression instanceof PsiMethodCallExpression && ((PsiMethodCallExpression) callExpression).getMethodExpression().isQualified()) { - return false; - } + for (PsiElement psiElement : replacements.keySet()) { + psiElement.replace(replacements.get(psiElement)); + } + } - if (InheritanceUtil.isInheritorOrSelf(anonymClass, containingClass, true) && !InheritanceUtil.hasEnclosingInstanceInScope(containingClass, anonymClass.getParent(), true, true)) { - return true; - } - } - return false; - } + private static String composeLambdaText(PsiMethod method) { + StringBuilder buf = new StringBuilder(); + PsiParameter[] parameters = method.getParameterList().getParameters(); + if (parameters.length != 1) { + buf.append("("); + } + buf.append(StringUtil.join(parameters, ReplaceWithLambdaFix::composeParameter, ",")); + if (parameters.length != 1) { + buf.append(")"); + } + buf.append("-> {}"); + return buf.toString(); + } - public static void restoreComments(Collection comments, PsiElement lambda) { - PsiElement anchor = PsiTreeUtil.getParentOfType(lambda, PsiStatement.class, PsiField.class); - if (anchor == null) { - anchor = lambda; - } - for (PsiComment comment : comments) { - anchor.getParent().addBefore(comment, anchor); + private static String composeParameter(PsiParameter parameter) { + return parameter.getName(); + } } - } - private static class ForbiddenRefsChecker extends JavaRecursiveElementWalkingVisitor { - private boolean myBodyContainsForbiddenRefs; + public static boolean functionalInterfaceMethodReferenced( + PsiMethod psiMethod, + PsiAnonymousClass anonymousClass, + PsiCallExpression callExpression + ) { + if (psiMethod != null && !psiMethod.isStatic()) { + PsiClass containingClass = psiMethod.getContainingClass(); + if (containingClass != null && CommonClassNames.JAVA_LANG_OBJECT.equals(containingClass.getQualifiedName())) { + return false; + } - private final PsiMethod myMethod; - private final PsiAnonymousClass myAnonymClass; + if (callExpression instanceof PsiMethodCallExpression methodCall && methodCall.getMethodExpression().isQualified()) { + return false; + } - public ForbiddenRefsChecker(PsiMethod method, PsiAnonymousClass aClass) { - myMethod = method; - myAnonymClass = aClass; + if (InheritanceUtil.isInheritorOrSelf(anonymousClass, containingClass, true) + && !InheritanceUtil.hasEnclosingInstanceInScope(containingClass, anonymousClass.getParent(), true, true)) { + return true; + } + } + return false; } - @Override - public void visitMethodCallExpression(PsiMethodCallExpression methodCallExpression) { - if (myBodyContainsForbiddenRefs) { - return; - } - - super.visitMethodCallExpression(methodCallExpression); - final PsiMethod psiMethod = methodCallExpression.resolveMethod(); - if (psiMethod == myMethod || - functionalInterfaceMethodReferenced(psiMethod, myAnonymClass, methodCallExpression) || - psiMethod != null && - !methodCallExpression.getMethodExpression().isQualified() && - "getClass".equals(psiMethod.getName()) && - psiMethod.getParameterList().getParametersCount() == 0) { - myBodyContainsForbiddenRefs = true; - } + @RequiredWriteAction + public static void restoreComments(Collection comments, PsiElement lambda) { + PsiElement anchor = PsiTreeUtil.getParentOfType(lambda, PsiStatement.class, PsiField.class); + if (anchor == null) { + anchor = lambda; + } + for (PsiComment comment : comments) { + anchor.getParent().addBefore(comment, anchor); + } } - @Override - public void visitThisExpression(PsiThisExpression expression) { - if (myBodyContainsForbiddenRefs) { - return; - } - - if (expression.getQualifier() == null) { - myBodyContainsForbiddenRefs = true; - } - } + private static class ForbiddenRefsChecker extends JavaRecursiveElementWalkingVisitor { + private boolean myBodyContainsForbiddenRefs; - @Override - public void visitSuperExpression(PsiSuperExpression expression) { - if (myBodyContainsForbiddenRefs) { - return; - } - - if (expression.getQualifier() == null) { - myBodyContainsForbiddenRefs = true; - } - } + private final PsiMethod myMethod; + private final PsiAnonymousClass myAnonClass; - @Override - public void visitVariable(PsiVariable variable) { - if (myBodyContainsForbiddenRefs) { - return; - } + public ForbiddenRefsChecker(PsiMethod method, PsiAnonymousClass aClass) { + myMethod = method; + myAnonClass = aClass; + } - super.visitVariable(variable); - } + @Override + public void visitMethodCallExpression(@Nonnull PsiMethodCallExpression methodCallExpression) { + if (myBodyContainsForbiddenRefs) { + return; + } - @Override - public void visitReferenceExpression(PsiReferenceExpression expression) { - if (myBodyContainsForbiddenRefs) { - return; - } - - super.visitReferenceExpression(expression); - if (!(expression.getParent() instanceof PsiMethodCallExpression)) { - final PsiMember member = PsiTreeUtil.getParentOfType(myAnonymClass, PsiMember.class); - if (member instanceof PsiField || member instanceof PsiClassInitializer) { - final PsiElement resolved = expression.resolve(); - final PsiClass memberContainingClass = member.getContainingClass(); - if (resolved instanceof PsiField && - memberContainingClass != null && - PsiTreeUtil.isAncestor(((PsiField) resolved).getContainingClass(), memberContainingClass, false) && - expression.getQualifierExpression() == null) { - final PsiExpression initializer = ((PsiField) resolved).getInitializer(); - if (initializer == null || - resolved == member || - initializer.getTextOffset() > myAnonymClass.getTextOffset() && ((PsiField) resolved).hasModifierProperty(PsiModifier.STATIC) == member.hasModifierProperty(PsiModifier - .STATIC)) { - myBodyContainsForbiddenRefs = true; + super.visitMethodCallExpression(methodCallExpression); + PsiMethod psiMethod = methodCallExpression.resolveMethod(); + if (psiMethod == myMethod || + functionalInterfaceMethodReferenced(psiMethod, myAnonClass, methodCallExpression) || + psiMethod != null && + !methodCallExpression.getMethodExpression().isQualified() && + "getClass".equals(psiMethod.getName()) && + psiMethod.getParameterList().getParametersCount() == 0) { + myBodyContainsForbiddenRefs = true; } - } - } else { - final PsiMethod method = PsiTreeUtil.getParentOfType(myAnonymClass, PsiMethod.class); - if (method != null && method.isConstructor()) { - final PsiElement resolved = expression.resolve(); - if (resolved instanceof PsiField && - ((PsiField) resolved).hasModifierProperty(PsiModifier.FINAL) && - ((PsiField) resolved).getInitializer() == null && - ((PsiField) resolved).getContainingClass() == method.getContainingClass()) { - try { - final PsiCodeBlock constructorBody = method.getBody(); - if (constructorBody != null) { - final ControlFlow flow = HighlightControlFlowUtil.getControlFlowNoConstantEvaluate(constructorBody); - final int startOffset = flow.getStartOffset(myAnonymClass); - final Collection writtenVariables = ControlFlowUtil.getWrittenVariables(flow, 0, startOffset, false); - if (!writtenVariables.contains(resolved)) { - myBodyContainsForbiddenRefs = true; - } - } - } catch (AnalysisCanceledException e) { + } + + @Override + public void visitThisExpression(@Nonnull PsiThisExpression expression) { + if (myBodyContainsForbiddenRefs) { + return; + } + + if (expression.getQualifier() == null) { myBodyContainsForbiddenRefs = true; - } } - } } - } - } - public boolean hasForbiddenRefs() { - return myBodyContainsForbiddenRefs; + @Override + public void visitSuperExpression(@Nonnull PsiSuperExpression expression) { + if (myBodyContainsForbiddenRefs) { + return; + } + + if (expression.getQualifier() == null) { + myBodyContainsForbiddenRefs = true; + } + } + + @Override + public void visitVariable(@Nonnull PsiVariable variable) { + if (myBodyContainsForbiddenRefs) { + return; + } + + super.visitVariable(variable); + } + + @Override + @RequiredReadAction + public void visitReferenceExpression(@Nonnull PsiReferenceExpression expression) { + if (myBodyContainsForbiddenRefs) { + return; + } + + super.visitReferenceExpression(expression); + if (!(expression.getParent() instanceof PsiMethodCallExpression)) { + PsiMember member = PsiTreeUtil.getParentOfType(myAnonClass, PsiMember.class); + if (member instanceof PsiField || member instanceof PsiClassInitializer) { + PsiElement resolved = expression.resolve(); + PsiClass memberContainingClass = member.getContainingClass(); + if (resolved instanceof PsiField field + && memberContainingClass != null + && PsiTreeUtil.isAncestor(field.getContainingClass(), memberContainingClass, false) + && expression.getQualifierExpression() == null) { + PsiExpression initializer = field.getInitializer(); + if (initializer == null + || resolved == member + || initializer.getTextOffset() > myAnonClass.getTextOffset() && field.isStatic() == member.isStatic()) { + myBodyContainsForbiddenRefs = true; + } + } + } + else { + PsiMethod method = PsiTreeUtil.getParentOfType(myAnonClass, PsiMethod.class); + if (method != null + && method.isConstructor() + && expression.resolve() instanceof PsiField field + && field.isFinal() + && field.getInitializer() == null + && field.getContainingClass() == method.getContainingClass()) { + try { + PsiCodeBlock constructorBody = method.getBody(); + if (constructorBody != null) { + ControlFlow flow = HighlightControlFlowUtil.getControlFlowNoConstantEvaluate(constructorBody); + int startOffset = flow.getStartOffset(myAnonClass); + Collection writtenVariables = ControlFlowUtil.getWrittenVariables(flow, 0, startOffset, false); + if (!writtenVariables.contains(field)) { + myBodyContainsForbiddenRefs = true; + } + } + } + catch (AnalysisCanceledException e) { + myBodyContainsForbiddenRefs = true; + } + } + } + } + } + + public boolean hasForbiddenRefs() { + return myBodyContainsForbiddenRefs; + } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/AnonymousCanBeMethodReferenceInspection.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/AnonymousCanBeMethodReferenceInspection.java index 9da7ac732b..85e0d8dc6b 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/AnonymousCanBeMethodReferenceInspection.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/AnonymousCanBeMethodReferenceInspection.java @@ -19,6 +19,8 @@ import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.codeStyle.JavaCodeStyleManager; import com.intellij.java.language.psi.util.RedundantCastUtil; +import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; import consulo.annotation.component.ExtensionImpl; import consulo.document.util.TextRange; import consulo.language.editor.inspection.*; @@ -32,9 +34,9 @@ import consulo.logging.Logger; import consulo.project.Project; import consulo.util.collection.ContainerUtil; -import org.jetbrains.annotations.Nls; import jakarta.annotation.Nonnull; + import java.util.Collection; import java.util.Collections; @@ -43,138 +45,167 @@ */ @ExtensionImpl public class AnonymousCanBeMethodReferenceInspection extends BaseJavaBatchLocalInspectionTool { - private static final Logger LOG = Logger.getInstance(AnonymousCanBeMethodReferenceInspection.class); - - - @Nonnull - @Override - public HighlightDisplayLevel getDefaultLevel() { - return HighlightDisplayLevel.WARNING; - } - - @Nonnull - @Override - public LocalizeValue getGroupDisplayName() { - return InspectionLocalize.groupNamesLanguageLevelSpecificIssuesAndMigrationAids(); - } - - @Nls - @Nonnull - @Override - public LocalizeValue getDisplayName() { - return LocalizeValue.localizeTODO("Anonymous type can be replaced with method reference"); - } - - @Override - public boolean isEnabledByDefault() { - return true; - } - - @Nonnull - @Override - public String getShortName() { - return "Anonymous2MethodRef"; - } - - @Nonnull - @Override - public InspectionToolState createStateProvider() { - return new AnonymousCanBeMethodReferenceInspectionState(); - } - - @Nonnull - @Override - public PsiElementVisitor buildVisitorImpl(@Nonnull final ProblemsHolder holder, - boolean isOnTheFly, - LocalInspectionToolSession session, - AnonymousCanBeMethodReferenceInspectionState state) { - return new JavaElementVisitor() { - @Override - public void visitAnonymousClass(PsiAnonymousClass aClass) { - super.visitAnonymousClass(aClass); - if (AnonymousCanBeLambdaInspection.canBeConvertedToLambda(aClass, true, state.reportNotAnnotatedInterfaces, Collections.emptySet())) { - final PsiMethod method = aClass.getMethods()[0]; - final PsiCodeBlock body = method.getBody(); - PsiExpression lambdaBodyCandidate = LambdaCanBeMethodReferenceInspection.extractMethodReferenceCandidateExpression(body, false); - final PsiExpression methodRefCandidate = LambdaCanBeMethodReferenceInspection.canBeMethodReferenceProblem(method.getParameterList().getParameters(), aClass.getBaseClassType(), - aClass.getParent(), lambdaBodyCandidate); - if (methodRefCandidate instanceof PsiCallExpression) { - final PsiCallExpression callExpression = (PsiCallExpression) methodRefCandidate; - final PsiMethod resolveMethod = callExpression.resolveMethod(); - if (resolveMethod != method && !AnonymousCanBeLambdaInspection.functionalInterfaceMethodReferenced(resolveMethod, aClass, callExpression)) { - final PsiElement parent = aClass.getParent(); - if (parent instanceof PsiNewExpression) { - final PsiJavaCodeReferenceElement classReference = ((PsiNewExpression) parent).getClassOrAnonymousClassReference(); - if (classReference != null) { - final PsiElement lBrace = aClass.getLBrace(); - LOG.assertTrue(lBrace != null); - final TextRange rangeInElement = new TextRange(0, aClass.getStartOffsetInParent() + lBrace.getStartOffsetInParent()); - ProblemHighlightType highlightType = LambdaCanBeMethodReferenceInspection.checkQualifier(lambdaBodyCandidate) ? ProblemHighlightType.LIKE_UNUSED_SYMBOL : - ProblemHighlightType.INFORMATION; - holder.registerProblem(parent, "Anonymous #ref #loc can be replaced with method reference", highlightType, rangeInElement, new ReplaceWithMethodRefFix()); - } - } - } - } - } - } - }; - } + private static final Logger LOG = Logger.getInstance(AnonymousCanBeMethodReferenceInspection.class); + - private static class ReplaceWithMethodRefFix implements LocalQuickFix { @Nonnull @Override - public LocalizeValue getName() { - return LocalizeValue.localizeTODO("Replace with method reference"); + public HighlightDisplayLevel getDefaultLevel() { + return HighlightDisplayLevel.WARNING; } + @Nonnull @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { - final PsiElement element = descriptor.getPsiElement(); - if (element instanceof PsiNewExpression) { - final PsiAnonymousClass anonymousClass = ((PsiNewExpression) element).getAnonymousClass(); - if (anonymousClass == null) { - return; - } - final PsiMethod[] methods = anonymousClass.getMethods(); - if (methods.length != 1) { - return; - } + public LocalizeValue getGroupDisplayName() { + return InspectionLocalize.groupNamesLanguageLevelSpecificIssuesAndMigrationAids(); + } - final PsiParameter[] parameters = methods[0].getParameterList().getParameters(); - final PsiType functionalInterfaceType = anonymousClass.getBaseClassType(); - PsiExpression methodRefCandidate = LambdaCanBeMethodReferenceInspection.extractMethodReferenceCandidateExpression(methods[0].getBody(), false); - final PsiExpression candidate = LambdaCanBeMethodReferenceInspection.canBeMethodReferenceProblem(parameters, functionalInterfaceType, anonymousClass.getParent(), methodRefCandidate); + @Nonnull + @Override + public LocalizeValue getDisplayName() { + return LocalizeValue.localizeTODO("Anonymous type can be replaced with method reference"); + } - final String methodRefText = LambdaCanBeMethodReferenceInspection.createMethodReferenceText(candidate, functionalInterfaceType, parameters); + @Override + public boolean isEnabledByDefault() { + return true; + } + + @Nonnull + @Override + public String getShortName() { + return "Anonymous2MethodRef"; + } - replaceWithMethodReference(project, methodRefText, anonymousClass.getBaseClassType(), anonymousClass.getParent()); - } + @Nonnull + @Override + public InspectionToolState createStateProvider() { + return new AnonymousCanBeMethodReferenceInspectionState(); } - } - - static void replaceWithMethodReference(@Nonnull Project project, String methodRefText, PsiType castType, PsiElement replacementTarget) { - final Collection comments = ContainerUtil.map(PsiTreeUtil.findChildrenOfType(replacementTarget, PsiComment.class), comment -> (PsiComment) comment.copy()); - - if (methodRefText != null) { - final String canonicalText = castType.getCanonicalText(); - final PsiExpression psiExpression = JavaPsiFacade.getElementFactory(project).createExpressionFromText("(" + canonicalText + ")" + methodRefText, replacementTarget); - - PsiElement castExpr = replacementTarget.replace(psiExpression); - if (RedundantCastUtil.isCastRedundant((PsiTypeCastExpression) castExpr)) { - final PsiExpression operand = ((PsiTypeCastExpression) castExpr).getOperand(); - LOG.assertTrue(operand != null); - castExpr = castExpr.replace(operand); - } - - PsiElement anchor = PsiTreeUtil.getParentOfType(castExpr, PsiStatement.class); - if (anchor == null) { - anchor = castExpr; - } - for (PsiComment comment : comments) { - anchor.getParent().addBefore(comment, anchor); - } - JavaCodeStyleManager.getInstance(project).shortenClassReferences(castExpr); + + @Nonnull + @Override + public PsiElementVisitor buildVisitorImpl( + @Nonnull final ProblemsHolder holder, + boolean isOnTheFly, + LocalInspectionToolSession session, + AnonymousCanBeMethodReferenceInspectionState state + ) { + return new JavaElementVisitor() { + @Override + @RequiredReadAction + public void visitAnonymousClass(@Nonnull PsiAnonymousClass aClass) { + super.visitAnonymousClass(aClass); + if (AnonymousCanBeLambdaInspection.canBeConvertedToLambda( + aClass, + true, + state.reportNotAnnotatedInterfaces, + Collections.emptySet() + )) { + PsiMethod method = aClass.getMethods()[0]; + PsiCodeBlock body = method.getBody(); + PsiExpression lambdaBodyCandidate = + LambdaCanBeMethodReferenceInspection.extractMethodReferenceCandidateExpression(body, false); + PsiExpression methodRefCandidate = LambdaCanBeMethodReferenceInspection.canBeMethodReferenceProblem( + method.getParameterList().getParameters(), + aClass.getBaseClassType(), + aClass.getParent(), + lambdaBodyCandidate + ); + if (methodRefCandidate instanceof PsiCallExpression call) { + PsiMethod resolveMethod = call.resolveMethod(); + if (resolveMethod != method + && !AnonymousCanBeLambdaInspection.functionalInterfaceMethodReferenced(resolveMethod, aClass, call) + && aClass.getParent() instanceof PsiNewExpression newExpr) { + PsiJavaCodeReferenceElement classReference = newExpr.getClassOrAnonymousClassReference(); + if (classReference != null) { + PsiElement lBrace = aClass.getLBrace(); + LOG.assertTrue(lBrace != null); + TextRange rangeInElement = + new TextRange(0, aClass.getStartOffsetInParent() + lBrace.getStartOffsetInParent()); + ProblemHighlightType highlightType = + LambdaCanBeMethodReferenceInspection.checkQualifier(lambdaBodyCandidate) + ? ProblemHighlightType.LIKE_UNUSED_SYMBOL + : ProblemHighlightType.INFORMATION; + holder.newProblem(LocalizeValue.localizeTODO("Anonymous #ref #loc can be replaced with method reference")) + .range(newExpr, rangeInElement) + .highlightType(highlightType) + .withFix(new ReplaceWithMethodRefFix()) + .create(); + } + } + } + } + } + }; + } + + private static class ReplaceWithMethodRefFix implements LocalQuickFix { + @Nonnull + @Override + public LocalizeValue getName() { + return LocalizeValue.localizeTODO("Replace with method reference"); + } + + @Override + @RequiredWriteAction + public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + if (descriptor.getPsiElement() instanceof PsiNewExpression newExpression) { + PsiAnonymousClass anonymousClass = newExpression.getAnonymousClass(); + if (anonymousClass == null) { + return; + } + PsiMethod[] methods = anonymousClass.getMethods(); + if (methods.length != 1) { + return; + } + + PsiParameter[] parameters = methods[0].getParameterList().getParameters(); + PsiType functionalInterfaceType = anonymousClass.getBaseClassType(); + PsiExpression methodRefCandidate = + LambdaCanBeMethodReferenceInspection.extractMethodReferenceCandidateExpression(methods[0].getBody(), false); + PsiExpression candidate = LambdaCanBeMethodReferenceInspection.canBeMethodReferenceProblem( + parameters, + functionalInterfaceType, + anonymousClass.getParent(), + methodRefCandidate + ); + + String methodRefText = + LambdaCanBeMethodReferenceInspection.createMethodReferenceText(candidate, functionalInterfaceType, parameters); + + replaceWithMethodReference(project, methodRefText, anonymousClass.getBaseClassType(), anonymousClass.getParent()); + } + } + } + + @RequiredWriteAction + static void replaceWithMethodReference(@Nonnull Project project, String methodRefText, PsiType castType, PsiElement replacementTarget) { + Collection comments = ContainerUtil.map( + PsiTreeUtil.findChildrenOfType(replacementTarget, PsiComment.class), + comment -> (PsiComment) comment.copy() + ); + + if (methodRefText != null) { + String canonicalText = castType.getCanonicalText(); + PsiExpression psiExpression = JavaPsiFacade.getElementFactory(project) + .createExpressionFromText("(" + canonicalText + ")" + methodRefText, replacementTarget); + + PsiElement castExpr = replacementTarget.replace(psiExpression); + if (RedundantCastUtil.isCastRedundant((PsiTypeCastExpression) castExpr)) { + PsiExpression operand = ((PsiTypeCastExpression) castExpr).getOperand(); + LOG.assertTrue(operand != null); + castExpr = castExpr.replace(operand); + } + + PsiElement anchor = PsiTreeUtil.getParentOfType(castExpr, PsiStatement.class); + if (anchor == null) { + anchor = castExpr; + } + for (PsiComment comment : comments) { + anchor.getParent().addBefore(comment, anchor); + } + JavaCodeStyleManager.getInstance(project).shortenClassReferences(castExpr); + } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/EnhancedSwitchMigrationInspection.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/EnhancedSwitchMigrationInspection.java index 0148e0f10b..efbbcbb40d 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/EnhancedSwitchMigrationInspection.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/EnhancedSwitchMigrationInspection.java @@ -13,6 +13,8 @@ import com.siyeh.ig.psiutils.CommentTracker; import com.siyeh.ig.psiutils.ExpressionUtils; import com.siyeh.ig.psiutils.StatementExtractor; +import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; import consulo.annotation.component.ExtensionImpl; import consulo.java.analysis.impl.localize.JavaInspectionsLocalize; import consulo.language.editor.inspection.*; @@ -35,7 +37,8 @@ public final class EnhancedSwitchMigrationInspection extends AbstractBaseJavaLoc private static final SwitchConversion[] ourInspections = new SwitchConversion[]{ EnhancedSwitchMigrationInspection::inspectReturningSwitch, EnhancedSwitchMigrationInspection::inspectVariableAssigningSwitch, - (statement, branches, isExhaustive, maxNumberStatementsForExpression) -> inspectReplacementWithStatement(statement, branches) + (statement, branches, isExhaustive, maxNumberStatementsForExpression) -> + inspectReplacementWithStatement(statement, branches) }; @Override @@ -49,8 +52,9 @@ public InspectionToolState cre return new EnhancedSwitchMigrationInspectionState(); } + @Nonnull @Override - public @Nonnull Set requiredFeatures() { + public Set requiredFeatures() { return Set.of(JavaFeature.ENHANCED_SWITCH); } @@ -60,15 +64,17 @@ public LocalizeValue getGroupDisplayName() { return JavaInspectionsLocalize.groupNamesLanguageLevelSpecificIssuesAndMigrationAids14(); } - @Override @Nonnull - public PsiElementVisitor buildVisitorImpl(@Nonnull ProblemsHolder holder, - boolean isOnTheFly, - LocalInspectionToolSession localInspectionToolSession, - EnhancedSwitchMigrationInspectionState state) { + @Override + public PsiElementVisitor buildVisitorImpl( + @Nonnull ProblemsHolder holder, + boolean isOnTheFly, + LocalInspectionToolSession localInspectionToolSession, + EnhancedSwitchMigrationInspectionState state + ) { return new JavaElementVisitor() { - @Override + @RequiredReadAction public void visitSwitchExpression(@Nonnull PsiSwitchExpression expression) { PsiElement switchKeyword = expression.getFirstChild(); if (switchKeyword == null) { @@ -98,10 +104,8 @@ public void visitSwitchExpression(@Nonnull PsiSwitchExpression expression) { if (!(statement instanceof PsiYieldStatement || statement instanceof PsiThrowStatement)) { onlyOneYieldAfterLabel = false; } - else { - if (statementAfterLabelCount > 1) { - onlyOneYieldAfterLabel = false; - } + else if (statementAfterLabelCount > 1) { + onlyOneYieldAfterLabel = false; } } } @@ -112,12 +116,15 @@ public void visitSwitchExpression(@Nonnull PsiSwitchExpression expression) { if (!onlyOneYieldAfterLabel) { warningType = ProblemHighlightType.INFORMATION; } - holder.registerProblem(switchKeyword, - JavaInspectionsLocalize.inspectionSwitchExpressionMigrationInspectionSwitchExpressionDescription().get(), - warningType, new ReplaceExpressionWithEnhancedSwitchExpressionFix()); + holder.newProblem(JavaInspectionsLocalize.inspectionSwitchExpressionMigrationInspectionSwitchExpressionDescription()) + .range(switchKeyword) + .withFix(new ReplaceExpressionWithEnhancedSwitchExpressionFix()) + .highlightType(warningType) + .create(); } @Override + @RequiredReadAction public void visitSwitchStatement(@Nonnull PsiSwitchStatement statement) { PsiElement switchKeyword = statement.getFirstChild(); if (switchKeyword == null) { @@ -136,7 +143,8 @@ public void visitSwitchStatement(@Nonnull PsiSwitchStatement statement) { fixes.add(new UpdateInspectionOptionFix( EnhancedSwitchMigrationInspection.this, JavaInspectionsLocalize.inspectionSwitchExpressionMigrationWarnOnlyOnExpression(), - state -> state.myWarnOnlyOnExpressionConversion = true)); + state -> state.myWarnOnlyOnExpressionConversion = true + )); } if (replacer.getType() == ReplacementType.Expression && replacer.getMaxNumberStatementsInBranch() != null && @@ -144,13 +152,16 @@ public void visitSwitchStatement(@Nonnull PsiSwitchStatement statement) { int newMaxValue = replacer.getMaxNumberStatementsInBranch() - 1; fixes.add(new UpdateInspectionOptionFix( - EnhancedSwitchMigrationInspection.this, - JavaInspectionsLocalize.inspectionSwitchExpressionMigrationOptionExpressionMaxStatements(newMaxValue), - state -> state.myMaxNumberStatementsForBranch = newMaxValue) + EnhancedSwitchMigrationInspection.this, + JavaInspectionsLocalize.inspectionSwitchExpressionMigrationOptionExpressionMaxStatements(newMaxValue), + state -> state.myMaxNumberStatementsForBranch = newMaxValue + ) ); } - holder.registerProblem(switchKeyword, JavaInspectionsLocalize.inspectionSwitchExpressionMigrationInspectionSwitchDescription().get(), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, fixes.toArray(LocalQuickFix.EMPTY_ARRAY)); + holder.newProblem(JavaInspectionsLocalize.inspectionSwitchExpressionMigrationInspectionSwitchDescription()) + .range(switchKeyword) + .withFixes(fixes) + .create(); replacers.remove(replacer); } if (!holder.isOnTheFly()) { @@ -159,9 +170,13 @@ public void visitSwitchStatement(@Nonnull PsiSwitchStatement statement) { if (replacers.isEmpty()) { return; } - List fixes = ContainerUtil.map(replacers, replacer -> new ReplaceWithSwitchExpressionFix(replacer.getType())); - holder.registerProblem(switchKeyword, JavaInspectionsLocalize.inspectionSwitchExpressionMigrationInspectionSwitchDescription().get(), - ProblemHighlightType.INFORMATION, fixes.toArray(LocalQuickFix.EMPTY_ARRAY)); + List fixes = + ContainerUtil.map(replacers, replacer -> new ReplaceWithSwitchExpressionFix(replacer.getType())); + holder.newProblem(JavaInspectionsLocalize.inspectionSwitchExpressionMigrationInspectionSwitchDescription()) + .range(switchKeyword) + .highlightType(ProblemHighlightType.INFORMATION) + .withFixes(fixes) + .create(); } private boolean isWarningLevel(@Nonnull SwitchReplacer replacer) { @@ -173,10 +188,12 @@ private boolean isWarningLevel(@Nonnull SwitchReplacer replacer) { }; } - private static List runInspections(@Nonnull PsiStatement statement, - boolean isExhaustive, - @Nonnull List branches, - int maxNumberStatementsForExpression) { + private static List runInspections( + @Nonnull PsiStatement statement, + boolean isExhaustive, + @Nonnull List branches, + int maxNumberStatementsForExpression + ) { List replacers = new ArrayList<>(); for (SwitchConversion inspection : ourInspections) { SwitchReplacer replacer = inspection.suggestReplacer(statement, branches, isExhaustive, maxNumberStatementsForExpression); @@ -187,11 +204,13 @@ private static List runInspections(@Nonnull PsiStatement stateme return replacers; } - private static OldSwitchStatementBranch addBranch(List branches, - PsiStatement[] statements, - int unmatchedCaseIndex, - int endIndexExcl, - boolean isFallthrough, PsiBreakStatement current) { + private static OldSwitchStatementBranch addBranch( + List branches, + PsiStatement[] statements, + int unmatchedCaseIndex, + int endIndexExcl, + boolean isFallthrough, PsiBreakStatement current + ) { PsiSwitchLabelStatement labelStatement = (PsiSwitchLabelStatement) statements[unmatchedCaseIndex]; PsiStatement[] branchStatements = Arrays.copyOfRange(statements, unmatchedCaseIndex + 1, endIndexExcl); OldSwitchStatementBranch branch = new OldSwitchStatementBranch(isFallthrough, branchStatements, labelStatement, current); @@ -199,8 +218,10 @@ private static OldSwitchStatementBranch addBranch(List extractBranches(@Nonnull PsiCodeBlock body, - PsiSwitchStatement switchStatement) { + private static @Nullable List extractBranches( + @Nonnull PsiCodeBlock body, + PsiSwitchStatement switchStatement + ) { List branches = new ArrayList<>(); PsiStatement[] statements = body.getStatements(); int unmatchedCaseIndex = -1; @@ -216,14 +237,14 @@ private static OldSwitchStatementBranch addBranch(List findSwitchReplacers(@Nonnull PsiSwitchStatement switchStatement, - int maxNumberStatementsForExpression) { + private static @Nonnull List findSwitchReplacers( + @Nonnull PsiSwitchStatement switchStatement, + int maxNumberStatementsForExpression + ) { PsiExpression expression = switchStatement.getExpression(); if (expression == null) { return List.of(); @@ -271,21 +294,24 @@ else if (current instanceof PsiSwitchLabeledRuleStatement) { return runInspections(switchStatement, isExhaustive, branches, maxNumberStatementsForExpression); } - private static @Nullable PsiSwitchBlock generateEnhancedSwitch(@Nonnull PsiStatement statementToReplace, - List newBranches, - CommentTracker ct, - boolean isExpr) { - PsiElementFactory factory = JavaPsiFacade.getElementFactory(statementToReplace.getProject()); - if (!(statementToReplace instanceof PsiSwitchStatement)) { + @RequiredReadAction + private static @Nullable PsiSwitchBlock generateEnhancedSwitch( + @Nonnull PsiStatement statementToReplace, + List newBranches, + CommentTracker ct, + boolean isExpr + ) { + if (!(statementToReplace instanceof PsiSwitchStatement switchStmt)) { return null; } - PsiCodeBlock body = ((PsiSwitchStatement) statementToReplace).getBody(); + PsiElementFactory factory = JavaPsiFacade.getElementFactory(switchStmt.getProject()); + PsiCodeBlock body = switchStmt.getBody(); if (body == null) { return null; } StringBuilder sb = new StringBuilder(); - for (PsiElement e = statementToReplace.getFirstChild(); e != null && e != body; e = e.getNextSibling()) { + for (PsiElement e = switchStmt.getFirstChild(); e != null && e != body; e = e.getNextSibling()) { sb.append(ct.text(e)); } PsiJavaToken lBrace = body.getLBrace(); @@ -297,15 +323,17 @@ else if (current instanceof PsiSwitchLabeledRuleStatement) { sb.append(rBrace != null ? ct.textWithComments(rBrace) : "}"); PsiSwitchBlock switchBlock; if (isExpr) { - switchBlock = (PsiSwitchBlock) factory.createExpressionFromText(sb.toString(), statementToReplace); + switchBlock = (PsiSwitchBlock) factory.createExpressionFromText(sb.toString(), switchStmt); } else { - switchBlock = (PsiSwitchBlock) factory.createStatementFromText(sb.toString(), statementToReplace); + switchBlock = (PsiSwitchBlock) factory.createStatementFromText(sb.toString(), switchStmt); } StreamEx.ofTree((PsiElement) switchBlock, block -> Arrays.stream(block.getChildren())) .select(PsiBreakStatement.class) - .filter(breakStmt -> ControlFlowUtils.statementCompletesWithStatement(switchBlock, breakStmt) && - breakStmt.findExitedStatement() == switchBlock) + .filter( + breakStmt -> ControlFlowUtils.statementCompletesWithStatement(switchBlock, breakStmt) + && breakStmt.findExitedStatement() == switchBlock + ) .forEach(statement -> new CommentTracker().delete(statement)); return switchBlock; } @@ -320,8 +348,8 @@ private static boolean isExhaustiveSwitch(List branche } } SwitchUtils.SwitchExhaustivenessState completenessResult = SwitchUtils.evaluateSwitchCompleteness(switchStatement, true); - return completenessResult == SwitchUtils.SwitchExhaustivenessState.EXHAUSTIVE_CAN_ADD_DEFAULT || - completenessResult == SwitchUtils.SwitchExhaustivenessState.EXHAUSTIVE_NO_DEFAULT; + return completenessResult == SwitchUtils.SwitchExhaustivenessState.EXHAUSTIVE_CAN_ADD_DEFAULT + || completenessResult == SwitchUtils.SwitchExhaustivenessState.EXHAUSTIVE_NO_DEFAULT; } private static boolean isConvertibleBranch(@Nonnull OldSwitchStatementBranch branch, boolean hasNext) { @@ -366,10 +394,12 @@ public interface SwitchReplacer { private interface SwitchConversion { @Nullable - SwitchReplacer suggestReplacer(@Nonnull PsiStatement statement, - @Nonnull List branches, - boolean isExhaustive, - int maxNumberStatementsForExpression); + SwitchReplacer suggestReplacer( + @Nonnull PsiStatement statement, + @Nonnull List branches, + boolean isExhaustive, + int maxNumberStatementsForExpression + ); } //Right part of switch rule (case labels -> result) @@ -386,6 +416,7 @@ public LocalizeValue getName() { } @Override + @RequiredWriteAction public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor problemDescriptor) { PsiElement element = problemDescriptor.getPsiElement(); @@ -403,9 +434,9 @@ public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor proble if (codeBlockChildren instanceof PsiSwitchLabelStatement switchLabelStatement) { boolean nextIsBodyStatement = checkNextIsStatement(codeBlockChildren); for (@Nonnull PsiElement labelStatementChild : switchLabelStatement.getChildren()) { - if (!previousHasBodyStatement && - labelStatementChild instanceof PsiJavaToken javaToken && - javaToken.textMatches("case")) { + if (!previousHasBodyStatement + && labelStatementChild instanceof PsiJavaToken javaToken + && javaToken.textMatches("case")) { continue; } if (labelStatementChild instanceof PsiJavaToken javaToken && javaToken.textMatches(":")) { @@ -422,8 +453,8 @@ public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor proble } } PsiElement nextOfSwitchLabelStatement = PsiTreeUtil.skipWhitespacesAndCommentsForward(switchLabelStatement); - if (nextIsBodyStatement && !(nextOfSwitchLabelStatement instanceof PsiBlockStatement) && - findOneYieldOrThrowStatement(PsiTreeUtil.skipWhitespacesAndCommentsForward(codeBlockChildren)) == null) { + if (nextIsBodyStatement && !(nextOfSwitchLabelStatement instanceof PsiBlockStatement) + && findOneYieldOrThrowStatement(PsiTreeUtil.skipWhitespacesAndCommentsForward(codeBlockChildren)) == null) { builder.append("{"); //wrap multiline rule into '{}' } previousHasBodyStatement = nextIsBodyStatement; @@ -440,12 +471,12 @@ public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor proble else { builder.append(codeBlockChildren.getText()); PsiElement nextOfCodeBlockChildren = PsiTreeUtil.skipWhitespacesAndCommentsForward(codeBlockChildren); - if (!(codeBlockChildren instanceof PsiComment || codeBlockChildren instanceof PsiWhiteSpace) && - !(PsiTreeUtil.skipWhitespacesAndCommentsBackward(codeBlockChildren) instanceof PsiJavaToken) && - findOneYieldOrThrowStatement(PsiTreeUtil.skipWhitespacesAndCommentsBackward(codeBlockChildren)) == null && - !(codeBlockChildren instanceof PsiJavaToken) && - (nextOfCodeBlockChildren instanceof PsiSwitchLabelStatement || - nextOfCodeBlockChildren instanceof PsiJavaToken javaToken && javaToken.textMatches("}"))) { + if (!(codeBlockChildren instanceof PsiComment || codeBlockChildren instanceof PsiWhiteSpace) + && !(PsiTreeUtil.skipWhitespacesAndCommentsBackward(codeBlockChildren) instanceof PsiJavaToken) + && findOneYieldOrThrowStatement(PsiTreeUtil.skipWhitespacesAndCommentsBackward(codeBlockChildren)) == null + && !(codeBlockChildren instanceof PsiJavaToken) + && (nextOfCodeBlockChildren instanceof PsiSwitchLabelStatement || + nextOfCodeBlockChildren instanceof PsiJavaToken javaToken && javaToken.textMatches("}"))) { builder.append("\n}"); //wrap multiline rule into '{}' } } @@ -461,23 +492,27 @@ public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor proble switchExpression.replace(newSwitchExpression); } + @RequiredReadAction private static boolean checkNextIsStatement(@Nullable PsiElement statement) { PsiElement forward = PsiTreeUtil.skipWhitespacesAndCommentsForward(statement); - return statement instanceof PsiSwitchLabelStatement && - !(forward instanceof PsiSwitchLabelStatement) && - forward instanceof PsiStatement; + return statement instanceof PsiSwitchLabelStatement + && !(forward instanceof PsiSwitchLabelStatement) + && forward instanceof PsiStatement; } - private static @Nullable PsiStatement findOneYieldOrThrowStatement(@Nullable PsiElement switchBlockChild) { + @Nullable + @RequiredReadAction + private static PsiStatement findOneYieldOrThrowStatement(@Nullable PsiElement switchBlockChild) { if (switchBlockChild == null) { return null; } - boolean isOldOrThrow = switchBlockChild instanceof PsiYieldStatement || - switchBlockChild instanceof PsiThrowStatement; + boolean isOldOrThrow = switchBlockChild instanceof PsiYieldStatement + || switchBlockChild instanceof PsiThrowStatement; if (!isOldOrThrow) { return null; } - boolean hasSwitchLabelBefore = PsiTreeUtil.skipWhitespacesAndCommentsBackward(switchBlockChild) instanceof PsiSwitchLabelStatement; + boolean hasSwitchLabelBefore = + PsiTreeUtil.skipWhitespacesAndCommentsBackward(switchBlockChild) instanceof PsiSwitchLabelStatement; if (!hasSwitchLabelBefore) { return null; } @@ -504,6 +539,7 @@ private static class ReplaceWithSwitchExpressionFix implements LocalQuickFix { } @Override + @RequiredWriteAction public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor problemDescriptor) { PsiElement element = problemDescriptor.getPsiElement(); @@ -527,8 +563,8 @@ public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor proble * * @return rearranged branches */ - private static @Nonnull List rearrangeBranches(@Nonnull List branches, - @Nonnull PsiElement context) { + @RequiredReadAction + private static @Nonnull List rearrangeBranches(@Nonnull List branches, @Nonnull PsiElement context) { if (branches.isEmpty()) { return branches; } @@ -557,9 +593,11 @@ public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor proble * * @param result - container, where sorted cases will be added */ - private static void rearrangeCases(@Nonnull SwitchBranch branch, - @Nonnull List caseExpressions, - @Nonnull List result) { + private static void rearrangeCases( + @Nonnull SwitchBranch branch, + @Nonnull List caseExpressions, + @Nonnull List result + ) { List previousExpressions = new ArrayList<>(); for (PsiCaseLabelElement expression : caseExpressions) { PsiCaseLabelElement external = findNullLabel(List.of(expression)); @@ -586,8 +624,10 @@ private static void rearrangeCases(@Nonnull SwitchBranch branch, } private static @Nullable PsiCaseLabelElement findNullLabel(@Nonnull List expressions) { - return ContainerUtil.find(expressions, - label -> label instanceof PsiExpression literal && TypeConversionUtil.isNullType(literal.getType())); + return ContainerUtil.find( + expressions, + label -> label instanceof PsiExpression literal && TypeConversionUtil.isNullType(literal.getType()) + ); } private static final class ReturningSwitchReplacer implements SwitchReplacer { @@ -599,13 +639,16 @@ private static final class ReturningSwitchReplacer implements SwitchReplacer { private final boolean myIsInfo; private final int myMaxNumberStatementsInBranch; - private ReturningSwitchReplacer(@Nonnull PsiStatement statement, - @Nonnull List newBranches, - @Nullable PsiReturnStatement returnToDelete, - @Nullable PsiThrowStatement throwToDelete, - @Nonnull List statementsToDelete, - boolean isInfo, - int maxNumberStatementsInBranch) { + @RequiredReadAction + private ReturningSwitchReplacer( + @Nonnull PsiStatement statement, + @Nonnull List newBranches, + @Nullable PsiReturnStatement returnToDelete, + @Nullable PsiThrowStatement throwToDelete, + @Nonnull List statementsToDelete, + boolean isInfo, + int maxNumberStatementsInBranch + ) { myStatement = statement; myNewBranches = rearrangeBranches(newBranches, statement); myReturnToDelete = returnToDelete; @@ -626,6 +669,7 @@ public boolean isInformLevel() { } @Override + @RequiredWriteAction public void replace(@Nonnull PsiStatement statement) { CommentTracker commentTracker = new CommentTracker(); PsiSwitchBlock switchBlock = generateEnhancedSwitch(statement, myNewBranches, commentTracker, true); @@ -670,9 +714,13 @@ public ReplacementType getType() { * */ - private static @Nullable SwitchReplacer inspectReturningSwitch(@Nonnull PsiStatement statement, - @Nonnull List branches, - boolean isExhaustive, int maxNumberStatementsForExpression) { + @RequiredReadAction + private static @Nullable SwitchReplacer inspectReturningSwitch( + @Nonnull PsiStatement statement, + @Nonnull List branches, + boolean isExhaustive, + int maxNumberStatementsForExpression + ) { PsiReturnStatement returnAfterSwitch = ObjectUtil.tryCast(PsiTreeUtil.getNextSiblingOfType(statement, PsiStatement.class), PsiReturnStatement.class); PsiThrowStatement throwAfterSwitch = @@ -719,9 +767,15 @@ else if (throwAfterSwitch != null) { isInfo = true; } int lastIndex = statements.length - 1; - if (ContainerUtil.exists(statements, - st -> !PsiTreeUtil.findChildrenOfAnyType(st, PsiContinueStatement.class, PsiBreakStatement.class, - PsiYieldStatement.class).isEmpty())) { + if (ContainerUtil.exists( + statements, + st -> !PsiTreeUtil.findChildrenOfAnyType( + st, + PsiContinueStatement.class, + PsiBreakStatement.class, + PsiYieldStatement.class + ).isEmpty() + )) { return null; } PsiReturnStatement returnStmt = ObjectUtil.tryCast(statements[lastIndex], PsiReturnStatement.class); @@ -785,18 +839,27 @@ else if (throwAfterSwitch != null) { current = current.getNextSibling(); } } - return new ReturningSwitchReplacer(statement, newBranches, returnAfterSwitch, throwAfterSwitch, statementsToDelete, isInfo, maxLines); + return new ReturningSwitchReplacer( + statement, + newBranches, + returnAfterSwitch, + throwAfterSwitch, + statementsToDelete, + isInfo, + maxLines + ); } + @RequiredReadAction private static PsiStatement[] replaceAllReturnWithYield(PsiStatement[] statements) { PsiStatement[] result = ArrayUtil.copyOf(statements); for (int i = 0; i < statements.length; i++) { PsiStatement statement = result[i]; if (statement instanceof PsiReturnStatement returnStatement) { PsiExpression returnValue = returnStatement.getReturnValue(); - if (returnValue == null || !returnValue.isValid() || PsiTreeUtil.hasErrorElements(returnValue) || + if (returnValue == null || !returnValue.isValid() || PsiTreeUtil.hasErrorElements(returnValue) //skip PsiCall not to resolve and get exceptions - (!(returnValue instanceof PsiCall) && returnValue.getType() == null)) { + || (!(returnValue instanceof PsiCall) && returnValue.getType() == null)) { return null; } result[i] = createYieldStatement(returnValue); @@ -807,15 +870,20 @@ private static PsiStatement[] replaceAllReturnWithYield(PsiStatement[] statement Collection returnStatements = PsiTreeUtil.findChildrenOfType(copy, PsiReturnStatement.class); for (PsiReturnStatement returnStatement : returnStatements) { PsiExpression returnValue = returnStatement.getReturnValue(); - if (returnValue == null || PsiTreeUtil.hasErrorElements(returnValue) || !returnValue.isValid() || returnValue.getType() == null) { + if (returnValue == null + || PsiTreeUtil.hasErrorElements(returnValue) + || !returnValue.isValid() + || returnValue.getType() == null) { return null; } + //noinspection RequiredXAction returnStatement.replace(createYieldStatement(returnValue)); } } return result; } + @RequiredReadAction private static PsiStatement[] withLastStatementReplacedWithYield(PsiStatement[] statements, @Nonnull PsiExpression expr) { PsiStatement[] result = ArrayUtil.copyOf(statements); PsiStatement yieldStatement = createYieldStatement(expr); @@ -823,6 +891,7 @@ private static PsiStatement[] withLastStatementReplacedWithYield(PsiStatement[] return result; } + @RequiredReadAction private static @Nonnull PsiStatement createYieldStatement(@Nonnull PsiExpression expr) { Project project = expr.getProject(); PsiElementFactory factory = JavaPsiFacade.getElementFactory(project); @@ -837,12 +906,15 @@ private static final class SwitchExistingVariableReplacer implements SwitchRepla private final boolean myIsInfo; private final int myMaxNumberStatementsInBranch; - private SwitchExistingVariableReplacer(@Nonnull PsiVariable variableToAssign, - @Nonnull PsiStatement statement, - List newBranches, - boolean isRightAfterDeclaration, - boolean isInfo, - int maxNumberStatementsInBranch) { + @RequiredReadAction + private SwitchExistingVariableReplacer( + @Nonnull PsiVariable variableToAssign, + @Nonnull PsiStatement statement, + List newBranches, + boolean isRightAfterDeclaration, + boolean isInfo, + int maxNumberStatementsInBranch + ) { myVariableToAssign = variableToAssign; myStatement = statement; myNewBranches = rearrangeBranches(newBranches, statement); @@ -862,6 +934,7 @@ public boolean isInformLevel() { } @Override + @RequiredWriteAction public void replace(@Nonnull PsiStatement switchStatement) { PsiLabeledStatement labeledStatement = ObjectUtil.tryCast(switchStatement.getParent(), PsiLabeledStatement.class); CommentTracker commentTracker = new CommentTracker(); @@ -897,6 +970,7 @@ public void replace(@Nonnull PsiStatement switchStatement) { } } + @RequiredReadAction private static boolean isNotUsed(@Nonnull PsiVariable variable, @Nonnull PsiElement switchElement) { try { ControlFlow controlFlow = ControlFlowFactory @@ -930,9 +1004,13 @@ public ReplacementType getType() { * } * */ - private static @Nullable SwitchReplacer inspectVariableAssigningSwitch(@Nonnull PsiStatement statement, - @Nonnull List branches, - boolean isExhaustive, int maxNumberStatementsForExpression) { + @RequiredReadAction + private static @Nullable SwitchReplacer inspectVariableAssigningSwitch( + @Nonnull PsiStatement statement, + @Nonnull List branches, + boolean isExhaustive, + int maxNumberStatementsForExpression + ) { PsiElement parent = statement.getParent(); PsiElement anchor = parent instanceof PsiLabeledStatement ? parent : statement; PsiLocalVariable assignedVariable = null; @@ -953,13 +1031,18 @@ public ReplacementType getType() { if (statements.length == 0) { return null; } - if (ContainerUtil.exists(statements, + if (ContainerUtil.exists( + statements, st -> !PsiTreeUtil.findChildrenOfAnyType(st, PsiContinueStatement.class, - PsiYieldStatement.class, PsiReturnStatement.class).isEmpty())) { + PsiYieldStatement.class, PsiReturnStatement.class + ).isEmpty() + )) { return null; } - if (ContainerUtil.exists(Arrays.stream(statements).toList().subList(0, statements.length - 1), - st -> !PsiTreeUtil.findChildrenOfAnyType(st, PsiBreakStatement.class).isEmpty())) { + if (ContainerUtil.exists( + Arrays.stream(statements).toList().subList(0, statements.length - 1), + st -> !PsiTreeUtil.findChildrenOfAnyType(st, PsiBreakStatement.class).isEmpty() + )) { return null; } if (statements.length > maxNumberStatementsForExpression) { @@ -1004,12 +1087,7 @@ else if (assignedVariable != var) { result = new SwitchStatementBranch(withLastStatementReplacedWithYield(statements, rExpression)); } } - if (branch.isDefault()) { - wasDefault = true; - } - else { - wasDefault = existsDefaultLabelElement(branch.myLabelStatement); - } + wasDefault = branch.isDefault() || existsDefaultLabelElement(branch.myLabelStatement); newBranches.add(SwitchBranch.fromOldBranch(branch, result, branch.getRelatedStatements())); } if (assignedVariable == null || !hasAssignedBranch) { @@ -1025,8 +1103,14 @@ else if (assignedVariable != var) { return null; } } - return new SwitchExistingVariableReplacer(assignedVariable, statement, newBranches, isRightAfterDeclaration, isInfo, - maxNumberStatementsInBranch); + return new SwitchExistingVariableReplacer( + assignedVariable, + statement, + newBranches, + isRightAfterDeclaration, + isInfo, + maxNumberStatementsInBranch + ); } private static boolean existsDefaultLabelElement(@Nonnull PsiSwitchLabelStatement statement) { @@ -1037,9 +1121,11 @@ private static boolean existsDefaultLabelElement(@Nonnull PsiSwitchLabelStatemen return ContainerUtil.exists(labelElementList.getElements(), el -> el instanceof PsiDefaultCaseLabelElement); } - private static @Nullable EnhancedSwitchMigrationInspection.SwitchBranch getVariableAssigningDefaultBranch(@Nullable PsiLocalVariable assignedVariable, - boolean isRightAfterDeclaration, - @Nonnull PsiStatement statement) { + private static @Nullable EnhancedSwitchMigrationInspection.SwitchBranch getVariableAssigningDefaultBranch( + @Nullable PsiLocalVariable assignedVariable, + boolean isRightAfterDeclaration, + @Nonnull PsiStatement statement + ) { if (assignedVariable == null) { return null; } @@ -1052,14 +1138,14 @@ private static boolean existsDefaultLabelElement(@Nonnull PsiSwitchLabelStatemen return null; } try { - final LocalsOrMyInstanceFieldsControlFlowPolicy policy = LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance(); - final ControlFlow controlFlow = + LocalsOrMyInstanceFieldsControlFlowPolicy policy = LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance(); + ControlFlow controlFlow = ControlFlowFactory.getInstance(declaration.getProject()).getControlFlow(declaration.getParent(), policy); - final int switchStart = controlFlow.getStartOffset(statement); + int switchStart = controlFlow.getStartOffset(statement); if (switchStart <= 0) { return null; } - final ControlFlow beforeFlow = new ControlFlowSubRange(controlFlow, 0, switchStart); + ControlFlow beforeFlow = new ControlFlowSubRange(controlFlow, 0, switchStart); if (!ControlFlowUtil.isVariableDefinitelyAssigned(assignedVariable, beforeFlow)) { return null; } @@ -1073,6 +1159,7 @@ private static boolean existsDefaultLabelElement(@Nonnull PsiSwitchLabelStatemen return SwitchBranch.createDefault(new SwitchRuleExpressionResult(reference)); } + @RequiredReadAction private static boolean isRightAfterDeclaration(PsiElement anchor, PsiVariable assignedVariable) { PsiDeclarationStatement declaration = ObjectUtil.tryCast(PsiTreeUtil.getPrevSiblingOfType(anchor, PsiStatement.class), PsiDeclarationStatement.class); @@ -1095,8 +1182,8 @@ private static final class SwitchStatementReplacer implements SwitchReplacer { final @Nonnull PsiStatement myStatement; final @Nonnull List myExpressionBranches; - private SwitchStatementReplacer(@Nonnull PsiStatement statement, - @Nonnull List ruleResults) { + @RequiredReadAction + private SwitchStatementReplacer(@Nonnull PsiStatement statement, @Nonnull List ruleResults) { myStatement = statement; myExpressionBranches = rearrangeBranches(ruleResults, statement); } @@ -1112,6 +1199,7 @@ public Integer getMaxNumberStatementsInBranch() { } @Override + @RequiredWriteAction public void replace(@Nonnull PsiStatement switchStatement) { CommentTracker commentTracker = new CommentTracker(); PsiSwitchBlock switchBlock = generateEnhancedSwitch(switchStatement, myExpressionBranches, commentTracker, false); @@ -1130,8 +1218,12 @@ public ReplacementType getType() { /** * Suggest replacement with an enhanced switch statement */ - private static @Nullable SwitchReplacer inspectReplacementWithStatement(@Nonnull PsiStatement statement, - @Nonnull List branches) { + @Nullable + @RequiredReadAction + private static SwitchReplacer inspectReplacementWithStatement( + @Nonnull PsiStatement statement, + @Nonnull List branches + ) { for (int i = 0, size = branches.size(); i < size; i++) { OldSwitchStatementBranch branch = branches.get(i); if (!isConvertibleBranch(branch, i != size - 1) && @@ -1166,9 +1258,11 @@ public ReplacementType getType() { return new SwitchStatementReplacer(statement, switchRules); } - - private static boolean isInBranchOrOutside(@Nonnull PsiStatement switchStmt, - OldSwitchStatementBranch branch, PsiLocalVariable variable) { + private static boolean isInBranchOrOutside( + @Nonnull PsiStatement switchStmt, + OldSwitchStatementBranch branch, + PsiLocalVariable variable + ) { return !PsiTreeUtil.isAncestor(switchStmt, variable, false) || ContainerUtil.or(branch.getStatements(), stmt -> PsiTreeUtil.isAncestor(stmt, variable, false)); } @@ -1184,13 +1278,16 @@ private SwitchStatementBranch(@Nullable PsiStatement[] resultStatements) { myOriginalResultStatements = null; } - private SwitchStatementBranch(@Nullable PsiStatement[] resultStatements, - @Nullable PsiStatement[] originalResultStatements) { + private SwitchStatementBranch( + @Nullable PsiStatement[] resultStatements, + @Nullable PsiStatement[] originalResultStatements + ) { myResultStatements = resultStatements; myOriginalResultStatements = originalResultStatements; } @Override + @RequiredReadAction public String generate(CommentTracker ct, SwitchBranch branch) { @Nullable PsiStatement[] resultStatements = myResultStatements; if (resultStatements == null) { @@ -1223,8 +1320,9 @@ public String generate(CommentTracker ct, SwitchBranch branch) { PsiElement current = getElementForComments(element, i); addWhiteSpaceAndComments(current, sb, ct); } - if (element.getNextSibling() == null && element.getLastChild() instanceof PsiComment comment && - comment.getTokenType() == JavaTokenType.END_OF_LINE_COMMENT) { + if (element.getNextSibling() == null + && element.getLastChild() instanceof PsiComment comment + && comment.getTokenType() == JavaTokenType.END_OF_LINE_COMMENT) { addNewLine(sb); } } @@ -1255,6 +1353,7 @@ private static void addNewLine(@Nonnull StringBuilder sb) { return current; } + @RequiredReadAction private static void addWhiteSpaceAndComments(@Nullable PsiElement element, @Nonnull StringBuilder sb, CommentTracker ct) { if (element == null) { return; @@ -1275,6 +1374,7 @@ private SwitchRuleExpressionResult(@Nonnull PsiExpression expression) { } @Override + @RequiredReadAction public String generate(CommentTracker ct, SwitchBranch branch) { return ct.textWithComments(myExpression) + ";"; } @@ -1288,6 +1388,7 @@ public String generate(CommentTracker ct, SwitchBranch branch) { * @param branch the SwitchBranch object representing a switch branch * @param builder the StringBuilder object to append comments to */ + @RequiredReadAction private static void addCommentsUntilNextLabel(CommentTracker ct, SwitchBranch branch, StringBuilder builder) { PsiElement label = ContainerUtil.find(branch.myUsedElements, e -> e instanceof PsiSwitchLabelStatement); if (!(label instanceof PsiSwitchLabelStatement labelStatement)) { @@ -1320,14 +1421,16 @@ private static void addCommentsUntilNextLabel(CommentTracker ct, SwitchBranch br } } + @RequiredReadAction private static @Nonnull String grubCommentsBefore(@Nonnull PsiElement untilComment, @Nonnull CommentTracker ct, SwitchBranch branch) { List comments = new ArrayList<>(); - PsiElement current = - (untilComment instanceof PsiComment || untilComment instanceof PsiWhiteSpace) ? untilComment : PsiTreeUtil.prevLeaf(untilComment); + PsiElement current = (untilComment instanceof PsiComment || untilComment instanceof PsiWhiteSpace) + ? untilComment + : PsiTreeUtil.prevLeaf(untilComment); while (current != null) { - if ((current instanceof PsiComment || current instanceof PsiWhiteSpace)) { - if (branch.myUsedElements.isEmpty() || - !PsiTreeUtil.isAncestor(branch.myUsedElements.get(branch.myUsedElements.size() - 1), current, false)) { + if (current instanceof PsiComment || current instanceof PsiWhiteSpace) { + if (branch.myUsedElements.isEmpty() + || !PsiTreeUtil.isAncestor(branch.myUsedElements.get(branch.myUsedElements.size() - 1), current, false)) { comments.add(ct.text(current)); } } @@ -1347,10 +1450,12 @@ private static final class SwitchBranch { final @Nonnull List myUsedElements; // used elements only for this branch private final @Nonnull SwitchRuleResult myRuleResult; - private SwitchBranch(boolean isDefault, - @Nonnull List caseExpressions, - @Nullable PsiExpression guard, @Nonnull SwitchRuleResult ruleResult, - @Nonnull List usedElements) { + private SwitchBranch( + boolean isDefault, + @Nonnull List caseExpressions, + @Nullable PsiExpression guard, @Nonnull SwitchRuleResult ruleResult, + @Nonnull List usedElements + ) { if (ContainerUtil.exists(caseExpressions, exp -> exp instanceof PsiDefaultCaseLabelElement)) { myIsDefault = true; } @@ -1374,6 +1479,7 @@ private SwitchBranch(boolean isDefault, myUsedElements = usedElements; } + @RequiredReadAction private String generate(CommentTracker ct) { StringBuilder sb = new StringBuilder(); PsiElement label = ContainerUtil.find(myUsedElements, e -> e instanceof PsiSwitchLabelStatement); @@ -1405,15 +1511,17 @@ else if (!myIsDefault) { sb.append(myRuleResult.generate(ct, this)); if (!myUsedElements.isEmpty()) { PsiElement element = PsiTreeUtil.nextCodeLeaf(myUsedElements.get(myUsedElements.size() - 1)); - if (element instanceof PsiJavaToken javaToken && javaToken.textMatches("}") && - element.getParent() instanceof PsiCodeBlock codeBlock && - codeBlock.getParent() instanceof PsiSwitchBlock) { - sb.append(ct.commentsBefore(element)); + if (element instanceof PsiJavaToken javaToken + && javaToken.textMatches("}") + && element.getParent() instanceof PsiCodeBlock codeBlock + && codeBlock.getParent() instanceof PsiSwitchBlock) { + sb.append(ct.commentsBefore(javaToken)); } } return sb.toString(); } + @RequiredReadAction private static void grabCommentsBeforeColon(PsiElement label, CommentTracker ct, StringBuilder sb) { if (label != null) { PsiElement child = label.getLastChild(); @@ -1434,9 +1542,11 @@ private static void grabCommentsBeforeColon(PsiElement label, CommentTracker ct, return new SwitchBranch(true, Collections.emptyList(), null, ruleResult, Collections.emptyList()); } - private static @Nonnull SwitchBranch fromOldBranch(@Nonnull OldSwitchStatementBranch branch, - @Nonnull SwitchRuleResult result, - @Nonnull List usedElements) { + private static @Nonnull SwitchBranch fromOldBranch( + @Nonnull OldSwitchStatementBranch branch, + @Nonnull SwitchRuleResult result, + @Nonnull List usedElements + ) { return new SwitchBranch(branch.isDefault(), branch.getCaseLabelElements(), branch.getGuardExpression(), result, usedElements); } } @@ -1449,10 +1559,12 @@ private static final class OldSwitchStatementBranch { @Nullable OldSwitchStatementBranch myPreviousSwitchBranch; - private OldSwitchStatementBranch(boolean isFallthrough, - PsiStatement[] statements, - @Nonnull PsiSwitchLabelStatement switchLabelStatement, - @Nullable PsiBreakStatement breakStatement) { + private OldSwitchStatementBranch( + boolean isFallthrough, + PsiStatement[] statements, + @Nonnull PsiSwitchLabelStatement switchLabelStatement, + @Nullable PsiBreakStatement breakStatement + ) { myIsFallthrough = isFallthrough; myStatements = statements; myLabelStatement = switchLabelStatement; @@ -1476,7 +1588,7 @@ private List getCaseLabelElements() { List branches = getWithFallthroughBranches(); Collections.reverse(branches); return StreamEx.of(branches).flatMap(branch -> { - final PsiCaseLabelElementList caseLabelElementList = branch.myLabelStatement.getCaseLabelElementList(); + PsiCaseLabelElementList caseLabelElementList = branch.myLabelStatement.getCaseLabelElementList(); if (caseLabelElementList == null) { return StreamEx.empty(); } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/deprecation/DeprecationInspection.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/deprecation/DeprecationInspection.java index 27e115f01c..42e4d7cd23 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/deprecation/DeprecationInspection.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/deprecation/DeprecationInspection.java @@ -24,6 +24,7 @@ import com.intellij.java.language.psi.infos.MethodCandidateInfo; import com.intellij.java.language.psi.util.MethodSignatureBackedByPsiMethod; import consulo.annotation.DeprecationInfo; +import consulo.annotation.access.RequiredReadAction; import consulo.annotation.component.ExtensionImpl; import consulo.document.util.TextRange; import consulo.java.analysis.codeInspection.DeprecationUtil; @@ -48,15 +49,19 @@ public class DeprecationInspection extends BaseJavaBatchLocalInspectionTool 0 && list != null) { JavaResolveResult[] results = resolveHelper.multiResolveConstructor((PsiClassType) type, list, list); MethodCandidateInfo result = null; @@ -170,13 +195,15 @@ public void visitNewExpression(PsiNewExpression expression) { return; } checkDeprecated(constructor, expression.getClassOrAnonymousClassReference(), null, myIgnoreInsideDeprecated, - myIgnoreImportStatements, true, myHolder); + myIgnoreImportStatements, true, myHolder + ); } } } @Override - public void visitMethod(PsiMethod method) { + @RequiredReadAction + public void visitMethod(@Nonnull PsiMethod method) { MethodSignatureBackedByPsiMethod methodSignature = MethodSignatureBackedByPsiMethod.create(method, PsiSubstitutor.EMPTY); if (!method.isConstructor()) { List superMethodSignatures = method.findSuperMethodSignaturesIncludingStatic(true); @@ -187,23 +214,24 @@ public void visitMethod(PsiMethod method) { } } + @RequiredReadAction private void checkImplicitCallToSuper(PsiMethod method) { - final PsiClass containingClass = method.getContainingClass(); + PsiClass containingClass = method.getContainingClass(); if (containingClass == null) { return; } - final PsiClass superClass = containingClass.getSuperClass(); + PsiClass superClass = containingClass.getSuperClass(); if (hasDefaultDeprecatedConstructor(superClass)) { - if (superClass instanceof PsiAnonymousClass) { - final PsiExpressionList argumentList = ((PsiAnonymousClass) superClass).getArgumentList(); + if (superClass instanceof PsiAnonymousClass anonymousClass) { + PsiExpressionList argumentList = anonymousClass.getArgumentList(); if (argumentList != null && argumentList.getExpressions().length > 0) { return; } } - final PsiCodeBlock body = method.getBody(); + PsiCodeBlock body = method.getBody(); if (body != null) { - final PsiStatement[] statements = body.getStatements(); + PsiStatement[] statements = body.getStatements(); if (statements.length == 0 || !JavaHighlightUtil.isSuperOrThisCall(statements[0], true, true)) { registerDefaultConstructorProblem(superClass, method.getNameIdentifier(), false); } @@ -211,29 +239,36 @@ private void checkImplicitCallToSuper(PsiMethod method) { } } + @RequiredReadAction private void registerDefaultConstructorProblem(PsiClass superClass, PsiElement nameIdentifier, boolean asDeprecated) { - myHolder.registerProblem(nameIdentifier, "Default constructor in " + superClass.getQualifiedName() + " is deprecated", - asDeprecated ? ProblemHighlightType.LIKE_DEPRECATED : ProblemHighlightType.GENERIC_ERROR_OR_WARNING); + myHolder.newProblem(LocalizeValue.of("Default constructor in " + superClass.getQualifiedName() + " is deprecated")) + .range(nameIdentifier) + .highlightType(asDeprecated ? ProblemHighlightType.LIKE_DEPRECATED : ProblemHighlightType.GENERIC_ERROR_OR_WARNING) + .create(); } @Override - public void visitClass(PsiClass aClass) { + @RequiredReadAction + public void visitClass(@Nonnull PsiClass aClass) { if (aClass instanceof PsiTypeParameter) { return; } - final PsiMethod[] currentConstructors = aClass.getConstructors(); + PsiMethod[] currentConstructors = aClass.getConstructors(); if (currentConstructors.length == 0) { - final PsiClass superClass = aClass.getSuperClass(); + PsiClass superClass = aClass.getSuperClass(); if (hasDefaultDeprecatedConstructor(superClass)) { - final boolean isAnonymous = aClass instanceof PsiAnonymousClass; + boolean isAnonymous = aClass instanceof PsiAnonymousClass; if (isAnonymous) { - final PsiExpressionList argumentList = ((PsiAnonymousClass) aClass).getArgumentList(); + PsiExpressionList argumentList = ((PsiAnonymousClass) aClass).getArgumentList(); if (argumentList != null && argumentList.getExpressions().length > 0) { return; } } - registerDefaultConstructorProblem(superClass, isAnonymous ? ((PsiAnonymousClass) aClass).getBaseClassReference() : aClass - .getNameIdentifier(), isAnonymous); + registerDefaultConstructorProblem( + superClass, + isAnonymous ? ((PsiAnonymousClass) aClass).getBaseClassReference() : aClass.getNameIdentifier(), + isAnonymous + ); } } } @@ -241,7 +276,7 @@ public void visitClass(PsiClass aClass) { private static boolean hasDefaultDeprecatedConstructor(PsiClass superClass) { if (superClass != null) { - final PsiMethod[] constructors = superClass.getConstructors(); + PsiMethod[] constructors = superClass.getConstructors(); for (PsiMethod constructor : constructors) { if (constructor.getParameterList().getParametersCount() == 0 && constructor.isDeprecated()) { return true; @@ -252,10 +287,12 @@ private static boolean hasDefaultDeprecatedConstructor(PsiClass superClass) { } //@top - static void checkMethodOverridesDeprecated(MethodSignatureBackedByPsiMethod methodSignature, - List superMethodSignatures, - boolean ignoreAbstractDeprecatedOverrides, - ProblemsHolder holder) { + static void checkMethodOverridesDeprecated( + MethodSignatureBackedByPsiMethod methodSignature, + List superMethodSignatures, + boolean ignoreAbstractDeprecatedOverrides, + ProblemsHolder holder + ) { PsiMethod method = methodSignature.getMethod(); PsiElement methodName = method.getNameIdentifier(); for (MethodSignatureBackedByPsiMethod superMethodSignature : superMethodSignatures) { @@ -265,38 +302,56 @@ static void checkMethodOverridesDeprecated(MethodSignatureBackedByPsiMethod meth continue; } // do not show deprecated warning for class implementing deprecated methods - if (ignoreAbstractDeprecatedOverrides && !aClass.isDeprecated() && superMethod.hasModifierProperty(PsiModifier.ABSTRACT)) { + if (ignoreAbstractDeprecatedOverrides && !aClass.isDeprecated() && superMethod.isAbstract()) { continue; } if (superMethod.isDeprecated()) { - String description = JavaErrorBundle.message("overrides.deprecated.method", HighlightMessageUtil.getSymbolName(aClass, - PsiSubstitutor.EMPTY)); - holder.registerProblem(methodName, description, ProblemHighlightType.LIKE_DEPRECATED); + String description = JavaErrorBundle.message("overrides.deprecated.method", HighlightMessageUtil.getSymbolName( + aClass, + PsiSubstitutor.EMPTY + )); + holder.newProblem(LocalizeValue.localizeTODO(description)) + .range(methodName) + .highlightType(ProblemHighlightType.LIKE_DEPRECATED) + .create(); } } } - public static void checkDeprecated(PsiElement refElement, - PsiElement elementToHighlight, - @Nullable TextRange rangeInElement, - ProblemsHolder holder) { + @RequiredReadAction + public static void checkDeprecated( + PsiElement refElement, + PsiElement elementToHighlight, + @Nullable TextRange rangeInElement, + ProblemsHolder holder + ) { checkDeprecated(refElement, elementToHighlight, rangeInElement, false, false, true, holder); } - public static void checkDeprecated(PsiElement refElement, - PsiElement elementToHighlight, - @Nullable TextRange rangeInElement, - boolean ignoreInsideDeprecated, - boolean ignoreImportStatements, - boolean ignoreMethodsOfDeprecated, - ProblemsHolder holder) { - if (!(refElement instanceof PsiDocCommentOwner)) { + @RequiredReadAction + public static void checkDeprecated( + PsiElement refElement, + PsiElement elementToHighlight, + @Nullable TextRange rangeInElement, + boolean ignoreInsideDeprecated, + boolean ignoreImportStatements, + boolean ignoreMethodsOfDeprecated, + ProblemsHolder holder + ) { + if (!(refElement instanceof PsiDocCommentOwner docCommentOwner)) { return; } - if (!((PsiDocCommentOwner) refElement).isDeprecated()) { + if (!docCommentOwner.isDeprecated()) { if (!ignoreMethodsOfDeprecated) { - checkDeprecated(((PsiDocCommentOwner) refElement).getContainingClass(), elementToHighlight, rangeInElement, ignoreInsideDeprecated, - ignoreImportStatements, false, holder); + checkDeprecated( + docCommentOwner.getContainingClass(), + elementToHighlight, + rangeInElement, + ignoreInsideDeprecated, + ignoreImportStatements, + false, + holder + ); } return; } @@ -326,6 +381,9 @@ public static void checkDeprecated(PsiElement refElement, if (description == null) { description = JavaErrorBundle.message("deprecated.symbol", symbolName); } - holder.registerProblem(elementToHighlight, description, ProblemHighlightType.LIKE_DEPRECATED, rangeInElement); + holder.newProblem(LocalizeValue.of(description)) + .range(elementToHighlight, rangeInElement) + .highlightType(ProblemHighlightType.LIKE_DEPRECATED) + .create(); } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/equalsAndHashcode/EqualsAndHashcode.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/equalsAndHashcode/EqualsAndHashcode.java index 44605c6ecb..cb7017f11b 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/equalsAndHashcode/EqualsAndHashcode.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/equalsAndHashcode/EqualsAndHashcode.java @@ -18,12 +18,11 @@ import com.intellij.java.analysis.codeInspection.BaseJavaBatchLocalInspectionTool; import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.MethodSignatureUtil; +import consulo.annotation.access.RequiredReadAction; import consulo.annotation.component.ExtensionImpl; import consulo.application.util.CachedValueProvider; import consulo.application.util.CachedValuesManager; -import consulo.application.util.function.Computable; import consulo.language.editor.inspection.LocalInspectionToolSession; -import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.ProblemsHolder; import consulo.language.editor.inspection.localize.InspectionLocalize; import consulo.language.psi.PsiElementVisitor; @@ -33,118 +32,125 @@ import consulo.project.Project; import consulo.util.lang.Couple; import jakarta.annotation.Nonnull; -import jakarta.annotation.Nullable; -import org.jetbrains.annotations.NonNls; + +import java.util.function.Supplier; /** * @author max */ @ExtensionImpl public class EqualsAndHashcode extends BaseJavaBatchLocalInspectionTool { - @Override - @Nonnull - public PsiElementVisitor buildVisitorImpl( - @Nonnull final ProblemsHolder holder, - boolean isOnTheFly, - LocalInspectionToolSession session, - Object state - ) { - final Project project = holder.getProject(); - Couple pair = CachedValuesManager.getManager(project).getCachedValue(project, () -> { - final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); - final PsiClass psiObjectClass = project.getApplication().runReadAction( - new Computable() { - @Override - @Nullable - public PsiClass compute() { - return psiFacade.findClass(CommonClassNames.JAVA_LANG_OBJECT, GlobalSearchScope.allScope(project)); - } - } - ); - if (psiObjectClass == null) { - return CachedValueProvider.Result.create(null, ProjectRootManager.getInstance(project)); - } - PsiMethod[] methods = psiObjectClass.getMethods(); - PsiMethod myEquals = null; - PsiMethod myHashCode = null; - for (PsiMethod method : methods) { - @NonNls final String name = method.getName(); - if ("equals".equals(name)) { - myEquals = method; + private static record CheckResult(boolean hasEquals, boolean hasHashCode) { + private static final String CODE_EQUALS = "equals()"; + private static final String CODE_HASH_CODE = "hashCode()"; + + public boolean hasInconsistency() { + return hasEquals != hasHashCode; } - else if ("hashCode".equals(name)) { - myHashCode = method; + + public LocalizeValue getErrorMessage() { + if (!hasInconsistency()) { + return LocalizeValue.empty(); + } + return hasEquals + ? InspectionLocalize.inspectionEqualsHashcodeOnlyOneDefinedProblemDescriptor(CODE_EQUALS, CODE_HASH_CODE) + : InspectionLocalize.inspectionEqualsHashcodeOnlyOneDefinedProblemDescriptor(CODE_HASH_CODE, CODE_EQUALS); } - } - return CachedValueProvider.Result.create(Couple.of(myEquals, myHashCode), psiObjectClass); - }); + } - if (pair == null) return new PsiElementVisitor() {}; + @Nonnull + @Override + @RequiredReadAction + public PsiElementVisitor buildVisitorImpl( + @Nonnull final ProblemsHolder holder, + boolean isOnTheFly, + LocalInspectionToolSession session, + Object state + ) { + Project project = holder.getProject(); + Couple pair = CachedValuesManager.getManager(project).getCachedValue( + project, + () -> { + JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); + PsiClass psiObjectClass = project.getApplication().runReadAction( + (Supplier) () -> psiFacade.findClass(CommonClassNames.JAVA_LANG_OBJECT, GlobalSearchScope.allScope(project)) + ); + if (psiObjectClass == null) { + return CachedValueProvider.Result.create(null, ProjectRootManager.getInstance(project)); + } + PsiMethod[] methods = psiObjectClass.getMethods(); + PsiMethod myEquals = null; + PsiMethod myHashCode = null; + for (PsiMethod method : methods) { + String name = method.getName(); + if ("equals".equals(name)) { + myEquals = method; + } + else if ("hashCode".equals(name)) { + myHashCode = method; + } + } + return CachedValueProvider.Result.create(Couple.of(myEquals, myHashCode), psiObjectClass); + } + ); - //jdk wasn't configured for the project - final PsiMethod myEquals = pair.first; - final PsiMethod myHashCode = pair.second; - if (myEquals == null || myHashCode == null || !myEquals.isValid() || !myHashCode.isValid()) return new PsiElementVisitor() {}; + if (pair == null) { + return new PsiElementVisitor() { + }; + } - return new JavaElementVisitor() { - @Override public void visitClass(PsiClass aClass) { - super.visitClass(aClass); - boolean [] hasEquals = {false}; - boolean [] hasHashCode = {false}; - processClass(aClass, hasEquals, hasHashCode, myEquals, myHashCode); - if (hasEquals[0] != hasHashCode[0]) { - PsiIdentifier identifier = aClass.getNameIdentifier(); - holder.registerProblem( - identifier != null ? identifier : aClass, - hasEquals[0] - ? InspectionLocalize.inspectionEqualsHashcodeOnlyOneDefinedProblemDescriptor( - "equals()", - "hashCode()" - ).get() - : InspectionLocalize.inspectionEqualsHashcodeOnlyOneDefinedProblemDescriptor( - "hashCode()", - "equals()" - ).get(), - (LocalQuickFix[])null - ); + //jdk wasn't configured for the project + final PsiMethod myEquals = pair.first; + final PsiMethod myHashCode = pair.second; + if (myEquals == null || myHashCode == null || !myEquals.isValid() || !myHashCode.isValid()) { + return new PsiElementVisitor() { + }; } - } - }; - } - private static void processClass( - final PsiClass aClass, - final boolean[] hasEquals, - final boolean[] hasHashCode, - PsiMethod equals, - PsiMethod hashcode - ) { - final PsiMethod[] methods = aClass.getMethods(); - for (PsiMethod method : methods) { - if (MethodSignatureUtil.areSignaturesEqual(method, equals)) { - hasEquals[0] = true; - } - else if (MethodSignatureUtil.areSignaturesEqual(method, hashcode)) { - hasHashCode[0] = true; - } + return new JavaElementVisitor() { + @Override + public void visitClass(@Nonnull PsiClass aClass) { + super.visitClass(aClass); + CheckResult checkResult = processClass(aClass, myEquals, myHashCode); + if (checkResult.hasInconsistency()) { + PsiIdentifier identifier = aClass.getNameIdentifier(); + holder.newProblem(checkResult.getErrorMessage()) + .range(identifier != null ? identifier : aClass) + .create(); + } + } + }; } - } - @Override - @Nonnull - public LocalizeValue getDisplayName() { - return InspectionLocalize.inspectionEqualsHashcodeDisplayName(); - } + private static CheckResult processClass(PsiClass aClass, PsiMethod equals, PsiMethod hashCode) { + boolean hasEquals = false, hasHashCode = false; + PsiMethod[] methods = aClass.getMethods(); + for (PsiMethod method : methods) { + if (MethodSignatureUtil.areSignaturesEqual(method, equals)) { + hasEquals = true; + } + else if (MethodSignatureUtil.areSignaturesEqual(method, hashCode)) { + hasHashCode = true; + } + } + return new CheckResult(hasEquals, hasHashCode); + } - @Override - @Nonnull - public LocalizeValue getGroupDisplayName() { - return LocalizeValue.of(); - } + @Nonnull + @Override + public LocalizeValue getDisplayName() { + return InspectionLocalize.inspectionEqualsHashcodeDisplayName(); + } + + @Nonnull + @Override + public LocalizeValue getGroupDisplayName() { + return LocalizeValue.of(); + } - @Override - @Nonnull - public String getShortName() { - return "EqualsAndHashcode"; - } + @Nonnull + @Override + public String getShortName() { + return "EqualsAndHashcode"; + } } diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/BaseInspectionVisitor.java b/java-analysis-impl/src/main/java/com/siyeh/ig/BaseInspectionVisitor.java index 1fdc1b3c81..268f71a23a 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/BaseInspectionVisitor.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/BaseInspectionVisitor.java @@ -16,217 +16,222 @@ package com.siyeh.ig; import com.intellij.java.language.psi.*; +import consulo.annotation.access.RequiredReadAction; import consulo.document.util.TextRange; import consulo.language.editor.inspection.ProblemHighlightType; import consulo.language.editor.inspection.ProblemsHolder; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; import consulo.language.psi.PsiWhiteSpace; +import consulo.localize.LocalizeValue; import jakarta.annotation.Nonnull; -import org.jetbrains.annotations.NonNls; public abstract class BaseInspectionVisitor extends JavaElementVisitor { - - private BaseInspection inspection = null; - private boolean onTheFly = false; - private ProblemsHolder holder = null; - - final void setInspection(BaseInspection inspection) { - this.inspection = inspection; - } - - final void setOnTheFly(boolean onTheFly) { - this.onTheFly = onTheFly; - } - - public final boolean isOnTheFly() { - return onTheFly; - } - - protected final void registerNewExpressionError( - @Nonnull PsiNewExpression expression, Object... infos) { - final PsiJavaCodeReferenceElement classReference = - expression.getClassOrAnonymousClassReference(); - if (classReference == null) { - registerError(expression, infos); - } else { - registerError(classReference, infos); - } - } - - protected final void registerMethodCallError( - @Nonnull PsiMethodCallExpression expression, - @NonNls Object... infos) { - final PsiReferenceExpression methodExpression = - expression.getMethodExpression(); - final PsiElement nameToken = methodExpression.getReferenceNameElement(); - if (nameToken == null) { - registerError(expression, infos); - } else { - registerError(nameToken, infos); - } - } - - protected final void registerStatementError(@Nonnull PsiStatement statement, - Object... infos) { - final PsiElement statementToken = statement.getFirstChild(); - if (statementToken == null) { - registerError(statement, infos); - } else { - registerError(statementToken, infos); - } - } - - protected final void registerClassError(@Nonnull PsiClass aClass, - Object... infos) { - PsiElement nameIdentifier; - if (aClass instanceof PsiEnumConstantInitializer) { - final PsiEnumConstantInitializer enumConstantInitializer = - (PsiEnumConstantInitializer) aClass; - final PsiEnumConstant enumConstant = - enumConstantInitializer.getEnumConstant(); - nameIdentifier = enumConstant.getNameIdentifier(); - } else if (aClass instanceof PsiAnonymousClass) { - final PsiAnonymousClass anonymousClass = (PsiAnonymousClass) aClass; - nameIdentifier = anonymousClass.getBaseClassReference(); - } else { - nameIdentifier = aClass.getNameIdentifier(); - } - if (nameIdentifier != null && !nameIdentifier.isPhysical()) { - nameIdentifier = nameIdentifier.getNavigationElement(); - } - if (nameIdentifier == null || !nameIdentifier.isPhysical()) { - registerError(aClass.getContainingFile(), infos); - } else { - registerError(nameIdentifier, infos); - } - } - - protected final void registerMethodError(@Nonnull PsiMethod method, - Object... infos) { - final PsiElement nameIdentifier = method.getNameIdentifier(); - if (nameIdentifier == null) { - registerError(method.getContainingFile(), infos); - } else { - registerError(nameIdentifier, infos); - } - } - - protected final void registerVariableError(@Nonnull PsiVariable variable, - Object... infos) { - final PsiElement nameIdentifier = variable.getNameIdentifier(); - if (nameIdentifier == null) { - registerError(variable, infos); - } else { - registerError(nameIdentifier, infos); - } - } - - protected final void registerTypeParameterError( - @Nonnull PsiTypeParameter typeParameter, Object... infos) { - final PsiElement nameIdentifier = typeParameter.getNameIdentifier(); - if (nameIdentifier == null) { - registerError(typeParameter, infos); - } else { - registerError(nameIdentifier, infos); - } - } - - protected final void registerFieldError(@Nonnull PsiField field, - Object... infos) { - final PsiElement nameIdentifier = field.getNameIdentifier(); - registerError(nameIdentifier, infos); - } - - protected final void registerModifierError( - @Nonnull String modifier, @Nonnull PsiModifierListOwner parameter, - Object... infos) { - final PsiModifierList modifiers = parameter.getModifierList(); - if (modifiers == null) { - return; - } - final PsiElement[] children = modifiers.getChildren(); - for (final PsiElement child : children) { - final String text = child.getText(); - if (modifier.equals(text)) { - registerError(child, infos); - } - } - } - - protected final void registerClassInitializerError( - @Nonnull PsiClassInitializer initializer, Object... infos) { - final PsiCodeBlock body = initializer.getBody(); - final PsiJavaToken lBrace = body.getLBrace(); - if (lBrace == null) { - registerError(initializer, infos); - } else { - registerError(lBrace, infos); - } - } - - protected final void registerError(@Nonnull PsiElement location, - Object... infos) { - registerError(location, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, infos); - } - - protected final void registerError(@Nonnull PsiElement location, - final ProblemHighlightType highlightType, - Object... infos) { - if (location.getTextLength() == 0 && !(location instanceof PsiFile)) { - return; - } - final InspectionGadgetsFix[] fixes = createFixes(infos); - for (InspectionGadgetsFix fix : fixes) { - fix.setOnTheFly(onTheFly); - } - final String description = inspection.buildErrorString(infos); - holder.registerProblem(location, description, highlightType, fixes); - } - - protected final void registerErrorAtOffset(@Nonnull PsiElement location, - int offset, int length, Object... infos) { - if (location.getTextLength() == 0 || length == 0) { - return; - } - final InspectionGadgetsFix[] fixes = createFixes(infos); - for (InspectionGadgetsFix fix : fixes) { - fix.setOnTheFly(onTheFly); - } - final String description = inspection.buildErrorString(infos); - final TextRange range = new TextRange(offset, offset + length); - holder.registerProblem(location, range, description, fixes); - } - - @Nonnull - private InspectionGadgetsFix[] createFixes(Object... infos) { - if (!onTheFly && inspection.buildQuickFixesOnlyForOnTheFlyErrors()) { - return InspectionGadgetsFix.EMPTY_ARRAY; - } - final InspectionGadgetsFix[] fixes = inspection.buildFixes(infos); - if (fixes.length > 0) { - return fixes; - } - final InspectionGadgetsFix fix = inspection.buildFix(infos); - if (fix == null) { - return InspectionGadgetsFix.EMPTY_ARRAY; - } - return new InspectionGadgetsFix[]{fix}; - } - - @Override - public void visitReferenceExpression( - PsiReferenceExpression expression) { - visitExpression(expression); - } - - @Override - public final void visitWhiteSpace(PsiWhiteSpace space) { - // none of our inspections need to do anything with white space, - // so this is a performance optimization - } - - public final void setProblemsHolder(ProblemsHolder holder) { - this.holder = holder; - } + private BaseInspection inspection = null; + private boolean onTheFly = false; + private ProblemsHolder holder = null; + + final void setInspection(BaseInspection inspection) { + this.inspection = inspection; + } + + final void setOnTheFly(boolean onTheFly) { + this.onTheFly = onTheFly; + } + + public final boolean isOnTheFly() { + return onTheFly; + } + + @RequiredReadAction + protected final void registerNewExpressionError(@Nonnull PsiNewExpression expression, Object... infos) { + PsiJavaCodeReferenceElement classReference = expression.getClassOrAnonymousClassReference(); + if (classReference == null) { + registerError(expression, infos); + } + else { + registerError(classReference, infos); + } + } + + @RequiredReadAction + protected final void registerMethodCallError(@Nonnull PsiMethodCallExpression expression, Object... infos) { + PsiReferenceExpression methodExpression = expression.getMethodExpression(); + PsiElement nameToken = methodExpression.getReferenceNameElement(); + if (nameToken == null) { + registerError(expression, infos); + } + else { + registerError(nameToken, infos); + } + } + + @RequiredReadAction + protected final void registerStatementError(@Nonnull PsiStatement statement, Object... infos) { + PsiElement statementToken = statement.getFirstChild(); + if (statementToken == null) { + registerError(statement, infos); + } + else { + registerError(statementToken, infos); + } + } + + @RequiredReadAction + protected final void registerClassError(@Nonnull PsiClass aClass, Object... infos) { + PsiElement nameIdentifier; + if (aClass instanceof PsiEnumConstantInitializer enumConstantInitializer) { + PsiEnumConstant enumConstant = enumConstantInitializer.getEnumConstant(); + nameIdentifier = enumConstant.getNameIdentifier(); + } + else if (aClass instanceof PsiAnonymousClass anonymousClass) { + nameIdentifier = anonymousClass.getBaseClassReference(); + } + else { + nameIdentifier = aClass.getNameIdentifier(); + } + if (nameIdentifier != null && !nameIdentifier.isPhysical()) { + nameIdentifier = nameIdentifier.getNavigationElement(); + } + if (nameIdentifier == null || !nameIdentifier.isPhysical()) { + registerError(aClass.getContainingFile(), infos); + } + else { + registerError(nameIdentifier, infos); + } + } + + @RequiredReadAction + protected final void registerMethodError(@Nonnull PsiMethod method, Object... infos) { + PsiElement nameIdentifier = method.getNameIdentifier(); + if (nameIdentifier == null) { + registerError(method.getContainingFile(), infos); + } + else { + registerError(nameIdentifier, infos); + } + } + + @RequiredReadAction + protected final void registerVariableError(@Nonnull PsiVariable variable, Object... infos) { + PsiElement nameIdentifier = variable.getNameIdentifier(); + if (nameIdentifier == null) { + registerError(variable, infos); + } + else { + registerError(nameIdentifier, infos); + } + } + + @RequiredReadAction + protected final void registerTypeParameterError(@Nonnull PsiTypeParameter typeParameter, Object... infos) { + PsiElement nameIdentifier = typeParameter.getNameIdentifier(); + if (nameIdentifier == null) { + registerError(typeParameter, infos); + } + else { + registerError(nameIdentifier, infos); + } + } + + @RequiredReadAction + protected final void registerFieldError(@Nonnull PsiField field, Object... infos) { + PsiElement nameIdentifier = field.getNameIdentifier(); + registerError(nameIdentifier, infos); + } + + @RequiredReadAction + protected final void registerModifierError(@Nonnull String modifier, @Nonnull PsiModifierListOwner parameter, Object... infos) { + PsiModifierList modifiers = parameter.getModifierList(); + if (modifiers == null) { + return; + } + PsiElement[] children = modifiers.getChildren(); + for (PsiElement child : children) { + String text = child.getText(); + if (modifier.equals(text)) { + registerError(child, infos); + } + } + } + + @RequiredReadAction + protected final void registerClassInitializerError(@Nonnull PsiClassInitializer initializer, Object... infos) { + PsiCodeBlock body = initializer.getBody(); + PsiJavaToken lBrace = body.getLBrace(); + if (lBrace == null) { + registerError(initializer, infos); + } + else { + registerError(lBrace, infos); + } + } + + @RequiredReadAction + protected final void registerError(@Nonnull PsiElement location, Object... infos) { + registerError(location, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, infos); + } + + @RequiredReadAction + protected final void registerError(@Nonnull PsiElement location, ProblemHighlightType highlightType, Object... infos) { + if (location.getTextLength() == 0 && !(location instanceof PsiFile)) { + return; + } + InspectionGadgetsFix[] fixes = createFixes(infos); + for (InspectionGadgetsFix fix : fixes) { + fix.setOnTheFly(onTheFly); + } + String description = inspection.buildErrorString(infos); + holder.newProblem(LocalizeValue.of(description)) + .range(location) + .withFixes(fixes) + .highlightType(highlightType) + .create(); + } + + @RequiredReadAction + protected final void registerErrorAtOffset(@Nonnull PsiElement location, int offset, int length, Object... infos) { + if (location.getTextLength() == 0 || length == 0) { + return; + } + InspectionGadgetsFix[] fixes = createFixes(infos); + for (InspectionGadgetsFix fix : fixes) { + fix.setOnTheFly(onTheFly); + } + holder.newProblem(LocalizeValue.localizeTODO(inspection.buildErrorString(infos))) + .range(location, new TextRange(offset, offset + length)) + .withFixes(fixes) + .create(); + } + + @Nonnull + private InspectionGadgetsFix[] createFixes(Object... infos) { + if (!onTheFly && inspection.buildQuickFixesOnlyForOnTheFlyErrors()) { + return InspectionGadgetsFix.EMPTY_ARRAY; + } + InspectionGadgetsFix[] fixes = inspection.buildFixes(infos); + if (fixes.length > 0) { + return fixes; + } + InspectionGadgetsFix fix = inspection.buildFix(infos); + if (fix == null) { + return InspectionGadgetsFix.EMPTY_ARRAY; + } + return new InspectionGadgetsFix[]{fix}; + } + + @Override + public void visitReferenceExpression(@Nonnull PsiReferenceExpression expression) { + visitExpression(expression); + } + + @Override + public final void visitWhiteSpace(PsiWhiteSpace space) { + // none of our inspections need to do anything with white space, + // so this is a performance optimization + } + + public final void setProblemsHolder(ProblemsHolder holder) { + this.holder = holder; + } } \ No newline at end of file diff --git a/plugin/src/main/java/com/intellij/java/impl/ig/classlayout/ProtectedMemberInFinalClassInspection.java b/plugin/src/main/java/com/intellij/java/impl/ig/classlayout/ProtectedMemberInFinalClassInspection.java index 422c2d5cef..e1afb28fab 100644 --- a/plugin/src/main/java/com/intellij/java/impl/ig/classlayout/ProtectedMemberInFinalClassInspection.java +++ b/plugin/src/main/java/com/intellij/java/impl/ig/classlayout/ProtectedMemberInFinalClassInspection.java @@ -25,6 +25,8 @@ import com.siyeh.ig.InspectionGadgetsFix; import com.siyeh.ig.psiutils.MethodUtils; import com.siyeh.localize.InspectionGadgetsLocalize; +import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; import consulo.annotation.component.ExtensionImpl; import consulo.application.AccessToken; import consulo.application.WriteAction; @@ -75,26 +77,24 @@ protected InspectionGadgetsFix[] buildFixes(Object... infos) { private static class MakePrivateFix extends InspectionGadgetsFix { @Nonnull + @Override public LocalizeValue getName() { return InspectionGadgetsLocalize.makePrivateQuickfix(); } @Override + @RequiredWriteAction public void doFix(Project project, ProblemDescriptor descriptor) throws IncorrectOperationException { - final PsiElement element = descriptor.getPsiElement(); - final PsiElement parent = element.getParent(); - final PsiElement grandParent = parent.getParent(); - if (!(grandParent instanceof PsiMember)) { + PsiElement element = descriptor.getPsiElement(); + if (!(element.getParent().getParent() instanceof PsiMember member)) { return; } - final PsiMember member = (PsiMember) grandParent; - final PsiModifierList modifierList = member.getModifierList(); + PsiModifierList modifierList = member.getModifierList(); if (modifierList == null) { return; } - final MultiMap conflicts = new MultiMap<>(); - if (member instanceof PsiMethod) { - final PsiMethod method = (PsiMethod) member; + MultiMap conflicts = new MultiMap<>(); + if (member instanceof PsiMethod method) { SuperMethodsSearch.search(method, method.getContainingClass(), true, false).forEach(methodSignature -> { PsiMethod superMethod = methodSignature.getMethod(); conflicts.putValue( @@ -117,9 +117,9 @@ public void doFix(Project project, ProblemDescriptor descriptor) throws Incorrec return false; }); } - final PsiModifierList modifierListCopy = (PsiModifierList) modifierList.copy(); + PsiModifierList modifierListCopy = (PsiModifierList) modifierList.copy(); modifierListCopy.setModifierProperty(PsiModifier.PRIVATE, true); - final Query search = ReferencesSearch.search(member, member.getResolveScope()); + Query search = ReferencesSearch.search(member, member.getResolveScope()); search.forEach(reference -> { PsiElement element1 = reference.getElement(); if (!JavaResolveUtil.isAccessible(member, member.getContainingClass(), modifierListCopy, element1, null, null)) { @@ -136,7 +136,7 @@ public void doFix(Project project, ProblemDescriptor descriptor) throws Incorrec } return true; }); - final boolean conflictsDialogOK; + boolean conflictsDialogOK; if (conflicts.isEmpty()) { conflictsDialogOK = true; } @@ -147,7 +147,7 @@ public void doFix(Project project, ProblemDescriptor descriptor) throws Incorrec ConflictsDialog conflictsDialog = new ConflictsDialog( member.getProject(), conflicts, - (Runnable) () -> { + () -> { AccessToken token = WriteAction.start(); try { modifierList.setModifierProperty(PsiModifier.PRIVATE, true); @@ -172,14 +172,14 @@ public BaseInspectionVisitor buildVisitor() { } private static class ProtectedMemberInFinalClassVisitor extends BaseInspectionVisitor { - @Override + @RequiredReadAction public void visitMethod(@Nonnull PsiMethod method) { - if (!method.hasModifierProperty(PsiModifier.PROTECTED)) { + if (!method.isProtected()) { return; } - final PsiClass containingClass = method.getContainingClass(); - if (containingClass == null || !containingClass.hasModifierProperty(PsiModifier.FINAL)) { + PsiClass containingClass = method.getContainingClass(); + if (containingClass == null || !containingClass.isFinal()) { return; } if (MethodUtils.hasSuper(method)) { @@ -189,12 +189,13 @@ public void visitMethod(@Nonnull PsiMethod method) { } @Override + @RequiredReadAction public void visitField(@Nonnull PsiField field) { - if (!field.hasModifierProperty(PsiModifier.PROTECTED)) { + if (!field.isProtected()) { return; } - final PsiClass containingClass = field.getContainingClass(); - if (containingClass == null || !containingClass.hasModifierProperty(PsiModifier.FINAL)) { + PsiClass containingClass = field.getContainingClass(); + if (containingClass == null || !containingClass.isFinal()) { return; } registerModifierError(PsiModifier.PROTECTED, field, PsiModifier.PROTECTED); diff --git a/plugin/src/main/java/com/intellij/java/impl/ipp/interfacetoclass/ConvertInterfaceToClassIntention.java b/plugin/src/main/java/com/intellij/java/impl/ipp/interfacetoclass/ConvertInterfaceToClassIntention.java index 394b3ccb49..11452e80d5 100644 --- a/plugin/src/main/java/com/intellij/java/impl/ipp/interfacetoclass/ConvertInterfaceToClassIntention.java +++ b/plugin/src/main/java/com/intellij/java/impl/ipp/interfacetoclass/ConvertInterfaceToClassIntention.java @@ -21,10 +21,10 @@ import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.PsiUtil; import com.siyeh.localize.IntentionPowerPackLocalize; +import consulo.annotation.access.RequiredWriteAction; import consulo.annotation.component.ExtensionImpl; import consulo.application.AccessToken; import consulo.application.WriteAction; -import consulo.application.util.function.Processor; import consulo.application.util.query.Query; import consulo.content.scope.SearchScope; import consulo.language.editor.intention.IntentionMetaData; @@ -50,33 +50,33 @@ public LocalizeValue getText() { return IntentionPowerPackLocalize.convertInterfaceToClassIntentionName(); } + @RequiredWriteAction private static void changeInterfaceToClass(PsiClass anInterface) throws IncorrectOperationException { - final PsiIdentifier nameIdentifier = anInterface.getNameIdentifier(); + PsiIdentifier nameIdentifier = anInterface.getNameIdentifier(); assert nameIdentifier != null; - final PsiElement whiteSpace = nameIdentifier.getPrevSibling(); + PsiElement whiteSpace = nameIdentifier.getPrevSibling(); assert whiteSpace != null; - final PsiElement interfaceToken = whiteSpace.getPrevSibling(); + PsiElement interfaceToken = whiteSpace.getPrevSibling(); assert interfaceToken != null; - final PsiKeyword interfaceKeyword = (PsiKeyword) interfaceToken.getOriginalElement(); - final Project project = anInterface.getProject(); - final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); - final PsiElementFactory factory = psiFacade.getElementFactory(); - final PsiKeyword classKeyword = factory.createKeyword("class"); + PsiKeyword interfaceKeyword = (PsiKeyword) interfaceToken.getOriginalElement(); + Project project = anInterface.getProject(); + JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); + PsiElementFactory factory = psiFacade.getElementFactory(); + PsiKeyword classKeyword = factory.createKeyword("class"); interfaceKeyword.replace(classKeyword); - final PsiModifierList classModifierList = anInterface.getModifierList(); + PsiModifierList classModifierList = anInterface.getModifierList(); if (classModifierList == null) { return; } classModifierList.setModifierProperty(PsiModifier.ABSTRACT, true); - final PsiElement parent = anInterface.getParent(); - if (parent instanceof PsiClass) { + if (anInterface.getParent() instanceof PsiClass) { classModifierList.setModifierProperty(PsiModifier.STATIC, true); } - final PsiMethod[] methods = anInterface.getMethods(); - for (final PsiMethod method : methods) { + PsiMethod[] methods = anInterface.getMethods(); + for (PsiMethod method : methods) { PsiUtil.setModifierProperty(method, PsiModifier.PUBLIC, true); if (method.hasModifierProperty(PsiModifier.DEFAULT)) { PsiUtil.setModifierProperty(method, PsiModifier.DEFAULT, false); @@ -86,9 +86,9 @@ private static void changeInterfaceToClass(PsiClass anInterface) throws Incorrec } } - final PsiField[] fields = anInterface.getFields(); - for (final PsiField field : fields) { - final PsiModifierList modifierList = field.getModifierList(); + PsiField[] fields = anInterface.getFields(); + for (PsiField field : fields) { + PsiModifierList modifierList = field.getModifierList(); if (modifierList != null) { modifierList.setModifierProperty(PsiModifier.PUBLIC, true); modifierList.setModifierProperty(PsiModifier.STATIC, true); @@ -96,9 +96,9 @@ private static void changeInterfaceToClass(PsiClass anInterface) throws Incorrec } } - final PsiClass[] innerClasses = anInterface.getInnerClasses(); + PsiClass[] innerClasses = anInterface.getInnerClasses(); for (PsiClass innerClass : innerClasses) { - final PsiModifierList modifierList = innerClass.getModifierList(); + PsiModifierList modifierList = innerClass.getModifierList(); if (modifierList != null) { modifierList.setModifierProperty(PsiModifier.PUBLIC, true); if (!innerClass.isInterface()) { @@ -109,36 +109,37 @@ private static void changeInterfaceToClass(PsiClass anInterface) throws Incorrec } @Override + @RequiredWriteAction protected void processIntention(@Nonnull PsiElement element) throws IncorrectOperationException { - final PsiClass anInterface = (PsiClass) element.getParent(); - final SearchScope searchScope = anInterface.getUseScope(); - final Query query = ClassInheritorsSearch.search(anInterface, searchScope, false); - final MultiMap conflicts = new MultiMap<>(); - query.forEach(new Processor() { - @Override - public boolean process(PsiClass aClass) { - final PsiReferenceList extendsList = aClass.getExtendsList(); - if (extendsList == null) { - return true; - } - final PsiJavaCodeReferenceElement[] referenceElements = extendsList.getReferenceElements(); - if (referenceElements.length > 0) { - final PsiElement target = referenceElements[0].resolve(); - if (target != null) { - conflicts.putValue( - aClass, - IntentionPowerPackLocalize.zeroAlreadyExtends1AndWillNotCompileAfterConverting2ToAClass( - RefactoringUIUtil.getDescription(aClass, true), - RefactoringUIUtil.getDescription(target, true), - RefactoringUIUtil.getDescription(anInterface, false) - ) - ); - } - } + PsiClass anInterface = (PsiClass) element.getParent(); + SearchScope searchScope = anInterface.getUseScope(); + Query query = ClassInheritorsSearch.search(anInterface, searchScope, false); + MultiMap conflicts = new MultiMap<>(); + query.forEach(aClass -> { + PsiReferenceList extendsList = aClass.getExtendsList(); + if (extendsList == null) { return true; } + PsiJavaCodeReferenceElement[] referenceElements = extendsList.getReferenceElements(); + if (referenceElements.length > 0) { + PsiElement target = referenceElements[0].resolve(); + if (target != null) { + conflicts.putValue( + aClass, + IntentionPowerPackLocalize.zeroAlreadyExtends1AndWillNotCompileAfterConverting2ToAClass( + RefactoringUIUtil.getDescription( + aClass, + true + ), + RefactoringUIUtil.getDescription(target, true), + RefactoringUIUtil.getDescription(anInterface, false) + ) + ); + } + } + return true; }); - final boolean conflictsDialogOK; + boolean conflictsDialogOK; if (conflicts.isEmpty()) { conflictsDialogOK = true; } @@ -164,8 +165,9 @@ public boolean process(PsiClass aClass) { } } + @RequiredWriteAction private static void convertInterfaceToClass(PsiClass anInterface) { - final boolean success = moveSubClassImplementsToExtends(anInterface); + boolean success = moveSubClassImplementsToExtends(anInterface); if (!success) { return; } @@ -173,17 +175,18 @@ private static void convertInterfaceToClass(PsiClass anInterface) { moveExtendsToImplements(anInterface); } - @Override @Nonnull + @Override protected PsiElementPredicate getElementPredicate() { return new ConvertInterfaceToClassPredicate(); } + @RequiredWriteAction private static void moveExtendsToImplements(PsiClass anInterface) throws IncorrectOperationException { - final PsiReferenceList extendsList = anInterface.getExtendsList(); - final PsiReferenceList implementsList = anInterface.getImplementsList(); + PsiReferenceList extendsList = anInterface.getExtendsList(); + PsiReferenceList implementsList = anInterface.getImplementsList(); assert extendsList != null; - final PsiJavaCodeReferenceElement[] referenceElements = extendsList.getReferenceElements(); + PsiJavaCodeReferenceElement[] referenceElements = extendsList.getReferenceElements(); for (PsiJavaCodeReferenceElement referenceElement : referenceElements) { assert implementsList != null; implementsList.add(referenceElement); @@ -191,21 +194,22 @@ private static void moveExtendsToImplements(PsiClass anInterface) throws Incorre } } + @RequiredWriteAction private static boolean moveSubClassImplementsToExtends(PsiClass oldInterface) throws IncorrectOperationException { - final Project project = oldInterface.getProject(); - final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); - final PsiElementFactory elementFactory = psiFacade.getElementFactory(); - final PsiJavaCodeReferenceElement oldInterfaceReference = elementFactory.createClassReferenceElement(oldInterface); - final SearchScope searchScope = oldInterface.getUseScope(); - final Query query = ClassInheritorsSearch.search(oldInterface, searchScope, false); - final Collection inheritors = query.findAll(); - final boolean success = CommonRefactoringUtil.checkReadOnlyStatusRecursively(project, inheritors, false); + Project project = oldInterface.getProject(); + JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); + PsiElementFactory elementFactory = psiFacade.getElementFactory(); + PsiJavaCodeReferenceElement oldInterfaceReference = elementFactory.createClassReferenceElement(oldInterface); + SearchScope searchScope = oldInterface.getUseScope(); + Query query = ClassInheritorsSearch.search(oldInterface, searchScope, false); + Collection inheritors = query.findAll(); + boolean success = CommonRefactoringUtil.checkReadOnlyStatusRecursively(project, inheritors, false); if (!success) { return false; } for (PsiClass inheritor : inheritors) { - final PsiReferenceList implementsList = inheritor.getImplementsList(); - final PsiReferenceList extendsList = inheritor.getExtendsList(); + PsiReferenceList implementsList = inheritor.getImplementsList(); + PsiReferenceList extendsList = inheritor.getExtendsList(); if (implementsList != null) { moveReference(implementsList, extendsList, oldInterfaceReference); } @@ -213,14 +217,16 @@ private static boolean moveSubClassImplementsToExtends(PsiClass oldInterface) th return true; } + @RequiredWriteAction private static void moveReference( - @Nonnull PsiReferenceList source, @Nullable PsiReferenceList target, + @Nonnull PsiReferenceList source, + @Nullable PsiReferenceList target, @Nonnull PsiJavaCodeReferenceElement reference ) throws IncorrectOperationException { - final PsiJavaCodeReferenceElement[] implementsReferences = source.getReferenceElements(); - final String qualifiedName = reference.getQualifiedName(); + PsiJavaCodeReferenceElement[] implementsReferences = source.getReferenceElements(); + String qualifiedName = reference.getQualifiedName(); for (PsiJavaCodeReferenceElement implementsReference : implementsReferences) { - final String implementsReferenceQualifiedName = implementsReference.getQualifiedName(); + String implementsReferenceQualifiedName = implementsReference.getQualifiedName(); if (qualifiedName.equals(implementsReferenceQualifiedName)) { if (target != null) { target.add(implementsReference); diff --git a/plugin/src/main/java/com/intellij/java/impl/ipp/modifiers/ModifierIntention.java b/plugin/src/main/java/com/intellij/java/impl/ipp/modifiers/ModifierIntention.java index 7fac0b4b1d..c858ae3587 100644 --- a/plugin/src/main/java/com/intellij/java/impl/ipp/modifiers/ModifierIntention.java +++ b/plugin/src/main/java/com/intellij/java/impl/ipp/modifiers/ModifierIntention.java @@ -22,6 +22,7 @@ import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.search.searches.SuperMethodsSearch; import com.siyeh.localize.IntentionPowerPackLocalize; +import consulo.annotation.access.RequiredReadAction; import consulo.application.WriteAction; import consulo.application.util.query.Query; import consulo.language.editor.intention.LowPriorityAction; @@ -37,6 +38,7 @@ import consulo.language.psi.util.PsiTreeUtil; import consulo.language.util.IncorrectOperationException; import consulo.localize.LocalizeValue; +import consulo.ui.annotation.RequiredUIAccess; import consulo.util.collection.MultiMap; import consulo.util.io.FileUtil; import jakarta.annotation.Nonnull; @@ -46,7 +48,6 @@ * @author Bas Leijdekkers */ abstract class ModifierIntention extends Intention implements LowPriorityAction { - @Nonnull @Override protected final PsiElementPredicate getElementPredicate() { @@ -59,14 +60,15 @@ public boolean startInWriteAction() { } @Override + @RequiredUIAccess protected final void processIntention(@Nonnull PsiElement element) throws IncorrectOperationException { - final PsiMember member = (PsiMember) element.getParent(); - final PsiModifierList modifierList = member.getModifierList(); + PsiMember member = (PsiMember) element.getParent(); + PsiModifierList modifierList = member.getModifierList(); if (modifierList == null) { return; } MultiMap conflicts = checkForConflicts(member); - final boolean conflictsDialogOK; + boolean conflictsDialogOK; if (conflicts.isEmpty()) { conflictsDialogOK = true; } @@ -85,44 +87,43 @@ protected final void processIntention(@Nonnull PsiElement element) throws Incorr } } + @RequiredReadAction private MultiMap checkForConflicts(@Nonnull PsiMember member) { - if (member instanceof PsiClass && getModifier().equals(PsiModifier.PUBLIC)) { - final PsiClass aClass = (PsiClass) member; - final PsiElement parent = aClass.getParent(); - if (!(parent instanceof PsiJavaFile)) { + if (member instanceof PsiClass aClass && getModifier().equals(PsiModifier.PUBLIC)) { + if (!(aClass.getParent() instanceof PsiJavaFile javaFile)) { return MultiMap.empty(); } - final PsiJavaFile javaFile = (PsiJavaFile) parent; - final String name = FileUtil.getNameWithoutExtension(javaFile.getName()); - final String className = aClass.getName(); + String name = FileUtil.getNameWithoutExtension(javaFile.getName()); + String className = aClass.getName(); if (name.equals(className)) { return MultiMap.empty(); } MultiMap conflicts = new MultiMap<>(); conflicts.putValue( aClass, - IntentionPowerPackLocalize.zeroIsDeclaredIn1ButWhenPublicShouldBeDeclaredInAFileNamed2( - RefactoringUIUtil.getDescription(aClass, false), - RefactoringUIUtil.getDescription(javaFile, false), - CommonRefactoringUtil.htmlEmphasize(className + ".java") - ) + IntentionPowerPackLocalize.zeroIsDeclaredIn1ButWhenPublicShouldBeDeclaredInAFileNamed2(RefactoringUIUtil.getDescription( + aClass, + false + ), RefactoringUIUtil.getDescription(javaFile, false), CommonRefactoringUtil.htmlEmphasize(className + ".java")) ); return conflicts; } - final PsiModifierList modifierList = member.getModifierList(); + PsiModifierList modifierList = member.getModifierList(); if (modifierList == null || modifierList.hasModifierProperty(PsiModifier.PRIVATE)) { return MultiMap.empty(); } MultiMap conflicts = new MultiMap<>(); - if (member instanceof PsiMethod) { - final PsiMethod method = (PsiMethod) member; + if (member instanceof PsiMethod method) { SuperMethodsSearch.search(method, method.getContainingClass(), true, false).forEach(methodSignature -> { - final PsiMethod superMethod = methodSignature.getMethod(); + PsiMethod superMethod = methodSignature.getMethod(); if (!hasCompatibleVisibility(superMethod, true)) { conflicts.putValue( superMethod, IntentionPowerPackLocalize.zeroWillHaveIncompatibleAccessPrivilegesWithSuper1( - RefactoringUIUtil.getDescription(method, false), + RefactoringUIUtil.getDescription( + method, + false + ), RefactoringUIUtil.getDescription(superMethod, true) ) ); @@ -142,24 +143,24 @@ private MultiMap checkForConflicts(@Nonnull PsiMember else if (!hasCompatibleVisibility(overridingMethod, false)) { conflicts.putValue( overridingMethod, - IntentionPowerPackLocalize.zeroWillHaveIncompatibleAccessPrivilegesWithOverriding1( - RefactoringUIUtil.getDescription(method, false), - RefactoringUIUtil.getDescription(overridingMethod, true) - ) + IntentionPowerPackLocalize.zeroWillHaveIncompatibleAccessPrivilegesWithOverriding1(RefactoringUIUtil.getDescription( + method, + false + ), RefactoringUIUtil.getDescription(overridingMethod, true)) ); } return false; }); } - final PsiModifierList modifierListCopy = (PsiModifierList) modifierList.copy(); + PsiModifierList modifierListCopy = (PsiModifierList) modifierList.copy(); modifierListCopy.setModifierProperty(getModifier(), true); - final Query search = ReferencesSearch.search(member, member.getResolveScope()); + Query search = ReferencesSearch.search(member, member.getResolveScope()); search.forEach(reference -> { - final PsiElement element = reference.getElement(); + PsiElement element = reference.getElement(); if (JavaResolveUtil.isAccessible(member, member.getContainingClass(), modifierListCopy, element, null, null)) { return true; } - final PsiElement context = PsiTreeUtil.getParentOfType(element, PsiMethod.class, PsiField.class, PsiClass.class, PsiFile.class); + PsiElement context = PsiTreeUtil.getParentOfType(element, PsiMethod.class, PsiField.class, PsiClass.class, PsiFile.class); if (context == null) { return true; } @@ -181,30 +182,24 @@ private boolean hasCompatibleVisibility(PsiMethod method, boolean isSuper) { return false; } else if (getModifier().equals(PsiModifier.PACKAGE_LOCAL)) { - if (isSuper) { - return !(method.hasModifierProperty(PsiModifier.PUBLIC) || method.hasModifierProperty(PsiModifier.PROTECTED)); - } - return true; + return !isSuper || !(method.isPublic() || method.isProtected()); } else if (getModifier().equals(PsiModifier.PROTECTED)) { if (isSuper) { - return !method.hasModifierProperty(PsiModifier.PUBLIC); + return !method.isPublic(); } else { - return method.hasModifierProperty(PsiModifier.PROTECTED) || method.hasModifierProperty(PsiModifier.PUBLIC); + return method.isProtected() || method.isPublic(); } } else if (getModifier().equals(PsiModifier.PUBLIC)) { - if (!isSuper) { - return method.hasModifierProperty(PsiModifier.PUBLIC); - } - return true; + return isSuper || method.isPublic(); } throw new AssertionError(); } private boolean isVisibleFromOverridingMethod(PsiMethod method, PsiMethod overridingMethod) { - final PsiModifierList modifierListCopy = (PsiModifierList) method.getModifierList().copy(); + PsiModifierList modifierListCopy = (PsiModifierList) method.getModifierList().copy(); modifierListCopy.setModifierProperty(getModifier(), true); return JavaResolveUtil.isAccessible(method, method.getContainingClass(), modifierListCopy, overridingMethod, null, null); } diff --git a/plugin/src/main/java/com/intellij/java/impl/refactoring/changeSignature/JavaChangeSignatureUsageProcessor.java b/plugin/src/main/java/com/intellij/java/impl/refactoring/changeSignature/JavaChangeSignatureUsageProcessor.java index 11aba62b49..03b7b386a7 100644 --- a/plugin/src/main/java/com/intellij/java/impl/refactoring/changeSignature/JavaChangeSignatureUsageProcessor.java +++ b/plugin/src/main/java/com/intellij/java/impl/refactoring/changeSignature/JavaChangeSignatureUsageProcessor.java @@ -151,13 +151,12 @@ else if (usage instanceof OverriderUsageInfo info) { if (usage instanceof DefaultConstructorImplicitUsageInfo defConstructorUsage) { PsiMethod constructor = defConstructorUsage.getConstructor(); if (!constructor.isPhysical()) { - boolean toPropagate = javaChangeInfo instanceof JavaChangeInfoImpl changeInfoImpl - && changeInfoImpl.propagateParametersMethods.remove(constructor); PsiClass containingClass = defConstructorUsage.getContainingClass(); constructor = (PsiMethod) containingClass.add(constructor); PsiUtil.setModifierProperty(constructor, VisibilityUtil.getVisibilityModifier(containingClass.getModifierList()), true); - if (toPropagate) { - ((JavaChangeInfoImpl) javaChangeInfo).propagateParametersMethods.add(constructor); + if (javaChangeInfo instanceof JavaChangeInfoImpl changeInfoImpl + && changeInfoImpl.propagateParametersMethods.remove(constructor)) { + changeInfoImpl.propagateParametersMethods.add(constructor); } } addSuperCall(javaChangeInfo, constructor, defConstructorUsage.getBaseConstructor(), usages); @@ -211,6 +210,7 @@ else if (!(usage instanceof OverriderUsageInfo)) { return false; } + @RequiredWriteAction private static void processParameterUsage( PsiReferenceExpression ref, String oldName, @@ -224,7 +224,7 @@ private static void processParameterUsage( } } - @RequiredReadAction + @RequiredWriteAction private static void addDefaultConstructor( JavaChangeInfo changeInfo, PsiClass aClass, @@ -245,7 +245,7 @@ else if (aClass.getParent() instanceof PsiNewExpression newExpr) { } } - @RequiredReadAction + @RequiredWriteAction private static void addSuperCall( JavaChangeInfo changeInfo, PsiMethod constructor, @@ -270,7 +270,7 @@ private static void addSuperCall( processMethodUsage(callExpression.getMethodExpression(), changeInfo, true, false, callee, substitutor, usages); } - @RequiredReadAction + @RequiredWriteAction private static void processMethodUsage( PsiElement ref, JavaChangeInfo changeInfo, @@ -292,11 +292,11 @@ private static void processMethodUsage( PsiExpressionList list = RefactoringUtil.getArgumentListByMethodReference(ref); LOG.assertTrue(list != null); boolean toInsertDefaultValue = needDefaultValue(changeInfo, caller); - if (toInsertDefaultValue && ref instanceof PsiReferenceExpression refExpr) { - PsiExpression qualifierExpression = refExpr.getQualifierExpression(); - if (qualifierExpression instanceof PsiSuperExpression && callerSignatureIsAboutToChangeToo(caller, usages)) { - toInsertDefaultValue = false; - } + if (toInsertDefaultValue + && ref instanceof PsiReferenceExpression refExpr + && refExpr.getQualifierExpression() instanceof PsiSuperExpression + && callerSignatureIsAboutToChangeToo(caller, usages)) { + toInsertDefaultValue = false; } fixActualArgumentsList(list, changeInfo, toInsertDefaultValue, substitutor); @@ -328,6 +328,7 @@ private static PsiClassType[] getCalleeChangedExceptionInfo(PsiMethod callee) { return callee.getThrowsList().getReferencedTypes(); //Callee method's throws list is already modified! } + @RequiredWriteAction private static void fixExceptions(PsiElement ref, PsiClassType[] newExceptions) throws IncorrectOperationException { //methods' throws lists are already modified, may use ExceptionUtil.collectUnhandledExceptions newExceptions = filterCheckedExceptions(newExceptions); @@ -341,14 +342,14 @@ private static void fixExceptions(PsiElement ref, PsiClassType[] newExceptions) for (PsiParameter parameter : catchParameters) { PsiType caughtType = parameter.getType(); - if (!(caughtType instanceof PsiClassType)) { + if (!(caughtType instanceof PsiClassType caughtClassType)) { continue; } - if (ExceptionUtil.isUncheckedExceptionOrSuperclass((PsiClassType) caughtType)) { + if (ExceptionUtil.isUncheckedExceptionOrSuperclass(caughtClassType)) { continue; } - if (!isCatchParameterRedundant((PsiClassType) caughtType, classes)) { + if (!isCatchParameterRedundant(caughtClassType, classes)) { continue; } parameter.getParent().delete(); //delete catch section @@ -379,7 +380,7 @@ private static void fixExceptions(PsiElement ref, PsiClassType[] newExceptions) } private static PsiClassType[] filterCheckedExceptions(PsiClassType[] exceptions) { - List result = new ArrayList(); + List result = new ArrayList<>(); for (PsiClassType exceptionType : exceptions) { if (!ExceptionUtil.isUncheckedException(exceptionType)) { result.add(exceptionType); @@ -388,6 +389,7 @@ private static PsiClassType[] filterCheckedExceptions(PsiClassType[] exceptions) return result.toArray(new PsiClassType[result.size()]); } + @RequiredWriteAction private static void adjustPossibleEmptyTryStatement(PsiTryStatement tryStatement) throws IncorrectOperationException { PsiCodeBlock tryBlock = tryStatement.getTryBlock(); if (tryBlock != null) { @@ -433,7 +435,7 @@ private static boolean isCatchParameterRedundant(PsiClassType catchParamType, Co } //This methods works equally well for primary usages as well as for propagated callers' usages - @RequiredUIAccess + @RequiredWriteAction private static void fixActualArgumentsList( PsiExpressionList list, JavaChangeInfo changeInfo, @@ -557,7 +559,7 @@ private static int getNonVarargCount(JavaChangeInfo changeInfo, PsiExpression[] @Nullable - @RequiredReadAction + @RequiredWriteAction private static PsiExpression createActualArgument( JavaChangeInfo changeInfo, PsiExpressionList list, @@ -579,7 +581,7 @@ else if (toInsertDefaultValue) { } @Nullable - @RequiredReadAction + @RequiredWriteAction private static PsiExpression createDefaultValue( JavaChangeInfo changeInfo, PsiElementFactory factory, @@ -714,6 +716,7 @@ public boolean setupDefaultValues(ChangeInfo changeInfo, SimpleReference snapshots, @Nonnull ResolveSnapshotProvider resolveSnapshotProvider, @@ -732,6 +735,7 @@ private static boolean needDefaultValue(ChangeInfo changeInfo, PsiMethod method) return !(changeInfo instanceof JavaChangeInfoImpl changeInfoImpl && changeInfoImpl.propagateParametersMethods.contains(method)); } + @RequiredWriteAction public static void generateDelegate(JavaChangeInfo changeInfo) throws IncorrectOperationException { PsiMethod delegate = generateDelegatePrototype(changeInfo); PsiClass targetClass = changeInfo.getMethod().getContainingClass(); @@ -739,6 +743,7 @@ public static void generateDelegate(JavaChangeInfo changeInfo) throws IncorrectO targetClass.addBefore(delegate, changeInfo.getMethod()); } + @RequiredWriteAction public static PsiMethod generateDelegatePrototype(JavaChangeInfo changeInfo) { PsiMethod delegate = (PsiMethod) changeInfo.getMethod().copy(); PsiClass targetClass = changeInfo.getMethod().getContainingClass(); @@ -875,6 +880,7 @@ private static PsiClassType[] getPrimaryChangedExceptionInfo(JavaChangeInfo chan return newExceptions; } + @RequiredWriteAction private static void processCallerMethod( JavaChangeInfo changeInfo, PsiMethod caller, @@ -891,11 +897,10 @@ private static void processCallerMethod( baseMethod == null ? PsiSubstitutor.EMPTY : ChangeSignatureProcessor.calculateSubstitutor(caller, baseMethod); PsiClass aClass = changeInfo.getMethod().getContainingClass(); PsiClass callerContainingClass = caller.getContainingClass(); - PsiSubstitutor psiSubstitutor = aClass != null && callerContainingClass != null && callerContainingClass.isInheritor( - aClass, - true - ) ? TypeConversionUtil.getSuperClassSubstitutor - (aClass, callerContainingClass, substitutor) : PsiSubstitutor.EMPTY; + PsiSubstitutor psiSubstitutor = + aClass != null && callerContainingClass != null && callerContainingClass.isInheritor(aClass, true) + ? TypeConversionUtil.getSuperClassSubstitutor(aClass, callerContainingClass, substitutor) + : PsiSubstitutor.EMPTY; for (JavaParameterInfo info : primaryNewParams) { if (info.getOldIndex() < 0) { newParameters.add(createNewParameter(changeInfo, info, psiSubstitutor, substitutor)); @@ -927,7 +932,7 @@ private static void processCallerMethod( } } - @RequiredReadAction + @RequiredWriteAction private static void fixPrimaryThrowsLists(PsiMethod method, PsiClassType[] newExceptions) throws IncorrectOperationException { PsiElementFactory elementFactory = JavaPsiFacade.getInstance(method.getProject()).getElementFactory(); PsiJavaCodeReferenceElement[] refs = new PsiJavaCodeReferenceElement[newExceptions.length]; @@ -988,13 +993,13 @@ private static PsiParameter createNewParameter( return factory.createParameter(newParam.getName(), type); } + @RequiredWriteAction private static void resolveParameterVsFieldsConflicts( PsiParameter[] newParams, PsiMethod method, PsiParameterList list, boolean[] toRemoveParam - ) throws - IncorrectOperationException { + ) throws IncorrectOperationException { List conflictResolvers = new ArrayList<>(); for (PsiParameter parameter : newParams) { conflictResolvers.add(new FieldConflictsResolver(parameter.getName(), method.getBody())); @@ -1076,10 +1081,12 @@ else if (element instanceof PsiMethodReferenceExpression) { } private boolean needToChangeCalls() { - return myChangeInfo.isNameChanged() || myChangeInfo.isParameterSetOrOrderChanged() || myChangeInfo.isExceptionSetOrOrderChanged(); + return myChangeInfo.isNameChanged() + || myChangeInfo.isParameterSetOrOrderChanged() + || myChangeInfo.isExceptionSetOrOrderChanged(); } - + @RequiredReadAction private void addInaccessibilityDescriptions( Set usages, MultiMap conflictDescriptions @@ -1091,23 +1098,21 @@ private void addInaccessibilityDescriptions( for (Iterator iterator = usages.iterator(); iterator.hasNext(); ) { UsageInfo usageInfo = iterator.next(); PsiElement element = usageInfo.getElement(); - if (element != null) { - if (element instanceof PsiQualifiedReference qualifiedRef) { - PsiClass accessObjectClass = qualifiedRef.getQualifier() instanceof PsiExpression expression - ? (PsiClass) PsiUtil.getAccessObjectClass(expression).getElement() - : null; - - if (!JavaPsiFacade.getInstance(element.getProject()).getResolveHelper() - .isAccessible(method, modifierList, element, accessObjectClass, null)) { - LocalizeValue message = RefactoringLocalize.zeroWith1VisibilityIsNotAccessibleFrom2( - RefactoringUIUtil.getDescription(method, true), - VisibilityUtil.toPresentableText(myChangeInfo.getNewVisibility()), - RefactoringUIUtil.getDescription(ConflictsUtil.getContainer(element), true) - ); - conflictDescriptions.putValue(method, message); - if (!needToChangeCalls()) { - iterator.remove(); - } + if (element instanceof PsiQualifiedReference qualifiedRef) { + PsiClass accessObjectClass = qualifiedRef.getQualifier() instanceof PsiExpression expression + ? (PsiClass) PsiUtil.getAccessObjectClass(expression).getElement() + : null; + + if (!JavaPsiFacade.getInstance(element.getProject()).getResolveHelper() + .isAccessible(method, modifierList, element, accessObjectClass, null)) { + LocalizeValue message = RefactoringLocalize.zeroWith1VisibilityIsNotAccessibleFrom2( + RefactoringUIUtil.getDescription(method, true), + VisibilityUtil.toPresentableText(myChangeInfo.getNewVisibility()), + RefactoringUIUtil.getDescription(ConflictsUtil.getContainer(element), true) + ); + conflictDescriptions.putValue(method, message); + if (!needToChangeCalls()) { + iterator.remove(); } } }