Skip to content

Commit

Permalink
Merge pull request #817 from jenkinsci/revApi
Browse files Browse the repository at this point in the history
Add support for Revapi
  • Loading branch information
uhafner authored Jul 29, 2022
2 parents edf2a00 + e9ffc30 commit 311354c
Show file tree
Hide file tree
Showing 15 changed files with 532 additions and 69 deletions.
1 change: 1 addition & 0 deletions etc/Jenkinsfile.analysis
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ node('java11-agent') {
spotBugs(pattern: 'target/spotbugsXml.xml'),
pmdParser(pattern: 'target/pmd.xml'),
cpd(pattern: 'target/cpd.xml'),
revApi(pattern: 'target/revapi-result.json'),
taskScanner(highTags:'FIXME', normalTags:'TODO', includePattern: '**/*.java', excludePattern: 'target/**/*')]
}

Expand Down
22 changes: 11 additions & 11 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -382,38 +382,38 @@
<groupId>org.revapi</groupId>
<artifactId>revapi-maven-plugin</artifactId>
<configuration>
<failBuildOnProblemsFound>false</failBuildOnProblemsFound>
<analysisConfiguration>
<revapi.differences id="manually-vetted">
<justification>Shaded artefacts are not part of the public API</justification>
<criticality>allowed</criticality>
<attachments>
<vetted>ok</vetted>
</attachments>
<differences combine.children="append">
<item>
<ignore>true</ignore>
<regex>true</regex>
<classQualifiedName>shaded.*</classQualifiedName>
<code>.*</code>
</item>
<item>
<ignore>true</ignore>
<class>edu.hm.hafner.analysis.parser.checkstyle.TopicRule</class>
<regex>true</regex>
<code>.*</code>
</item>
<item>
<ignore>true</ignore>
<package>javax.xml.transform</package>
<code>java.class.externalClassExposedInAPI</code>
</item>
</differences>
<filter.classes>
<item>
<regex>true</regex>
<exclude>edu.hm.hafner.analysis.parser.checkstyle.TopicRule</exclude>
</item>
<item>
<!-- No part of codingstyle library -->
<ignore>true</ignore>
<regex>true</regex>
<classQualifiedName>edu.hm.hafner.util.LookaheadStream</classQualifiedName>
<oldArchive>edu.hm.hafner:analysis-model:jar.*</oldArchive>
<newArchive>edu.hm.hafner:codingstyle:jar.*</newArchive>
<code>.*</code>
</item>
</differences>
</filter.classes>
</revapi.differences>
</analysisConfiguration>
</configuration>
Expand Down
58 changes: 58 additions & 0 deletions src/main/java/edu/hm/hafner/analysis/RevApiInfoExtension.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package edu.hm.hafner.analysis;

import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;

import edu.umd.cs.findbugs.annotations.CheckForNull;

/**
* Stores additional information of the Revapi analysis (severities, issue name, old file, and new file).
*/
@SuppressWarnings("PMD.DataClass")
public final class RevApiInfoExtension implements Serializable {
private static final long serialVersionUID = 6058160289391492934L;
private final Map<String, String> severities = new HashMap<>();
private final String issueName;
private final String oldFile;
private final String newFile;

/**
* Creates an object to store additional information of the Revapi analysis.
*
* @param code
* of the parsed issue
* @param oldFile
* the oldFile where something was changed
* @param newFile
* the newFile where something was changed
* @param severities
* the severities of Binary and source
*/
public RevApiInfoExtension(@CheckForNull final String code, final String oldFile,
final String newFile, final Map<String, String> severities) {
this.issueName = StringUtils.defaultString(code, "-");
this.oldFile = oldFile;
this.newFile = newFile;
this.severities.putAll(severities);
}

public Map<String, String> getSeverities() {
return Collections.unmodifiableMap(severities);
}

public String getIssueName() {
return issueName;
}

public String getOldFile() {
return oldFile;
}

public String getNewFile() {
return newFile;
}
}
124 changes: 124 additions & 0 deletions src/main/java/edu/hm/hafner/analysis/parser/RevApiParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package edu.hm.hafner.analysis.parser;

import java.text.MessageFormat;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.json.JSONArray;
import org.json.JSONObject;

import edu.hm.hafner.analysis.Issue;
import edu.hm.hafner.analysis.IssueBuilder;
import edu.hm.hafner.analysis.Report;
import edu.hm.hafner.analysis.RevApiInfoExtension;
import edu.hm.hafner.analysis.Severity;

/**
* Parser for Revapi reports.
*/
public class RevApiParser extends JsonIssueParser {
private static final long serialVersionUID = -2452699725595063377L;

@Override
protected void parseJsonArray(final Report report, final JSONArray jsonReport, final IssueBuilder issueBuilder) {
for (Object issue : jsonReport) {
if (issue instanceof JSONObject) {
report.add(convertToIssue((JSONObject) issue, issueBuilder));
}
else {
report.logError("RevApi issues no instance of JSON");
}
}
}

private Issue convertToIssue(final JSONObject jsonIssue, final IssueBuilder builder) {
builder.setSeverity(evaluateSeverity(jsonIssue.getJSONArray("classification")));
builder.setDescription(getDescription(jsonIssue));
addAttachments(jsonIssue.getJSONArray("attachments"), builder);
builder.setAdditionalProperties(convertToGroup(jsonIssue));
return builder.build();
}

private RevApiInfoExtension convertToGroup(final JSONObject jsonIssue) {
return new RevApiInfoExtension(
jsonIssue.getString("code"),
extractChange(jsonIssue, "old"),
extractChange(jsonIssue, "new"),
extractSeverities(jsonIssue));
}

private static Map<String, String> extractSeverities(final JSONObject jsonIssue) {
Map<String, String> allSeverities = new HashMap<>();
for (Object severity : jsonIssue.getJSONArray("classification")) {
if (severity instanceof JSONObject) {
allSeverities.put(((JSONObject) severity).getString("compatibility"), ((JSONObject) severity).getString("severity"));
}
}
return allSeverities;
}

private static String extractChange(final JSONObject jsonIssue, final String key) {
String value = jsonIssue.get(key).toString();
return "null".equals(value) ? "-" : value;
}

private void addAttachments(final JSONArray attachments, final IssueBuilder builder) {
String packageName = attachments.getJSONObject(0).getString("value");
String classSimpleName = attachments.getJSONObject(2).getString("value");
String elementKind = attachments.getJSONObject(3).getString("value");
builder.setFileName(classSimpleName);
builder.setPackageName(packageName);
builder.setCategory(elementKind);
}

private Severity evaluateSeverity(final JSONArray classification) {
Set<Severity> allSeverities = new HashSet<>();
for (Object severity : classification) {
if (severity instanceof JSONObject) {
allSeverities.add(toSeverity(((JSONObject) severity).getString("severity")));
}
}
if (allSeverities.contains(Severity.WARNING_HIGH)) {
return Severity.WARNING_HIGH;
}
if (allSeverities.contains(Severity.WARNING_NORMAL)) {
return Severity.WARNING_NORMAL;
}
return Severity.WARNING_LOW;
}

private Severity toSeverity(final String level) {
switch (level) {
case "NON_BREAKING":
return Severity.WARNING_LOW;
case "BREAKING":
return Severity.WARNING_HIGH;
case "POTENTIALLY_BREAKING":
default:
return Severity.WARNING_NORMAL;
}
}

private String getDescription(final JSONObject jsonIssue) {
StringBuilder severityDescription = new StringBuilder();
for (Object severity : jsonIssue.getJSONArray("classification")) {
if (severity instanceof JSONObject) {
severityDescription.append("<p>Compatibility: ")
.append(((JSONObject) severity).getString("compatibility"))
.append(" Severity: ")
.append(((JSONObject) severity).getString("severity"))
.append("</p>");
}
}

return MessageFormat.format(
"<p><div><b>File</b>: {0}</div><div><b>Description:</b> {1} {2}</div><div><b>Change type:</b> {3}",
jsonIssue.getJSONArray("attachments").getJSONObject(1).getString("value"),
jsonIssue.getString("description"),
jsonIssue.getString("name"),
jsonIssue.getString("code")) + severityDescription;

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ public class ParserRegistry {
new QacSourceCodeAnalyserDescriptor(),
new QtTranslationDescriptor(),
new ResharperDescriptor(),
new RevApiDescriptor(),
new RfLintDescriptor(),
new RoboCopyDescriptor(),
new RuboCopDescriptor(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package edu.hm.hafner.analysis.registry;

import edu.hm.hafner.analysis.IssueParser;
import edu.hm.hafner.analysis.parser.RevApiParser;

/**
* Parser for Revapi Json reports.
*
* @author Dominik Jantschar
*/
public class RevApiDescriptor extends ParserDescriptor {
private static final String ID = "revapi";
private static final String NAME = "Revapi";

RevApiDescriptor() {
super(ID, NAME);
}

@Override
public IssueParser createParser(final Option... options) {
return new RevApiParser();
}

@Override
public String getPattern() {
return "**/target/revapi-result.json";
}

@Override
public String getUrl() {
return "https://revapi.org/revapi-site/main/index.html";
}
}
5 changes: 4 additions & 1 deletion src/test/java/edu/hm/hafner/analysis/AbstractParserTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,10 @@ protected void assertThatReportHasSeverities(final Report report, final int expe
* @return the issues in the default file
*/
protected Report parseDefaultFile() {
return createParser().parse(getDefaultFileFactory());
IssueParser parser = createParser();
ReaderFactory factory = getDefaultFileFactory();
assertThat(parser.accepts(factory)).isTrue();
return parser.parse(factory);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import org.junit.jupiter.api.Test;

import edu.hm.hafner.analysis.AbstractParserTest;
import edu.hm.hafner.analysis.ParsingException;
import edu.hm.hafner.analysis.Report;
import edu.hm.hafner.analysis.Severity;
import edu.hm.hafner.analysis.assertions.SoftAssertions;
Expand All @@ -15,7 +13,7 @@
*
* @author Andre Pany
*/
class DScannerParserTest extends AbstractParserTest {
class DScannerParserTest extends StructuredFileParserTest {
DScannerParserTest() {
super("dscanner-report.json");
}
Expand Down Expand Up @@ -58,26 +56,13 @@ protected void assertThatIssuesArePresent(final Report report, final SoftAsserti
.hasSeverity(Severity.WARNING_NORMAL);
}

@Test
void shouldNotAcceptTextFiles() {
assertThat(createParser().accepts(createReaderFactory("gcc.txt"))).isFalse();
}

@Test
void shouldHandleIncompleteReports() {
Report report = createParser().parse(createReaderFactory("dscanner-incomplete-report.json"));
assertThat(report).hasSize(2);
assertThat(report.getErrorMessages()).isEmpty();
}

@Test
void shouldThrowParserException() {
assertThatThrownBy(() -> createParser().parse(createReaderFactory("issues-invalid.json")))
.isInstanceOf(ParsingException.class);
assertThatThrownBy(() -> createParser().parse(createReaderFactory("issues-broken.json")))
.isInstanceOf(ParsingException.class);
}

@Override
protected DScannerParser createParser() {
return new DScannerParser();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import org.junit.jupiter.api.Test;

import edu.hm.hafner.analysis.AbstractParserTest;
import edu.hm.hafner.analysis.Categories;
import edu.hm.hafner.analysis.Report;
import edu.hm.hafner.analysis.Severity;
Expand All @@ -15,7 +14,7 @@
*
* @author Jason Faust
*/
class EclipseXMLParserTest extends AbstractParserTest {
class EclipseXMLParserTest extends StructuredFileParserTest {
EclipseXMLParserTest() {
super("eclipse-withinfo.xml");
}
Expand Down
10 changes: 1 addition & 9 deletions src/test/java/edu/hm/hafner/analysis/parser/FlowParserTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import org.junit.jupiter.api.Test;

import edu.hm.hafner.analysis.AbstractParserTest;
import edu.hm.hafner.analysis.IssueParser;
import edu.hm.hafner.analysis.Report;
import edu.hm.hafner.analysis.Severity;
Expand All @@ -13,7 +12,7 @@
/**
* Tests the class {@link FlowParser}.
*/
class FlowParserTest extends AbstractParserTest {
class FlowParserTest extends StructuredFileParserTest {
private static final String CATEGORY = DEFAULT_CATEGORY;
private static final String FILENAME = "flow.json";

Expand Down Expand Up @@ -41,15 +40,8 @@ protected void assertThatIssuesArePresent(final Report report, final SoftAsserti
.hasColumnEnd(19);
}

@Test
void shouldNotAcceptXmlAndOtherJsonFiles() {
assertThat(createParser().accepts(createReaderFactory("xmlParserDefault.xml"))).isFalse();
assertThat(createParser().accepts(createReaderFactory("issues.json"))).isFalse();
}

@Test
void shouldAcceptJsonFile() {
assertThat(createParser().accepts(createReaderFactory(FILENAME))).isTrue();
}

}
Loading

0 comments on commit 311354c

Please sign in to comment.