diff --git a/src/main/java/org/openrewrite/java/template/Semantics.java b/src/main/java/org/openrewrite/java/template/Semantics.java index d370cd75..a7400fb8 100644 --- a/src/main/java/org/openrewrite/java/template/Semantics.java +++ b/src/main/java/org/openrewrite/java/template/Semantics.java @@ -15,6 +15,7 @@ */ package org.openrewrite.java.template; +import org.openrewrite.ExecutionContext; import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.JavaVisitor; import org.openrewrite.java.template.function.*; @@ -112,4 +113,94 @@ public static JavaTemplate.Builder statement(JavaVisitor owner, String name, public static JavaTemplate.Builder statement(JavaVisitor owner, String name, Stat10 p) { return new PatternBuilder(name).build(owner); } + + // Again, but with a first ExecutionContext argument for JavaParser classpathFromResources + + public static JavaTemplate.Builder expression(ExecutionContext ctx, JavaVisitor owner, String name, Expr0 f) { + return new PatternBuilder(name).build(ctx, owner); + } + + public static JavaTemplate.Builder expression(ExecutionContext ctx, JavaVisitor owner, String name, Expr1 f) { + return new PatternBuilder(name).build(ctx, owner); + } + + public static JavaTemplate.Builder expression(ExecutionContext ctx, JavaVisitor owner, String name, Expr2 f) { + return new PatternBuilder(name).build(ctx, owner); + } + + public static JavaTemplate.Builder expression(ExecutionContext ctx, JavaVisitor owner, String name, Expr3 f) { + return new PatternBuilder(name).build(ctx, owner); + } + + public static JavaTemplate.Builder expression(ExecutionContext ctx, JavaVisitor owner, String name, Expr4 f) { + return new PatternBuilder(name).build(ctx, owner); + } + + public static JavaTemplate.Builder expression(ExecutionContext ctx, JavaVisitor owner, String name, Expr5 f) { + return new PatternBuilder(name).build(ctx, owner); + } + + public static JavaTemplate.Builder expression(ExecutionContext ctx, JavaVisitor owner, String name, Expr6 f) { + return new PatternBuilder(name).build(ctx, owner); + } + + public static JavaTemplate.Builder expression(ExecutionContext ctx, JavaVisitor owner, String name, Expr7 f) { + return new PatternBuilder(name).build(ctx, owner); + } + + public static JavaTemplate.Builder expression(ExecutionContext ctx, JavaVisitor owner, String name, Expr8 f) { + return new PatternBuilder(name).build(ctx, owner); + } + + public static JavaTemplate.Builder expression(ExecutionContext ctx, JavaVisitor owner, String name, Expr9 f) { + return new PatternBuilder(name).build(ctx, owner); + } + + public static JavaTemplate.Builder expression(ExecutionContext ctx, JavaVisitor owner, String name, Expr10 f) { + return new PatternBuilder(name).build(ctx, owner); + } + + public static JavaTemplate.Builder statement(ExecutionContext ctx, JavaVisitor owner, String name, Stat0 p) { + return new PatternBuilder(name).build(ctx, owner); + } + + public static JavaTemplate.Builder statement(ExecutionContext ctx, JavaVisitor owner, String name, Stat1 p) { + return new PatternBuilder(name).build(ctx, owner); + } + + public static JavaTemplate.Builder statement(ExecutionContext ctx, JavaVisitor owner, String name, Stat2 p) { + return new PatternBuilder(name).build(ctx, owner); + } + + public static JavaTemplate.Builder statement(ExecutionContext ctx, JavaVisitor owner, String name, Stat3 p) { + return new PatternBuilder(name).build(ctx, owner); + } + + public static JavaTemplate.Builder statement(ExecutionContext ctx, JavaVisitor owner, String name, Stat4 p) { + return new PatternBuilder(name).build(ctx, owner); + } + + public static JavaTemplate.Builder statement(ExecutionContext ctx, JavaVisitor owner, String name, Stat5 p) { + return new PatternBuilder(name).build(ctx, owner); + } + + public static JavaTemplate.Builder statement(ExecutionContext ctx, JavaVisitor owner, String name, Stat6 p) { + return new PatternBuilder(name).build(ctx, owner); + } + + public static JavaTemplate.Builder statement(ExecutionContext ctx, JavaVisitor owner, String name, Stat7 p) { + return new PatternBuilder(name).build(ctx, owner); + } + + public static JavaTemplate.Builder statement(ExecutionContext ctx, JavaVisitor owner, String name, Stat8 p) { + return new PatternBuilder(name).build(ctx, owner); + } + + public static JavaTemplate.Builder statement(ExecutionContext ctx, JavaVisitor owner, String name, Stat9 p) { + return new PatternBuilder(name).build(ctx, owner); + } + + public static JavaTemplate.Builder statement(ExecutionContext ctx, JavaVisitor owner, String name, Stat10 p) { + return new PatternBuilder(name).build(ctx, owner); + } } diff --git a/src/main/java/org/openrewrite/java/template/internal/ClasspathJarNameDetector.java b/src/main/java/org/openrewrite/java/template/internal/ClasspathJarNameDetector.java index ab59a8a0..d8c4fd16 100644 --- a/src/main/java/org/openrewrite/java/template/internal/ClasspathJarNameDetector.java +++ b/src/main/java/org/openrewrite/java/template/internal/ClasspathJarNameDetector.java @@ -19,6 +19,7 @@ import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCFieldAccess; import com.sun.tools.javac.tree.TreeScanner; +import org.jspecify.annotations.Nullable; import javax.tools.JavaFileObject; import java.util.LinkedHashSet; @@ -38,7 +39,7 @@ public class ClasspathJarNameDetector { public static Set classpathFor(JCTree input, List imports) { Set jarNames = new LinkedHashSet() { @Override - public boolean add(String s) { + public boolean add(@Nullable String s) { return s != null && super.add(s); } }; @@ -47,13 +48,13 @@ public boolean add(String s) { jarNames.add(jarNameFor(anImport)); } - // Detect fully qualified classes new TreeScanner() { @Override public void scan(JCTree tree) { + // Detect fully qualified classes if (tree instanceof JCFieldAccess && - ((JCFieldAccess) tree).sym instanceof Symbol.ClassSymbol && - Character.isUpperCase(((JCFieldAccess) tree).getIdentifier().toString().charAt(0))) { + ((JCFieldAccess) tree).sym instanceof Symbol.ClassSymbol && + Character.isUpperCase(((JCFieldAccess) tree).getIdentifier().toString().charAt(0))) { jarNames.add(jarNameFor(((JCFieldAccess) tree).sym)); } super.scan(tree); @@ -64,7 +65,7 @@ public void scan(JCTree tree) { } - private static String jarNameFor(Symbol anImport) { + private static @Nullable String jarNameFor(Symbol anImport) { Symbol.ClassSymbol enclClass = anImport instanceof Symbol.ClassSymbol ? (Symbol.ClassSymbol) anImport : anImport.enclClass(); while (enclClass.enclClass() != null && enclClass.enclClass() != enclClass) { enclClass = enclClass.enclClass(); diff --git a/src/main/java/org/openrewrite/java/template/internal/PatternBuilder.java b/src/main/java/org/openrewrite/java/template/internal/PatternBuilder.java index 7f6f45e7..839ae72c 100644 --- a/src/main/java/org/openrewrite/java/template/internal/PatternBuilder.java +++ b/src/main/java/org/openrewrite/java/template/internal/PatternBuilder.java @@ -16,6 +16,7 @@ package org.openrewrite.java.template.internal; import lombok.Value; +import org.openrewrite.ExecutionContext; import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.JavaVisitor; @@ -38,4 +39,16 @@ public JavaTemplate.Builder build(JavaVisitor owner) { throw new RuntimeException(e); } } + + public JavaTemplate.Builder build(ExecutionContext ctx, JavaVisitor owner) { + try { + Class templateClass = Class.forName(owner.getClass().getName() + "_" + name, true, + owner.getClass().getClassLoader()); + Method getTemplate = templateClass.getDeclaredMethod("getTemplate", ExecutionContext.class); + return (JavaTemplate.Builder) getTemplate.invoke(null, ctx); + } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | + IllegalAccessException e) { + throw new RuntimeException(e); + } + } } diff --git a/src/main/java/org/openrewrite/java/template/internal/TemplateCode.java b/src/main/java/org/openrewrite/java/template/internal/TemplateCode.java index a9a22d25..30eb4407 100644 --- a/src/main/java/org/openrewrite/java/template/internal/TemplateCode.java +++ b/src/main/java/org/openrewrite/java/template/internal/TemplateCode.java @@ -35,7 +35,15 @@ public class TemplateCode { - public static String process(T tree, @Nullable Type returnType, List parameters, List typeParameters, int pos, boolean asStatement, boolean fullyQualified) { + public static String process( + T tree, + @Nullable Type returnType, + List parameters, + List typeParameters, + int pos, + boolean asStatement, + boolean fullyQualified, + boolean classpathFromResources) { StringWriter writer = new StringWriter(); TemplateCodePrinter printer = new TemplateCodePrinter(writer, parameters, pos, fullyQualified); try { @@ -62,18 +70,18 @@ public static String process(T tree, @Nullable Type returnTyp if (!printer.staticImports.isEmpty()) { builder.append("\n .staticImports(").append(printer.staticImports.stream().map(i -> '"' + i + '"').collect(joining(", "))).append(")"); } - List imports = ImportDetector.imports(tree); - Set jarNames = ClasspathJarNameDetector.classpathFor(tree, imports); + Set jarNames = ClasspathJarNameDetector.classpathFor(tree, ImportDetector.imports(tree)); for (JCTree.JCVariableDecl parameter : parameters) { jarNames.addAll(ClasspathJarNameDetector.classpathFor(parameter, ImportDetector.imports(parameter))); } if (!jarNames.isEmpty()) { - // It might be preferable to enumerate exactly the needed dependencies rather than the full classpath - // But this is expedient - // See https://github.com/openrewrite/rewrite-templating/issues/86 - // String classpath = jarNames.stream().map(jarName -> '"' + jarName + '"').sorted().collect(joining(", ")); - // builder.append("\n .javaParser(JavaParser.fromJavaVersion().classpath(").append(classpath).append("))"); - builder.append("\n .javaParser(JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath()))"); + builder.append("\n .javaParser(JavaParser.fromJavaVersion()"); + if (classpathFromResources) { + String joinedJarNames = jarNames.stream().collect(joining("\", \"", "\"", "\"")); + builder.append(".classpathFromResources(ctx, ").append(joinedJarNames).append("))\n "); + } else { + builder.append(".classpath(JavaParser.runtimeClasspath()))\n "); + } } return builder.toString(); } catch (IOException e) { diff --git a/src/main/java/org/openrewrite/java/template/processor/TemplateDescriptor.java b/src/main/java/org/openrewrite/java/template/processor/TemplateDescriptor.java index 7a3a6b14..445b821a 100644 --- a/src/main/java/org/openrewrite/java/template/processor/TemplateDescriptor.java +++ b/src/main/java/org/openrewrite/java/template/processor/TemplateDescriptor.java @@ -148,8 +148,19 @@ public String toJavaTemplateBuilder(int pos) { tree = ((JCTree.JCReturn) tree).getExpression(); } + String javaParserClasspathFrom = processingEnv.getOptions().get("rewrite.javaParserClasspathFrom"); + boolean classpathFromResources = "resources".equals(javaParserClasspathFrom); + List typeParameters = classDecl.typarams == null ? emptyList() : classDecl.typarams; - return TemplateCode.process(tree, method.getReturnType().type, method.getParameters(), typeParameters, pos, method.restype.type instanceof Type.JCVoidType, true); + return TemplateCode.process( + tree, + method.getReturnType().type, + method.getParameters(), + typeParameters, + pos, + method.restype.type instanceof Type.JCVoidType, + true, + classpathFromResources); } public boolean validate() { diff --git a/src/main/java/org/openrewrite/java/template/processor/TemplateProcessor.java b/src/main/java/org/openrewrite/java/template/processor/TemplateProcessor.java index f683a21a..5816268d 100644 --- a/src/main/java/org/openrewrite/java/template/processor/TemplateProcessor.java +++ b/src/main/java/org/openrewrite/java/template/processor/TemplateProcessor.java @@ -74,118 +74,145 @@ public void visitApply(JCTree.JCMethodInvocation tree) { ((JCTree.JCFieldAccess) jcSelect).name.toString() : ((JCTree.JCIdent) jcSelect).getName().toString(); - if (("expression".equals(name) || "statement".equals(name)) && tree.getArguments().size() == 3) { - JCTree.JCMethodInvocation resolvedMethod; - Map resolved; - try { - resolved = res.resolveAll(context, cu, singletonList(tree)); - resolvedMethod = (JCTree.JCMethodInvocation) resolved.get(tree); - } catch (Throwable t) { - processingEnv.getMessager().printMessage(Kind.WARNING, "Had trouble type attributing the template."); - return; - } + int numberOfArguments = tree.getArguments().size(); + if (!"expression".equals(name) && !"statement".equals(name)) { + super.visitApply(tree); + return; + } + if (numberOfArguments < 3 || 4 < numberOfArguments) { + return; + } + boolean classpathFromResources = numberOfArguments == 4; + + JCTree.JCMethodInvocation resolvedMethod; + Map resolved; + try { + resolved = res.resolveAll(context, cu, singletonList(tree)); + resolvedMethod = (JCTree.JCMethodInvocation) resolved.get(tree); + } catch (Throwable t) { + processingEnv.getMessager().printMessage(Kind.WARNING, "Had trouble type attributing the template."); + return; + } - JCTree.JCExpression arg2 = tree.getArguments().get(2); - if (isOfClassType(resolvedMethod.type, "org.openrewrite.java.JavaTemplate.Builder") && + JCTree.JCExpression arg2 = tree.getArguments().get(2 + (classpathFromResources ? 1 : 0)); + if (isOfClassType(resolvedMethod.type, "org.openrewrite.java.JavaTemplate.Builder") && (arg2 instanceof JCTree.JCLambda || arg2 instanceof JCTree.JCTypeCast && ((JCTree.JCTypeCast) arg2).getExpression() instanceof JCTree.JCLambda)) { - JCTree.JCLambda template = arg2 instanceof JCTree.JCLambda ? (JCTree.JCLambda) arg2 : (JCTree.JCLambda) ((JCTree.JCTypeCast) arg2).getExpression(); + JCTree.JCLambda template = arg2 instanceof JCTree.JCLambda ? (JCTree.JCLambda) arg2 : (JCTree.JCLambda) ((JCTree.JCTypeCast) arg2).getExpression(); - List parameters; - if (template.getParameters().isEmpty()) { - parameters = emptyList(); - } else { - Map parameterResolution = res.resolveAll(context, cu, template.getParameters()); - parameters = new ArrayList<>(template.getParameters().size()); - for (VariableTree p : template.getParameters()) { - parameters.add((JCTree.JCVariableDecl) parameterResolution.get((JCTree) p)); - } + List parameters; + if (template.getParameters().isEmpty()) { + parameters = emptyList(); + } else { + Map parameterResolution = res.resolveAll(context, cu, template.getParameters()); + parameters = new ArrayList<>(template.getParameters().size()); + for (VariableTree p : template.getParameters()) { + parameters.add((JCTree.JCVariableDecl) parameterResolution.get((JCTree) p)); } + } + + JCTree.JCLiteral templateName = (JCTree.JCLiteral) tree.getArguments().get(1 + (classpathFromResources ? 1 : 0)); + if (templateName.value == null) { + processingEnv.getMessager().printMessage(Kind.WARNING, "Can't compile a template with a null name."); + return; + } - try { - JCTree.JCLiteral templateName = (JCTree.JCLiteral) tree.getArguments().get(1); - if (templateName.value == null) { - processingEnv.getMessager().printMessage(Kind.WARNING, "Can't compile a template with a null name."); - return; - } - - // this could be a visitor in the case that the visitor is in its own file or - // named inner class, or a recipe if the visitor is defined in an anonymous class - JCTree.JCClassDecl classDecl = cursor(cu, template).stream() - .filter(JCTree.JCClassDecl.class::isInstance) - .map(JCTree.JCClassDecl.class::cast) - .reduce((next, acc) -> next) - .orElseThrow(() -> new IllegalStateException("Expected to find an enclosing class")); - - String templateFqn; - - if (isOfClassType(classDecl.type, "org.openrewrite.java.JavaVisitor")) { - templateFqn = classDecl.sym.fullname.toString() + "_" + templateName.getValue().toString(); - } else { - JCTree.JCNewClass visitorClass = cursor(cu, template).stream() - .filter(JCTree.JCNewClass.class::isInstance) - .map(JCTree.JCNewClass.class::cast) - .reduce((next, acc) -> next) - .orElse(null); - - JCTree.JCNewClass resolvedVisitorClass = (JCTree.JCNewClass) resolved.get(visitorClass); - - if (resolvedVisitorClass != null && isOfClassType(resolvedVisitorClass.clazz.type, "org.openrewrite.java.JavaVisitor")) { - templateFqn = ((Symbol.ClassSymbol) resolvedVisitorClass.type.tsym).flatname.toString() + "_" + - templateName.getValue().toString(); - } else { - processingEnv.getMessager().printMessage(Kind.WARNING, "Can't compile a template outside of a visitor or recipe."); - return; - } - } - - String templateCode = TemplateCode.process(resolved.get(template.getBody()), null, parameters, emptyList(), 0, "statement".equals(name), false); - - Symbol.PackageSymbol pkg = classDecl.sym.packge(); - JavaFileObject builderFile = processingEnv.getFiler().createSourceFile(templateFqn); - try (Writer out = new BufferedWriter(builderFile.openWriter())) { - if (!pkg.isUnnamed()) { - out.write("package " + pkg.fullname + ";\n"); - out.write("\n"); - } - if (templateCode.contains("JavaParser")) { - out.write("import org.openrewrite.java.JavaParser;\n"); - } - out.write("import org.openrewrite.java.JavaTemplate;\n"); - - out.write("\n"); - out.write("/**\n * OpenRewrite `" + templateName.getValue() + "` template created for {@code " + templateFqn.split("_")[0] + "}.\n */\n"); - String templateClassName = templateFqn.substring(templateFqn.lastIndexOf('.') + 1); - out.write("@SuppressWarnings(\"all\")\n"); - out.write("public class " + templateClassName + " {\n"); - out.write(" /**\n"); - out.write(" * Instantiates a new instance.\n"); - out.write(" */\n"); - out.write(" public " + templateClassName + "() {}\n\n"); - out.write(" /**\n"); - out.write(" * Get the {@code JavaTemplate.Builder} to match or replace.\n"); - out.write(" * @return the JavaTemplate builder.\n"); - out.write(" */\n"); - out.write(" public static JavaTemplate.Builder getTemplate() {\n"); - out.write(" return " + indentNewLine(templateCode, 12) + ";\n"); - out.write(" }\n"); - out.write("}\n"); - out.flush(); - } - } catch (IOException e) { - throw new RuntimeException(e); + // this could be a visitor in the case that the visitor is in its own file or + // named inner class, or a recipe if the visitor is defined in an anonymous class + JCTree.JCClassDecl classDecl = cursor(cu, template).stream() + .filter(JCTree.JCClassDecl.class::isInstance) + .map(JCTree.JCClassDecl.class::cast) + .reduce((next, acc) -> next) + .orElseThrow(() -> new IllegalStateException("Expected to find an enclosing class")); + + String templateFqn; + if (isOfClassType(classDecl.type, "org.openrewrite.java.JavaVisitor")) { + templateFqn = classDecl.sym.fullname.toString() + "_" + templateName.getValue().toString(); + } else { + JCTree.JCNewClass visitorClass = cursor(cu, template).stream() + .filter(JCTree.JCNewClass.class::isInstance) + .map(JCTree.JCNewClass.class::cast) + .reduce((next, acc) -> next) + .orElse(null); + + JCTree.JCNewClass resolvedVisitorClass = (JCTree.JCNewClass) resolved.get(visitorClass); + + if (resolvedVisitorClass != null && isOfClassType(resolvedVisitorClass.clazz.type, "org.openrewrite.java.JavaVisitor")) { + templateFqn = ((Symbol.ClassSymbol) resolvedVisitorClass.type.tsym).flatname.toString() + "_" + + templateName.getValue().toString(); + } else { + processingEnv.getMessager().printMessage(Kind.WARNING, "Can't compile a template outside of a visitor or recipe."); + return; } } + + String templateCode = TemplateCode.process( + resolved.get(template.getBody()), + null, + parameters, + emptyList(), + 0, + "statement".equals(name), + false, + classpathFromResources); + + writeClass(classDecl, templateFqn, classpathFromResources, templateCode, templateName); } super.visitApply(tree); } + + private void writeClass( + JCTree.JCClassDecl classDecl, + String templateFqn, + boolean classpathFromResources, + String templateCode, + JCTree.JCLiteral templateName) { + try { + Symbol.PackageSymbol pkg = classDecl.sym.packge(); + JavaFileObject builderFile = processingEnv.getFiler().createSourceFile(templateFqn); + try (Writer out = new BufferedWriter(builderFile.openWriter())) { + if (!pkg.isUnnamed()) { + out.write("package " + pkg.fullname + ";\n\n"); + } + if (classpathFromResources) { + out.write("import org.openrewrite.ExecutionContext;\n"); + } + if (templateCode.contains("JavaParser")) { + out.write("import org.openrewrite.java.JavaParser;\n"); + } + out.write("import org.openrewrite.java.JavaTemplate;\n\n"); + + out.write("/**\n * OpenRewrite `" + templateName.getValue() + "` template created for {@code " + templateFqn.split("_")[0] + "}.\n */\n"); + String templateClassName = templateFqn.substring(templateFqn.lastIndexOf('.') + 1); + out.write("@SuppressWarnings(\"all\")\n"); + out.write("public class " + templateClassName + " {\n"); + out.write(" /**\n"); + out.write(" * Instantiates a new instance.\n"); + out.write(" */\n"); + out.write(" public " + templateClassName + "() {}\n\n"); + out.write(" /**\n"); + out.write(" * Get the {@code JavaTemplate.Builder} to match or replace.\n"); + out.write(" * @return the JavaTemplate builder.\n"); + out.write(" */\n"); + out.write(" public static JavaTemplate.Builder getTemplate(" + + (classpathFromResources ? "ExecutionContext ctx" : "") + + ") {\n"); + out.write(" return " + indentNewLine(templateCode, 12) + ";\n"); + out.write(" }\n"); + out.write("}\n"); + out.flush(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } }.scan(cu); } private static boolean isOfClassType(Type type, String fqn) { return type instanceof Type.ClassType && (((Symbol.ClassSymbol) type.tsym) - .fullname.contentEquals(fqn) || isOfClassType(((Type.ClassType) type).supertype_field, fqn)); + .fullname.contentEquals(fqn) || isOfClassType(((Type.ClassType) type).supertype_field, fqn)); } private static Stack cursor(JCCompilationUnit cu, Tree t) { diff --git a/src/test/java/org/openrewrite/java/template/RefasterTemplateProcessorTest.java b/src/test/java/org/openrewrite/java/template/RefasterTemplateProcessorTest.java index bba67c12..0234bd18 100644 --- a/src/test/java/org/openrewrite/java/template/RefasterTemplateProcessorTest.java +++ b/src/test/java/org/openrewrite/java/template/RefasterTemplateProcessorTest.java @@ -20,7 +20,6 @@ import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; import jakarta.annotation.Generated; -import org.intellij.lang.annotations.Language; import org.jspecify.annotations.NullMarked; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -161,22 +160,11 @@ void jakartaGeneratedAnnotationOverride() throws Exception { } private static Compilation compileResource(String resourceName) { - return compileResource(resourceName, new RefasterTemplateProcessor()); - } - - static Compilation compileResource(String resourceName, TypeAwareProcessor processor) { // As per https://github.com/google/compile-testing/blob/v0.21.0/src/main/java/com/google/testing/compile/package-info.java#L53-L55 - return compile(JavaFileObjects.forResource(resourceName), processor); - } - - @SuppressWarnings("unused") // use when text blocks are available - static Compilation compileSource(String fqn, @Language("java") String source) { - return compile(JavaFileObjects.forSourceString(fqn, source), new RefasterTemplateProcessor()); - } - - @SuppressWarnings("unused") // use when text blocks are available - static Compilation compileSource(String fqn, @Language("java") String source, TypeAwareProcessor processor) { - return compile(JavaFileObjects.forSourceString(fqn, source), processor); + return compile( + JavaFileObjects.forResource(resourceName), + new RefasterTemplateProcessor(), + "-Arewrite.javaParserClasspathFrom=resources"); } static Compilation compile(JavaFileObject javaFileObject, TypeAwareProcessor processor, Object... options) { diff --git a/src/test/java/org/openrewrite/java/template/TemplateProcessorTest.java b/src/test/java/org/openrewrite/java/template/TemplateProcessorTest.java index ad7ea4c0..840e3204 100644 --- a/src/test/java/org/openrewrite/java/template/TemplateProcessorTest.java +++ b/src/test/java/org/openrewrite/java/template/TemplateProcessorTest.java @@ -66,6 +66,18 @@ void parserClasspath() { .hasSourceEquivalentTo(JavaFileObjects.forResource("template/LoggerRecipe$1_info.java")); } + @Test + void parserClasspathFromResources() { + Compilation compilation = compileResource("template/LoggerRecipeFromResources.java"); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("template/LoggerRecipeFromResources$1_logger") + .hasSourceEquivalentTo(JavaFileObjects.forResource("template/LoggerRecipeFromResources$1_logger.java")); + assertThat(compilation) + .generatedSourceFile("template/LoggerRecipeFromResources$1_info") + .hasSourceEquivalentTo(JavaFileObjects.forResource("template/LoggerRecipeFromResources$1_info.java")); + } + @Test void anonymousClass() { Compilation compilation = compileResource("template/AnonymousClass.java"); @@ -107,8 +119,8 @@ void unnamedPackage() { .hasSourceEquivalentTo(JavaFileObjects.forResource("template/UnnamedPackage$1_message.java")); } - static Compilation compileResource(String resourceName) { + static Compilation compileResource(String resourceName, Object... options) { // As per https://github.com/google/compile-testing/blob/v0.21.0/src/main/java/com/google/testing/compile/package-info.java#L53-L55 - return compile(JavaFileObjects.forResource(resourceName), new TemplateProcessor()); + return compile(JavaFileObjects.forResource(resourceName), new TemplateProcessor(), options); } } diff --git a/src/test/resources/refaster/EmptyAfterMethodRecipes.java b/src/test/resources/refaster/EmptyAfterMethodRecipes.java index d24e8466..a31d719c 100644 --- a/src/test/resources/refaster/EmptyAfterMethodRecipes.java +++ b/src/test/resources/refaster/EmptyAfterMethodRecipes.java @@ -94,7 +94,6 @@ public String getDescription() { public TreeVisitor getVisitor() { JavaVisitor javaVisitor = new AbstractRefasterJavaVisitor() { JavaTemplate before; - @Override public J visitBinary(J.Binary elem, ExecutionContext ctx) { JavaTemplate.Matcher matcher; @@ -144,7 +143,6 @@ public String getDescription() { public TreeVisitor getVisitor() { JavaVisitor javaVisitor = new AbstractRefasterJavaVisitor() { JavaTemplate before; - @Override public J visitMethodInvocation(J.MethodInvocation elem, ExecutionContext ctx) { JavaTemplate.Matcher matcher; diff --git a/src/test/resources/refaster/EscapesRecipes.java b/src/test/resources/refaster/EscapesRecipes.java index 39350613..fb210eae 100644 --- a/src/test/resources/refaster/EscapesRecipes.java +++ b/src/test/resources/refaster/EscapesRecipes.java @@ -102,13 +102,15 @@ public J visitMethodInvocation(J.MethodInvocation elem, ExecutionContext ctx) { if (before == null) { before = JavaTemplate.builder("String.format(\"\\\"%s\\\"\", com.google.common.base.Strings.nullToEmpty(#{value:any(java.lang.String)}))") .bindType("java.lang.String") - .javaParser(JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath())).build(); + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "guava")) + .build(); } if ((matcher = before.matcher(getCursor())).find()) { if (after == null) { after = JavaTemplate.builder("com.google.common.base.Strings.lenientFormat(#{value:any(java.lang.String)})") .bindType("java.lang.String") - .javaParser(JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath())).build(); + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "guava")) + .build(); } return embed( after.apply(getCursor(), elem.getCoordinates().replace(), matcher.parameter(0)), diff --git a/src/test/resources/refaster/FindListAddRecipe.java b/src/test/resources/refaster/FindListAddRecipe.java index 63fd9cd1..99182aca 100644 --- a/src/test/resources/refaster/FindListAddRecipe.java +++ b/src/test/resources/refaster/FindListAddRecipe.java @@ -64,7 +64,6 @@ public String getDescription() { public TreeVisitor getVisitor() { JavaVisitor javaVisitor = new AbstractRefasterJavaVisitor() { JavaTemplate before; - @Override public J visitMethodInvocation(J.MethodInvocation elem, ExecutionContext ctx) { JavaTemplate.Matcher matcher; diff --git a/src/test/resources/refaster/GenericsRecipes.java b/src/test/resources/refaster/GenericsRecipes.java index f187dde9..ad63615b 100644 --- a/src/test/resources/refaster/GenericsRecipes.java +++ b/src/test/resources/refaster/GenericsRecipes.java @@ -166,7 +166,6 @@ public TreeVisitor getVisitor() { JavaTemplate emptyMap; JavaTemplate newList; JavaTemplate newMap; - @Override public J visitMethodInvocation(J.MethodInvocation elem, ExecutionContext ctx) { JavaTemplate.Matcher matcher; @@ -273,7 +272,6 @@ public TreeVisitor getVisitor() { JavaTemplate wilcard2; JavaTemplate wilcard3; JavaTemplate wilcard4; - @Override public J visitMethodInvocation(J.MethodInvocation elem, ExecutionContext ctx) { JavaTemplate.Matcher matcher; @@ -352,7 +350,6 @@ public String getDescription() { public TreeVisitor getVisitor() { JavaVisitor javaVisitor = new AbstractRefasterJavaVisitor() { JavaTemplate before; - @Override public J visitMethodInvocation(J.MethodInvocation elem, ExecutionContext ctx) { JavaTemplate.Matcher matcher; @@ -407,7 +404,6 @@ public TreeVisitor getVisitor() { JavaVisitor javaVisitor = new AbstractRefasterJavaVisitor() { JavaTemplate lambda; JavaTemplate reference; - @Override public J visitExpression(Expression elem, ExecutionContext ctx) { JavaTemplate.Matcher matcher; diff --git a/src/test/resources/refaster/NoGuavaRefasterRecipes.java b/src/test/resources/refaster/NoGuavaRefasterRecipes.java index 8f7f5f7a..1d9dfd2e 100644 --- a/src/test/resources/refaster/NoGuavaRefasterRecipes.java +++ b/src/test/resources/refaster/NoGuavaRefasterRecipes.java @@ -103,7 +103,8 @@ public J visitMethodInvocation(J.MethodInvocation elem, ExecutionContext ctx) { if (before == null) { before = JavaTemplate.builder("com.google.common.base.Preconditions.checkNotNull(#{object:any(java.lang.Object)})") .bindType("java.lang.Object") - .javaParser(JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath())).build(); + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "guava")) + .build(); } if ((matcher = before.matcher(getCursor())).find()) { maybeRemoveImport("com.google.common.base.Preconditions"); @@ -169,7 +170,8 @@ public J visitMethodInvocation(J.MethodInvocation elem, ExecutionContext ctx) { if (before == null) { before = JavaTemplate.builder("com.google.common.base.Preconditions.checkNotNull(#{object:any(java.lang.Object)}, #{message:any(java.lang.String)})") .bindType("java.lang.Object") - .javaParser(JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath())).build(); + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "guava")) + .build(); } if ((matcher = before.matcher(getCursor())).find()) { maybeRemoveImport("com.google.common.base.Preconditions"); @@ -235,7 +237,8 @@ public J visitMethodInvocation(J.MethodInvocation elem, ExecutionContext ctx) { if (before == null) { before = JavaTemplate.builder("com.google.common.base.Preconditions.checkNotNull(#{object:any(java.lang.Object)}, #{message:any(java.lang.Object)})") .bindType("java.lang.Object") - .javaParser(JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath())).build(); + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "guava")) + .build(); } if ((matcher = before.matcher(getCursor())).find()) { maybeRemoveImport("com.google.common.base.Preconditions"); diff --git a/src/test/resources/refaster/PreconditionsVerifierRecipes.java b/src/test/resources/refaster/PreconditionsVerifierRecipes.java index b3717130..9abff430 100644 --- a/src/test/resources/refaster/PreconditionsVerifierRecipes.java +++ b/src/test/resources/refaster/PreconditionsVerifierRecipes.java @@ -182,13 +182,15 @@ public J visitMethodInvocation(J.MethodInvocation elem, ExecutionContext ctx) { if (before == null) { before = JavaTemplate.builder("com.google.common.base.Strings.nullToEmpty(#{value:any(java.lang.String)})") .bindType("java.lang.String") - .javaParser(JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath())).build(); + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "guava")) + .build(); } if ((matcher = before.matcher(getCursor())).find()) { if (after == null) { after = JavaTemplate.builder("com.google.common.base.Strings.nullToEmpty(String.valueOf(#{value:any(java.lang.Object)}))") .bindType("java.lang.Object") - .javaParser(JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath())).build(); + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "guava")) + .build(); } return embed( after.apply(getCursor(), elem.getCoordinates().replace(), matcher.parameter(0)), @@ -205,7 +207,8 @@ public J visitMethodInvocation(J.MethodInvocation elem, ExecutionContext ctx) { if (after == null) { after = JavaTemplate.builder("com.google.common.base.Strings.nullToEmpty(String.valueOf(#{value:any(java.lang.Object)}))") .bindType("java.lang.Object") - .javaParser(JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath())).build(); + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "guava")) + .build(); } return embed( after.apply(getCursor(), elem.getCoordinates().replace(), matcher.parameter(0)), @@ -269,13 +272,15 @@ public J visitMethodInvocation(J.MethodInvocation elem, ExecutionContext ctx) { if (before == null) { before = JavaTemplate.builder("com.google.common.base.Strings.nullToEmpty(#{value:any(java.lang.String)})") .bindType("java.lang.String") - .javaParser(JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath())).build(); + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "guava")) + .build(); } if ((matcher = before.matcher(getCursor())).find()) { if (after == null) { after = JavaTemplate.builder("com.google.common.base.Strings.nullToEmpty(String.valueOf(#{value:any(java.lang.Object)}))") .bindType("java.lang.Object") - .javaParser(JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath())).build(); + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "guava")) + .build(); } return embed( after.apply(getCursor(), elem.getCoordinates().replace(), matcher.parameter(0)), @@ -287,13 +292,15 @@ public J visitMethodInvocation(J.MethodInvocation elem, ExecutionContext ctx) { if (before0 == null) { before0 = JavaTemplate.builder("com.google.common.base.Strings.nullToEmpty(String.valueOf(#{value:any(int)}))") .bindType("java.lang.String") - .javaParser(JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath())).build(); + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "guava")) + .build(); } if ((matcher = before0.matcher(getCursor())).find()) { if (after == null) { after = JavaTemplate.builder("com.google.common.base.Strings.nullToEmpty(String.valueOf(#{value:any(java.lang.Object)}))") .bindType("java.lang.Object") - .javaParser(JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath())).build(); + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "guava")) + .build(); } return embed( after.apply(getCursor(), elem.getCoordinates().replace(), matcher.parameter(0)), diff --git a/src/test/resources/template/LoggerRecipeFromResources$1_info.java b/src/test/resources/template/LoggerRecipeFromResources$1_info.java new file mode 100644 index 00000000..168d404d --- /dev/null +++ b/src/test/resources/template/LoggerRecipeFromResources$1_info.java @@ -0,0 +1,30 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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 template; +import org.openrewrite.ExecutionContext; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.JavaTemplate; + +@SuppressWarnings("all") +public class LoggerRecipeFromResources$1_info { + public LoggerRecipeFromResources$1_info() {} + + public static JavaTemplate.Builder getTemplate(ExecutionContext ctx) { + return JavaTemplate + .builder("#{l:any(org.slf4j.Logger)}.info(#{s:any(java.lang.String)})") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "slf4j-api")); + } +} diff --git a/src/test/resources/template/LoggerRecipeFromResources$1_logger.java b/src/test/resources/template/LoggerRecipeFromResources$1_logger.java new file mode 100644 index 00000000..740dfd85 --- /dev/null +++ b/src/test/resources/template/LoggerRecipeFromResources$1_logger.java @@ -0,0 +1,32 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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 template; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.JavaTemplate; + +@SuppressWarnings("all") +public class LoggerRecipeFromResources$1_logger { + public LoggerRecipeFromResources$1_logger() {} + + public static JavaTemplate.Builder getTemplate(ExecutionContext ctx) { + return JavaTemplate + .builder("LoggerFactory.getLogger(#{s:any(java.lang.String)})") + .imports("org.slf4j.LoggerFactory") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "slf4j-api")); + } +} diff --git a/src/test/resources/template/LoggerRecipeFromResources.java b/src/test/resources/template/LoggerRecipeFromResources.java new file mode 100644 index 00000000..7d48b022 --- /dev/null +++ b/src/test/resources/template/LoggerRecipeFromResources.java @@ -0,0 +1,31 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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 template; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.template.Semantics; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LoggerRecipeFromResources { + ExecutionContext ctx; + JavaIsoVisitor visitor = new JavaIsoVisitor() { + JavaTemplate.Builder logger = Semantics.expression(ctx, this, "logger", (String s) -> LoggerFactory.getLogger(s)); + JavaTemplate.Builder info = Semantics.statement(ctx, this, "info", (Logger l, String s) -> l.info(s)); + }; +}