Skip to content

Commit 286241c

Browse files
Allowing AddDependency to broaden the scope of existing dependency, provided you're requesting an equal or higher version number. If requesting same scope but higher version number, the version number will be upgraded.
1 parent dfb15b1 commit 286241c

3 files changed

Lines changed: 615 additions & 87 deletions

File tree

rewrite-maven/src/main/java/org/openrewrite/maven/AddDependency.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,10 @@ public Xml visitDocument(Xml.Document document, ExecutionContext ctx) {
224224
if (dependencies.get(Scope.Compile) != null) {
225225
for (ResolvedDependency d : dependencies.get(Scope.Compile)) {
226226
if (hasAcceptableTransitivity(d, acc) &&
227-
groupId.equals(d.getGroupId()) && artifactId.equals(d.getArtifactId())) {
227+
groupId.equals(d.getGroupId()) &&
228+
artifactId.equals(d.getArtifactId()) &&
229+
version.equals(d.getVersion())
230+
) {
228231
return maven;
229232
}
230233
}

rewrite-maven/src/main/java/org/openrewrite/maven/AddDependencyVisitor.java

Lines changed: 147 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,10 @@
1919
import org.jspecify.annotations.Nullable;
2020
import org.openrewrite.ExecutionContext;
2121
import org.openrewrite.Validated;
22+
import org.openrewrite.marker.Markup;
2223
import org.openrewrite.maven.internal.InsertDependencyComparator;
2324
import org.openrewrite.maven.table.MavenMetadataFailures;
24-
import org.openrewrite.maven.tree.MavenMetadata;
25-
import org.openrewrite.maven.tree.ResolvedDependency;
26-
import org.openrewrite.maven.tree.Scope;
27-
import org.openrewrite.maven.tree.Version;
25+
import org.openrewrite.maven.tree.*;
2826
import org.openrewrite.semver.ExactVersion;
2927
import org.openrewrite.semver.LatestRelease;
3028
import org.openrewrite.semver.Semver;
@@ -39,6 +37,7 @@
3937
import java.util.regex.Pattern;
4038

4139
import static java.util.Collections.emptyList;
40+
import static java.util.Objects.requireNonNull;
4241

4342
@RequiredArgsConstructor
4443
public class AddDependencyVisitor extends MavenIsoVisitor<ExecutionContext> {
@@ -89,38 +88,86 @@ public AddDependencyVisitor(String groupId, String artifactId, String version,
8988

9089
@Override
9190
public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext executionContext) {
92-
if (isDependencyTag() &&
93-
groupId.equals(tag.getChildValue("groupId").orElse(null)) &&
94-
artifactId.equals(tag.getChildValue("artifactId").orElse(null)) &&
95-
Scope.fromName(scope) == Scope.fromName(tag.getChildValue("scope").orElse(null))) {
96-
getCursor().putMessageOnFirstEnclosing(Xml.Document.class, "alreadyHasDependency", true);
97-
return tag;
91+
if (isDependencyTag()) {
92+
ResolvedPom resolvedPom = getResolutionResult().getPom();
93+
String existingGroupId = resolvedPom.getValue(tag.getChildValue("groupId").orElse(null));
94+
String existingArtifactId = resolvedPom.getValue(tag.getChildValue("artifactId").orElse(null));
95+
if (groupId.equals(existingGroupId) && artifactId.equals(existingArtifactId)) {
96+
Scope requestedScope = Scope.fromName(scope);
97+
Scope existingScope = Scope.fromName(resolvedPom.getValue(tag.getChildValue("scope").orElse(null)));
98+
if (tag.getMarkers().getMarkers().stream()
99+
.anyMatch(m -> m instanceof Markup.Warn &&
100+
((Markup.Warn) m).getDetail().startsWith("org.openrewrite.maven.MavenDownloadingException"))
101+
) {
102+
getCursor().putMessageOnFirstEnclosing(Xml.Document.class, "existingDependencyFailure", true);
103+
} else if (requestedScope != existingScope &&
104+
// Scope reduction
105+
((existingScope.isInClasspathOf(requestedScope) &&
106+
Scope.maxPrecedence(existingScope, requestedScope) == existingScope) ||
107+
// System / Import / Invalid which we don't support changing to
108+
(!existingScope.isInClasspathOf(requestedScope) &&
109+
!requestedScope.isInClasspathOf(existingScope) &&
110+
Scope.Test != requestedScope &&
111+
Scope.maxPrecedence(Scope.Test, requestedScope) == Scope.Test))
112+
) {
113+
getCursor().putMessageOnFirstEnclosing(Xml.Document.class, "doNotAlterDependency", true);
114+
} else {
115+
String versionToUse = null;
116+
String managedVersion = getResolutionResult().getPom().getManagedVersion(groupId, artifactId, type, classifier);
117+
if (managedVersion == null || (versionComparator != null && !versionComparator.isValid(version, managedVersion))) {
118+
versionToUse = tryGetFamilyVersion();
119+
if (versionToUse == null) {
120+
try {
121+
versionToUse = findVersionToUse(executionContext);
122+
} catch (MavenDownloadingException e) {
123+
return e.warn(tag);
124+
}
125+
}
126+
}
127+
if (versionToUse != null) {
128+
getCursor().putMessageOnFirstEnclosing(Xml.Document.class, "requestedVersionChange", true);
129+
getCursor().putMessageOnFirstEnclosing(Xml.Document.class, "newResolvedVersion", versionToUse);
130+
}
131+
if (requestedScope != existingScope) {
132+
getCursor().putMessageOnFirstEnclosing(Xml.Document.class, "requestedScopeChange", true);
133+
getCursor().putMessageOnFirstEnclosing(Xml.Document.class, "oldScope", existingScope);
134+
}
135+
}
136+
return tag;
137+
}
98138
}
99139
return super.visitTag(tag, executionContext);
100140
}
101141

102142

103143
@Override
104144
public Xml.Document visitDocument(Xml.Document document, ExecutionContext executionContext) {
145+
Validated<VersionComparator> versionValidation = Semver.validate(version, versionPattern);
146+
if (versionValidation.isValid()) {
147+
versionComparator = versionValidation.getValue();
148+
}
149+
105150
Xml.Document maven = super.visitDocument(document, executionContext);
106151

107-
if (getCursor().getMessage("alreadyHasDependency", false)) {
152+
if (getCursor().getMessage("doNotAlterDependency", false) ||
153+
getCursor().getMessage("existingDependencyFailure", false)
154+
) {
108155
return document;
109156
}
110157

111-
Scope resolvedScope = scope == null ? Scope.Compile : Scope.fromName(scope);
158+
boolean requestedVersionChange = getCursor().getMessage("requestedVersionChange", false);
159+
Scope resolvedScope = Scope.fromName(scope);
112160
Map<Scope, List<ResolvedDependency>> dependencies = getResolutionResult().getDependencies();
113161
if (dependencies.containsKey(resolvedScope)) {
114162
for (ResolvedDependency d : dependencies.get(resolvedScope)) {
115163
if (d.isDirect() && groupId.equals(d.getGroupId()) && artifactId.equals(d.getArtifactId())) {
164+
if (requestedVersionChange) {
165+
checkVersionUpdate(d.getVersion());
166+
}
116167
return maven;
117168
}
118169
}
119-
}
120170

121-
Validated<VersionComparator> versionValidation = Semver.validate(version, versionPattern);
122-
if (versionValidation.isValid()) {
123-
versionComparator = versionValidation.getValue();
124171
}
125172

126173
Xml.Tag root = maven.getRoot();
@@ -129,7 +176,43 @@ public Xml.Document visitDocument(Xml.Document document, ExecutionContext execut
129176
new MavenTagInsertionComparator(root.getContent() == null ? emptyList() : root.getContent())));
130177
}
131178

132-
doAfterVisit(new InsertDependencyInOrder(scope));
179+
boolean isUpdating = false;
180+
if (getCursor().getMessage("requestedScopeChange", false)) {
181+
isUpdating = true;
182+
Scope oldScope = getCursor().getMessage("oldScope");
183+
if (dependencies.containsKey(oldScope)) {
184+
for (ResolvedDependency d : dependencies.get(oldScope)) {
185+
if (d.isDirect() && groupId.equals(d.getGroupId()) && artifactId.equals(d.getArtifactId())) {
186+
if (requestedVersionChange) {
187+
checkVersionUpdate(d.getVersion());
188+
}
189+
doAfterVisit(new ChangeDependencyScope(groupId, artifactId, scope).getVisitor());
190+
maybeUpdateModel();
191+
break;
192+
}
193+
}
194+
} else { // Going from System / Import / Invalid to something else
195+
ResolvedPom resolvedPom = getResolutionResult().getPom();
196+
for (Dependency d : resolvedPom.getRequestedDependencies()) {
197+
if (groupId.equals(resolvedPom.getValue(d.getGroupId())) && artifactId.equals(resolvedPom.getValue(d.getArtifactId()))) {
198+
// This is run in this order because `ChangeDependencyScope` can end up moving a dependency from just requested to an actual dependency
199+
// whereas updating the version relies on something being in the dependencies
200+
doAfterVisit(new ChangeDependencyScope(groupId, artifactId, scope).getVisitor());
201+
maybeUpdateModel();
202+
if (requestedVersionChange && d.getVersion() != null) {
203+
checkVersionUpdate(requireNonNull(resolvedPom.getValue(d.getVersion())));
204+
maybeUpdateModel();
205+
}
206+
break;
207+
}
208+
}
209+
210+
}
211+
}
212+
213+
if (!isUpdating) {
214+
doAfterVisit(new InsertDependencyInOrder(scope));
215+
}
133216

134217
return maven;
135218
}
@@ -144,21 +227,19 @@ private class InsertDependencyInOrder extends MavenVisitor<ExecutionContext> {
144227
public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) {
145228
if (DEPENDENCIES_MATCHER.matches(getCursor())) {
146229
String versionToUse = null;
147-
148-
if (getResolutionResult().getPom().getManagedVersion(groupId, artifactId, type, classifier) == null) {
149-
if (familyRegex != null) {
150-
versionToUse = findDependencies(d -> familyRegex.matcher(d.getGroupId()).matches()).stream()
151-
.max(Comparator.comparing(d -> new Version(d.getVersion())))
152-
.map(d -> d.getRequested().getVersion())
153-
.orElse(null);
154-
}
230+
String managedVersion = getResolutionResult().getPom().getManagedVersion(groupId, artifactId, type, classifier);
231+
boolean scheduleVersionUpgrade = false;
232+
if (managedVersion == null) {
233+
versionToUse = tryGetFamilyVersion();
155234
if (versionToUse == null) {
156235
try {
157236
versionToUse = findVersionToUse(ctx);
158237
} catch (MavenDownloadingException e) {
159238
return e.warn(tag);
160239
}
161240
}
241+
} else if (versionComparator != null && !versionComparator.isValid(version, managedVersion)) {
242+
scheduleVersionUpgrade = true;
162243
}
163244

164245
Xml.Tag dependencyTag = Xml.Tag.build(
@@ -179,36 +260,56 @@ public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) {
179260

180261
doAfterVisit(new AddToTagVisitor<>(tag, dependencyTag, new InsertDependencyComparator(tag.getContent() == null ? emptyList() : tag.getContent(), dependencyTag)));
181262
maybeUpdateModel();
263+
if (scheduleVersionUpgrade) {
264+
doAfterVisit(new UpgradeDependencyVersion(groupId, artifactId, version, versionPattern, true, null).getVisitor());
265+
}
182266

183267
return tag;
184268
}
185269

186270
return super.visitTag(tag, ctx);
187271
}
272+
}
188273

189-
private String findVersionToUse(ExecutionContext ctx) throws MavenDownloadingException {
190-
if (resolvedVersion == null) {
191-
if (versionComparator == null || versionComparator instanceof ExactVersion) {
192-
resolvedVersion = version;
193-
} else {
194-
MavenMetadata mavenMetadata = metadataFailures == null ?
195-
downloadMetadata(groupId, artifactId, ctx) :
196-
metadataFailures.insertRows(ctx, () -> downloadMetadata(groupId, artifactId, ctx));
197-
// TODO This is hacky, but the class structure of LatestRelease is suboptimal, see https://github.com/openrewrite/rewrite/pull/5029
198-
// Fix it when we have a chance to refactor the code.
199-
if ("LatestRelease".equals(versionComparator.getClass().getSimpleName()) && mavenMetadata.getVersioning().getRelease() != null) {
200-
return mavenMetadata.getVersioning().getRelease();
201-
}
202-
LatestRelease latest = new LatestRelease(versionPattern);
203-
resolvedVersion = mavenMetadata.getVersioning().getVersions().stream()
204-
.filter(v -> versionComparator.isValid(null, v))
205-
.filter(v -> !Boolean.TRUE.equals(releasesOnly) || latest.isValid(null, v))
206-
.max((v1, v2) -> versionComparator.compare(null, v1, v2))
207-
.orElse(version);
274+
private @Nullable String tryGetFamilyVersion() {
275+
if (familyRegex != null) {
276+
return findDependencies(d -> familyRegex.matcher(d.getGroupId()).matches()).stream()
277+
.max(Comparator.comparing(d -> new Version(d.getVersion())))
278+
.map(d -> d.getRequested().getVersion())
279+
.orElse(null);
280+
}
281+
return null;
282+
}
283+
284+
private void checkVersionUpdate( String existingVersion) {
285+
String newResolvedVersion = requireNonNull(getCursor().getMessage("newResolvedVersion"));
286+
if (!existingVersion.equals(getResolutionResult().getPom().getValue(newResolvedVersion))) {
287+
doAfterVisit(new UpgradeDependencyVersion(groupId, artifactId, newResolvedVersion, versionPattern, true, null).getVisitor());
288+
}
289+
}
290+
291+
private String findVersionToUse(ExecutionContext ctx) throws MavenDownloadingException {
292+
if (resolvedVersion == null) {
293+
if (versionComparator == null || versionComparator instanceof ExactVersion) {
294+
resolvedVersion = version;
295+
} else {
296+
MavenMetadata mavenMetadata = metadataFailures == null ?
297+
downloadMetadata(groupId, artifactId, ctx) :
298+
metadataFailures.insertRows(ctx, () -> downloadMetadata(groupId, artifactId, ctx));
299+
// TODO This is hacky, but the class structure of LatestRelease is suboptimal, see https://github.com/openrewrite/rewrite/pull/5029
300+
// Fix it when we have a chance to refactor the code.
301+
if ("LatestRelease".equals(versionComparator.getClass().getSimpleName()) && mavenMetadata.getVersioning().getRelease() != null) {
302+
return mavenMetadata.getVersioning().getRelease();
208303
}
304+
LatestRelease latest = new LatestRelease(versionPattern);
305+
resolvedVersion = mavenMetadata.getVersioning().getVersions().stream()
306+
.filter(v -> versionComparator.isValid(null, v))
307+
.filter(v -> !Boolean.TRUE.equals(releasesOnly) || latest.isValid(null, v))
308+
.max((v1, v2) -> versionComparator.compare(null, v1, v2))
309+
.orElse(version);
209310
}
210-
211-
return resolvedVersion;
212311
}
312+
313+
return resolvedVersion;
213314
}
214315
}

0 commit comments

Comments
 (0)