From cf4c7df05a9e54cb4922a6f098b860a1efbb8ebe Mon Sep 17 00:00:00 2001 From: UNV Date: Wed, 21 May 2025 18:28:50 +0300 Subject: [PATCH 1/3] Reformatting CommentTracker and AddNullableNotNullAnnotationFix. --- .../impl/AddNullableNotNullAnnotationFix.java | 53 +- .../com/siyeh/ig/psiutils/CommentTracker.java | 1164 +++++++++-------- 2 files changed, 614 insertions(+), 603 deletions(-) diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/impl/AddNullableNotNullAnnotationFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/impl/AddNullableNotNullAnnotationFix.java index 238640c72c..ad68e4e486 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/impl/AddNullableNotNullAnnotationFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/impl/AddNullableNotNullAnnotationFix.java @@ -35,31 +35,34 @@ import com.intellij.java.language.psi.PsiPrimitiveType; import com.intellij.java.language.psi.PsiType; -public class AddNullableNotNullAnnotationFix extends AddAnnotationPsiFix -{ - public AddNullableNotNullAnnotationFix(@Nonnull String fqn, @Nonnull PsiModifierListOwner owner, @Nonnull String... annotationToRemove) - { - super(fqn, owner, PsiNameValuePair.EMPTY_ARRAY, annotationToRemove); - } +public class AddNullableNotNullAnnotationFix extends AddAnnotationPsiFix { + public AddNullableNotNullAnnotationFix( + @Nonnull String fqn, + @Nonnull PsiModifierListOwner owner, + @Nonnull String... annotationToRemove + ) { + super(fqn, owner, PsiNameValuePair.EMPTY_ARRAY, annotationToRemove); + } - @Override - public boolean isAvailable(@Nonnull Project project, @Nonnull PsiFile file, @Nonnull PsiElement startElement, @Nonnull PsiElement endElement) - { - if(!super.isAvailable(project, file, startElement, endElement)) - { - return false; - } - PsiModifierListOwner owner = getContainer(file, startElement.getTextRange().getStartOffset()); - if(owner == null || AnnotationUtil.isAnnotated(owner, getAnnotationsToRemove()[0], false, false)) - { - return false; - } - if(owner instanceof PsiMethod) - { - PsiType returnType = ((PsiMethod) owner).getReturnType(); + @Override + public boolean isAvailable( + @Nonnull Project project, + @Nonnull PsiFile file, + @Nonnull PsiElement startElement, + @Nonnull PsiElement endElement + ) { + if (!super.isAvailable(project, file, startElement, endElement)) { + return false; + } + PsiModifierListOwner owner = getContainer(file, startElement.getTextRange().getStartOffset()); + if (owner == null || AnnotationUtil.isAnnotated(owner, getAnnotationsToRemove()[0], false, false)) { + return false; + } + if (owner instanceof PsiMethod) { + PsiType returnType = ((PsiMethod)owner).getReturnType(); - return returnType != null && !(returnType instanceof PsiPrimitiveType); - } - return true; - } + return returnType != null && !(returnType instanceof PsiPrimitiveType); + } + return true; + } } diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/CommentTracker.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/CommentTracker.java index d05e270c8a..6e1b20ea89 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/CommentTracker.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/CommentTracker.java @@ -30,582 +30,590 @@ * @author Tagir Valeev */ public final class CommentTracker { - private final Set ignoredParents = new HashSet<>(); - private List comments = new ArrayList<>(); - private PsiElement lastTextWithCommentsElement = null; - - /** - * Marks the element as unchanged and returns its text. The unchanged elements are assumed to be preserved - * in the resulting code as is, so the comments from them will not be extracted. - * - * @param element element to return the text - * @return a text to be inserted into refactored code - */ - public - @Nonnull - String text(@Nonnull PsiElement element) { - checkState(); - addIgnored(element); - return element.getText(); - } - - /** - * Marks the expression as unchanged and returns its text, adding parentheses if necessary. - * The unchanged elements are assumed to be preserved in the resulting code as is, - * so the comments from them will not be extracted. - * - * @param element expression to return the text - * @param precedence precedence of surrounding operation - * @return a text to be inserted into refactored code - * @see ParenthesesUtils#getText(PsiExpression, int) - */ - public - @Nonnull - String text(@Nonnull PsiExpression element, int precedence) { - checkState(); - addIgnored(element); - return ParenthesesUtils.getText(element, precedence + 1); - } - - /** - * Marks the expression as unchanged and returns a single-parameter lambda text which parameter - * is the name of supplied variable and body is the supplied expression - * - * @param variable a variable to use as lambda parameter - * @param expression an expression to use as lambda body - * @return a string representation of lambda - */ - public - @Nonnull - String lambdaText(@Nonnull PsiVariable variable, @Nonnull PsiExpression expression) { - return variable.getName() + " -> " + text(expression); - } - - /** - * Marks the element as unchanged and returns it. The unchanged elements are assumed to be preserved - * in the resulting code as is, so the comments from them will not be extracted. - * - * @param element element to mark - * @param the type of the element - * @return the passed argument - */ - @Contract("_ -> param1") - public T markUnchanged(@Nullable T element) { - checkState(); - if (element != null) - addIgnored(element); - return element; - } - - /** - * Marks the range of elements as unchanged and returns their text. The unchanged elements are assumed to be preserved - * in the resulting code as is, so the comments from them will not be extracted. - * - * @param firstElement first element to mark - * @param lastElement last element to mark (must be equal to firstElement or its sibling) - * @return a text to be inserted into refactored code - * @throws IllegalArgumentException if firstElement and lastElements are not siblings or firstElement goes after last element - */ - public String rangeText(@Nonnull PsiElement firstElement, @Nonnull PsiElement lastElement) { - checkState(); - PsiElement e; - StringBuilder result = new StringBuilder(); - for (e = firstElement; e != null && e != lastElement; e = e.getNextSibling()) { - addIgnored(e); - result.append(e.getText()); - } - if (e == null) { - throw new IllegalArgumentException("Elements must be siblings: " + firstElement + " and " + lastElement); - } - addIgnored(lastElement); - result.append(lastElement.getText()); - return result.toString(); - } - - /** - * Marks the range of elements as unchanged. The unchanged elements are assumed to be preserved - * in the resulting code as is, so the comments from them will not be extracted. - * - * @param firstElement first element to mark - * @param lastElement last element to mark (must be equal to firstElement or its sibling) - * @throws IllegalArgumentException if firstElement and lastElements are not siblings or firstElement goes after last element - */ - public void markRangeUnchanged(@Nonnull PsiElement firstElement, @Nonnull PsiElement lastElement) { - checkState(); - PsiElement e; - for (e = firstElement; e != null && e != lastElement; e = e.getNextSibling()) { - addIgnored(e); - } - if (e == null) { - throw new IllegalArgumentException("Elements must be siblings: " + firstElement + " and " + lastElement); - } - addIgnored(lastElement); - } - - /** - * Returns the comments which are located between the supplied element - * and the previous element passed into {@link #textWithComments(PsiElement)} or {@link #commentsBefore(PsiElement)}. - * The used comments are deleted from the original document. - * - *

This method can be used if several parts of original code are reused in the generated replacement. - * - * @param element an element grab the comments before it - * @return the string containing the element text and possibly some comments. - */ - public String commentsBefore(@Nonnull PsiElement element) { - List comments = grabCommentsBefore(element); - if (comments.isEmpty()) - return ""; - StringBuilder sb = new StringBuilder(); - for (PsiElement comment : comments) { - PsiElement prev = comment.getPrevSibling(); - if (sb.length() == 0 && prev instanceof PsiWhiteSpace) { - sb.append(prev.getText()); - } - sb.append(comment.getText()); - PsiElement next = PsiTreeUtil.nextLeaf(comment); - if (next instanceof PsiWhiteSpace) { - sb.append(next.getText()); - } - } - comments.forEach(PsiElement::delete); - return sb.toString(); - } - - private List grabCommentsBefore(@Nonnull PsiElement element) { - if (lastTextWithCommentsElement == null) { - lastTextWithCommentsElement = element; - return Collections.emptyList(); - } - List result = new SmartList<>(); - int start = lastTextWithCommentsElement.getTextRange().getEndOffset(); - int end = element.getTextRange().getStartOffset(); - PsiElement parent = PsiTreeUtil.findCommonParent(lastTextWithCommentsElement, element); - if (parent != null && start < end) { - PsiTreeUtil.processElements(parent, e -> { - if (e instanceof PsiComment) { - TextRange range = e.getTextRange(); - if (range.getStartOffset() >= start && range.getEndOffset() <= end && !shouldIgnore((PsiComment) e)) { - result.add(e); - } - } - return true; - }); - } - - lastTextWithCommentsElement = element; - return result; - } - - /** - * Returns an element text, possibly prepended with comments which are located between the supplied element - * and the previous element passed into {@link #textWithComments(PsiElement)} or {@link #commentsBefore(PsiElement)}. - * The used comments are deleted from the original document. - * - *

Note that if PsiExpression was passed, the resulting text may not parse as an PsiExpression, - * because PsiExpression cannot start with comment. - * - *

This method can be used if several parts of original code are reused in the generated replacement. - * - * @param element an element to convert to the text - * @return the string containing the element text and possibly some comments. - */ - public String textWithComments(@Nonnull PsiElement element) { - return commentsBefore(element) + element.getText(); - } - - /** - * Returns an element text, adding parentheses if necessary, possibly prepended with comments which are - * located between the supplied element and the previous element passed into - * {@link #textWithComments(PsiElement)} or {@link #commentsBefore(PsiElement)}. - * The used comments are deleted from the original document. - * - *

Note that if PsiExpression was passed, the resulting text may not parse as an PsiExpression, - * because PsiExpression cannot start with comment. - * - *

This method can be used if several parts of original code are reused in the generated replacement. - * - * @param expression an expression to convert to the text - * @param precedence precedence of surrounding operation - * @return the string containing the element text and possibly some comments. - */ - public String textWithComments(@Nonnull PsiExpression expression, int precedence) { - return commentsBefore(expression) + ParenthesesUtils.getText(expression, precedence + 1); - } - - /** - * Deletes given PsiElement collecting all the comments inside it. - * - * @param element element to delete - */ - public void delete(@Nonnull PsiElement element) { - grabCommentsOnDelete(element); - element.delete(); - } - - /** - * Deletes all given PsiElement's collecting all the comments inside them. - * - * @param elements elements to delete (all not null) - */ - public void delete(@Nonnull PsiElement... elements) { - for (PsiElement element : elements) { - delete(element); - } - } - - /** - * Deletes given PsiElement replacing it with the comments including comments inside the deleted element - * and previously gathered comments. - * - *

After calling this method the tracker cannot be used anymore.

- * - * @param element element to delete - */ - public void deleteAndRestoreComments(@Nonnull PsiElement element) { - grabCommentsOnDelete(element); - PsiElement anchor = element; - while (anchor.getParent() != null && !(anchor.getParent() instanceof PsiFile) && anchor.getParent().getFirstChild() == anchor) { - anchor = anchor.getParent(); - } - insertCommentsBefore(anchor); - element.delete(); - } - - /** - * Replaces given PsiElement collecting all the comments inside it. - * - * @param element element to replace - * @param replacement replacement element. It's also marked as unchanged (see {@link #markUnchanged(PsiElement)}) - * @return the element which was actually inserted in the tree (either {@code replacement} or its copy) - */ - public - @Nonnull - PsiElement replace(@Nonnull PsiElement element, @Nonnull PsiElement replacement) { - markUnchanged(replacement); - grabComments(element); - return element.replace(replacement); - } - - /** - * Creates a replacement element from the text and replaces given element, - * collecting all the comments inside it. - * - *

- * The type of the created replacement will mimic the type of supplied element. - * Supported element types are: {@link PsiExpression}, {@link PsiStatement}, - * {@link PsiTypeElement}, {@link PsiIdentifier}, {@link PsiComment}. - *

- * - * @param element element to replace - * @param text replacement text - * @return the element which was actually inserted in the tree - */ - public - @Nonnull - PsiElement replace(@Nonnull PsiElement element, @Nonnull String text) { - PsiElement replacement = createElement(element, text); - return replace(element, replacement); - } - - /** - * Replaces given PsiElement collecting all the comments inside it and restores comments putting them - * to the appropriate place before replaced element. - * - *

After calling this method the tracker cannot be used anymore.

- * - * @param element element to replace - * @param replacement replacement element. It's also marked as unchanged (see {@link #markUnchanged(PsiElement)}) - * @return the element which was actually inserted in the tree (either {@code replacement} or its copy) - */ - public - @Nonnull - PsiElement replaceAndRestoreComments(@Nonnull PsiElement element, @Nonnull PsiElement replacement) { - List suffix = grabSuffixComments(element); - PsiElement result = replace(element, replacement); - PsiElement anchor = PsiTreeUtil - .getNonStrictParentOfType(result, PsiStatement.class, PsiLambdaExpression.class, PsiVariable.class, PsiNameValuePair.class); - if (anchor instanceof PsiLambdaExpression && anchor != result) { - anchor = ((PsiLambdaExpression) anchor).getBody(); - } - if (anchor instanceof PsiVariable && anchor.getParent() instanceof PsiDeclarationStatement) { - anchor = anchor.getParent(); - } - if (anchor instanceof PsiStatement && (anchor.getParent() instanceof PsiIfStatement || anchor.getParent() instanceof PsiLoopStatement)) { - anchor = anchor.getParent(); - } - if (anchor == null) - anchor = result; - restoreSuffixComments(result, suffix); - insertCommentsBefore(anchor); - return result; - } - - /** - * Replaces the specified expression and restores any comments to their appropriate place before and/or after the expression. - * Meant to be used with {@link #commentsBefore(PsiElement)} and {@link #commentsBetween(PsiElement, PsiElement)} - * - * @param expression the expression to replace - * @param replacementText text of the replacement expression - * @return the element which was inserted in the tree - */ - public - @Nonnull - PsiElement replaceExpressionAndRestoreComments(@Nonnull PsiExpression expression, @Nonnull String replacementText) { - return replaceExpressionAndRestoreComments(expression, replacementText, Collections.emptyList()); - } - - public - @Nonnull - PsiElement replaceExpressionAndRestoreComments(@Nonnull PsiExpression expression, @Nonnull String replacementText, - List toDelete) { - List trailingComments = new SmartList<>(); - List comments = grabCommentsBefore(PsiTreeUtil.lastChild(expression)); - if (!comments.isEmpty()) { - PsiParserFacade parser = PsiParserFacade.SERVICE.getInstance(expression.getProject()); - for (PsiElement comment : comments) { - PsiElement prev = comment.getPrevSibling(); - if (prev instanceof PsiWhiteSpace) { - String text = prev.getText(); - if (!text.contains("\n")) - trailingComments.add(parser.createWhiteSpaceFromText(" ")); - else if (text.endsWith("\n")) - trailingComments.add(parser.createWhiteSpaceFromText("\n")); // comment at first column - else - trailingComments.add(parser.createWhiteSpaceFromText("\n ")); // newline followed by space will cause formatter to indent - } - ignoredParents.add(comment); - trailingComments.add(comment.copy()); - } - Collections.reverse(trailingComments); - } - PsiElement replacement = replace(expression, replacementText); - for (PsiElement element : trailingComments) { - replacement.getParent().addAfter(element, replacement); - } - toDelete.forEach(this::delete); - insertCommentsBefore(replacement); - return replacement; - } - - @Nonnull - private List grabSuffixComments(@Nonnull PsiElement element) { - if (!(element instanceof PsiStatement)) { - return Collections.emptyList(); - } - List suffix = new ArrayList<>(); - PsiElement lastChild = element.getLastChild(); - boolean hasComment = false; - while (lastChild instanceof PsiComment || lastChild instanceof PsiWhiteSpace) { - hasComment |= lastChild instanceof PsiComment; - if (!(lastChild instanceof PsiComment) || !(shouldIgnore((PsiComment) lastChild))) { - suffix.add(markUnchanged(lastChild).copy()); - } - lastChild = lastChild.getPrevSibling(); - } - return hasComment ? suffix : Collections.emptyList(); - } - - private static void restoreSuffixComments(PsiElement target, List suffix) { - if (!suffix.isEmpty()) { - PsiElement lastChild = target.getLastChild(); - if (lastChild instanceof PsiComment && JavaTokenType.END_OF_LINE_COMMENT.equals(((PsiComment) lastChild).getTokenType())) { - PsiElement nextSibling = target.getNextSibling(); - if (nextSibling instanceof PsiWhiteSpace) { - target.add(nextSibling); - } else { - target.add(PsiParserFacade.SERVICE.getInstance(target.getProject()).createWhiteSpaceFromText("\n")); - } - } - StreamEx.ofReversed(suffix).forEach(target::add); - } - } - - /** - * Creates a replacement element from the text and replaces given element, - * collecting all the comments inside it and restores comments putting them - * to the appropriate place before replaced element. - * - *

After calling this method the tracker cannot be used anymore.

- * - *

- * The type of the created replacement will mimic the type of supplied element. - * Supported element types are: {@link PsiExpression}, {@link PsiStatement}, - * {@link PsiTypeElement}, {@link PsiIdentifier}, {@link PsiComment}. - *

- * - * @param element element to replace - * @param text replacement text - * @return the element which was actually inserted in the tree - */ - public - @Nonnull - PsiElement replaceAndRestoreComments(@Nonnull PsiElement element, @Nonnull String text) { - PsiElement replacement = createElement(element, text); - return replaceAndRestoreComments(element, replacement); - } - - private static - @Nonnull - PsiElement createElement(@Nonnull PsiElement element, @Nonnull String text) { - PsiElementFactory factory = JavaPsiFacade.getElementFactory(element.getProject()); - if (element instanceof PsiExpression) { - return factory.createExpressionFromText(text, element); - } else if (element instanceof PsiStatement) { - return factory.createStatementFromText(text, element); - } else if (element instanceof PsiTypeElement) { - return factory.createTypeElementFromText(text, element); - } else if (element instanceof PsiIdentifier) { - return factory.createIdentifier(text); - } else if (element instanceof PsiComment) { - return factory.createCommentFromText(text, element); - } else { - throw new IllegalArgumentException("Unsupported element type: " + element); - } - } - - /** - * Inserts gathered comments just before given anchor element - * - *

After calling this method the tracker cannot be used anymore.

- * - * @param anchor - */ - public void insertCommentsBefore(@Nonnull PsiElement anchor) { - checkState(); - if (!comments.isEmpty()) { - PsiElement parent = anchor.getParent(); - PsiElementFactory factory = JavaPsiFacade.getElementFactory(anchor.getProject()); - for (PsiComment comment : comments) { - if (shouldIgnore(comment)) - continue; - PsiElement added = parent.addBefore(factory.createCommentFromText(comment.getText(), anchor), anchor); - PsiElement prevSibling = added.getPrevSibling(); - if (prevSibling instanceof PsiWhiteSpace) { - PsiElement prev = anchor.getPrevSibling(); - ASTNode whiteSpaceBefore = normalizeWhiteSpace((PsiWhiteSpace) prevSibling, prev); - parent.getNode().addChild(whiteSpaceBefore, anchor.getNode()); - if (prev instanceof PsiWhiteSpace) { - prev.delete(); - } - } - } - } - comments = null; - } - - private static - @Nonnull - ASTNode normalizeWhiteSpace(PsiWhiteSpace whiteSpace, PsiElement nextElement) { - String text = whiteSpace.getText(); - int endLPos = text.lastIndexOf('\n'); - if (text.lastIndexOf('\n', endLPos - 1) >= 0) { - // has at least two line breaks - return ASTFactory.whitespace(text.substring(endLPos)); - } - if (nextElement instanceof PsiWhiteSpace && nextElement.getText().contains("\n") && !text.contains("\n")) { - text = '\n' + text; - } - return ASTFactory.whitespace(text); - } - - private boolean shouldIgnore(PsiComment comment) { - return ignoredParents.stream().anyMatch(p -> PsiTreeUtil.isAncestor(p, comment, false)); - } - - private void grabCommentsOnDelete(PsiElement element) { - if (element instanceof PsiExpression && element.getParent() instanceof PsiExpressionStatement || - (element.getParent() instanceof PsiDeclarationStatement && - ((PsiDeclarationStatement) element.getParent()).getDeclaredElements().length == 1)) { - element = element.getParent(); - } else if (element.getParent() instanceof PsiJavaCodeReferenceElement) { - PsiElement parent = element.getParent(); - if (element instanceof PsiJavaCodeReferenceElement && ((PsiJavaCodeReferenceElement) parent).getQualifier() == element) { - ASTNode dot = ((CompositeElement) parent).findChildByRole(ChildRole.DOT); - if (dot != null) { - PsiElement nextSibling = dot.getPsi().getNextSibling(); - if (nextSibling != null && nextSibling.getTextLength() == 0) { - nextSibling = skipWhitespacesAndCommentsForward(nextSibling); - } - while (nextSibling != null) { - nextSibling = markUnchanged(nextSibling).getNextSibling(); - } - } - } - element = parent; - } - grabComments(element); - } - - @SuppressWarnings("unchecked") - private static final Class[] - WS_COMMENTS = new Class[]{ - PsiWhiteSpace.class, - PsiComment.class - }; - - @Nullable - @Contract("null -> null") - @Deprecated - public static PsiElement skipWhitespacesAndCommentsForward(@Nullable PsiElement element) { - return PsiTreeUtil.skipSiblingsForward(element, WS_COMMENTS); - } - - /** - * Grab the comments from given element which should be restored. Normally you don't need to call this method. - * It should be called only if element is about to be deleted by other code which is not CommentTracker-aware. - * - *

Calling this method repeatedly has no effect. It's also safe to call this method, then delete element using - * other methods from this class like {@link #delete(PsiElement)}. - * - * @param element element to grab the comments from. - */ - public void grabComments(PsiElement element) { - checkState(); - for (PsiComment comment : PsiTreeUtil.collectElementsOfType(element, PsiComment.class)) { - if (!shouldIgnore(comment)) { - comments.add(comment); - } - } - } - - private void checkState() { - if (comments == null) { - throw new IllegalStateException(getClass().getSimpleName() + " has been already used"); - } - } - - private void addIgnored(PsiElement element) { - if (!(element instanceof LeafPsiElement) || element instanceof PsiComment) { - ignoredParents.add(element); - } - } - - public static String textWithSurroundingComments(PsiElement element) { - Predicate commentOrWhiteSpace = e -> e instanceof PsiComment || e instanceof PsiWhiteSpace; - List prev = StreamEx.iterate(element.getPrevSibling(), commentOrWhiteSpace, PsiElement::getPrevSibling).toList(); - List next = StreamEx.iterate(element.getNextSibling(), commentOrWhiteSpace, PsiElement::getNextSibling).toList(); - if (StreamEx.of(prev, next).flatCollection(Function.identity()).anyMatch(PsiComment.class::isInstance)) { - return StreamEx.ofReversed(prev).append(element).append(next).map(PsiElement::getText).joining(); - } - return element.getText(); - } - - /** - * Returns a string containing all the comments (possibly with some white-spaces) between given elements - * (not including given elements themselves). This method also deletes all the comments actually used - * in the returned string. - * - * @param start start element - * @param end end element, must strictly follow the start element and be located in the same file - * (though possibly on another hierarchy level) - * @return a string containing all the comments between start and end. - */ - public static - @Nonnull - String commentsBetween(@Nonnull PsiElement start, @Nonnull PsiElement end) { - CommentTracker ct = new CommentTracker(); - ct.lastTextWithCommentsElement = start; - return ct.commentsBefore(end); - } + private final Set ignoredParents = new HashSet<>(); + private List comments = new ArrayList<>(); + private PsiElement lastTextWithCommentsElement = null; + + /** + * Marks the element as unchanged and returns its text. The unchanged elements are assumed to be preserved + * in the resulting code as is, so the comments from them will not be extracted. + * + * @param element element to return the text + * @return a text to be inserted into refactored code + */ + @Nonnull + public String text(@Nonnull PsiElement element) { + checkState(); + addIgnored(element); + return element.getText(); + } + + /** + * Marks the expression as unchanged and returns its text, adding parentheses if necessary. + * The unchanged elements are assumed to be preserved in the resulting code as is, + * so the comments from them will not be extracted. + * + * @param element expression to return the text + * @param precedence precedence of surrounding operation + * @return a text to be inserted into refactored code + * @see ParenthesesUtils#getText(PsiExpression, int) + */ + @Nonnull + public String text(@Nonnull PsiExpression element, int precedence) { + checkState(); + addIgnored(element); + return ParenthesesUtils.getText(element, precedence + 1); + } + + /** + * Marks the expression as unchanged and returns a single-parameter lambda text which parameter + * is the name of supplied variable and body is the supplied expression + * + * @param variable a variable to use as lambda parameter + * @param expression an expression to use as lambda body + * @return a string representation of lambda + */ + @Nonnull + public String lambdaText(@Nonnull PsiVariable variable, @Nonnull PsiExpression expression) { + return variable.getName() + " -> " + text(expression); + } + + /** + * Marks the element as unchanged and returns it. The unchanged elements are assumed to be preserved + * in the resulting code as is, so the comments from them will not be extracted. + * + * @param element element to mark + * @param the type of the element + * @return the passed argument + */ + @Contract("_ -> param1") + public T markUnchanged(@Nullable T element) { + checkState(); + if (element != null) { + addIgnored(element); + } + return element; + } + + /** + * Marks the range of elements as unchanged and returns their text. The unchanged elements are assumed to be preserved + * in the resulting code as is, so the comments from them will not be extracted. + * + * @param firstElement first element to mark + * @param lastElement last element to mark (must be equal to firstElement or its sibling) + * @return a text to be inserted into refactored code + * @throws IllegalArgumentException if firstElement and lastElements are not siblings or firstElement goes after last element + */ + public String rangeText(@Nonnull PsiElement firstElement, @Nonnull PsiElement lastElement) { + checkState(); + PsiElement e; + StringBuilder result = new StringBuilder(); + for (e = firstElement; e != null && e != lastElement; e = e.getNextSibling()) { + addIgnored(e); + result.append(e.getText()); + } + if (e == null) { + throw new IllegalArgumentException("Elements must be siblings: " + firstElement + " and " + lastElement); + } + addIgnored(lastElement); + result.append(lastElement.getText()); + return result.toString(); + } + + /** + * Marks the range of elements as unchanged. The unchanged elements are assumed to be preserved + * in the resulting code as is, so the comments from them will not be extracted. + * + * @param firstElement first element to mark + * @param lastElement last element to mark (must be equal to firstElement or its sibling) + * @throws IllegalArgumentException if firstElement and lastElements are not siblings or firstElement goes after last element + */ + public void markRangeUnchanged(@Nonnull PsiElement firstElement, @Nonnull PsiElement lastElement) { + checkState(); + PsiElement e; + for (e = firstElement; e != null && e != lastElement; e = e.getNextSibling()) { + addIgnored(e); + } + if (e == null) { + throw new IllegalArgumentException("Elements must be siblings: " + firstElement + " and " + lastElement); + } + addIgnored(lastElement); + } + + /** + * Returns the comments which are located between the supplied element + * and the previous element passed into {@link #textWithComments(PsiElement)} or {@link #commentsBefore(PsiElement)}. + * The used comments are deleted from the original document. + * + *

This method can be used if several parts of original code are reused in the generated replacement. + * + * @param element an element grab the comments before it + * @return the string containing the element text and possibly some comments. + */ + public String commentsBefore(@Nonnull PsiElement element) { + List comments = grabCommentsBefore(element); + if (comments.isEmpty()) { + return ""; + } + StringBuilder sb = new StringBuilder(); + for (PsiElement comment : comments) { + PsiElement prev = comment.getPrevSibling(); + if (sb.length() == 0 && prev instanceof PsiWhiteSpace) { + sb.append(prev.getText()); + } + sb.append(comment.getText()); + PsiElement next = PsiTreeUtil.nextLeaf(comment); + if (next instanceof PsiWhiteSpace) { + sb.append(next.getText()); + } + } + comments.forEach(PsiElement::delete); + return sb.toString(); + } + + private List grabCommentsBefore(@Nonnull PsiElement element) { + if (lastTextWithCommentsElement == null) { + lastTextWithCommentsElement = element; + return Collections.emptyList(); + } + List result = new SmartList<>(); + int start = lastTextWithCommentsElement.getTextRange().getEndOffset(); + int end = element.getTextRange().getStartOffset(); + PsiElement parent = PsiTreeUtil.findCommonParent(lastTextWithCommentsElement, element); + if (parent != null && start < end) { + PsiTreeUtil.processElements( + parent, + e -> { + if (e instanceof PsiComment) { + TextRange range = e.getTextRange(); + if (range.getStartOffset() >= start && range.getEndOffset() <= end && !shouldIgnore((PsiComment)e)) { + result.add(e); + } + } + return true; + } + ); + } + + lastTextWithCommentsElement = element; + return result; + } + + /** + * Returns an element text, possibly prepended with comments which are located between the supplied element + * and the previous element passed into {@link #textWithComments(PsiElement)} or {@link #commentsBefore(PsiElement)}. + * The used comments are deleted from the original document. + * + *

Note that if PsiExpression was passed, the resulting text may not parse as an PsiExpression, + * because PsiExpression cannot start with comment. + * + *

This method can be used if several parts of original code are reused in the generated replacement. + * + * @param element an element to convert to the text + * @return the string containing the element text and possibly some comments. + */ + public String textWithComments(@Nonnull PsiElement element) { + return commentsBefore(element) + element.getText(); + } + + /** + * Returns an element text, adding parentheses if necessary, possibly prepended with comments which are + * located between the supplied element and the previous element passed into + * {@link #textWithComments(PsiElement)} or {@link #commentsBefore(PsiElement)}. + * The used comments are deleted from the original document. + * + *

Note that if PsiExpression was passed, the resulting text may not parse as an PsiExpression, + * because PsiExpression cannot start with comment. + * + *

This method can be used if several parts of original code are reused in the generated replacement. + * + * @param expression an expression to convert to the text + * @param precedence precedence of surrounding operation + * @return the string containing the element text and possibly some comments. + */ + public String textWithComments(@Nonnull PsiExpression expression, int precedence) { + return commentsBefore(expression) + ParenthesesUtils.getText(expression, precedence + 1); + } + + /** + * Deletes given PsiElement collecting all the comments inside it. + * + * @param element element to delete + */ + public void delete(@Nonnull PsiElement element) { + grabCommentsOnDelete(element); + element.delete(); + } + + /** + * Deletes all given PsiElement's collecting all the comments inside them. + * + * @param elements elements to delete (all not null) + */ + public void delete(@Nonnull PsiElement... elements) { + for (PsiElement element : elements) { + delete(element); + } + } + + /** + * Deletes given PsiElement replacing it with the comments including comments inside the deleted element + * and previously gathered comments. + * + *

After calling this method the tracker cannot be used anymore.

+ * + * @param element element to delete + */ + public void deleteAndRestoreComments(@Nonnull PsiElement element) { + grabCommentsOnDelete(element); + PsiElement anchor = element; + while (anchor.getParent() != null && !(anchor.getParent() instanceof PsiFile) && anchor.getParent().getFirstChild() == anchor) { + anchor = anchor.getParent(); + } + insertCommentsBefore(anchor); + element.delete(); + } + + /** + * Replaces given PsiElement collecting all the comments inside it. + * + * @param element element to replace + * @param replacement replacement element. It's also marked as unchanged (see {@link #markUnchanged(PsiElement)}) + * @return the element which was actually inserted in the tree (either {@code replacement} or its copy) + */ + @Nonnull + public PsiElement replace(@Nonnull PsiElement element, @Nonnull PsiElement replacement) { + markUnchanged(replacement); + grabComments(element); + return element.replace(replacement); + } + + /** + * Creates a replacement element from the text and replaces given element, + * collecting all the comments inside it. + * + *

+ * The type of the created replacement will mimic the type of supplied element. + * Supported element types are: {@link PsiExpression}, {@link PsiStatement}, + * {@link PsiTypeElement}, {@link PsiIdentifier}, {@link PsiComment}. + *

+ * + * @param element element to replace + * @param text replacement text + * @return the element which was actually inserted in the tree + */ + @Nonnull + public PsiElement replace(@Nonnull PsiElement element, @Nonnull String text) { + PsiElement replacement = createElement(element, text); + return replace(element, replacement); + } + + /** + * Replaces given PsiElement collecting all the comments inside it and restores comments putting them + * to the appropriate place before replaced element. + * + *

After calling this method the tracker cannot be used anymore.

+ * + * @param element element to replace + * @param replacement replacement element. It's also marked as unchanged (see {@link #markUnchanged(PsiElement)}) + * @return the element which was actually inserted in the tree (either {@code replacement} or its copy) + */ + @Nonnull + public PsiElement replaceAndRestoreComments(@Nonnull PsiElement element, @Nonnull PsiElement replacement) { + List suffix = grabSuffixComments(element); + PsiElement result = replace(element, replacement); + PsiElement anchor = PsiTreeUtil + .getNonStrictParentOfType(result, PsiStatement.class, PsiLambdaExpression.class, PsiVariable.class, PsiNameValuePair.class); + if (anchor instanceof PsiLambdaExpression && anchor != result) { + anchor = ((PsiLambdaExpression)anchor).getBody(); + } + if (anchor instanceof PsiVariable && anchor.getParent() instanceof PsiDeclarationStatement) { + anchor = anchor.getParent(); + } + if (anchor instanceof PsiStatement && (anchor.getParent() instanceof PsiIfStatement || anchor.getParent() instanceof PsiLoopStatement)) { + anchor = anchor.getParent(); + } + if (anchor == null) { + anchor = result; + } + restoreSuffixComments(result, suffix); + insertCommentsBefore(anchor); + return result; + } + + /** + * Replaces the specified expression and restores any comments to their appropriate place before and/or after the expression. + * Meant to be used with {@link #commentsBefore(PsiElement)} and {@link #commentsBetween(PsiElement, PsiElement)} + * + * @param expression the expression to replace + * @param replacementText text of the replacement expression + * @return the element which was inserted in the tree + */ + @Nonnull + public PsiElement replaceExpressionAndRestoreComments(@Nonnull PsiExpression expression, @Nonnull String replacementText) { + return replaceExpressionAndRestoreComments(expression, replacementText, Collections.emptyList()); + } + + @Nonnull + public PsiElement replaceExpressionAndRestoreComments( + @Nonnull PsiExpression expression, + @Nonnull String replacementText, + List toDelete + ) { + List trailingComments = new SmartList<>(); + List comments = grabCommentsBefore(PsiTreeUtil.lastChild(expression)); + if (!comments.isEmpty()) { + PsiParserFacade parser = PsiParserFacade.SERVICE.getInstance(expression.getProject()); + for (PsiElement comment : comments) { + PsiElement prev = comment.getPrevSibling(); + if (prev instanceof PsiWhiteSpace) { + String text = prev.getText(); + if (!text.contains("\n")) { + trailingComments.add(parser.createWhiteSpaceFromText(" ")); + } + else if (text.endsWith("\n")) { + trailingComments.add(parser.createWhiteSpaceFromText("\n")); // comment at first column + } + else { + trailingComments.add(parser.createWhiteSpaceFromText("\n ")); // newline followed by space will cause formatter to indent + } + } + ignoredParents.add(comment); + trailingComments.add(comment.copy()); + } + Collections.reverse(trailingComments); + } + PsiElement replacement = replace(expression, replacementText); + for (PsiElement element : trailingComments) { + replacement.getParent().addAfter(element, replacement); + } + toDelete.forEach(this::delete); + insertCommentsBefore(replacement); + return replacement; + } + + @Nonnull + private List grabSuffixComments(@Nonnull PsiElement element) { + if (!(element instanceof PsiStatement)) { + return Collections.emptyList(); + } + List suffix = new ArrayList<>(); + PsiElement lastChild = element.getLastChild(); + boolean hasComment = false; + while (lastChild instanceof PsiComment || lastChild instanceof PsiWhiteSpace) { + hasComment |= lastChild instanceof PsiComment; + if (!(lastChild instanceof PsiComment) || !(shouldIgnore((PsiComment)lastChild))) { + suffix.add(markUnchanged(lastChild).copy()); + } + lastChild = lastChild.getPrevSibling(); + } + return hasComment ? suffix : Collections.emptyList(); + } + + private static void restoreSuffixComments(PsiElement target, List suffix) { + if (!suffix.isEmpty()) { + PsiElement lastChild = target.getLastChild(); + if (lastChild instanceof PsiComment && JavaTokenType.END_OF_LINE_COMMENT.equals(((PsiComment)lastChild).getTokenType())) { + PsiElement nextSibling = target.getNextSibling(); + if (nextSibling instanceof PsiWhiteSpace) { + target.add(nextSibling); + } + else { + target.add(PsiParserFacade.SERVICE.getInstance(target.getProject()).createWhiteSpaceFromText("\n")); + } + } + StreamEx.ofReversed(suffix).forEach(target::add); + } + } + + /** + * Creates a replacement element from the text and replaces given element, + * collecting all the comments inside it and restores comments putting them + * to the appropriate place before replaced element. + * + *

After calling this method the tracker cannot be used anymore.

+ * + *

+ * The type of the created replacement will mimic the type of supplied element. + * Supported element types are: {@link PsiExpression}, {@link PsiStatement}, + * {@link PsiTypeElement}, {@link PsiIdentifier}, {@link PsiComment}. + *

+ * + * @param element element to replace + * @param text replacement text + * @return the element which was actually inserted in the tree + */ + @Nonnull + public PsiElement replaceAndRestoreComments(@Nonnull PsiElement element, @Nonnull String text) { + PsiElement replacement = createElement(element, text); + return replaceAndRestoreComments(element, replacement); + } + + @Nonnull + private static PsiElement createElement(@Nonnull PsiElement element, @Nonnull String text) { + PsiElementFactory factory = JavaPsiFacade.getElementFactory(element.getProject()); + if (element instanceof PsiExpression) { + return factory.createExpressionFromText(text, element); + } + else if (element instanceof PsiStatement) { + return factory.createStatementFromText(text, element); + } + else if (element instanceof PsiTypeElement) { + return factory.createTypeElementFromText(text, element); + } + else if (element instanceof PsiIdentifier) { + return factory.createIdentifier(text); + } + else if (element instanceof PsiComment) { + return factory.createCommentFromText(text, element); + } + else { + throw new IllegalArgumentException("Unsupported element type: " + element); + } + } + + /** + * Inserts gathered comments just before given anchor element + * + *

After calling this method the tracker cannot be used anymore.

+ * + * @param anchor + */ + public void insertCommentsBefore(@Nonnull PsiElement anchor) { + checkState(); + if (!comments.isEmpty()) { + PsiElement parent = anchor.getParent(); + PsiElementFactory factory = JavaPsiFacade.getElementFactory(anchor.getProject()); + for (PsiComment comment : comments) { + if (shouldIgnore(comment)) { + continue; + } + PsiElement added = parent.addBefore(factory.createCommentFromText(comment.getText(), anchor), anchor); + PsiElement prevSibling = added.getPrevSibling(); + if (prevSibling instanceof PsiWhiteSpace) { + PsiElement prev = anchor.getPrevSibling(); + ASTNode whiteSpaceBefore = normalizeWhiteSpace((PsiWhiteSpace)prevSibling, prev); + parent.getNode().addChild(whiteSpaceBefore, anchor.getNode()); + if (prev instanceof PsiWhiteSpace) { + prev.delete(); + } + } + } + } + comments = null; + } + + @Nonnull + private static ASTNode normalizeWhiteSpace(PsiWhiteSpace whiteSpace, PsiElement nextElement) { + String text = whiteSpace.getText(); + int endLPos = text.lastIndexOf('\n'); + if (text.lastIndexOf('\n', endLPos - 1) >= 0) { + // has at least two line breaks + return ASTFactory.whitespace(text.substring(endLPos)); + } + if (nextElement instanceof PsiWhiteSpace && nextElement.getText().contains("\n") && !text.contains("\n")) { + text = '\n' + text; + } + return ASTFactory.whitespace(text); + } + + private boolean shouldIgnore(PsiComment comment) { + return ignoredParents.stream().anyMatch(p -> PsiTreeUtil.isAncestor(p, comment, false)); + } + + private void grabCommentsOnDelete(PsiElement element) { + if (element instanceof PsiExpression && element.getParent() instanceof PsiExpressionStatement || + (element.getParent() instanceof PsiDeclarationStatement && + ((PsiDeclarationStatement)element.getParent()).getDeclaredElements().length == 1)) { + element = element.getParent(); + } + else if (element.getParent() instanceof PsiJavaCodeReferenceElement) { + PsiElement parent = element.getParent(); + if (element instanceof PsiJavaCodeReferenceElement && ((PsiJavaCodeReferenceElement)parent).getQualifier() == element) { + ASTNode dot = ((CompositeElement)parent).findChildByRole(ChildRole.DOT); + if (dot != null) { + PsiElement nextSibling = dot.getPsi().getNextSibling(); + if (nextSibling != null && nextSibling.getTextLength() == 0) { + nextSibling = skipWhitespacesAndCommentsForward(nextSibling); + } + while (nextSibling != null) { + nextSibling = markUnchanged(nextSibling).getNextSibling(); + } + } + } + element = parent; + } + grabComments(element); + } + + @SuppressWarnings("unchecked") + private static final Class[] + WS_COMMENTS = new Class[]{ + PsiWhiteSpace.class, + PsiComment.class + }; + + @Nullable + @Contract("null -> null") + @Deprecated + public static PsiElement skipWhitespacesAndCommentsForward(@Nullable PsiElement element) { + return PsiTreeUtil.skipSiblingsForward(element, WS_COMMENTS); + } + + /** + * Grab the comments from given element which should be restored. Normally you don't need to call this method. + * It should be called only if element is about to be deleted by other code which is not CommentTracker-aware. + * + *

Calling this method repeatedly has no effect. It's also safe to call this method, then delete element using + * other methods from this class like {@link #delete(PsiElement)}. + * + * @param element element to grab the comments from. + */ + public void grabComments(PsiElement element) { + checkState(); + for (PsiComment comment : PsiTreeUtil.collectElementsOfType(element, PsiComment.class)) { + if (!shouldIgnore(comment)) { + comments.add(comment); + } + } + } + + private void checkState() { + if (comments == null) { + throw new IllegalStateException(getClass().getSimpleName() + " has been already used"); + } + } + + private void addIgnored(PsiElement element) { + if (!(element instanceof LeafPsiElement) || element instanceof PsiComment) { + ignoredParents.add(element); + } + } + + public static String textWithSurroundingComments(PsiElement element) { + Predicate commentOrWhiteSpace = e -> e instanceof PsiComment || e instanceof PsiWhiteSpace; + List prev = StreamEx.iterate(element.getPrevSibling(), commentOrWhiteSpace, PsiElement::getPrevSibling).toList(); + List next = StreamEx.iterate(element.getNextSibling(), commentOrWhiteSpace, PsiElement::getNextSibling).toList(); + if (StreamEx.of(prev, next).flatCollection(Function.identity()).anyMatch(PsiComment.class::isInstance)) { + return StreamEx.ofReversed(prev).append(element).append(next).map(PsiElement::getText).joining(); + } + return element.getText(); + } + + /** + * Returns a string containing all the comments (possibly with some white-spaces) between given elements + * (not including given elements themselves). This method also deletes all the comments actually used + * in the returned string. + * + * @param start start element + * @param end end element, must strictly follow the start element and be located in the same file + * (though possibly on another hierarchy level) + * @return a string containing all the comments between start and end. + */ + @Nonnull + public static String commentsBetween(@Nonnull PsiElement start, @Nonnull PsiElement end) { + CommentTracker ct = new CommentTracker(); + ct.lastTextWithCommentsElement = start; + return ct.commentsBefore(end); + } } \ No newline at end of file From de95461c2c333db25be8f30b5ea8ed8e8c80e143 Mon Sep 17 00:00:00 2001 From: UNV Date: Wed, 21 May 2025 18:48:24 +0300 Subject: [PATCH 2/3] Refactoring CommentTracker and AddAnnotationPsiFix. --- .../intention/AddAnnotationPsiFix.java | 9 +- .../impl/AddNullableNotNullAnnotationFix.java | 18 +-- .../com/siyeh/ig/psiutils/CommentTracker.java | 108 +++++++++++------- 3 files changed, 82 insertions(+), 53 deletions(-) diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/AddAnnotationPsiFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/AddAnnotationPsiFix.java index 4d961b20d8..e86c93039b 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/AddAnnotationPsiFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/AddAnnotationPsiFix.java @@ -12,6 +12,7 @@ import com.intellij.java.language.psi.util.PsiUtil; import com.siyeh.ig.psiutils.CommentTracker; import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; import consulo.java.analysis.localize.JavaAnalysisLocalize; import consulo.language.editor.WriteCommandAction; import consulo.language.editor.inspection.LocalQuickFixOnPsiElement; @@ -260,6 +261,7 @@ private ExternalAnnotationsManager.AnnotationPlace choosePlace(@Nonnull PsiModif */ @Deprecated //@ApiStatus.ScheduledForRemoval(inVersion = "2020.3") + @RequiredWriteAction public static PsiAnnotation addPhysicalAnnotation(String fqn, PsiNameValuePair[] pairs, PsiModifierList modifierList) { return addPhysicalAnnotationTo(fqn, pairs, modifierList); } @@ -278,7 +280,7 @@ public static PsiAnnotation addPhysicalAnnotation(String fqn, PsiNameValuePair[] * @return added physical annotation; null if annotation already exists (in this case, no changes are performed) */ @Nullable - @RequiredReadAction + @RequiredWriteAction public static PsiAnnotation addPhysicalAnnotationIfAbsent( @Nonnull String fqn, @Nonnull PsiNameValuePair[] pairs, @@ -306,7 +308,7 @@ public static PsiAnnotation addPhysicalAnnotationIfAbsent( return addPhysicalAnnotationTo(fqn, pairs, owner); } - @RequiredReadAction + @RequiredWriteAction public static PsiAnnotation addPhysicalAnnotationTo(String fqn, PsiNameValuePair[] pairs, PsiAnnotationOwner owner) { owner = expandParameterIfNecessary(owner); PsiAnnotation inserted; @@ -332,7 +334,7 @@ public static PsiAnnotation addPhysicalAnnotationTo(String fqn, PsiNameValuePair return inserted; } - @RequiredReadAction + @RequiredWriteAction private static PsiAnnotationOwner expandParameterIfNecessary(PsiAnnotationOwner owner) { if (owner instanceof PsiModifierList modifierList) { PsiParameter parameter = ObjectUtil.tryCast(modifierList.getParent(), PsiParameter.class); @@ -366,6 +368,7 @@ private static PsiAnnotationOwner expandParameterIfNecessary(PsiAnnotationOwner return owner; } + @RequiredWriteAction public static void removePhysicalAnnotations(@Nonnull PsiModifierListOwner owner, @Nonnull String... fqns) { for (String fqn : fqns) { PsiAnnotation annotation = AnnotationUtil.findAnnotation(owner, true, fqn); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/impl/AddNullableNotNullAnnotationFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/impl/AddNullableNotNullAnnotationFix.java index ad68e4e486..e2c182f529 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/impl/AddNullableNotNullAnnotationFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/impl/AddNullableNotNullAnnotationFix.java @@ -13,15 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -/* - * Created by IntelliJ IDEA. - * User: cdr - * Date: Jul 20, 2007 - * Time: 2:57:59 PM - */ package com.intellij.java.analysis.impl.codeInsight.intention.impl; +import consulo.annotation.access.RequiredReadAction; import jakarta.annotation.Nonnull; import com.intellij.java.language.codeInsight.AnnotationUtil; @@ -35,7 +29,12 @@ import com.intellij.java.language.psi.PsiPrimitiveType; import com.intellij.java.language.psi.PsiType; +/** + * @author cdr + * @since 2007-07-20 + */ public class AddNullableNotNullAnnotationFix extends AddAnnotationPsiFix { + @RequiredReadAction public AddNullableNotNullAnnotationFix( @Nonnull String fqn, @Nonnull PsiModifierListOwner owner, @@ -45,6 +44,7 @@ public AddNullableNotNullAnnotationFix( } @Override + @RequiredReadAction public boolean isAvailable( @Nonnull Project project, @Nonnull PsiFile file, @@ -58,8 +58,8 @@ public boolean isAvailable( if (owner == null || AnnotationUtil.isAnnotated(owner, getAnnotationsToRemove()[0], false, false)) { return false; } - if (owner instanceof PsiMethod) { - PsiType returnType = ((PsiMethod)owner).getReturnType(); + if (owner instanceof PsiMethod method) { + PsiType returnType = method.getReturnType(); return returnType != null && !(returnType instanceof PsiPrimitiveType); } diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/CommentTracker.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/CommentTracker.java index 6e1b20ea89..d98a35d732 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/CommentTracker.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/CommentTracker.java @@ -4,6 +4,8 @@ import com.intellij.java.language.impl.psi.impl.source.tree.ChildRole; import com.intellij.java.language.psi.PsiElementFactory; import com.intellij.java.language.psi.*; +import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; import consulo.document.util.TextRange; import consulo.language.ast.ASTNode; import consulo.language.impl.ast.ASTFactory; @@ -42,6 +44,7 @@ public final class CommentTracker { * @return a text to be inserted into refactored code */ @Nonnull + @RequiredReadAction public String text(@Nonnull PsiElement element) { checkState(); addIgnored(element); @@ -74,6 +77,7 @@ public String text(@Nonnull PsiExpression element, int precedence) { * @return a string representation of lambda */ @Nonnull + @RequiredReadAction public String lambdaText(@Nonnull PsiVariable variable, @Nonnull PsiExpression expression) { return variable.getName() + " -> " + text(expression); } @@ -104,6 +108,7 @@ public T markUnchanged(@Nullable T element) { * @return a text to be inserted into refactored code * @throws IllegalArgumentException if firstElement and lastElements are not siblings or firstElement goes after last element */ + @RequiredReadAction public String rangeText(@Nonnull PsiElement firstElement, @Nonnull PsiElement lastElement) { checkState(); PsiElement e; @@ -128,6 +133,7 @@ public String rangeText(@Nonnull PsiElement firstElement, @Nonnull PsiElement la * @param lastElement last element to mark (must be equal to firstElement or its sibling) * @throws IllegalArgumentException if firstElement and lastElements are not siblings or firstElement goes after last element */ + @RequiredReadAction public void markRangeUnchanged(@Nonnull PsiElement firstElement, @Nonnull PsiElement lastElement) { checkState(); PsiElement e; @@ -150,6 +156,7 @@ public void markRangeUnchanged(@Nonnull PsiElement firstElement, @Nonnull PsiEle * @param element an element grab the comments before it * @return the string containing the element text and possibly some comments. */ + @RequiredReadAction public String commentsBefore(@Nonnull PsiElement element) { List comments = grabCommentsBefore(element); if (comments.isEmpty()) { @@ -158,19 +165,20 @@ public String commentsBefore(@Nonnull PsiElement element) { StringBuilder sb = new StringBuilder(); for (PsiElement comment : comments) { PsiElement prev = comment.getPrevSibling(); - if (sb.length() == 0 && prev instanceof PsiWhiteSpace) { - sb.append(prev.getText()); + if (sb.length() == 0 && prev instanceof PsiWhiteSpace whiteSpace) { + sb.append(whiteSpace.getText()); } sb.append(comment.getText()); PsiElement next = PsiTreeUtil.nextLeaf(comment); - if (next instanceof PsiWhiteSpace) { - sb.append(next.getText()); + if (next instanceof PsiWhiteSpace whiteSpace) { + sb.append(whiteSpace.getText()); } } comments.forEach(PsiElement::delete); return sb.toString(); } + @RequiredReadAction private List grabCommentsBefore(@Nonnull PsiElement element) { if (lastTextWithCommentsElement == null) { lastTextWithCommentsElement = element; @@ -184,10 +192,10 @@ private List grabCommentsBefore(@Nonnull PsiElement element) { PsiTreeUtil.processElements( parent, e -> { - if (e instanceof PsiComment) { - TextRange range = e.getTextRange(); - if (range.getStartOffset() >= start && range.getEndOffset() <= end && !shouldIgnore((PsiComment)e)) { - result.add(e); + if (e instanceof PsiComment comment) { + TextRange range = comment.getTextRange(); + if (range.getStartOffset() >= start && range.getEndOffset() <= end && !shouldIgnore(comment)) { + result.add(comment); } } return true; @@ -212,6 +220,7 @@ private List grabCommentsBefore(@Nonnull PsiElement element) { * @param element an element to convert to the text * @return the string containing the element text and possibly some comments. */ + @RequiredReadAction public String textWithComments(@Nonnull PsiElement element) { return commentsBefore(element) + element.getText(); } @@ -231,6 +240,7 @@ public String textWithComments(@Nonnull PsiElement element) { * @param precedence precedence of surrounding operation * @return the string containing the element text and possibly some comments. */ + @RequiredReadAction public String textWithComments(@Nonnull PsiExpression expression, int precedence) { return commentsBefore(expression) + ParenthesesUtils.getText(expression, precedence + 1); } @@ -240,6 +250,7 @@ public String textWithComments(@Nonnull PsiExpression expression, int precedence * * @param element element to delete */ + @RequiredWriteAction public void delete(@Nonnull PsiElement element) { grabCommentsOnDelete(element); element.delete(); @@ -250,6 +261,7 @@ public void delete(@Nonnull PsiElement element) { * * @param elements elements to delete (all not null) */ + @RequiredWriteAction public void delete(@Nonnull PsiElement... elements) { for (PsiElement element : elements) { delete(element); @@ -264,6 +276,7 @@ public void delete(@Nonnull PsiElement... elements) { * * @param element element to delete */ + @RequiredWriteAction public void deleteAndRestoreComments(@Nonnull PsiElement element) { grabCommentsOnDelete(element); PsiElement anchor = element; @@ -282,6 +295,7 @@ public void deleteAndRestoreComments(@Nonnull PsiElement element) { * @return the element which was actually inserted in the tree (either {@code replacement} or its copy) */ @Nonnull + @RequiredWriteAction public PsiElement replace(@Nonnull PsiElement element, @Nonnull PsiElement replacement) { markUnchanged(replacement); grabComments(element); @@ -303,6 +317,7 @@ public PsiElement replace(@Nonnull PsiElement element, @Nonnull PsiElement repla * @return the element which was actually inserted in the tree */ @Nonnull + @RequiredWriteAction public PsiElement replace(@Nonnull PsiElement element, @Nonnull String text) { PsiElement replacement = createElement(element, text); return replace(element, replacement); @@ -319,16 +334,17 @@ public PsiElement replace(@Nonnull PsiElement element, @Nonnull String text) { * @return the element which was actually inserted in the tree (either {@code replacement} or its copy) */ @Nonnull + @RequiredWriteAction public PsiElement replaceAndRestoreComments(@Nonnull PsiElement element, @Nonnull PsiElement replacement) { List suffix = grabSuffixComments(element); PsiElement result = replace(element, replacement); PsiElement anchor = PsiTreeUtil .getNonStrictParentOfType(result, PsiStatement.class, PsiLambdaExpression.class, PsiVariable.class, PsiNameValuePair.class); - if (anchor instanceof PsiLambdaExpression && anchor != result) { - anchor = ((PsiLambdaExpression)anchor).getBody(); + if (anchor instanceof PsiLambdaExpression lambda && anchor != result) { + anchor = lambda.getBody(); } - if (anchor instanceof PsiVariable && anchor.getParent() instanceof PsiDeclarationStatement) { - anchor = anchor.getParent(); + if (anchor instanceof PsiVariable variable && variable.getParent() instanceof PsiDeclarationStatement declaration) { + anchor = declaration; } if (anchor instanceof PsiStatement && (anchor.getParent() instanceof PsiIfStatement || anchor.getParent() instanceof PsiLoopStatement)) { anchor = anchor.getParent(); @@ -350,11 +366,13 @@ public PsiElement replaceAndRestoreComments(@Nonnull PsiElement element, @Nonnul * @return the element which was inserted in the tree */ @Nonnull + @RequiredWriteAction public PsiElement replaceExpressionAndRestoreComments(@Nonnull PsiExpression expression, @Nonnull String replacementText) { return replaceExpressionAndRestoreComments(expression, replacementText, Collections.emptyList()); } @Nonnull + @RequiredWriteAction public PsiElement replaceExpressionAndRestoreComments( @Nonnull PsiExpression expression, @Nonnull String replacementText, @@ -365,17 +383,18 @@ public PsiElement replaceExpressionAndRestoreComments( if (!comments.isEmpty()) { PsiParserFacade parser = PsiParserFacade.SERVICE.getInstance(expression.getProject()); for (PsiElement comment : comments) { - PsiElement prev = comment.getPrevSibling(); - if (prev instanceof PsiWhiteSpace) { - String text = prev.getText(); + if (comment.getPrevSibling() instanceof PsiWhiteSpace whiteSpace) { + String text = whiteSpace.getText(); if (!text.contains("\n")) { trailingComments.add(parser.createWhiteSpaceFromText(" ")); } else if (text.endsWith("\n")) { - trailingComments.add(parser.createWhiteSpaceFromText("\n")); // comment at first column + // comment at first column + trailingComments.add(parser.createWhiteSpaceFromText("\n")); } else { - trailingComments.add(parser.createWhiteSpaceFromText("\n ")); // newline followed by space will cause formatter to indent + // newline followed by space will cause formatter to indent + trailingComments.add(parser.createWhiteSpaceFromText("\n ")); } } ignoredParents.add(comment); @@ -393,6 +412,7 @@ else if (text.endsWith("\n")) { } @Nonnull + @RequiredReadAction private List grabSuffixComments(@Nonnull PsiElement element) { if (!(element instanceof PsiStatement)) { return Collections.emptyList(); @@ -402,7 +422,7 @@ private List grabSuffixComments(@Nonnull PsiElement element) { boolean hasComment = false; while (lastChild instanceof PsiComment || lastChild instanceof PsiWhiteSpace) { hasComment |= lastChild instanceof PsiComment; - if (!(lastChild instanceof PsiComment) || !(shouldIgnore((PsiComment)lastChild))) { + if (!(lastChild instanceof PsiComment comment) || !(shouldIgnore(comment))) { suffix.add(markUnchanged(lastChild).copy()); } lastChild = lastChild.getPrevSibling(); @@ -410,13 +430,13 @@ private List grabSuffixComments(@Nonnull PsiElement element) { return hasComment ? suffix : Collections.emptyList(); } + @RequiredReadAction private static void restoreSuffixComments(PsiElement target, List suffix) { if (!suffix.isEmpty()) { PsiElement lastChild = target.getLastChild(); - if (lastChild instanceof PsiComment && JavaTokenType.END_OF_LINE_COMMENT.equals(((PsiComment)lastChild).getTokenType())) { - PsiElement nextSibling = target.getNextSibling(); - if (nextSibling instanceof PsiWhiteSpace) { - target.add(nextSibling); + if (lastChild instanceof PsiComment comment && JavaTokenType.END_OF_LINE_COMMENT.equals(comment.getTokenType())) { + if (target.getNextSibling() instanceof PsiWhiteSpace whiteSpace) { + target.add(whiteSpace); } else { target.add(PsiParserFacade.SERVICE.getInstance(target.getProject()).createWhiteSpaceFromText("\n")); @@ -444,6 +464,7 @@ private static void restoreSuffixComments(PsiElement target, ListAfter calling this method the tracker cannot be used anymore.

* - * @param anchor + * @param anchor element to insert comments before */ + @RequiredWriteAction public void insertCommentsBefore(@Nonnull PsiElement anchor) { checkState(); if (!comments.isEmpty()) { @@ -489,13 +511,12 @@ public void insertCommentsBefore(@Nonnull PsiElement anchor) { continue; } PsiElement added = parent.addBefore(factory.createCommentFromText(comment.getText(), anchor), anchor); - PsiElement prevSibling = added.getPrevSibling(); - if (prevSibling instanceof PsiWhiteSpace) { + if (added.getPrevSibling() instanceof PsiWhiteSpace prevSiblingWhiteSpace) { PsiElement prev = anchor.getPrevSibling(); - ASTNode whiteSpaceBefore = normalizeWhiteSpace((PsiWhiteSpace)prevSibling, prev); + ASTNode whiteSpaceBefore = normalizeWhiteSpace(prevSiblingWhiteSpace, prev); parent.getNode().addChild(whiteSpaceBefore, anchor.getNode()); - if (prev instanceof PsiWhiteSpace) { - prev.delete(); + if (prev instanceof PsiWhiteSpace whiteSpace) { + whiteSpace.delete(); } } } @@ -504,6 +525,7 @@ public void insertCommentsBefore(@Nonnull PsiElement anchor) { } @Nonnull + @RequiredReadAction private static ASTNode normalizeWhiteSpace(PsiWhiteSpace whiteSpace, PsiElement nextElement) { String text = whiteSpace.getText(); int endLPos = text.lastIndexOf('\n'); @@ -521,27 +543,27 @@ private boolean shouldIgnore(PsiComment comment) { return ignoredParents.stream().anyMatch(p -> PsiTreeUtil.isAncestor(p, comment, false)); } + @RequiredReadAction private void grabCommentsOnDelete(PsiElement element) { - if (element instanceof PsiExpression && element.getParent() instanceof PsiExpressionStatement || - (element.getParent() instanceof PsiDeclarationStatement && - ((PsiDeclarationStatement)element.getParent()).getDeclaredElements().length == 1)) { - element = element.getParent(); - } - else if (element.getParent() instanceof PsiJavaCodeReferenceElement) { - PsiElement parent = element.getParent(); - if (element instanceof PsiJavaCodeReferenceElement && ((PsiJavaCodeReferenceElement)parent).getQualifier() == element) { - ASTNode dot = ((CompositeElement)parent).findChildByRole(ChildRole.DOT); + PsiElement parent = element.getParent(); + if (element instanceof PsiExpression && parent instanceof PsiExpressionStatement + || (parent instanceof PsiDeclarationStatement declarationStmt && declarationStmt.getDeclaredElements().length == 1)) { + element = parent; + } + else if (parent instanceof PsiJavaCodeReferenceElement parentCodeRefElem) { + if (element instanceof PsiJavaCodeReferenceElement codeRefElem && codeRefElem.getQualifier() == element) { + ASTNode dot = ((CompositeElement)parentCodeRefElem).findChildByRole(ChildRole.DOT); if (dot != null) { PsiElement nextSibling = dot.getPsi().getNextSibling(); if (nextSibling != null && nextSibling.getTextLength() == 0) { - nextSibling = skipWhitespacesAndCommentsForward(nextSibling); + nextSibling = PsiTreeUtil.skipSiblingsForward(nextSibling, WS_COMMENTS); } while (nextSibling != null) { nextSibling = markUnchanged(nextSibling).getNextSibling(); } } } - element = parent; + element = parentCodeRefElem; } grabComments(element); } @@ -553,9 +575,10 @@ else if (element.getParent() instanceof PsiJavaCodeReferenceElement) { PsiComment.class }; - @Nullable - @Contract("null -> null") @Deprecated + @Contract("null -> null") + @Nullable + @RequiredReadAction public static PsiElement skipWhitespacesAndCommentsForward(@Nullable PsiElement element) { return PsiTreeUtil.skipSiblingsForward(element, WS_COMMENTS); } @@ -569,6 +592,7 @@ public static PsiElement skipWhitespacesAndCommentsForward(@Nullable PsiElement * * @param element element to grab the comments from. */ + @RequiredReadAction public void grabComments(PsiElement element) { checkState(); for (PsiComment comment : PsiTreeUtil.collectElementsOfType(element, PsiComment.class)) { @@ -590,6 +614,7 @@ private void addIgnored(PsiElement element) { } } + @RequiredReadAction public static String textWithSurroundingComments(PsiElement element) { Predicate commentOrWhiteSpace = e -> e instanceof PsiComment || e instanceof PsiWhiteSpace; List prev = StreamEx.iterate(element.getPrevSibling(), commentOrWhiteSpace, PsiElement::getPrevSibling).toList(); @@ -611,6 +636,7 @@ public static String textWithSurroundingComments(PsiElement element) { * @return a string containing all the comments between start and end. */ @Nonnull + @RequiredReadAction public static String commentsBetween(@Nonnull PsiElement start, @Nonnull PsiElement end) { CommentTracker ct = new CommentTracker(); ct.lastTextWithCommentsElement = start; From 4cda53d675a5fb42a8ff020e4fb5df1a6e28721f Mon Sep 17 00:00:00 2001 From: UNV Date: Wed, 21 May 2025 19:03:56 +0300 Subject: [PATCH 3/3] Replacing EP_NAME with getExtensionPoint. --- .../InferredAnnotationsManagerImpl.java | 18 ++++---- .../daemon/impl/quickfix/OrderEntryFix.java | 44 +++++++++---------- .../generation/GenerateMembersUtil.java | 10 ++--- .../GetterSetterPrototypeProvider.java | 38 ++++++++-------- .../generation/OverrideImplementUtil.java | 28 ++++-------- 5 files changed, 61 insertions(+), 77 deletions(-) diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/InferredAnnotationsManagerImpl.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/InferredAnnotationsManagerImpl.java index 1ab385c1dd..2db0305cf4 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/InferredAnnotationsManagerImpl.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/InferredAnnotationsManagerImpl.java @@ -8,12 +8,11 @@ import consulo.annotation.component.ServiceImpl; import consulo.project.Project; import consulo.util.dataholder.Key; +import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import jakarta.inject.Inject; import jakarta.inject.Singleton; -import jakarta.annotation.Nonnull; - import java.util.ArrayList; import java.util.List; @@ -21,37 +20,37 @@ @ServiceImpl public class InferredAnnotationsManagerImpl extends InferredAnnotationsManager { private static final Key INFERRED_ANNOTATION = Key.create("INFERRED_ANNOTATION"); + @Nonnull private final Project myProject; @Inject - public InferredAnnotationsManagerImpl(Project project) { + public InferredAnnotationsManagerImpl(@Nonnull Project project) { myProject = project; } @Nullable @Override public PsiAnnotation findInferredAnnotation(@Nonnull PsiModifierListOwner listOwner, @Nonnull String annotationFQN) { - for (InferredAnnotationProvider provider : InferredAnnotationProvider.EP_NAME.getExtensionList(myProject)) { + return myProject.getExtensionPoint(InferredAnnotationProvider.class).computeSafeIfAny(provider -> { PsiAnnotation annotation = provider.findInferredAnnotation(listOwner, annotationFQN); if (annotation != null) { markInferred(annotation); - return annotation; } - } - return null; + return annotation; + }); } @Nonnull @Override public PsiAnnotation[] findInferredAnnotations(@Nonnull PsiModifierListOwner listOwner) { List result = new ArrayList<>(); - for (InferredAnnotationProvider provider : InferredAnnotationProvider.EP_NAME.getExtensionList(myProject)) { + myProject.getExtensionPoint(InferredAnnotationProvider.class).forEach(provider -> { List annotations = provider.findInferredAnnotations(listOwner); for (PsiAnnotation annotation : annotations) { markInferred(annotation); result.add(annotation); } - } + }); return result.toArray(PsiAnnotation.EMPTY_ARRAY); } @@ -63,5 +62,4 @@ public boolean isInferredAnnotation(@Nonnull PsiAnnotation annotation) { private static void markInferred(@Nonnull PsiAnnotation annotation) { annotation.putUserData(INFERRED_ANNOTATION, Boolean.TRUE); } - } diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/quickfix/OrderEntryFix.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/quickfix/OrderEntryFix.java index 0984a7d397..64fa5bed95 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/quickfix/OrderEntryFix.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/quickfix/OrderEntryFix.java @@ -223,31 +223,29 @@ private static void registerExternalFixes( List result ) { String fullReferenceText = reference.getCanonicalText(); - for (ExternalLibraryResolver resolver : ExternalLibraryResolver.EP_NAME.getExtensionList()) { - ExternalClassResolveResult resolveResult = - resolver.resolveClass(shortReferenceName, isReferenceToAnnotation(psiElement), currentModule); - OrderEntryFix fix = null; - if (resolveResult != null && facade.findClass( - resolveResult.getQualifiedClassName(), - GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(currentModule, true) - ) == null) { - fix = new AddExternalLibraryToDependenciesQuickFix( - currentModule, - resolveResult.getLibrary(), - reference, - resolveResult.getQualifiedClassName() - ); - } - else if (!fullReferenceText.equals(shortReferenceName)) { - ExternalLibraryDescriptor descriptor = resolver.resolvePackage(fullReferenceText); - if (descriptor != null) { - fix = new AddExternalLibraryToDependenciesQuickFix(currentModule, descriptor, reference, null); + GlobalSearchScope scope = GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(currentModule, true); + currentModule.getApplication().getExtensionPoint(ExternalLibraryResolver.class).collectMapped( + result, + resolver -> { + ExternalClassResolveResult resolveResult = + resolver.resolveClass(shortReferenceName, isReferenceToAnnotation(psiElement) , currentModule); + if (resolveResult != null && facade.findClass(resolveResult.getQualifiedClassName(), scope) == null) { + return new AddExternalLibraryToDependenciesQuickFix( + currentModule, + resolveResult.getLibrary(), + reference, + resolveResult.getQualifiedClassName() + ); } + else if (!fullReferenceText.equals(shortReferenceName)) { + ExternalLibraryDescriptor descriptor = resolver.resolvePackage(fullReferenceText); + if (descriptor != null) { + return new AddExternalLibraryToDependenciesQuickFix(currentModule, descriptor, reference, null); + } + } + return null; } - if (fix != null) { - result.add(fix); - } - } + ); } private static List filterAllowedDependencies(PsiElement element, PsiClass[] classes) { diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/GenerateMembersUtil.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/GenerateMembersUtil.java index ba2dff8c6f..d8c36a219f 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/GenerateMembersUtil.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/GenerateMembersUtil.java @@ -61,10 +61,9 @@ import consulo.util.lang.Comparing; import consulo.util.lang.ObjectUtil; import consulo.util.lang.StringUtil; -import org.jetbrains.annotations.Contract; import jakarta.annotation.Nonnull; - import jakarta.annotation.Nullable; +import org.jetbrains.annotations.Contract; import java.util.*; @@ -735,19 +734,19 @@ public static void copyOrReplaceModifierList(@Nonnull PsiModifierListOwner sourc } } - private static void filterAnnotations(Project project, PsiModifierList modifierList, GlobalSearchScope moduleScope) { + private static void filterAnnotations(@Nonnull Project project, PsiModifierList modifierList, GlobalSearchScope moduleScope) { Set toRemove = new HashSet<>(); JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); for (PsiAnnotation annotation : modifierList.getAnnotations()) { String qualifiedName = annotation.getQualifiedName(); if (qualifiedName != null) { - for (OverrideImplementsAnnotationsHandler handler : OverrideImplementsAnnotationsHandler.EP_NAME.getExtensionList()) { + project.getApplication().getExtensionPoint(OverrideImplementsAnnotationsHandler.class).forEach(handler -> { String[] annotations2Remove = handler.annotationsToRemove(project, qualifiedName); Collections.addAll(toRemove, annotations2Remove); if (moduleScope != null && psiFacade.findClass(qualifiedName, moduleScope) == null) { toRemove.add(qualifiedName); } - } + }); } } for (String fqn : toRemove) { @@ -819,6 +818,7 @@ static PsiMethod generateGetterPrototype(@Nonnull PsiField field, boolean ignore return generatePrototype(field, field.getContainingClass(), ignoreInvalidTemplate, GetterTemplatesManager.getInstance()); } + @RequiredWriteAction static PsiMethod generateSetterPrototype(@Nonnull PsiField field, boolean ignoreInvalidTemplate) { return generatePrototype(field, field.getContainingClass(), ignoreInvalidTemplate, SetterTemplatesManager.getInstance()); } diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/GetterSetterPrototypeProvider.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/GetterSetterPrototypeProvider.java index d228d45049..4d76972a9b 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/GetterSetterPrototypeProvider.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/GetterSetterPrototypeProvider.java @@ -18,12 +18,12 @@ import com.intellij.java.language.psi.PsiClass; import com.intellij.java.language.psi.PsiField; import com.intellij.java.language.psi.PsiMethod; -import com.intellij.java.language.psi.PsiModifier; import com.intellij.java.language.psi.util.PropertyUtil; +import consulo.annotation.access.RequiredWriteAction; import consulo.annotation.component.ComponentScope; import consulo.annotation.component.ExtensionAPI; +import consulo.component.extension.ExtensionPoint; import consulo.component.extension.ExtensionPointName; -import consulo.component.extension.Extensions; /** * @author anna @@ -54,15 +54,21 @@ public boolean isSimpleGetter(PsiMethod method, String oldPropertyName) { public abstract boolean isReadOnly(PsiField field); + @RequiredWriteAction public static PsiMethod[] generateGetterSetters(PsiField field, boolean generateGetter) { return generateGetterSetters(field, generateGetter, true); } + @RequiredWriteAction public static PsiMethod[] generateGetterSetters(PsiField field, boolean generateGetter, boolean invalidTemplate) { - for (GetterSetterPrototypeProvider provider : Extensions.getExtensions(EP_NAME)) { + PsiMethod[] methods = field.getApplication().getExtensionPoint(GetterSetterPrototypeProvider.class).computeSafeIfAny(provider -> { if (provider.canGeneratePrototypeFor(field)) { return generateGetter ? provider.generateGetters(field) : provider.generateSetters(field); } + return null; + }); + if (methods != null) { + return methods; } return new PsiMethod[]{ generateGetter @@ -72,21 +78,16 @@ public static PsiMethod[] generateGetterSetters(PsiField field, boolean generate } public static boolean isReadOnlyProperty(PsiField field) { - for (GetterSetterPrototypeProvider provider : Extensions.getExtensions(EP_NAME)) { - if (provider.canGeneratePrototypeFor(field)) { - return provider.isReadOnly(field); - } - } - return field.hasModifierProperty(PsiModifier.FINAL); + ExtensionPoint ep = field.getApplication().getExtensionPoint(GetterSetterPrototypeProvider.class); + return ep.anyMatchSafe(provider -> provider.canGeneratePrototypeFor(field) && provider.isReadOnly(field)) || field.isFinal(); } public static PsiMethod[] findGetters(PsiClass aClass, String propertyName, boolean isStatic) { if (!isStatic) { - for (GetterSetterPrototypeProvider provider : Extensions.getExtensions(EP_NAME)) { - PsiMethod[] getterSetter = provider.findGetters(aClass, propertyName); - if (getterSetter != null) { - return getterSetter; - } + PsiMethod[] getterSetter = aClass.getApplication().getExtensionPoint(GetterSetterPrototypeProvider.class) + .computeSafeIfAny(provider -> provider.findGetters(aClass, propertyName)); + if (getterSetter != null) { + return getterSetter; } } PsiMethod propertyGetterSetter = PropertyUtil.findPropertyGetter(aClass, propertyName, isStatic, false); @@ -97,11 +98,8 @@ public static PsiMethod[] findGetters(PsiClass aClass, String propertyName, bool } public static String suggestNewGetterName(String oldPropertyName, String newPropertyName, PsiMethod method) { - for (GetterSetterPrototypeProvider provider : Extensions.getExtensions(EP_NAME)) { - if (provider.isSimpleGetter(method, oldPropertyName)) { - return provider.suggestGetterName(newPropertyName); - } - } - return null; + return method.getApplication().getExtensionPoint(GetterSetterPrototypeProvider.class).computeSafeIfAny( + provider -> provider.isSimpleGetter(method, oldPropertyName) ? provider.suggestGetterName(newPropertyName) : null + ); } } diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/OverrideImplementUtil.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/OverrideImplementUtil.java index 6a69cc5960..984a4d3611 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/OverrideImplementUtil.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/OverrideImplementUtil.java @@ -20,7 +20,6 @@ import com.intellij.java.impl.codeInsight.MethodImplementor; import com.intellij.java.language.JavaLanguage; import com.intellij.java.language.codeInsight.AnnotationUtil; -import com.intellij.java.language.impl.codeInsight.generation.GenerationInfo; import com.intellij.java.language.impl.codeInsight.generation.OverrideImplementExploreUtil; import com.intellij.java.language.impl.codeInsight.template.JavaTemplateUtil; import com.intellij.java.language.psi.*; @@ -30,7 +29,6 @@ import com.intellij.java.language.psi.util.*; import consulo.annotation.access.RequiredReadAction; import consulo.annotation.access.RequiredWriteAction; -import consulo.application.Application; import consulo.application.Result; import consulo.codeEditor.Editor; import consulo.codeEditor.ScrollType; @@ -83,10 +81,6 @@ public class OverrideImplementUtil extends OverrideImplementExploreUtil { private OverrideImplementUtil() { } - protected static List getImplementors() { - return Application.get().getExtensionList(MethodImplementor.class); - } - /** * generate methods (with bodies) corresponding to given method declaration * there are maybe two method implementations for one declaration @@ -149,13 +143,13 @@ public static List overrideOrImplementMethod( } List results = new ArrayList<>(); - for (MethodImplementor implementor : getImplementors()) { + aClass.getApplication().getExtensionPoint(MethodImplementor.class).forEach(implementor -> { PsiMethod[] prototypes = implementor.createImplementationPrototypes(aClass, method); for (PsiMethod prototype : prototypes) { implementor.createDecorator(aClass, method, toCopyJavaDoc, insertOverrideIfPossible).accept(prototype); results.add(prototype); } - } + }); if (results.isEmpty()) { PsiMethod method1 = GenerateMembersUtil.substituteGenericMethod(method, substitutor, aClass); @@ -287,7 +281,7 @@ public static void annotateOnOverrideImplement(PsiMethod method, PsiClass target GlobalSearchScope moduleScope = module != null ? GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module) : null; Project project = targetClass.getProject(); JavaPsiFacade facade = JavaPsiFacade.getInstance(project); - for (OverrideImplementsAnnotationsHandler each : OverrideImplementsAnnotationsHandler.EP_NAME.getExtensions()) { + project.getApplication().getExtensionPoint(OverrideImplementsAnnotationsHandler.class).forEach(each -> { for (String annotation : each.getAnnotations(project)) { if (moduleScope != null && facade.findClass(annotation, moduleScope) == null) { continue; @@ -296,14 +290,14 @@ public static void annotateOnOverrideImplement(PsiMethod method, PsiClass target && !AnnotationUtil.isAnnotated(method, annotation, false, false)) { PsiAnnotation psiAnnotation = AnnotationUtil.findAnnotation(overridden, annotation); if (psiAnnotation != null && AnnotationUtil.isInferredAnnotation(psiAnnotation)) { - continue; + return; } AddAnnotationPsiFix.removePhysicalAnnotations(method, each.annotationsToRemove(project, annotation)); AddAnnotationPsiFix.addPhysicalAnnotationTo(annotation, PsiNameValuePair.EMPTY_ARRAY, method.getModifierList()); } } - } + }); } public static void annotate(@Nonnull PsiMethod result, String fqn, String... annosToRemove) throws IncorrectOperationException { @@ -357,14 +351,10 @@ public static PsiGenerationInfo createGenerationInfo(PsiMethod s) { } public static PsiGenerationInfo createGenerationInfo(PsiMethod s, boolean mergeIfExists) { - for (MethodImplementor implementor : getImplementors()) { - GenerationInfo info = implementor.createGenerationInfo(s, mergeIfExists); - if (info instanceof PsiGenerationInfo generationInfo) { - //noinspection unchecked - return generationInfo; - } - } - return new PsiGenerationInfo<>(s); + PsiGenerationInfo generationInfo = s.getApplication().getExtensionPoint(MethodImplementor.class).computeSafeIfAny( + implementor -> implementor.createGenerationInfo(s, mergeIfExists) instanceof PsiGenerationInfo genInfo ? genInfo : null + ); + return generationInfo != null ? generationInfo : new PsiGenerationInfo<>(s); } @Nonnull