Skip to content

Commit

Permalink
Add support for protoLint JSON format
Browse files Browse the repository at this point in the history
  • Loading branch information
profhenry committed Jul 15, 2024
1 parent dbd8542 commit 83b78c5
Show file tree
Hide file tree
Showing 7 changed files with 6,347 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package edu.hm.hafner.analysis.parser;

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.Severity;

/**
* Parser for ProtoLint JSON reports.
*
* <p>The recommended report format is JSON. The JSON report contains more information (affected rule, severity,
* basedir) as the plaintext format. For full feature set please use protoLint &gt;= 0.50.2.
*
* <p>We still support plaintext reports and JSON reports generated with protoLint &lt; 0.50.2 as well.
*
* @author [email protected]
* @see <a href="https://github.com/yoheimuta/protolint">https://github.com/yoheimuta/protolint</a>
*/
public class ProtoLintJsonParser extends JsonIssueParser {
private static final long serialVersionUID = 573706779074579673L;

@Override
protected void parseJsonObject(final Report report, final JSONObject jsonReport, final IssueBuilder issueBuilder) {
String basedir = jsonReport.optString("basedir");
JSONArray results = jsonReport.optJSONArray("lints", new JSONArray(0));

parseResults(report, basedir, results, issueBuilder);
}

private void parseResults(final Report report, final String basedir, final JSONArray jsonReport, final IssueBuilder issueBuilder) {
for (int i = 0; i < jsonReport.length(); i++) {
JSONObject finding = (JSONObject) jsonReport.get(i);
report.add(convertToIssue(basedir, finding, issueBuilder));
}
}

private Issue convertToIssue(final String basedir, final JSONObject finding, final IssueBuilder issueBuilder) {
// The filename is always relative to the working dir the protoLint process was started with.
// In order to get the absolute filename we need to prepend the basedir which is available with protoLint >= 0.50.2
String filename = finding.getString("filename");
if (!basedir.isEmpty()) {
filename = basedir + "/" + filename;
}
return issueBuilder.setFileName(filename)
.setLineStart(finding.getInt("line"))
.setColumnStart(finding.getInt("column"))
.setMessage(finding.getString("message"))
.setSeverity(mapSeverity(finding.optString("severity")))
.setType(finding.getString("rule"))
.buildAndClean();
}

private Severity mapSeverity(final String aProtoLintSeverity) {
// ProtoLint knows the following severities
// https://github.com/yoheimuta/protolint/blob/master/linter/rule/rule.go#L9
// which can be mapped with the provided mapping method
return Severity.guessFromString(aProtoLintSeverity);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
import edu.hm.hafner.util.LookaheadStream;

/**
* Parser for ProtoLint.
* Parser for ProtoLint plaintext reports.
*
* <p>The recommended report format is now JSON! This parser is just used as fallback.
*
* @author David Hart
* @see <a href="https://github.com/yoheimuta/protolint">https://github.com/yoheimuta/protolint</a>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
package edu.hm.hafner.analysis.registry;

import static j2html.TagCreator.a;
import static j2html.TagCreator.code;
import static j2html.TagCreator.join;
import static j2html.TagCreator.text;

import java.util.Collection;

import edu.hm.hafner.analysis.IssueParser;
import edu.hm.hafner.analysis.parser.ProtoLintJsonParser;
import edu.hm.hafner.analysis.parser.ProtoLintParser;

/**
* A descriptor for ProtoLint.
*
* <p>We use a composite parser for supporting JSON (preferred) and plaintext (fallback) format.
*
* @author Lorenz Munsch
* @author [email protected]
*/
class ProtoLintDescriptor extends ParserDescriptor {
class ProtoLintDescriptor extends CompositeParserDescriptor {
private static final String ID = "protolint";
private static final String NAME = "ProtoLint";

Expand All @@ -17,8 +28,17 @@ class ProtoLintDescriptor extends ParserDescriptor {
}

@Override
public IssueParser createParser(final Option... options) {
return new ProtoLintParser();
protected Collection<? extends IssueParser> createParsers() {
return asList(new ProtoLintJsonParser(), new ProtoLintParser());
}

@Override
public String getHelp() {
return join(text("Use protolint with options"),
code("-reporter=json -output_file=protolint-report.json"),
text(", see"),
a("protoLint CLI options").withHref("https://github.com/yoheimuta/protolint?tab=readme-ov-file#usage"),
text("for usage details.")).render();

Check warning on line 41 in src/main/java/edu/hm/hafner/analysis/registry/ProtoLintDescriptor.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 37-41 are not covered by tests

Check warning on line 41 in src/main/java/edu/hm/hafner/analysis/registry/ProtoLintDescriptor.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/edu/hm/hafner/analysis/registry/ProtoLintDescriptor.java#L37-L41

Added lines #L37 - L41 were not covered by tests
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package edu.hm.hafner.analysis.parser;

import edu.hm.hafner.analysis.IssueParser;
import edu.hm.hafner.analysis.Report;
import edu.hm.hafner.analysis.Severity;
import edu.hm.hafner.analysis.assertions.SoftAssertions;
import edu.hm.hafner.analysis.registry.AbstractParserTest;

import static edu.hm.hafner.analysis.assertions.Assertions.*;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

/**
* Test class for {@link ProtoLintJsonParser}.
*
* @author [email protected]
*/
class ProtoLintJsonParserTest extends AbstractParserTest {
ProtoLintJsonParserTest() {
super("protolint_0.50.2.json");
}

@Override
public void assertThatIssuesArePresent(final Report report, final SoftAssertions softly) {
assertThat(report).hasSize(462);

softly.assertThat(report.get(4))
.hasSeverity(Severity.WARNING_LOW)
.hasLineStart(3)
.hasColumnStart(5)
.hasMessage("Found an incorrect indentation style \" \". \" \" is correct.")
.hasFileName("/home/jwiesner/Development/github/profhenry/protolint/_example/proto/issue_111/multipleFixersApplicable.proto")
.hasType("INDENT");

softly.assertThat(report.get(12))
.hasSeverity(Severity.ERROR)
.hasLineStart(3)
.hasColumnStart(5)
.hasMessage("EnumField name \"UNKNOWN\" should have the prefix \"ENUM_ALLOWING_ALIAS\"")
.hasFileName("/home/jwiesner/Development/github/profhenry/protolint/_example/proto/issue_111/multipleFixersApplicable.proto")
.hasType("ENUM_FIELD_NAMES_PREFIX");

softly.assertThat(report.get(224))
.hasSeverity(Severity.WARNING_NORMAL)
.hasLineStart(207)
.hasColumnStart(3)
.hasMessage("Field \"amount\" should have a comment")
.hasFileName("/home/jwiesner/Development/github/profhenry/protolint/_example/proto/issue_128/grpc-gateway_a_bit_of_everything.proto")
.hasType("FIELDS_HAVE_COMMENT");
}

@Override
public IssueParser createParser() {
return new ProtoLintJsonParser();
}

@Test
@DisplayName("Parsing json report generated with protolint 0.49.8")
void json0498() {
Report report = parse("protolint_0.49.8.json");

assertThat(report).hasSize(352);

try (SoftAssertions softly = new SoftAssertions()) {
softly.assertThat(report.get(2))
.hasSeverity(Severity.WARNING_LOW)
.hasLineStart(3)
.hasColumnStart(5)
.hasMessage("Found an incorrect indentation style \" \". \" \" is correct.")
.hasFileName("_example/proto/issue_111/multipleFixersApplicable.proto")
.hasType("INDENT");

softly.assertThat(report.get(10))
.hasSeverity(Severity.WARNING_LOW)
.hasLineStart(3)
.hasColumnStart(5)
.hasMessage("EnumField name \"UNKNOWN\" should have the prefix \"ENUM_ALLOWING_ALIAS\"")
.hasFileName("_example/proto/issue_111/multipleFixersApplicable.proto")
.hasType("ENUM_FIELD_NAMES_PREFIX");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -807,12 +807,18 @@ void shouldFindNoJavacIssuesInEclipseOutput() {
findIssuesOfTool(0, "java", "eclipse.txt");
}

/** Runs the ProtoLint parser on an output file that contains 10 issues. */
/** Runs the ProtoLint parser on a plaintext output file that contains 2591 issues. */
@Test
void shouldFindAllProtoLintIssues() {
findIssuesOfTool(2591, "protolint", "protolint.txt");
}

/** Runs the ProtoLint parser on a json output file that contains 462 issues. */
@Test
void shouldFindAllProtoLintIssuesJsonFormat() {
findIssuesOfTool(462, "protolint", "protolint_0.50.2.json");
}

/** Runs the HadoLint parser on an output file that contains 5 issues. */
@Test
void shouldFindAllHadoLintIssues() {
Expand Down
Loading

0 comments on commit 83b78c5

Please sign in to comment.