Skip to content

Commit

Permalink
Merge branch 'main' into gradle-wrapper-scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
jkschneider authored Jan 30, 2025
2 parents 0816bba + 7c98672 commit 2979711
Show file tree
Hide file tree
Showing 14 changed files with 443 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,4 @@ public enum LineWrapSetting {
DoNotWrap, WrapAlways;
// Eventually we would add values like WrapIfTooLong or ChopIfTooLong

public String delimiter(GeneralFormatStyle generalFormatStyle) {
return this == DoNotWrap ? "" : generalFormatStyle.newLine();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.openrewrite.java.tree.Expression;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;
import org.openrewrite.java.tree.TypeUtils;
import org.openrewrite.maven.MavenDownloadingException;
import org.openrewrite.maven.internal.MavenPomDownloader;
import org.openrewrite.maven.tree.GroupArtifactVersion;
Expand All @@ -44,38 +45,42 @@
import org.openrewrite.semver.VersionComparator;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.util.Objects.requireNonNull;

@Value
@EqualsAndHashCode(callSuper = false)
public class RemoveRedundantDependencyVersions extends Recipe {
@Option(displayName = "Group",
description = "Group glob expression pattern used to match dependencies that should be managed." +
"Group is the first part of a dependency coordinate `com.google.guava:guava:VERSION`.",
"Group is the first part of a dependency coordinate `com.google.guava:guava:VERSION`.",
example = "com.google.*",
required = false)
@Nullable
String groupPattern;

@Option(displayName = "Artifact",
description = "Artifact glob expression pattern used to match dependencies that should be managed." +
"Artifact is the second part of a dependency coordinate `com.google.guava:guava:VERSION`.",
"Artifact is the second part of a dependency coordinate `com.google.guava:guava:VERSION`.",
example = "guava*",
required = false)
@Nullable
String artifactPattern;

@Option(displayName = "Only if managed version is ...",
description = "Only remove the explicit version if the managed version has the specified comparative relationship to the explicit version. " +
"For example, `gte` will only remove the explicit version if the managed version is the same or newer. " +
"Default `eq`.",
"For example, `gte` will only remove the explicit version if the managed version is the same or newer. " +
"Default `eq`.",
valid = {"any", "eq", "lt", "lte", "gt", "gte"},
required = false)
@Nullable
Comparator onlyIfManagedVersionIs;

@Option(displayName = "Except",
description = "Accepts a list of GAVs. Dependencies matching a GAV will be ignored by this recipe."
+ " GAV versions are ignored if provided.",
+ " GAV versions are ignored if provided.",
example = "com.jcraft:jsch",
required = false)
@Nullable
Expand All @@ -100,6 +105,10 @@ public String getDescription() {
return "Remove explicitly-specified dependency versions that are managed by a Gradle `platform`/`enforcedPlatform`.";
}

private static final MethodMatcher CONSTRAINTS_MATCHER = new MethodMatcher("DependencyHandlerSpec constraints(..)");
private static final MethodMatcher INDIVIDUAL_CONSTRAINTS_MATCHER = new MethodMatcher("DependencyHandlerSpec *(..)");
private static final VersionComparator VERSION_COMPARATOR = requireNonNull(Semver.validate("latest.release", null).getValue());

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(
Expand Down Expand Up @@ -192,6 +201,80 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu
return m;
}
}.visit(cu, ctx);
cu = (G.CompilationUnit) new GroovyIsoVisitor<ExecutionContext>() {

@Override
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
J.MethodInvocation m = super.visitMethodInvocation(method, ctx);
if (CONSTRAINTS_MATCHER.matches(m)) {
if (m.getArguments().isEmpty() || !(m.getArguments().get(0) instanceof J.Lambda)) {
return m;
}
J.Lambda l = (J.Lambda) m.getArguments().get(0);
if (!(l.getBody() instanceof J.Block)) {
return m;
}
J.Block b = (J.Block) l.getBody();
if (b.getStatements().isEmpty()) {
//noinspection DataFlowIssue
return null;
}
return m;
} else if (INDIVIDUAL_CONSTRAINTS_MATCHER.matches(m)) {
if (!TypeUtils.isAssignableTo("org.gradle.api.artifacts.Dependency", requireNonNull(m.getMethodType()).getReturnType())) {
return m;
}
if (m.getArguments().isEmpty() || !(m.getArguments().get(0) instanceof J.Literal) || !(((J.Literal) m.getArguments().get(0)).getValue() instanceof String)) {
return m;
}
//noinspection DataFlowIssue
if (shouldRemoveRedundantConstraint((String) ((J.Literal) m.getArguments().get(0)).getValue(), m.getSimpleName())) {
//noinspection DataFlowIssue
return null;
}
}
return m;
}

@Override
public J.Return visitReturn(J.Return _return, ExecutionContext executionContext) {
J.Return r = super.visitReturn(_return, executionContext);
if (r.getExpression() == null) {
//noinspection DataFlowIssue
return null;
}
return r;
}

boolean shouldRemoveRedundantConstraint(String dependencyNotation, String configurationName) {
return shouldRemoveRedundantConstraint(
DependencyStringNotationConverter.parse(dependencyNotation),
gp.getConfiguration(configurationName));
}

boolean shouldRemoveRedundantConstraint(@Nullable Dependency constraint, @Nullable GradleDependencyConfiguration c) {
if (c == null || constraint == null || constraint.getVersion() == null) {
return false;
}
if(constraint.getVersion().contains("[") || constraint.getVersion().contains("!!")) {
// https://docs.gradle.org/current/userguide/dependency_versions.html#sec:strict-version
return false;
}
if ((groupPattern != null && !StringUtils.matchesGlob(constraint.getGroupId(), groupPattern))
|| (artifactPattern != null && !StringUtils.matchesGlob(constraint.getArtifactId(), artifactPattern))) {
return false;
}
return Stream.concat(
Stream.of(c),
gp.configurationsExtendingFrom(c, true).stream()
)
.filter(GradleDependencyConfiguration::isCanBeResolved)
.distinct()
.map(conf -> conf.findResolvedDependency(requireNonNull(constraint.getGroupId()), constraint.getArtifactId()))
.filter(Objects::nonNull)
.anyMatch(resolvedDependency -> VERSION_COMPARATOR.compare(null, resolvedDependency.getVersion(), constraint.getVersion()) > 0);
}
}.visitNonNull(cu, ctx);

return super.visitCompilationUnit(cu, ctx);
}
Expand Down Expand Up @@ -258,7 +341,7 @@ private J.MethodInvocation maybeRemoveVersion(J.MethodInvocation m) {
G.MapLiteral mapLiteral = (G.MapLiteral) arg;
return mapLiteral.withElements(ListUtils.map(mapLiteral.getElements(), entry -> {
if (entry.getKey() instanceof J.Literal &&
"version".equals(((J.Literal) entry.getKey()).getValue())) {
"version".equals(((J.Literal) entry.getKey()).getValue())) {
return null;
}
return entry;
Expand All @@ -268,7 +351,7 @@ private J.MethodInvocation maybeRemoveVersion(J.MethodInvocation m) {
return m.withArguments(ListUtils.map(m.getArguments(), arg -> {
G.MapEntry entry = (G.MapEntry) arg;
if (entry.getKey() instanceof J.Literal &&
"version".equals(((J.Literal) entry.getKey()).getValue())) {
"version".equals(((J.Literal) entry.getKey()).getValue())) {
return null;
}
return entry;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,65 @@ void freestandingScript() {
);
}

@Test
void removeUnneededConstraint() {
rewriteRun(
buildGradle(
"""
plugins {
id 'java'
}
repositories {
mavenCentral()
}
dependencies {
constraints {
implementation('org.springframework:spring-core:6.2.1') {
because 'Gradle is resolving 6.2.2 already, this constraint has no effect and can be removed'
}
}
implementation 'org.springframework.boot:spring-boot:3.4.2'
}
""",
"""
plugins {
id 'java'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot:3.4.2'
}
"""
)
);
}

@Test
void keepStrictConstraint() {
rewriteRun(
buildGradle(
"""
plugins {
id 'java'
}
repositories {
mavenCentral()
}
dependencies {
constraints {
implementation('org.springframework:spring-core:6.2.1!!') {
because 'The !! forces the usage of 6.2.1'
}
}
implementation 'org.springframework.boot:spring-boot:3.4.2'
}
"""
)
);
}

@Test
void transitiveConfiguration() {
rewriteRun(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public String getDisplayName() {

@Override
public String getDescription() {
return "Indents JSON using the most common indentation size and tabs or space choice in use in the file.";
return "Format JSON code using a standard comprehensive set of JSON formatting recipes.";
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,11 @@ public Json preVisit(Json tree, P p) {
Cursor cursor = getCursor().getParentOrThrow();
Autodetect autodetectedStyle = Autodetect.detector().sample(doc).build();
Json js = tree;

TabsAndIndentsStyle tabsIndentsStyle = Style.from(TabsAndIndentsStyle.class, doc, () -> autodetectedStyle.getStyle(TabsAndIndentsStyle.class));
GeneralFormatStyle generalStyle = Style.from(GeneralFormatStyle.class, doc, () -> autodetectedStyle.getStyle(GeneralFormatStyle.class));
WrappingAndBracesStyle wrappingStyle = Style.from(WrappingAndBracesStyle.class, doc, () -> autodetectedStyle.getStyle(WrappingAndBracesStyle.class));

js = new WrappingAndBracesVisitor<>(wrappingStyle, generalStyle, stopAfter).visitNonNull(js, p, cursor.fork());
js = new WrappingAndBracesVisitor<>(wrappingStyle, tabsIndentsStyle, generalStyle, stopAfter).visitNonNull(js, p, cursor.fork());
js = new TabsAndIndentsVisitor<>(wrappingStyle, tabsIndentsStyle, generalStyle, stopAfter).visitNonNull(js, p, cursor.fork());
js = new NormalizeLineBreaksVisitor<>(generalStyle, stopAfter).visitNonNull(js, p, cursor.fork());
return js;
Expand All @@ -64,7 +63,7 @@ public Json.Document visitDocument(Json.Document js, P p) {
GeneralFormatStyle generalStyle = Style.from(GeneralFormatStyle.class, js, () -> autodetectedStyle.getStyle(GeneralFormatStyle.class));
WrappingAndBracesStyle wrappingStyle = Style.from(WrappingAndBracesStyle.class, js, () -> autodetectedStyle.getStyle(WrappingAndBracesStyle.class));

js = (Json.Document) new WrappingAndBracesVisitor<>(wrappingStyle, generalStyle, stopAfter).visitNonNull(js, p);
js = (Json.Document) new WrappingAndBracesVisitor<>(wrappingStyle, tabsIndentsStyle, generalStyle, stopAfter).visitNonNull(js, p);
js = (Json.Document) new TabsAndIndentsVisitor<>(wrappingStyle, tabsIndentsStyle, generalStyle, stopAfter).visitNonNull(js, p);
js = (Json.Document) new NormalizeLineBreaksVisitor<>(generalStyle, stopAfter).visitNonNull(js, p);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
import org.openrewrite.json.style.WrappingAndBracesStyle;
import org.openrewrite.json.tree.Json;
import org.openrewrite.json.tree.JsonRightPadded;
import org.openrewrite.json.tree.JsonValue;
import org.openrewrite.json.tree.Space;
import org.openrewrite.style.GeneralFormatStyle;
import org.openrewrite.style.LineWrapSetting;

import java.util.List;
import java.util.Optional;
Expand All @@ -33,7 +35,8 @@

public class TabsAndIndentsVisitor<P> extends JsonIsoVisitor<P> {
private final String singleIndent;
private final String objectsWrappingDelimiter;
private final GeneralFormatStyle generalFormatStyle;
private final WrappingAndBracesStyle wrappingAndBracesStyle;

@Nullable
private final Tree stopAfter;
Expand All @@ -44,22 +47,15 @@ public TabsAndIndentsVisitor(WrappingAndBracesStyle wrappingAndBracesStyle,
@Nullable Tree stopAfter) {
this.stopAfter = stopAfter;

if (tabsAndIndentsStyle.getUseTabCharacter()) {
this.singleIndent = "\t";
} else {
StringBuilder sb = new StringBuilder();
for (int j = 0; j < tabsAndIndentsStyle.getIndentSize(); j++) {
sb.append(" ");
}
singleIndent = sb.toString();
}
this.objectsWrappingDelimiter = wrappingAndBracesStyle.getWrapObjects().delimiter(generalFormatStyle);
this.singleIndent = tabsAndIndentsStyle.singleIndent();
this.wrappingAndBracesStyle = wrappingAndBracesStyle;
this.generalFormatStyle = generalFormatStyle;
}

@Override
public Json preVisit(Json tree, P p) {
Json json = super.preVisit(tree, p);
if (tree instanceof Json.JsonObject) {
if (tree instanceof Json.JsonObject || tree instanceof Json.Array) {
String newIndent = getCurrentIndent() + this.singleIndent;
getCursor().putMessage("indentToUse", newIndent);
}
Expand Down Expand Up @@ -94,21 +90,42 @@ public Json preVisit(Json tree, P p) {
}
if (json instanceof Json.JsonObject) {
Json.JsonObject obj = (Json.JsonObject) json;
List<JsonRightPadded<Json>> children = obj.getPadding().getMembers();
children = ListUtils.mapLast(children, last -> {
String currentAfter = last.getAfter().getWhitespace();
String newAfter = objectsWrappingDelimiter + relativeIndent;
if (!newAfter.equals(currentAfter)) {
return last.withAfter(Space.build(newAfter, emptyList()));
} else {
return last;
}
});
json = obj.getPadding().withMembers(children);
List<JsonRightPadded<Json>> members = obj.getPadding().getMembers();
LineWrapSetting wrappingSetting = this.wrappingAndBracesStyle.getWrapObjects();
members = applyWrappingStyleToLastChildSuffix(members, wrappingSetting, relativeIndent);
json = obj.getPadding().withMembers(members);
}

if (json instanceof Json.Array) {
Json.Array array = (Json.Array) json;
List<JsonRightPadded<JsonValue>> members = array.getPadding().getValues();
LineWrapSetting wrappingSetting = this.wrappingAndBracesStyle.getWrapArrays();
members = applyWrappingStyleToLastChildSuffix(members, wrappingSetting, relativeIndent);
json = array.getPadding().withValues(members);
}

return json;
}

private <JS extends Json> List<JsonRightPadded<JS>> applyWrappingStyleToLastChildSuffix(List<JsonRightPadded<JS>> elements, LineWrapSetting wrapping, String relativeIndent) {
return ListUtils.mapLast(elements, elem -> {
String currentAfter = elem.getAfter().getWhitespace();
final String newAfter;
if (wrapping == LineWrapSetting.DoNotWrap) {
newAfter = "";
} else if (wrapping == LineWrapSetting.WrapAlways) {
newAfter = this.generalFormatStyle.newLine() + relativeIndent;
} else {
throw new UnsupportedOperationException("Unknown LineWrapSetting: " + wrapping);
}
if (!newAfter.equals(currentAfter)) {
return elem.withAfter(Space.build(newAfter, emptyList()));
} else {
return elem;
}
});
}

private String getCurrentIndent() {
String ret = getCursor().getNearestMessage("indentToUse");
if (ret == null) {
Expand Down
Loading

0 comments on commit 2979711

Please sign in to comment.