All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openrewrite.maven.RemoveUnusedProperties Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2024 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 org.openrewrite.maven; import lombok.EqualsAndHashCode; import lombok.Value; import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.java.tree.JavaSourceFile; import org.openrewrite.maven.internal.MavenPomDownloader; import org.openrewrite.maven.tree.MavenResolutionResult; import org.openrewrite.maven.tree.ResolvedGroupArtifactVersion; import org.openrewrite.maven.tree.ResolvedPom; import org.openrewrite.text.PlainText; import org.openrewrite.text.PlainTextParser; import org.openrewrite.text.PlainTextVisitor; import org.openrewrite.xml.RemoveContentVisitor; import org.openrewrite.xml.XPathMatcher; import org.openrewrite.xml.tree.Xml; import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @Value @EqualsAndHashCode(callSuper = false) public class RemoveUnusedProperties extends ScanningRecipe { @Option(displayName = "Property pattern", description = "A pattern to filter properties to remove. Defaults to `.+?` to match anything", required = false, example = ".+\\.version") @Nullable String propertyPattern; @Override public String getDisplayName() { return "Remove unused properties"; } @Override public String getDescription() { return "Detect and remove Maven property declarations which do not have any usage within the project."; } public static class Accumulator { public Map> propertiesToUsingPoms = new HashMap<>(); public Map filteredResourcePathsToDeclaringPoms = new HashMap<>(); public Map> nonPomPathsToUsages = new HashMap<>(); public Map> getFilteredResourceUsages() { Map> result = new HashMap<>(); filteredResourcePathsToDeclaringPoms.forEach((filteredResourcePath, mrr) -> nonPomPathsToUsages.forEach((usagePath, properties) -> { if (usagePath.startsWith(filteredResourcePath)) { properties.forEach(property -> { result.putIfAbsent(property, new HashSet<>()); result.get(property).add(mrr); }); } } )); return result; } } @Override public RemoveUnusedProperties.Accumulator getInitialValue(ExecutionContext ctx) { return new RemoveUnusedProperties.Accumulator(); } private String getPropertyPattern() { return propertyPattern != null ? propertyPattern : ".+?"; } @Override public TreeVisitor getScanner(RemoveUnusedProperties.Accumulator acc) { String patternOrDefault = getPropertyPattern(); MavenIsoVisitor findPomUsagesVisitor = new FindPomUsagesVisitor(dollarPropertyMatcher(patternOrDefault), acc); MavenIsoVisitor findFilteredResourcePathsVisitor = new FindFilteredResourcePathsVisitor(acc); PlainTextVisitor findResourceUsagesVisitor = new FindResourceUsagesVisitor(patternOrDefault, acc); return new TreeVisitor() { @Override public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { if (tree instanceof SourceFile) { SourceFile sf = (SourceFile) tree; if (findPomUsagesVisitor.isAcceptable(sf, ctx)) { // ie: is a pom findPomUsagesVisitor.visit(sf, ctx); findFilteredResourcePathsVisitor.visit(sf, ctx); } else if (!(tree instanceof JavaSourceFile)) { // optimization: avoid visiting code files which are almost always not filtered resources findResourceUsagesVisitor.visit(PlainTextParser.convert(sf), ctx); } } return tree; } }; } private static Pattern dollarPropertyMatcher(String patternOrDefault) { return Pattern.compile("[^$]*\\$\\{(" + patternOrDefault + ")}[^$]*"); } private static Pattern atPropertyMatcher(String patternOrDefault) { return Pattern.compile("@(" + patternOrDefault + ")@"); } @Override public TreeVisitor getVisitor(RemoveUnusedProperties.Accumulator acc) { Pattern propertyMatcher = Pattern.compile(getPropertyPattern()); Map> filteredResourceUsages = acc.getFilteredResourceUsages(); return new MavenIsoVisitor() { @Override public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { Xml.Tag t = super.visitTag(tag, ctx); String propertyName = t.getName(); if (isPropertyTag() && propertyMatcher.matcher(propertyName).matches()) { if (isMavenBuiltinProperty(propertyName)) { return t; } if (parentHasProperty(getResolutionResult(), propertyName, ctx)) { return t; } if (acc.propertiesToUsingPoms.containsKey(propertyName)) { for (MavenResolutionResult pomWhereUsed : acc.propertiesToUsingPoms.get(propertyName)) { if (isAncestor(pomWhereUsed, getResolutionResult().getPom().getGav())) { return t; } } } if (filteredResourceUsages.containsKey(propertyName)) { for (MavenResolutionResult pomWhereUsed : filteredResourceUsages.get(propertyName)) { if (isAncestor(pomWhereUsed, getResolutionResult().getPom().getGav())) { return t; } } } doAfterVisit(new RemoveContentVisitor<>(tag, true, true)); maybeUpdateModel(); } return t; } private boolean isMavenBuiltinProperty(String propertyName) { return propertyName.startsWith("project.") || propertyName.startsWith("maven."); } private boolean isAncestor(MavenResolutionResult project, ResolvedGroupArtifactVersion possibleAncestorGav) { MavenResolutionResult projectAncestor = project; while (projectAncestor != null) { if (projectAncestor.getPom().getGav().equals(possibleAncestorGav)) { return true; } projectAncestor = projectAncestor.getParent(); } return false; } private boolean parentHasProperty(MavenResolutionResult resolutionResult, String propertyName, ExecutionContext ctx) { MavenPomDownloader downloader = new MavenPomDownloader(resolutionResult.getProjectPoms(), ctx, resolutionResult.getMavenSettings(), resolutionResult.getActiveProfiles()); try { ResolvedPom resolvedBarePom = resolutionResult.getPom().getRequested() .withProperties(Collections.emptyMap()) .withDependencies(Collections.emptyList()) .withDependencyManagement(Collections.emptyList()) .withPlugins(Collections.emptyList()) .withPluginManagement(Collections.emptyList()) .resolve(resolutionResult.getActiveProfiles(), downloader, ctx); return resolvedBarePom.getProperties().containsKey(propertyName); } catch (MavenDownloadingException e) { // assume parent *does* have property if error to do no harm return true; } } }; } private static class FindPomUsagesVisitor extends MavenIsoVisitor { private final Pattern propertyUsageMatcher; private final Accumulator acc; public FindPomUsagesVisitor(Pattern propertyUsageMatcher, Accumulator acc) { this.propertyUsageMatcher = propertyUsageMatcher; this.acc = acc; } @Override public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { Xml.Tag t = super.visitTag(tag, ctx); Optional value = t.getValue(); if (value.isPresent()) { Matcher matcher = propertyUsageMatcher.matcher(value.get()); while (matcher.find()) { acc.propertiesToUsingPoms.putIfAbsent(matcher.group(1), new HashSet<>()); acc.propertiesToUsingPoms.get(matcher.group(1)).add(getResolutionResult()); } } return t; } } private static class FindFilteredResourcePathsVisitor extends MavenIsoVisitor { private final XPathMatcher resourceMatcher = new XPathMatcher("/project/build/resources/resource"); private final Accumulator acc; public FindFilteredResourcePathsVisitor(Accumulator acc) { this.acc = acc; } @Override public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { if (resourceMatcher.matches(getCursor())) { String directory = tag.getChildValue("directory").orElse(null); if (tag.getChildValue("filtering").map(Boolean::valueOf).orElse(false) && directory != null) { Path path = getCursor().firstEnclosingOrThrow(SourceFile.class).getSourcePath(); try { acc.filteredResourcePathsToDeclaringPoms.put(path.getParent().resolve(directory), getResolutionResult()); } catch (InvalidPathException ignored) { } // fail quietly } return tag; } else { return super.visitTag(tag, ctx); } } } private static class FindResourceUsagesVisitor extends PlainTextVisitor { private final Pattern dollarMatcher; private final Pattern atMatcher; private final Accumulator acc; public FindResourceUsagesVisitor(String pattern, Accumulator acc) { this.dollarMatcher = dollarPropertyMatcher(pattern); this.atMatcher = atPropertyMatcher(pattern); this.acc = acc; } @Override public PlainText visitText(PlainText text, ExecutionContext ctx) { Matcher matcher = dollarMatcher.matcher(text.getText()); while (matcher.find()) { acc.nonPomPathsToUsages.putIfAbsent(text.getSourcePath(), new HashSet<>()); acc.nonPomPathsToUsages.get(text.getSourcePath()).add(matcher.group(1)); } matcher = atMatcher.matcher(text.getText()); while (matcher.find()) { acc.nonPomPathsToUsages.putIfAbsent(text.getSourcePath(), new HashSet<>()); acc.nonPomPathsToUsages.get(text.getSourcePath()).add(matcher.group(1)); } return text; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy