Skip to content

Commit 47206d4

Browse files
committed
Add support for PMD and CPD reports
1 parent 2ae2f6e commit 47206d4

13 files changed

Lines changed: 643 additions & 3 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
target/
22
*.iml
33
.idea
4+
.classpath
5+
.project
6+
.settings/

README.adoc

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
:version_snapshot: 1.2.0-SNAPSHOT
44

55
This Maven plugin allows you to transform XML reports created by code quality tools like
6-
https://spotbugs.github.io/[SpotBugs] and https://checkstyle.org/[Checkstyle]
6+
https://spotbugs.github.io/[SpotBugs], https://checkstyle.org/[Checkstyle]
7+
and https://pmd.github.io/[PMD/CPD]
78
into a JSON format supported by https://about.gitlab.com/[GitLab] to displayed
89
identified issues in the merge request widget.
910

@@ -14,7 +15,7 @@ image::.readme/gitlab-merge-request-widget.png[]
1415
=== Step 1: Set up SpotBugs and/or Checkstyle
1516

1617
As this plugin processes XML reports of other code quality tools, you have to set up
17-
the Maven plugins for SpotBugs and/or Checkstyle first.
18+
the Maven plugins for SpotBugs and/or Checkstyle and/or PMD/CPD first.
1819

1920
Such a setup could look like this:
2021

@@ -67,6 +68,22 @@ Such a setup could look like this:
6768
</dependencies>
6869
</plugin>
6970
71+
<!-- PMD/CPD --->
72+
<plugin>
73+
<groupId>org.apache.maven.plugins</groupId>
74+
<artifactId>maven-pmd-plugin</artifactId>
75+
<version>3.28.0</version>
76+
<executions>
77+
<execution>
78+
<phase>verify</phase>
79+
<goals>
80+
<goal>pmd</goal>
81+
<goal>cpd</goal>
82+
</goals>
83+
</execution>
84+
</executions>
85+
</plugin>
86+
7087
</plugins>
7188
</build>
7289
</project>
@@ -109,6 +126,8 @@ Without any explicit configuration, the plugin will look for XML reports in the
109126

110127
* `target/spotbugsXml.xml`
111128
* `target/checkstyle-result.xml`
129+
* `target/pmd.xml`
130+
* `target/cpd.xml`
112131

113132
If corresponding XML files are found and contain at least one issue, the plugin will
114133
generate the following JSON file:
@@ -178,6 +197,18 @@ default values:
178197
<!-- Location of the Checkstyle XML report -->
179198
<checkstyleInputFile>${project.build.directory}/checkstyle-result.xml</checkstyleInputFile>
180199
200+
<!-- Whether to enable support for PMD -->
201+
<pmdEnabled>true</pmdEnabled>
202+
203+
<!-- Location of the PMD XML report -->
204+
<pmdInputFile>${project.build.directory}/pmd.xml</pmdInputFile>
205+
206+
<!-- Whether to enable support for CPD -->
207+
<cpdEnabled>true</cpdEnabled>
208+
209+
<!-- Location of the CPD XML report -->
210+
<cpdInputFile>${project.build.directory}/pmd.xml</cpdInputFile>
211+
181212
<!-- Location of the JSON output file -->
182213
<outputFile>${project.build.directory}/gl-code-quality-report.json</outputFile>
183214
@@ -227,6 +258,10 @@ The plugin may also be used and configured using the Maven CLI. Available config
227258
* `glcqp.spotbugsInputFile`
228259
* `glcqp.checkstyleEnabled`
229260
* `glcqp.checkstyeInputFile`
261+
* `glcqp.pmdEnabled`
262+
* `glcqp.pmdInputFile`
263+
* `glcqp.cpdEnabled`
264+
* `glcqp.cpdInputFile`
230265
* `glcqp.outputFile`
231266

232267
They are used like this:
@@ -238,6 +273,10 @@ mvn de.chkal.maven:gitlab-code-quality-plugin:{version_stable}:check \
238273
-Dglcqp.spotbugsInputFile=target/spotbugsXml.xml \
239274
-Dglcqp.checkstyleEnabled=true \
240275
-Dglcqp.checkstyeInputFile=target/checkstyle-result.xml \
276+
-Dglcqp.pmdEnabled=true \
277+
-Dglcqp.pmdInputFile=target/pmd.xml \
278+
-Dglcqp.cpdEnabled=true \
279+
-Dglcqp.cpdInputFile=target/cpd.xml \
241280
-Dglcqp.outputFile=target/gl-code-quality-report.json
242281
----
243282

@@ -282,4 +321,4 @@ To use these latest snapshots, you will have to modify your `pom.xml` like this:
282321
</pluginRepositories>
283322
284323
</project>
285-
----
324+
----

pom.xml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,32 @@
8383
<clearOutputDir>false</clearOutputDir>
8484
</configuration>
8585
</execution>
86+
<execution>
87+
<id>schema-pmd</id>
88+
<goals>
89+
<goal>xjc</goal>
90+
</goals>
91+
<configuration>
92+
<sources>
93+
<source>src/main/xsd/report_2_0_0.xsd</source>
94+
</sources>
95+
<packageName>de.chkal.maven.gitlab.codequality.pmd</packageName>
96+
<clearOutputDir>false</clearOutputDir>
97+
</configuration>
98+
</execution>
99+
<execution>
100+
<id>schema-cpd</id>
101+
<goals>
102+
<goal>xjc</goal>
103+
</goals>
104+
<configuration>
105+
<sources>
106+
<source>src/main/xsd/cpd-report_1_0_0.xsd</source>
107+
</sources>
108+
<packageName>de.chkal.maven.gitlab.codequality.cpd</packageName>
109+
<clearOutputDir>false</clearOutputDir>
110+
</configuration>
111+
</execution>
86112
</executions>
87113
</plugin>
88114

src/main/java/de/chkal/maven/gitlab/codequality/GenerateMojo.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package de.chkal.maven.gitlab.codequality;
22

33
import de.chkal.maven.gitlab.codequality.checkstyle.CheckstyleFindingProvider;
4+
import de.chkal.maven.gitlab.codequality.cpd.CpdFindingProvider;
5+
import de.chkal.maven.gitlab.codequality.pmd.PmdFindingProvider;
46
import de.chkal.maven.gitlab.codequality.spotbugs.SpotbugsFindingProvider;
57
import java.io.File;
68
import java.io.FileInputStream;
@@ -32,6 +34,19 @@ public class GenerateMojo extends AbstractMojo {
3234
@Parameter(property = "glcqp.checkstyeInputFile", defaultValue = "${project.build.directory}/checkstyle-result.xml")
3335
public File checkstyleInputFile;
3436

37+
@Parameter(property = "glcqp.pmdEnabled", defaultValue = "true")
38+
public boolean pmdEnabled;
39+
40+
@Parameter(property = "glcqp.pmdInputFile", defaultValue = "${project.build.directory}/pmd.xml")
41+
public File pmdInputFile;
42+
43+
44+
@Parameter(property = "glcqp.cpdEnabled", defaultValue = "true")
45+
public boolean cpdEnabled;
46+
47+
@Parameter(property = "glcqp.cpdInputFile", defaultValue = "${project.build.directory}/cpd.xml")
48+
public File cpdInputFile;
49+
3550
@Parameter(property = "glcqp.outputFile", defaultValue = "${project.build.directory}/gl-code-quality-report.json")
3651
public File outputFile;
3752

@@ -63,6 +78,22 @@ public void execute() throws MojoFailureException {
6378
log
6479
));
6580

81+
// Run PMD provider
82+
findings.addAll(executeProvider(
83+
new PmdFindingProvider(repositoryRoot),
84+
pmdEnabled,
85+
pmdInputFile,
86+
log
87+
));
88+
89+
// Run CPD provider
90+
findings.addAll(executeProvider(
91+
new CpdFindingProvider(repositoryRoot),
92+
cpdEnabled,
93+
cpdInputFile,
94+
log
95+
));
96+
6697
// Create GitLab report
6798
try (FileOutputStream stream = new FileOutputStream(outputFile)) {
6899
new ReportSerializer().write(findings, stream);
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package de.chkal.maven.gitlab.codequality.cpd;
2+
3+
import de.chkal.maven.gitlab.codequality.Finding;
4+
import de.chkal.maven.gitlab.codequality.Finding.Severity;
5+
import de.chkal.maven.gitlab.codequality.FindingProvider;
6+
import jakarta.xml.bind.DatatypeConverter;
7+
import jakarta.xml.bind.JAXBContext;
8+
import jakarta.xml.bind.JAXBException;
9+
import jakarta.xml.bind.Unmarshaller;
10+
import java.io.File;
11+
import java.io.InputStream;
12+
import java.nio.charset.StandardCharsets;
13+
import java.nio.file.Path;
14+
import java.security.MessageDigest;
15+
import java.security.NoSuchAlgorithmException;
16+
import java.util.List;
17+
import java.util.Locale;
18+
import java.util.stream.Collectors;
19+
import java.util.stream.Stream;
20+
21+
public class CpdFindingProvider implements FindingProvider {
22+
23+
private final File repositoryRoot;
24+
25+
public CpdFindingProvider(File repositoryRoot) {
26+
this.repositoryRoot = repositoryRoot;
27+
}
28+
29+
@Override
30+
public String getName() {
31+
return "CPD";
32+
}
33+
34+
@Override
35+
public List<Finding> getFindings(InputStream stream) {
36+
37+
try {
38+
39+
JAXBContext jaxbContext = JAXBContext.newInstance(PmdCpd.class);
40+
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
41+
PmdCpd cpdWarnings = (PmdCpd) unmarshaller.unmarshal(stream);
42+
43+
return cpdWarnings.getDuplication().stream()
44+
.flatMap(this::transformDuplication)
45+
.collect(Collectors.toList());
46+
47+
} catch (JAXBException e) {
48+
throw new IllegalStateException(e);
49+
}
50+
}
51+
52+
private Stream<Finding> transformDuplication(Duplication duplication) {
53+
return duplication.getFile().stream().map(fileLocation -> transformFileLocation(duplication, fileLocation));
54+
}
55+
56+
private Finding transformFileLocation(Duplication duplication, FileLocation fileLocation) {
57+
58+
Finding finding = new Finding();
59+
finding.setDescription(String.format("%s: Duplication (%d lines)", getName(), duplication.getLines().intValue()));
60+
finding.setFingerprint(createFingerprint(duplication, fileLocation));
61+
finding.setSeverity(getSeverity(duplication));
62+
finding.setPath(getRepositoryRelativePath(fileLocation));
63+
finding.setLine(getLineNumber(fileLocation));
64+
return finding;
65+
66+
}
67+
68+
private String getRepositoryRelativePath(FileLocation file) {
69+
Path absolutePath = Path.of(file.getPath());
70+
return repositoryRoot.toPath().relativize(absolutePath).toString();
71+
}
72+
73+
private Severity getSeverity(Duplication duplication) {
74+
if (duplication.getLines().intValue() > 30) {
75+
return Severity.MAJOR;
76+
}
77+
if (duplication.getLines().intValue() > 10) {
78+
return Severity.MINOR;
79+
}
80+
return Severity.INFO;
81+
}
82+
83+
private String createFingerprint(Duplication duplication, FileLocation fileLocation) {
84+
85+
try {
86+
87+
/*
88+
* The fingerprint is created from:
89+
* - file path
90+
* - number of lines
91+
* - number of tokens
92+
* - code fragment
93+
* - column index (which will most likely not change for a finding)
94+
* - end column index (which will most likely not change for a finding)
95+
*
96+
* We do NOT use:
97+
* - line number (will change if code is added/removed above or below the finding)
98+
*/
99+
String key = String.format("%s:%s:%s:%s:%s:%s",
100+
getRepositoryRelativePath(fileLocation),
101+
duplication.getLines().intValue(),
102+
duplication.getTokens().intValue(),
103+
duplication.getCodefragment().value,
104+
fileLocation.getColumn(),
105+
fileLocation.getEndcolumn()
106+
);
107+
108+
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
109+
messageDigest.update(key.getBytes(StandardCharsets.UTF_8));
110+
byte[] digest = messageDigest.digest();
111+
112+
return DatatypeConverter.printHexBinary(digest).toLowerCase(Locale.ROOT);
113+
114+
} catch (NoSuchAlgorithmException e) {
115+
throw new RuntimeException(e);
116+
}
117+
118+
}
119+
120+
private static Integer getLineNumber(FileLocation fileLocation) {
121+
return fileLocation.getLine().intValue();
122+
}
123+
124+
}

0 commit comments

Comments
 (0)