diff --git a/pom.xml b/pom.xml index 514062a..1f1dac0 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,7 @@ ${skipTests} 7.22.0 + 6.0.3 diff --git a/src/main/java/com/solubris/enforcer/UnusedPropertyRule.java b/src/main/java/com/solubris/enforcer/UnusedPropertyRule.java index 1474c88..24f3e5d 100644 --- a/src/main/java/com/solubris/enforcer/UnusedPropertyRule.java +++ b/src/main/java/com/solubris/enforcer/UnusedPropertyRule.java @@ -7,10 +7,15 @@ import javax.inject.Inject; import javax.inject.Named; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.concurrent.atomic.LongAdder; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -24,23 +29,37 @@ * Ensures that version properties are used. * *

This catches the typical scenario where a dependency is removed, but the property left orphaned. + * + *

Suppression: add a comment on the line before the property, e.g. {@code } */ @Named("unusedPropertyRule") public class UnusedPropertyRule extends AbstractEnforcerRule { private static final Pattern PROPERTY_PATTERN = Pattern.compile("\\$\\{([^}]+)\\}"); + private static final Pattern SUPPRESS_COMMENT = Pattern.compile("", Pattern.CASE_INSENSITIVE); + private static final Pattern PROPERTY_ELEMENT = Pattern.compile("<([a-zA-Z0-9.-]+)>"); private final Model originalModel; private final Model effectiveModel; + private final Set suppressedProperties; @SuppressWarnings("unused") @Inject public UnusedPropertyRule(MavenSession session) { - this(ModelScanner.modelFrom(session), session.getCurrentProject().getModel()); + this( + ModelScanner.modelFrom(session), + session.getCurrentProject().getModel(), + parseSuppressions(session.getCurrentProject().getFile()) + ); } protected UnusedPropertyRule(Model originalModel, Model effectiveModel) { + this(originalModel, effectiveModel, Collections.emptySet()); + } + + protected UnusedPropertyRule(Model originalModel, Model effectiveModel, Set suppressedProperties) { this.originalModel = originalModel; this.effectiveModel = effectiveModel; + this.suppressedProperties = suppressedProperties != null ? suppressedProperties : Collections.emptySet(); } @Override @@ -68,6 +87,7 @@ protected Stream scanProperties() { return originalModel.getProperties().entrySet().stream() .filter(UnusedPropertyRule::isVersionProperty) // only check properties that look like versions + .filter(e -> !suppressedProperties.contains(e.getKey().toString())) .map(e -> { String propName = e.getKey().toString(); String propValue = e.getValue() != null ? e.getValue().toString() : ""; @@ -76,6 +96,43 @@ protected Stream scanProperties() { }).filter(Objects::nonNull); } + /** + * Parses the pom file for suppression comments on the line before property elements. + * Format: {@code } on the line immediately before the property. + */ + static Set parseSuppressions(File pomFile) { + if (pomFile == null || !pomFile.isFile()) { + return Collections.emptySet(); + } + try { + List lines = Files.readAllLines(pomFile.toPath()); + Set suppressed = new HashSet<>(); + boolean inProperties = false; + for (int i = 0; i < lines.size(); i++) { + String line = lines.get(i); + if (line.contains("")) { + inProperties = true; + continue; + } + if (inProperties && line.contains("")) { + break; + } + if (inProperties && i > 0) { + var matcher = PROPERTY_ELEMENT.matcher(line); + if (matcher.find() && matcher.start() == line.indexOf("<")) { + String prevLine = lines.get(i - 1); + if (SUPPRESS_COMMENT.matcher(prevLine).find()) { + suppressed.add(matcher.group(1)); + } + } + } + } + return suppressed; + } catch (IOException e) { + return Collections.emptySet(); + } + } + private static boolean isVersionProperty(Map.Entry e) { return e.getKey().toString().endsWith(".version"); } diff --git a/src/test/java/com/solubris/enforcer/UnusedPropertyRuleTest.java b/src/test/java/com/solubris/enforcer/UnusedPropertyRuleTest.java index 96e7442..ab635ff 100644 --- a/src/test/java/com/solubris/enforcer/UnusedPropertyRuleTest.java +++ b/src/test/java/com/solubris/enforcer/UnusedPropertyRuleTest.java @@ -9,7 +9,12 @@ import org.apache.maven.model.PluginManagement; import org.apache.maven.model.Reporting; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Set; import java.util.stream.Stream; import static com.solubris.enforcer.ModelStubber.dependencyOf; @@ -203,6 +208,36 @@ void executePassesWhenAllPropertiesUsed() { assertThatNoException().isThrownBy(rule::execute); } + @Test + void suppressedUnusedPropertyPasses() { + originalModel.addProperty("kept-for-compat.version", "1.0.0"); + UnusedPropertyRule ruleWithSuppression = new UnusedPropertyRule( + originalModel, effectiveModel, Set.of("kept-for-compat.version")); + ruleWithSuppression.setLog(mock(EnforcerLogger.class)); + + Stream violations = ruleWithSuppression.scanProperties(); + + assertThat(violations).isEmpty(); + } + + @Test + void parseSuppressions_readsCommentFromPom(@TempDir Path tempDir) throws IOException { + Path pom = tempDir.resolve("pom.xml"); + Files.writeString(pom, """ + + + 1.0 + + 2.0 + + + """); + + Set suppressed = UnusedPropertyRule.parseSuppressions(pom.toFile()); + + assertThat(suppressed).containsExactly("suppressed.version"); + } + @Test void versionPropertyUsedByPluginDependencyPasses() { originalModel.addProperty("api.version", "3.0.0");