Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RemoveEmptyJavaDocParameters should remove "empty" lines #98 #190

Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,9 @@
*/
package org.openrewrite.staticanalysis;

import org.openrewrite.ExecutionContext;
import org.openrewrite.Incubating;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.*;
import org.openrewrite.internal.ListUtils;
import org.openrewrite.internal.StringUtils;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.JavadocVisitor;
import org.openrewrite.java.tree.Comment;
Expand Down Expand Up @@ -77,89 +75,95 @@ public RemoveEmptyParamVisitor() {
public Javadoc visitDocComment(Javadoc.DocComment javadoc, ExecutionContext ctx) {
List<Javadoc> newBody = new ArrayList<>(javadoc.getBody().size());
boolean useNewBody = false;
boolean removeTrailingLineBreaks = false;

List<Javadoc> body = new ArrayList<>(javadoc.getBody());
// We add a trailing element, to fix elements on the first line without space
// We can use null since this element is never going to be read, and nulls get filtered later on.
body.add(0, null);
List<Javadoc> body = javadoc.getBody();

for (int i = 0; i < body.size(); i++) {
// JavaDocs require a look ahead, because the current element may be an element that exists on the same line as a parameter.
// I.E. the space that precedes `* @param` will be a `Javadoc.Text` and needs to be removed along with the empty `@param`.
// A `Javadoc` will always precede a parameter even if there is empty space like `*@param`.
Javadoc currentDoc = body.get(i);
if (i + 1 < body.size()) {
Javadoc nextDoc = body.get(i + 1);
if (nextDoc instanceof Javadoc.Parameter) {
Javadoc.Parameter nextParameter = (Javadoc.Parameter) nextDoc;
if (isEmptyParameter(nextParameter)) {
// The `@param` being removed is the last item in the JavaDoc body, and contains
// relevant whitespace via the JavaDoc.LineBreak.
if (i + 1 == body.size() - 1) {
// If we have a previous LineBreak we need to remove it before adding the new one
if (!newBody.isEmpty() && newBody.get(newBody.size() - 1) instanceof Javadoc.LineBreak) {
newBody.remove(newBody.size() - 1);
}
if (!nextParameter.getDescription().isEmpty()) {
newBody.add(nextParameter.getDescription().get(0));
}
}

// No need to reprocess the next element.
i += 1;

useNewBody = true;
currentDoc = null;
}
} else if (nextDoc instanceof Javadoc.Return) {
Javadoc.Return nextReturn = (Javadoc.Return) nextDoc;
if (isEmptyReturn(nextReturn)) {
// The `@return` being removed is the last item in the JavaDoc body, and contains
// relevant whitespace via the JavaDoc.LineBreak.
if (i + 1 == body.size() - 1) {
// If we have a previous LineBreak we need to remove it before adding the new one
if (!newBody.isEmpty() && newBody.get(newBody.size() - 1) instanceof Javadoc.LineBreak) {
newBody.remove(newBody.size() - 1);
}
if (!nextReturn.getDescription().isEmpty()) {
newBody.add(nextReturn.getDescription().get(0));
}
}

// No need to reprocess the next element.
i += 1;

useNewBody = true;
currentDoc = null;

if (removeTrailingLineBreaks && i != body.size() - 1) {
if (currentDoc instanceof Javadoc.LineBreak && !(body.get(i + 1) instanceof Javadoc.LineBreak)) {
removeTrailingLineBreaks = false;
} else {
continue;
}
}

if (currentDoc instanceof Javadoc.Parameter) {
Javadoc.Parameter parameter = (Javadoc.Parameter) currentDoc;
if (isEmptyParameter(parameter)) {
removeSpaceAndLinebreaks(newBody);
// Add last LineBreak which may be '\n ' or '\n *' fixes '**/' at the end of docs after removal of empty @param
List<Javadoc> descriptions = ((Javadoc.Parameter) body.get(i)).getDescription();
if (!descriptions.isEmpty()) {
newBody.add(descriptions.get(descriptions.size() - 1));
}
} else if (nextDoc instanceof Javadoc.Erroneous) {
Javadoc.Erroneous nextErroneous = (Javadoc.Erroneous) nextDoc;
if (isEmptyErroneous(nextErroneous)) {
if (!newBody.isEmpty() && newBody.get(newBody.size() - 1) instanceof Javadoc.LineBreak) {
newBody.remove(newBody.size() - 1);
}

// No need to reprocess the next element.
i += 1;

useNewBody = true;
currentDoc = null;

useNewBody = true;
} else {
visitParameter(parameter, ctx);
newBody.add(currentDoc);
}
} else if (currentDoc instanceof Javadoc.Return) {
Javadoc.Return aReturn = (Javadoc.Return) currentDoc;
if (isEmptyReturn(aReturn)) {
removeSpaceAndLinebreaks(newBody);
// Add last LineBreak which may be '\n ' or '\n *' fixes '**/' at the end of docs after removal of empty @return
List<Javadoc> descriptions = ((Javadoc.Return) body.get(i)).getDescription();
if (!descriptions.isEmpty()) {
newBody.add(descriptions.get(descriptions.size() - 1));
}

useNewBody = true;
} else {
visitReturn(aReturn, ctx);
newBody.add(currentDoc);
}
}
} else if (currentDoc instanceof Javadoc.Throws) {
Javadoc.Throws aThrows = (Javadoc.Throws) currentDoc;
if (isEmptyThrows(aThrows)) {
removeSpaceAndLinebreaks(newBody);
// Add last LineBreak which may be '\n ' or '\n *' fixes '**/' at the end of docs after removal of empty @throws
List<Javadoc> descriptions = ((Javadoc.Throws) body.get(i)).getDescription();
if (!descriptions.isEmpty()) {
newBody.add(descriptions.get(descriptions.size() - 1));
}

if (currentDoc != null) {
useNewBody = true;
} else {
visitThrows(aThrows, ctx);
newBody.add(currentDoc);
}
} else if (currentDoc instanceof Javadoc.Erroneous) {
Javadoc.Erroneous erroneous = (Javadoc.Erroneous) currentDoc;
if (isEmptyErroneous(erroneous)) {
removeSpaceAndLinebreaks(newBody);

useNewBody = true;
// Erroneous doesn't have list of descriptions, so have to skip trailing LineBreaks manually.
removeTrailingLineBreaks = true;
} else {
visitErroneous(erroneous, ctx);
newBody.add(currentDoc);
}
} else {
newBody.add(currentDoc);
}
}

// If after "cleaning" JavaDocs is empty remove it.
if (isWholeDescriptionBlank(getCursor(), newBody)) {
return null;
}

if (useNewBody) {
javadoc = javadoc.withBody(newBody);
}
// No need to call super visitor, already covered all cases by adding an empty first element when needed.
return javadoc;
return super.visitDocComment(javadoc, ctx);
}


public boolean isEmptyParameter(Javadoc.Parameter parameter) {
return parameter.getDescription().stream().allMatch(Javadoc.LineBreak.class::isInstance);
}
Expand All @@ -168,13 +172,40 @@ public boolean isEmptyReturn(Javadoc.Return aReturn) {
return aReturn.getDescription().stream().allMatch(Javadoc.LineBreak.class::isInstance);
}

public boolean isEmptyThrows(Javadoc.Throws aThrows) {
return aThrows.getDescription().stream().allMatch(Javadoc.LineBreak.class::isInstance);
}

public boolean isEmptyErroneous(Javadoc.Erroneous erroneous) {
// Empty throws result in an Erroneous type.
return erroneous.getText().size() == 1 &&
erroneous.getText().get(0) instanceof Javadoc.Text &&
"@throws".equals(((Javadoc.Text) erroneous.getText().get(0)).getText());
if (erroneous.getText().size() == 1 && erroneous.getText().get(0) instanceof Javadoc.Text) {
Javadoc.Text text = (Javadoc.Text) erroneous.getText().get(0);
// Empty param/return/throws result in an Erroneous type.
return "@param".equals(text.getText()) || "@return".equals(text.getText()) || "@throws".equals(text.getText());
}
return false;
}
}
};
}
}

private boolean isWholeDescriptionBlank(Cursor cursor, List<Javadoc> newBody) {
return newBody.stream().allMatch(jd -> {
PrintOutputCapture<Object> p = new PrintOutputCapture<>(null);
jd.printer(cursor).visit(jd, p);
return StringUtils.isBlank(p.getOut());
});
}

private void removeSpaceAndLinebreaks(List<Javadoc> newBody) {
// The space that precedes `* @param`/`* @return`/`* @throws` will be a `Javadoc.Text` and needs to be removed along with the empty `@param`/`@return`/`@throws`.
if (!newBody.isEmpty() && newBody.get(newBody.size() - 1) instanceof Javadoc.Text) {
if (StringUtils.isBlank(((Javadoc.Text) newBody.get(newBody.size() - 1)).getText())) {
newBody.remove(newBody.size() - 1);
}
}
// Last added LineBreak which is in the same line as currentDoc.
if (!newBody.isEmpty() && newBody.get(newBody.size() - 1) instanceof Javadoc.LineBreak) {
newBody.remove(newBody.size() - 1);
}
}
}
Loading