Skip to content

Commit df16f0a

Browse files
committed
Maven, Gradle: emit DependenciesDeclared data table from FindDependency
Adds a new shared DependenciesDeclared data table in org.openrewrite.maven.table. Both rewrite-maven and rewrite-gradle FindDependency recipes populate it alongside the existing SearchResult markers, one row per declared dependency match. Schema mirrors DependenciesInUse minus the count column (every row is direct by construction). Single shared class follows the existing precedent: rewrite-gradle already imports org.openrewrite.maven.table.DependenciesInUse from rewrite-maven.
1 parent c3fc21b commit df16f0a

7 files changed

Lines changed: 367 additions & 12 deletions

File tree

rewrite-gradle/src/main/java/org/openrewrite/gradle/search/FindDependency.java

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,22 @@
2121
import org.openrewrite.ExecutionContext;
2222
import org.openrewrite.Option;
2323
import org.openrewrite.Recipe;
24+
import org.openrewrite.SourceFile;
2425
import org.openrewrite.TreeVisitor;
2526
import org.openrewrite.gradle.trait.GradleDependency;
27+
import org.openrewrite.java.marker.JavaProject;
28+
import org.openrewrite.java.marker.JavaSourceSet;
2629
import org.openrewrite.marker.SearchResult;
30+
import org.openrewrite.maven.table.DependenciesDeclared;
2731
import org.openrewrite.semver.Semver;
2832
import org.openrewrite.semver.VersionComparator;
2933
import org.openrewrite.Validated;
3034

3135
@Value
3236
@EqualsAndHashCode(callSuper = false)
3337
public class FindDependency extends Recipe {
38+
transient DependenciesDeclared dependenciesDeclared = new DependenciesDeclared(this);
39+
3440
@Option(displayName = "Group",
3541
description = "The first part of a dependency coordinate identifying its publisher.",
3642
example = "com.google.guava")
@@ -71,7 +77,9 @@ public String getInstanceNameSuffix() {
7177
return String.format("`%s:%s%s`", groupId, artifactId, maybeVersionSuffix);
7278
}
7379

74-
String description = "Finds dependencies declared in gradle build files. See the [reference](https://docs.gradle.org/current/userguide/java_library_plugin.html#sec:java_library_configurations_graph) on Gradle configurations or the diagram below for a description of what configuration to use. " +
80+
String description = "Finds dependencies declared in gradle build files. " +
81+
"Each match is also recorded as a row in the `DependenciesDeclared` data table. " +
82+
"See the [reference](https://docs.gradle.org/current/userguide/java_library_plugin.html#sec:java_library_configurations_graph) on Gradle configurations or the diagram below for a description of what configuration to use. " +
7583
"A project's compile and runtime classpath is based on these configurations.\n\n<img alt=\"Gradle compile classpath\" src=\"https://docs.gradle.org/current/userguide/img/java-library-ignore-deprecated-main.png\" width=\"200px\"/>\n" +
7684
"A project's test classpath is based on these configurations.\n\n<img alt=\"Gradle test classpath\" src=\"https://docs.gradle.org/current/userguide/img/java-library-ignore-deprecated-test.png\" width=\"200px\"/>.";
7785

@@ -81,7 +89,30 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
8189
.groupId(groupId)
8290
.artifactId(artifactId)
8391
.configuration(configuration)
84-
.asVisitor(gd -> versionIsValid(version, versionPattern, gd) ? SearchResult.found(gd.getTree()) : gd.getTree());
92+
.asVisitor((gd, ctx) -> {
93+
if (!versionIsValid(version, versionPattern, gd)) {
94+
return gd.getTree();
95+
}
96+
SourceFile sourceFile = gd.getCursor().firstEnclosing(SourceFile.class);
97+
String projectName = sourceFile == null ? "" : sourceFile.getMarkers()
98+
.findFirst(JavaProject.class)
99+
.map(JavaProject::getProjectName)
100+
.orElse("");
101+
String sourceSetName = sourceFile == null ? "main" : sourceFile.getMarkers()
102+
.findFirst(JavaSourceSet.class)
103+
.map(JavaSourceSet::getName)
104+
.orElse("main");
105+
dependenciesDeclared.insertRow(ctx, new DependenciesDeclared.Row(
106+
projectName,
107+
sourceSetName,
108+
gd.getGroupId(),
109+
gd.getArtifactId(),
110+
gd.getVersion(),
111+
null,
112+
gd.getConfigurationName()
113+
));
114+
return SearchResult.found(gd.getTree());
115+
});
85116
}
86117

87118
private static boolean versionIsValid(@Nullable String desiredVersion, @Nullable String versionPattern,

rewrite-gradle/src/main/resources/META-INF/rewrite/recipes.csv

Lines changed: 5 additions & 5 deletions
Large diffs are not rendered by default.

rewrite-gradle/src/test/java/org/openrewrite/gradle/search/FindDependencyTest.java

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
import org.junit.jupiter.api.Test;
2020
import org.openrewrite.DocumentExample;
2121
import org.openrewrite.Issue;
22+
import org.openrewrite.maven.table.DependenciesDeclared;
2223
import org.openrewrite.test.RewriteTest;
2324

25+
import static org.assertj.core.api.Assertions.assertThat;
2426
import static org.openrewrite.gradle.Assertions.buildGradle;
2527
import static org.openrewrite.gradle.Assertions.buildGradleKts;
2628
import static org.openrewrite.gradle.toolingapi.Assertions.withToolingApi;
@@ -376,6 +378,84 @@ void findDependencyByVersionPatternNoMatch() {
376378
);
377379
}
378380

381+
@Test
382+
void emitsDataTableRow() {
383+
rewriteRun(
384+
spec -> spec.recipe(new FindDependency("org.openrewrite", "rewrite-core", "api", null, null))
385+
.dataTable(DependenciesDeclared.Row.class, rows -> {
386+
assertThat(rows).hasSize(1);
387+
DependenciesDeclared.Row row = rows.get(0);
388+
assertThat(row.getGroupId()).isEqualTo("org.openrewrite");
389+
assertThat(row.getArtifactId()).isEqualTo("rewrite-core");
390+
assertThat(row.getScope()).isEqualTo("api");
391+
assertThat(row.getDatedSnapshotVersion()).isNull();
392+
}),
393+
buildGradle(
394+
//language=gradle
395+
"""
396+
plugins {
397+
id 'java-library'
398+
}
399+
repositories {
400+
mavenCentral()
401+
}
402+
dependencies {
403+
api "org.openrewrite:rewrite-core:latest.release"
404+
}
405+
""",
406+
"""
407+
plugins {
408+
id 'java-library'
409+
}
410+
repositories {
411+
mavenCentral()
412+
}
413+
dependencies {
414+
/*~~>*/api "org.openrewrite:rewrite-core:latest.release"
415+
}
416+
"""
417+
)
418+
);
419+
}
420+
421+
@Test
422+
void emitsDataTableRowKotlinDsl() {
423+
rewriteRun(spec -> spec
424+
.beforeRecipe(withToolingApi())
425+
.recipe(new FindDependency("org.openrewrite", "rewrite-core", "api", null, null))
426+
.dataTable(DependenciesDeclared.Row.class, rows -> {
427+
assertThat(rows).hasSize(1);
428+
assertThat(rows.get(0).getArtifactId()).isEqualTo("rewrite-core");
429+
assertThat(rows.get(0).getScope()).isEqualTo("api");
430+
}),
431+
buildGradleKts(
432+
//language=gradle
433+
"""
434+
plugins {
435+
`java-library`
436+
}
437+
repositories {
438+
mavenCentral()
439+
}
440+
dependencies {
441+
api("org.openrewrite:rewrite-core:latest.release")
442+
}
443+
""",
444+
"""
445+
plugins {
446+
`java-library`
447+
}
448+
repositories {
449+
mavenCentral()
450+
}
451+
dependencies {
452+
/*~~>*/api("org.openrewrite:rewrite-core:latest.release")
453+
}
454+
"""
455+
)
456+
);
457+
}
458+
379459
@Issue("https://github.com/openrewrite/rewrite/issues/5599")
380460
@Test
381461
void constraintsVsRegularDependencies() {

rewrite-maven/src/main/java/org/openrewrite/maven/search/FindDependency.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@
1919
import lombok.Value;
2020
import org.jspecify.annotations.Nullable;
2121
import org.openrewrite.*;
22+
import org.openrewrite.java.marker.JavaProject;
23+
import org.openrewrite.java.marker.JavaSourceSet;
2224
import org.openrewrite.marker.SearchResult;
2325
import org.openrewrite.maven.MavenIsoVisitor;
26+
import org.openrewrite.maven.table.DependenciesDeclared;
2427
import org.openrewrite.maven.tree.ResolvedDependency;
2528
import org.openrewrite.semver.Semver;
2629
import org.openrewrite.semver.VersionComparator;
@@ -33,6 +36,7 @@
3336
@EqualsAndHashCode(callSuper = false)
3437
@Value
3538
public class FindDependency extends Recipe {
39+
transient DependenciesDeclared dependenciesDeclared = new DependenciesDeclared(this);
3640

3741
@Option(displayName = "Group",
3842
description = "The first part of a dependency coordinate `com.google.guava:guava:VERSION`. Supports glob.",
@@ -87,15 +91,44 @@ public String getInstanceNameSuffix() {
8791
return String.format("`%s:%s%s`", groupId, artifactId, maybeVersionSuffix);
8892
}
8993

90-
String description = "Finds first-order dependency uses, i.e. dependencies that are defined directly in a project.";
94+
String description = "Finds first-order dependency uses, i.e. dependencies that are defined directly in a project. " +
95+
"Each match is also recorded as a row in the `DependenciesDeclared` data table.";
9196

9297
@Override
9398
public TreeVisitor<?, ExecutionContext> getVisitor() {
9499
return new MavenIsoVisitor<ExecutionContext>() {
100+
String projectName = "";
101+
String sourceSetName = "main";
102+
103+
@Override
104+
public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) {
105+
projectName = document.getMarkers()
106+
.findFirst(JavaProject.class)
107+
.map(JavaProject::getProjectName)
108+
.orElse("");
109+
sourceSetName = document.getMarkers()
110+
.findFirst(JavaSourceSet.class)
111+
.map(JavaSourceSet::getName)
112+
.orElse("main");
113+
return super.visitDocument(document, ctx);
114+
}
115+
95116
@Override
96117
public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) {
97118
if (isDependencyTag(groupId, artifactId) &&
98119
versionIsValid(version, versionPattern, () -> findDependency(tag))) {
120+
ResolvedDependency resolved = findDependency(tag);
121+
if (resolved != null) {
122+
dependenciesDeclared.insertRow(ctx, new DependenciesDeclared.Row(
123+
projectName,
124+
sourceSetName,
125+
resolved.getGroupId(),
126+
resolved.getArtifactId(),
127+
resolved.getVersion(),
128+
resolved.getDatedSnapshotVersion(),
129+
tag.getChildValue("scope").orElse("compile")
130+
));
131+
}
99132
return SearchResult.found(tag);
100133
}
101134
return super.visitTag(tag, ctx);
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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.maven.table;
17+
18+
import com.fasterxml.jackson.annotation.JsonIgnoreType;
19+
import lombok.NonNull;
20+
import lombok.Value;
21+
import org.jspecify.annotations.Nullable;
22+
import org.openrewrite.Column;
23+
import org.openrewrite.DataTable;
24+
import org.openrewrite.Recipe;
25+
26+
@JsonIgnoreType
27+
public class DependenciesDeclared extends DataTable<DependenciesDeclared.@NonNull Row> {
28+
29+
public DependenciesDeclared(Recipe recipe) {
30+
super(recipe, "Dependencies declared",
31+
"Direct (first-order) dependencies declared by the project.");
32+
}
33+
34+
@Value
35+
public static class Row {
36+
@Column(displayName = "Project name",
37+
description = "The name of the project that contains the dependency.")
38+
String projectName;
39+
40+
@Column(displayName = "Source set",
41+
description = "The source set that contains the dependency.")
42+
String sourceSet;
43+
44+
@Column(displayName = "Group",
45+
description = "The first part of a dependency coordinate `com.google.guava:guava:VERSION`.")
46+
String groupId;
47+
48+
@Column(displayName = "Artifact",
49+
description = "The second part of a dependency coordinate `com.google.guava:guava:VERSION`.")
50+
String artifactId;
51+
52+
@Column(displayName = "Version",
53+
description = "The resolved version.")
54+
String version;
55+
56+
@Column(displayName = "Dated snapshot version",
57+
description = "The resolved dated snapshot version or `null` if this dependency is not a snapshot.")
58+
@Nullable
59+
String datedSnapshotVersion;
60+
61+
@Column(displayName = "Scope",
62+
description = "Maven scope (e.g. `compile`, `test`) or Gradle configuration name (e.g. `implementation`, " +
63+
"`testImplementation`). For Maven, defaults to `compile` when no scope is declared.")
64+
String scope;
65+
}
66+
}

0 commit comments

Comments
 (0)