Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<skipChecks>${skipTests}</skipChecks>

<pmd.version>7.22.0</pmd.version>
<!--suppress MavenModelInspection -->
<junit.version>6.0.3</junit.version>
</properties>

Expand Down
59 changes: 58 additions & 1 deletion src/main/java/com/solubris/enforcer/UnusedPropertyRule.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -24,23 +29,37 @@
* Ensures that version properties are used.
*
* <p>This catches the typical scenario where a dependency is removed, but the property left orphaned.
*
* <p>Suppression: add a comment on the line before the property, e.g. {@code <!-- suppress UnusedProperty -->}
*/
@Named("unusedPropertyRule")
public class UnusedPropertyRule extends AbstractEnforcerRule {
private static final Pattern PROPERTY_PATTERN = Pattern.compile("\\$\\{([^}]+)\\}");
private static final Pattern SUPPRESS_COMMENT = Pattern.compile("<!--\\s*suppress\\s+UnusedProperty\\s*-->", Pattern.CASE_INSENSITIVE);
private static final Pattern PROPERTY_ELEMENT = Pattern.compile("<([a-zA-Z0-9.-]+)>");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 PROPERTY_ELEMENT regex excludes underscores, silently skipping suppression for properties with underscores

The PROPERTY_ELEMENT pattern <([a-zA-Z0-9.-]+)> does not include underscores (_) in the character class. Maven property names commonly contain underscores (e.g., <my_lib.version>). If a user places <!-- suppress UnusedProperty --> before such a property, the suppression will silently fail because the element won't be matched by the regex, and the property will still be reported as unused despite the suppression comment.

Suggested change
private static final Pattern PROPERTY_ELEMENT = Pattern.compile("<([a-zA-Z0-9.-]+)>");
private static final Pattern PROPERTY_ELEMENT = Pattern.compile("<([a-zA-Z0-9._-]+)>");
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


private final Model originalModel;
private final Model effectiveModel;
private final Set<String> 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<String> suppressedProperties) {
this.originalModel = originalModel;
this.effectiveModel = effectiveModel;
this.suppressedProperties = suppressedProperties != null ? suppressedProperties : Collections.emptySet();
}

@Override
Expand Down Expand Up @@ -68,6 +87,7 @@ protected Stream<String> 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() : "";
Expand All @@ -76,6 +96,43 @@ protected Stream<String> scanProperties() {
}).filter(Objects::nonNull);
}

/**
* Parses the pom file for suppression comments on the line before property elements.
* Format: {@code <!-- suppress UnusedProperty -->} on the line immediately before the property.
*/
static Set<String> parseSuppressions(File pomFile) {
if (pomFile == null || !pomFile.isFile()) {
return Collections.emptySet();
}
try {
List<String> lines = Files.readAllLines(pomFile.toPath());
Set<String> suppressed = new HashSet<>();
boolean inProperties = false;
for (int i = 0; i < lines.size(); i++) {
String line = lines.get(i);
if (line.contains("<properties>")) {
inProperties = true;
continue;
}
if (inProperties && line.contains("</properties>")) {
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<Object, Object> e) {
return e.getKey().toString().endsWith(".version");
}
Expand Down
35 changes: 35 additions & 0 deletions src/test/java/com/solubris/enforcer/UnusedPropertyRuleTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String> violations = ruleWithSuppression.scanProperties();

assertThat(violations).isEmpty();
}

@Test
void parseSuppressions_readsCommentFromPom(@TempDir Path tempDir) throws IOException {
Path pom = tempDir.resolve("pom.xml");
Files.writeString(pom, """
<project>
<properties>
<used.version>1.0</used.version>
<!-- suppress UnusedProperty -->
<suppressed.version>2.0</suppressed.version>
</properties>
</project>
""");

Set<String> suppressed = UnusedPropertyRule.parseSuppressions(pom.toFile());

assertThat(suppressed).containsExactly("suppressed.version");
}

@Test
void versionPropertyUsedByPluginDependencyPasses() {
originalModel.addProperty("api.version", "3.0.0");
Expand Down