Skip to content

Commit 6c333fd

Browse files
authored
Merge branch 'main' into fix-adddependency-duplicate-applied-scripts
2 parents ee97af8 + 2ae43a8 commit 6c333fd

446 files changed

Lines changed: 43960 additions & 3156 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/comment-pr.yml

Lines changed: 0 additions & 15 deletions
This file was deleted.

.github/workflows/receive-pr.yml

Lines changed: 0 additions & 23 deletions
This file was deleted.

build.gradle.kts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,11 @@ allprojects {
2222
group = "org.openrewrite"
2323
description = "Eliminate tech-debt. Automatically."
2424
}
25+
26+
// Use this task locally between different dependency check runs to have updated analysis:
27+
// OSSINDEX_PASSWORD=... OSSINDEX_USERNAME=... gradle cleanReports dCAg --no-parallel
28+
tasks.register<Delete>("cleanReports") {
29+
description = "Removes build/reports folder from all modules"
30+
group = "owasp dependency-check"
31+
delete(allprojects.map { it.layout.buildDirectory.dir("reports") })
32+
}

rewrite-core/src/main/java/org/openrewrite/Recipe.java

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -449,12 +449,7 @@ public final RecipeRun run(LargeSourceSet before, ExecutionContext ctx, int maxC
449449

450450
@SuppressWarnings("unused")
451451
public Validated<Object> validate(ExecutionContext ctx) {
452-
Validated<Object> validated = validate();
453-
454-
for (Recipe recipe : getRecipeList()) {
455-
validated = validated.and(recipe.validate(ctx));
456-
}
457-
return validated;
452+
return validate();
458453
}
459454

460455
/**
@@ -475,9 +470,6 @@ public Validated<Object> validate() {
475470
validated = Validated.invalid(field.getName(), null, "Unable to access " + clazz.getName() + "." + field.getName(), e);
476471
}
477472
}
478-
for (Recipe recipe : getRecipeList()) {
479-
validated = validated.and(recipe.validate());
480-
}
481473
return validated;
482474
}
483475

@@ -561,6 +553,10 @@ public Recipe withOptions(@Nullable Map<String, Object> options) {
561553
Map<String, Object> option = new HashMap<>();
562554
option.put("value", value);
563555
objectMapper.updateValue(optionDescriptor, option);
556+
557+
if (optionDescriptor.getType().equals("List")) {
558+
m.put(optionDescriptor.getName(), Arrays.asList(((String) value).split(",")));
559+
}
564560
}
565561
}
566562
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2026 the original author or authors.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite;
17+
18+
import lombok.EqualsAndHashCode;
19+
import lombok.Value;
20+
import org.jspecify.annotations.Nullable;
21+
22+
import java.net.URI;
23+
24+
@Value
25+
@EqualsAndHashCode(callSuper = false)
26+
public class RecipeNotFoundException extends RuntimeException {
27+
String recipeName;
28+
29+
@Nullable
30+
URI source;
31+
32+
public RecipeNotFoundException(String recipeName) {
33+
this(recipeName, null);
34+
}
35+
36+
public RecipeNotFoundException(String recipeName, @Nullable URI source) {
37+
super("Unable to find recipe " + recipeName + (source == null ? "" : " listed in " + source));
38+
this.recipeName = recipeName;
39+
this.source = source;
40+
}
41+
}

rewrite-core/src/main/java/org/openrewrite/Validated.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -374,10 +374,22 @@ public boolean isValid() {
374374

375375
@Override
376376
public Iterator<Validated<T>> iterator() {
377-
return Stream.concat(
378-
stream(left.spliterator(), false),
379-
stream(right.spliterator(), false)
380-
).iterator();
377+
List<Validated<T>> result = new ArrayList<>();
378+
Deque<Validated<T>> stack = new ArrayDeque<>();
379+
stack.push(this);
380+
while (!stack.isEmpty()) {
381+
Validated<T> current = stack.pop();
382+
if (current instanceof Both) {
383+
Both<T> both = (Both<T>) current;
384+
stack.push(both.right);
385+
stack.push(both.left);
386+
} else {
387+
for (Validated<T> v : current) {
388+
result.add(v);
389+
}
390+
}
391+
}
392+
return result.iterator();
381393
}
382394
}
383395
}

rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,17 @@
2929
import org.openrewrite.internal.RecipeLoader;
3030
import org.openrewrite.style.NamedStyles;
3131

32+
import java.io.IOException;
33+
import java.io.UncheckedIOException;
3234
import java.lang.reflect.Constructor;
3335
import java.lang.reflect.Modifier;
34-
import java.nio.file.Path;
36+
import java.nio.file.*;
37+
import java.nio.file.attribute.BasicFileAttributes;
3538
import java.util.*;
39+
import java.util.jar.JarEntry;
40+
import java.util.jar.JarOutputStream;
3641

42+
import static java.nio.file.Files.*;
3743
import static java.util.Collections.emptyList;
3844
import static java.util.Collections.emptyMap;
3945

@@ -97,26 +103,73 @@ public ClasspathScanningLoader(Properties properties, ClassLoader classLoader) {
97103
/**
98104
* Construct a ClasspathScanningLoader as used from `Environment.scanJar` for
99105
* `MavenRecipeBundleReader.marketplaceFromClasspathScan`.
106+
* Supports both jar files and directories containing class files.
100107
*/
101108
public ClasspathScanningLoader(Path jar, Properties properties, Collection<? extends ResourceLoader> dependencyResourceLoaders, ClassLoader classLoader) {
102109
this.classLoader = classLoader;
103110
this.recipeLoader = new RecipeLoader(classLoader);
104-
String jarName = jar.toFile().getName();
105111

106112
this.performScan = () -> {
107-
scanClasses(new ClassGraph()
113+
Path jarPath;
114+
if (isDirectory(jar)) {
115+
try {
116+
jarPath = createTempJarFromDirectory(jar);
117+
} catch (IOException e) {
118+
throw new UncheckedIOException("Failed to create temporary jar from directory: " + jar, e);
119+
}
120+
} else {
121+
jarPath = jar;
122+
}
123+
124+
String jarName = jarPath.toFile().getName();
125+
ClassGraph classGraph = new ClassGraph()
126+
.overrideClasspath(jarPath.toString())
108127
.acceptJars(jarName)
109-
.ignoreParentClassLoaders()
110-
.overrideClassLoaders(classLoader), classLoader);
128+
.overrideClassLoaders(classLoader);
111129

112-
scanYaml(new ClassGraph()
130+
ClassGraph yamlGraph = new ClassGraph()
131+
.overrideClasspath(jarPath.toString())
113132
.acceptJars(jarName)
114-
.ignoreParentClassLoaders()
115133
.overrideClassLoaders(classLoader)
116-
.acceptPaths("META-INF/rewrite"), properties, dependencyResourceLoaders, classLoader);
134+
.acceptPaths("META-INF/rewrite");
135+
136+
scanClasses(classGraph, classLoader);
137+
scanYaml(yamlGraph, properties, dependencyResourceLoaders, classLoader);
117138
};
118139
}
119140

141+
/**
142+
* Creates a temporary jar file containing all files from the given directory.
143+
*/
144+
private static Path createTempJarFromDirectory(Path directory) throws IOException {
145+
Path tempJar = createTempFile("recipe-scan-", ".jar");
146+
tempJar.toFile().deleteOnExit();
147+
148+
try (JarOutputStream jos = new JarOutputStream(newOutputStream(tempJar))) {
149+
walkFileTree(directory, new SimpleFileVisitor<Path>() {
150+
@Override
151+
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
152+
String entryName = directory.relativize(file).toString().replace('\\', '/');
153+
jos.putNextEntry(new JarEntry(entryName));
154+
copy(file, jos);
155+
jos.closeEntry();
156+
return FileVisitResult.CONTINUE;
157+
}
158+
159+
@Override
160+
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
161+
if (!dir.equals(directory)) {
162+
String entryName = directory.relativize(dir).toString().replace('\\', '/') + "/";
163+
jos.putNextEntry(new JarEntry(entryName));
164+
jos.closeEntry();
165+
}
166+
return FileVisitResult.CONTINUE;
167+
}
168+
});
169+
}
170+
return tempJar;
171+
}
172+
120173
/**
121174
* Construct a ClasspathScanningLoader to load Yaml categories and recipes from the runtime classpath, as part of
122175
* running tests or inferring local recipe categories.

rewrite-core/src/main/java/org/openrewrite/config/DeclarativeRecipe.java

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -169,33 +169,47 @@ private void initializeDeclarativeRecipe(DeclarativeRecipe declarativeRecipe, St
169169
public Accumulator getInitialValue(ExecutionContext ctx) {
170170
Accumulator acc = new Accumulator();
171171
for (Recipe precondition : preconditions) {
172-
if (precondition instanceof ScanningRecipe && isScanningRequired(precondition)) {
173-
acc.recipeToAccumulator.put(precondition, ((ScanningRecipe<?>) precondition).getInitialValue(ctx));
174-
}
172+
registerNestedScanningRecipes(precondition, acc, ctx);
175173
}
176174
accumulator = acc;
177175
return acc;
178176
}
179177

178+
private void registerNestedScanningRecipes(Recipe recipe, Accumulator acc, ExecutionContext ctx) {
179+
if (recipe instanceof ScanningRecipe && isScanningRequired(recipe)) {
180+
acc.recipeToAccumulator.put(recipe, ((ScanningRecipe<?>) recipe).getInitialValue(ctx));
181+
}
182+
for (Recipe nested : recipe.getRecipeList()) {
183+
registerNestedScanningRecipes(nested, acc, ctx);
184+
}
185+
}
186+
180187
@Override
181188
public TreeVisitor<?, ExecutionContext> getScanner(Accumulator acc) {
182189
return new TreeVisitor<Tree, ExecutionContext>() {
183190
@SuppressWarnings({"rawtypes", "unchecked"})
184191
@Override
185192
public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) {
186193
for (Recipe precondition : preconditions) {
187-
if (precondition instanceof ScanningRecipe && isScanningRequired(precondition)) {
188-
ScanningRecipe preconditionRecipe = (ScanningRecipe) precondition;
189-
Object preconditionAcc = acc.recipeToAccumulator.get(precondition);
190-
preconditionRecipe.getScanner(preconditionAcc)
191-
.visit(tree, ctx);
192-
}
194+
scanNestedScanningRecipes(precondition, acc, tree, ctx);
193195
}
194196
return tree;
195197
}
196198
};
197199
}
198200

201+
@SuppressWarnings({"rawtypes", "unchecked"})
202+
private void scanNestedScanningRecipes(Recipe recipe, Accumulator acc, @Nullable Tree tree, ExecutionContext ctx) {
203+
if (recipe instanceof ScanningRecipe && isScanningRequired(recipe)) {
204+
ScanningRecipe scanningRecipe = (ScanningRecipe) recipe;
205+
Object recipeAcc = acc.recipeToAccumulator.get(recipe);
206+
scanningRecipe.getScanner(recipeAcc).visit(tree, ctx);
207+
}
208+
for (Recipe nested : recipe.getRecipeList()) {
209+
scanNestedScanningRecipes(nested, acc, tree, ctx);
210+
}
211+
}
212+
199213
public static class Accumulator {
200214
Map<Recipe, Object> recipeToAccumulator = new HashMap<>();
201215
}

rewrite-core/src/main/java/org/openrewrite/config/Environment.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ public Builder scanYamlResources() {
319319
}
320320

321321
/**
322-
* @param jar A path to a jar file to scan.
322+
* @param jar A path to a jar file or directory containing class files to scan.
323323
* @param classLoader A classloader that is populated with the transitive dependencies of the jar.
324324
* @return This builder.
325325
*/

rewrite-core/src/main/java/org/openrewrite/config/YamlResourceLoader.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public YamlResourceLoader(InputStream yamlInput, URI source, Properties properti
9898
this.recipeLoader = (recipeName, options) -> {
9999
RecipeListing listing = marketplace.findRecipe(recipeName);
100100
if (listing == null) {
101-
throw new IllegalStateException("Unable to find recipe " + recipeName + " listed in " + source);
101+
throw new RecipeNotFoundException(recipeName, source);
102102
}
103103
return listing.prepare(resolvers, options == null ? emptyMap() : options);
104104
};
@@ -356,6 +356,12 @@ void loadRecipe(@Language("markdown") String name,
356356
} catch (IllegalArgumentException ignored) {
357357
// it's probably declarative
358358
addLazyLoadRecipe.accept(recipeName);
359+
} catch (RecipeNotFoundException e) {
360+
addInvalidRecipeValidation(
361+
addValidation,
362+
recipeName,
363+
null,
364+
e.getMessage());
359365
} catch (NoClassDefFoundError e) {
360366
addInvalidRecipeValidation(
361367
addValidation,
@@ -385,6 +391,12 @@ void loadRecipe(@Language("markdown") String name,
385391
recipeArgs,
386392
"Unable to load Recipe: " + e);
387393
}
394+
} catch (RecipeNotFoundException e) {
395+
addInvalidRecipeValidation(
396+
addValidation,
397+
recipeName,
398+
recipeArgs,
399+
e.getMessage());
388400
} catch (NoClassDefFoundError e) {
389401
addInvalidRecipeValidation(
390402
addValidation,
@@ -404,7 +416,7 @@ void loadRecipe(@Language("markdown") String name,
404416
addValidation,
405417
recipeName,
406418
recipeArgs,
407-
"Unexpected declarative recipe parsing exception " + e.getClass().getName());
419+
"Unexpected declarative recipe parsing exception " + e.getClass().getName() + ": " + e.getMessage());
408420
}
409421
} else {
410422
addValidation.accept(invalid(

0 commit comments

Comments
 (0)