Skip to content

Commit

Permalink
Visit multiple elements for diverse before templates (#117)
Browse files Browse the repository at this point in the history
* Visit multiple elements for diverse before templates

* Generate visit methods per type used in before template

* Update other recipes outputs affected by these changes
  • Loading branch information
timtebeek authored Nov 24, 2024
1 parent dc10492 commit ab684fd
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -353,82 +353,95 @@ private String newAbstractRefasterJavaVisitor(Map<String, TemplateDescriptor> be
}
visitor.append("\n");

List<String> lstTypes = LST_TYPE_MAP.get(getType(descriptor.beforeTemplates.get(0).method));
for (String lstType : lstTypes) {
String methodSuffix = lstType.startsWith("J.") ? lstType.substring(2) : lstType;
visitor.append(" @Override\n");
visitor.append(" public J visit").append(methodSuffix).append("(").append(lstType).append(" elem, ExecutionContext ctx) {\n");
if (lstType.equals("Statement")) {
visitor.append(" if (elem instanceof J.Block) {\n");
visitor.append(" // FIXME workaround\n");
visitor.append(" return elem;\n");
visitor.append(" }\n");
}
SortedSet<String> visitMethods = new TreeSet<>();
beforeTemplates.entrySet().stream()
.collect(groupingBy(
entry -> getType(entry.getValue().method),
toMap(Map.Entry::getKey, Map.Entry::getValue)))
.forEach((type, templates) -> {
for (String lstType : LST_TYPE_MAP.get(type)) {
visitMethods.add(generateVisitMethod(templates, after, descriptor, lstType));
}
});
visitMethods.forEach(visitor::append);
visitor.append(" }");
return visitor.toString();
}

visitor.append(" JavaTemplate.Matcher matcher;\n");
for (Map.Entry<String, TemplateDescriptor> entry : beforeTemplates.entrySet()) {
int arity = entry.getValue().getArity();
for (int i = 0; i < arity; i++) {
visitor.append(" if (" + "(matcher = ").append(entry.getKey()).append(arity > 1 ? "$" + i : "").append(".matcher(getCursor())).find()").append(") {\n");
com.sun.tools.javac.util.List<JCTree.JCVariableDecl> jcVariableDecls = entry.getValue().method.getParameters();
for (int j = 0; j < jcVariableDecls.size(); j++) {
JCTree.JCVariableDecl param = jcVariableDecls.get(j);
com.sun.tools.javac.util.List<JCTree.JCAnnotation> annotations = param.getModifiers().getAnnotations();
for (JCTree.JCAnnotation jcAnnotation : annotations) {
String annotationType = jcAnnotation.attribute.type.tsym.getQualifiedName().toString();
if (annotationType.equals("org.openrewrite.java.template.NotMatches")) {
String matcher = ((Type.ClassType) jcAnnotation.attribute.getValue().values.get(0).snd.getValue()).tsym.getQualifiedName().toString();
visitor.append(" if (new ").append(matcher).append("().matches((Expression) matcher.parameter(").append(j).append("))) {\n");
visitor.append(" return super.visit").append(methodSuffix).append("(elem, ctx);\n");
visitor.append(" }\n");
} else if (annotationType.equals("org.openrewrite.java.template.Matches")) {
String matcher = ((Type.ClassType) jcAnnotation.attribute.getValue().values.get(0).snd.getValue()).tsym.getQualifiedName().toString();
visitor.append(" if (!new ").append(matcher).append("().matches((Expression) matcher.parameter(").append(j).append("))) {\n");
visitor.append(" return super.visit").append(methodSuffix).append("(elem, ctx);\n");
visitor.append(" }\n");
}
private String generateVisitMethod(Map<String, TemplateDescriptor> beforeTemplates, String after, RuleDescriptor descriptor, String lstType) {
StringBuilder visitMethod = new StringBuilder();
String methodSuffix = lstType.startsWith("J.") ? lstType.substring(2) : lstType;
visitMethod.append(" @Override\n");
visitMethod.append(" public J visit").append(methodSuffix).append("(").append(lstType).append(" elem, ExecutionContext ctx) {\n");
if (lstType.equals("Statement")) {
visitMethod.append(" if (elem instanceof J.Block) {\n");
visitMethod.append(" // FIXME workaround\n");
visitMethod.append(" return elem;\n");
visitMethod.append(" }\n");
}

visitMethod.append(" JavaTemplate.Matcher matcher;\n");
for (Map.Entry<String, TemplateDescriptor> entry : beforeTemplates.entrySet()) {
int arity = entry.getValue().getArity();
for (int i = 0; i < arity; i++) {
visitMethod.append(" if (" + "(matcher = ").append(entry.getKey()).append(arity > 1 ? "$" + i : "").append(".matcher(getCursor())).find()").append(") {\n");
com.sun.tools.javac.util.List<JCTree.JCVariableDecl> jcVariableDecls = entry.getValue().method.getParameters();
for (int j = 0; j < jcVariableDecls.size(); j++) {
JCTree.JCVariableDecl param = jcVariableDecls.get(j);
com.sun.tools.javac.util.List<JCTree.JCAnnotation> annotations = param.getModifiers().getAnnotations();
for (JCTree.JCAnnotation jcAnnotation : annotations) {
String annotationType = jcAnnotation.attribute.type.tsym.getQualifiedName().toString();
if (annotationType.equals("org.openrewrite.java.template.NotMatches")) {
String matcher = ((Type.ClassType) jcAnnotation.attribute.getValue().values.get(0).snd.getValue()).tsym.getQualifiedName().toString();
visitMethod.append(" if (new ").append(matcher).append("().matches((Expression) matcher.parameter(").append(j).append("))) {\n");
visitMethod.append(" return super.visit").append(methodSuffix).append("(elem, ctx);\n");
visitMethod.append(" }\n");
} else if (annotationType.equals("org.openrewrite.java.template.Matches")) {
String matcher = ((Type.ClassType) jcAnnotation.attribute.getValue().values.get(0).snd.getValue()).tsym.getQualifiedName().toString();
visitMethod.append(" if (!new ").append(matcher).append("().matches((Expression) matcher.parameter(").append(j).append("))) {\n");
visitMethod.append(" return super.visit").append(methodSuffix).append("(elem, ctx);\n");
visitMethod.append(" }\n");
}
}
}

if (descriptor.afterTemplate == null) {
visitor.append(" return SearchResult.found(elem);\n");
} else {
maybeRemoveImports(imports, visitor, entry.getValue(), i, descriptor.afterTemplate);
maybeRemoveStaticImports(staticImports, visitor, entry.getValue(), i, descriptor.afterTemplate);

List<String> embedOptions = new ArrayList<>();
JCTree.JCExpression afterReturn = getReturnExpression(descriptor.afterTemplate.method);
if (afterReturn instanceof JCTree.JCParens ||
afterReturn instanceof JCTree.JCUnary && ((JCTree.JCUnary) afterReturn).getExpression() instanceof JCTree.JCParens) {
embedOptions.add("REMOVE_PARENS");
}
// TODO check if after template contains type or member references
embedOptions.add("SHORTEN_NAMES");
if (simplifyBooleans(descriptor.afterTemplate.method)) {
embedOptions.add("SIMPLIFY_BOOLEANS");
}
if (descriptor.afterTemplate == null) {
visitMethod.append(" return SearchResult.found(elem);\n");
} else {
maybeRemoveImports(imports, visitMethod, entry.getValue(), i, descriptor.afterTemplate);
maybeRemoveStaticImports(staticImports, visitMethod, entry.getValue(), i, descriptor.afterTemplate);

List<String> embedOptions = new ArrayList<>();
JCTree.JCExpression afterReturn = getReturnExpression(descriptor.afterTemplate.method);
if (afterReturn instanceof JCTree.JCParens ||
afterReturn instanceof JCTree.JCUnary && ((JCTree.JCUnary) afterReturn).getExpression() instanceof JCTree.JCParens) {
embedOptions.add("REMOVE_PARENS");
}
// TODO check if after template contains type or member references
embedOptions.add("SHORTEN_NAMES");
if (simplifyBooleans(descriptor.afterTemplate.method)) {
embedOptions.add("SIMPLIFY_BOOLEANS");
}

visitor.append(" return embed(\n");
visitor.append(" ").append(after).append(".apply(getCursor(), elem.getCoordinates().replace()");
String parameters = parameters(entry.getValue(), descriptor);
if (!parameters.isEmpty()) {
visitor.append(", ").append(parameters);
}
visitor.append("),\n");
visitor.append(" getCursor(),\n");
visitor.append(" ctx,\n");
visitor.append(" ").append(String.join(", ", embedOptions)).append("\n");
visitor.append(" );\n");
visitMethod.append(" return embed(\n");
visitMethod.append(" ").append(after).append(".apply(getCursor(), elem.getCoordinates().replace()");
String parameters = parameters(entry.getValue(), descriptor);
if (!parameters.isEmpty()) {
visitMethod.append(", ").append(parameters);
}
visitor.append(" }\n");
visitMethod.append("),\n");
visitMethod.append(" getCursor(),\n");
visitMethod.append(" ctx,\n");
visitMethod.append(" ").append(String.join(", ", embedOptions)).append("\n");
visitMethod.append(" );\n");
}
visitMethod.append(" }\n");
}
visitor.append(" return super.visit").append(methodSuffix).append("(elem, ctx);\n");
visitor.append(" }\n");
visitor.append("\n");
}
visitor.append(" }");
return visitor.toString();
visitMethod.append(" return super.visit").append(methodSuffix).append("(elem, ctx);\n");
visitMethod.append(" }\n");
visitMethod.append("\n");
return visitMethod.toString();
}

private boolean simplifyBooleans(JCTree.JCMethodDecl template) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class RefasterTemplateProcessorTest {
"NewBufferedWriter",
"UseStringIsEmpty",
"SimplifyBooleans",
"TwoVisitMethods",
"FindListAdd",
})
void generateRecipe(String recipeName) {
Expand Down
8 changes: 4 additions & 4 deletions src/test/resources/refaster/ParametersRecipes.java
Original file line number Diff line number Diff line change
Expand Up @@ -153,17 +153,17 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
@Override
public J visitBinary(J.Binary elem, ExecutionContext ctx) {
JavaTemplate.Matcher matcher;
if ((matcher = before1.matcher(getCursor())).find()) {
if ((matcher = before2.matcher(getCursor())).find()) {
return embed(
after.apply(getCursor(), elem.getCoordinates().replace(), matcher.parameter(0), matcher.parameter(1)),
after.apply(getCursor(), elem.getCoordinates().replace(), matcher.parameter(1), matcher.parameter(0)),
getCursor(),
ctx,
SHORTEN_NAMES, SIMPLIFY_BOOLEANS
);
}
if ((matcher = before2.matcher(getCursor())).find()) {
if ((matcher = before1.matcher(getCursor())).find()) {
return embed(
after.apply(getCursor(), elem.getCoordinates().replace(), matcher.parameter(1), matcher.parameter(0)),
after.apply(getCursor(), elem.getCoordinates().replace(), matcher.parameter(0), matcher.parameter(1)),
getCursor(),
ctx,
SHORTEN_NAMES, SIMPLIFY_BOOLEANS
Expand Down
14 changes: 10 additions & 4 deletions src/test/resources/refaster/ShouldAddImportsRecipes.java
Original file line number Diff line number Diff line change
Expand Up @@ -159,18 +159,24 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
.build();

@Override
public J visitMethodInvocation(J.MethodInvocation elem, ExecutionContext ctx) {
public J visitBinary(J.Binary elem, ExecutionContext ctx) {
JavaTemplate.Matcher matcher;
if ((matcher = equals.matcher(getCursor())).find()) {
maybeRemoveImport("java.util.Objects");
if ((matcher = compareZero.matcher(getCursor())).find()) {
return embed(
isis.apply(getCursor(), elem.getCoordinates().replace(), matcher.parameter(0), matcher.parameter(1)),
getCursor(),
ctx,
SHORTEN_NAMES, SIMPLIFY_BOOLEANS
);
}
if ((matcher = compareZero.matcher(getCursor())).find()) {
return super.visitBinary(elem, ctx);
}

@Override
public J visitMethodInvocation(J.MethodInvocation elem, ExecutionContext ctx) {
JavaTemplate.Matcher matcher;
if ((matcher = equals.matcher(getCursor())).find()) {
maybeRemoveImport("java.util.Objects");
return embed(
isis.apply(getCursor(), elem.getCoordinates().replace(), matcher.parameter(0), matcher.parameter(1)),
getCursor(),
Expand Down
38 changes: 38 additions & 0 deletions src/test/resources/refaster/TwoVisitMethods.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package foo;

import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;

public class TwoVisitMethods {

@BeforeTemplate
boolean lengthIsZero(String s) {
return s.length() == 0;
}

@BeforeTemplate
boolean equalsEmptyString(String s) {
return s.equals("");
}

@AfterTemplate
boolean isEmpty(String s) {
return s.isEmpty();
}

}
Loading

0 comments on commit ab684fd

Please sign in to comment.