Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added SonarQube parsers #67

Merged
merged 1 commit into from Jul 10, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions pom.xml
Expand Up @@ -156,6 +156,14 @@
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>

<!-- SonarQube Parser Dependencies -->

<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20180130</version>
</dependency>

<!-- Test Dependencies -->
<dependency>
Expand Down
@@ -0,0 +1,40 @@
package edu.hm.hafner.analysis.parser;

import org.json.JSONObject;

/**
* Class which parses SonarQube reports taken from Sonarqube differential scan report (preview).
*
* @author Carles Capdevila
*/
public class SonarQubeDiffParser extends SonarQubeParser {
private static final long serialVersionUID = -47634856667313368L;

private static final String ISSUE_IS_NEW = "isNew";
private static final String COMPONENT_MODULE_KEY = "moduleKey";

/** {@inheritDoc} */
@Override
public boolean issueFilter(final JSONObject issue) {
return issue.optBoolean(ISSUE_IS_NEW, false);
}

@Override
public String parseFilename(final JSONObject issue) {
//Get component
String componentKey = issue.optString(ISSUE_COMPONENT, null);
JSONObject component = findComponentByKey(componentKey);

if (component != null) {
//Get file path inside module
String filePath = component.optString(COMPONENT_PATH);

//Get module file path
String modulePath = parseModulePath(component, COMPONENT_MODULE_KEY);
return modulePath + filePath;
} else {
return super.parseFilename(issue);
}
}

}
@@ -0,0 +1,33 @@
package edu.hm.hafner.analysis.parser;

import org.json.JSONObject;

/**
* Class which parses SonarQube reports taken from the Sonarqube API (api/issues/search).
*
* @author Carles Capdevila
*/
public class SonarQubeIssuesParser extends SonarQubeParser {
private static final long serialVersionUID = -8213765181968340929L;

private static final String ISSUE_SUB_PROJECT= "subProject";

@Override
public String parseFilename(final JSONObject issue) {
//Get component
String componentKey = issue.optString(ISSUE_COMPONENT, null);
JSONObject component = findComponentByKey(componentKey);

if (component != null) {
//Get file path inside module
String filePath = component.optString(COMPONENT_PATH);

//Get module file path
String modulePath = parseModulePath(issue, ISSUE_SUB_PROJECT);
return modulePath + filePath;
} else {
return super.parseFilename(issue);
}
}

}
270 changes: 270 additions & 0 deletions src/main/java/edu/hm/hafner/analysis/parser/SonarQubeParser.java
@@ -0,0 +1,270 @@
package edu.hm.hafner.analysis.parser;

import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;

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

import edu.hm.hafner.analysis.AbstractParser;
import edu.hm.hafner.analysis.Issue;
import edu.hm.hafner.analysis.IssueBuilder;
import edu.hm.hafner.analysis.ParsingCanceledException;
import edu.hm.hafner.analysis.ParsingException;
import edu.hm.hafner.analysis.Priority;
import edu.hm.hafner.analysis.Report;

/**
* Base class for SonarQube parsers.
*
* @author Carles Capdevila
*/
public abstract class SonarQubeParser extends AbstractParser {
private static final long serialVersionUID = 1958805067002376816L;

//Arrays
/** The components array. */
protected static final String COMPONENTS = "components";
/** The issues array. */
protected static final String ISSUES = "issues";

//Issues attributes
/** issue.component attribute. */
protected static final String ISSUE_COMPONENT = "component";
/** issue.message attribute. */
protected static final String ISSUE_MESSAGE = "message";
/** issue.line attribute. */
protected static final String ISSUE_LINE = "line";
/** issue.line attribute. */
protected static final String ISSUE_SEVERITY = "severity";
/** issue.type attribute. */
protected static final String ISSUE_TYPE = "type";

//Component attributes
/** component.key attribute. */
protected static final String COMPONENT_KEY = "key";
/** component.path attribute. */
protected static final String COMPONENT_PATH = "path";

// Severity values
/** severity value: BLOCKER */
protected static final String SEVERITY_BLOCKER = "BLOCKER";
/** severity value: CRITICAL */
protected static final String SEVERITY_CRITICAL = "CRITICAL";
// Severity MAJOR is omitted as it corresponds with default Priority: NORMAL
/** severity value: MINOR */
protected static final String SEVERITY_MINOR = "MINOR";
/** severity value: INFO */
protected static final String SEVERITY_INFO = "INFO";
/** Fixed category: SonarQube */
protected static final String CATEGORY_SONARQUBE = "SonarQube";

/** The issues array. */
protected JSONArray issues;
/** The components array. */
protected JSONArray components;

@Override
public Report parse(Reader reader, Function<String, String> preProcessor)
throws ParsingCanceledException, ParsingException {
Report warnings = new Report();

JSONObject jsonReport = (JSONObject)new JSONTokener(reader).nextValue();

//Get the components part to get the file paths on each issue (the component objects contain the most concise path)
components = new JSONArray();
if (jsonReport.has(COMPONENTS)) {
components = jsonReport.optJSONArray(COMPONENTS);
}

issues = new JSONArray();
if (jsonReport.has(ISSUES)) {
issues = jsonReport.optJSONArray(ISSUES);
}

//Iterate the issues
for (Object issue : issues) {
if (issue instanceof JSONObject) {
//Filter the issues
if (issueFilter((JSONObject)issue)) {
//Parse each issue
warnings.add(parseIssue((JSONObject)issue));
}
}
}

return warnings;
}


/**
* Decides whether or not to parse and add an issue.
* @param issue the issue to filter.
* @return true if the issue is to be parsed and added, otherwise false.
*/
public boolean issueFilter(final JSONObject issue) {
return true;//Parse all issues by default
}

/**
* The core of the parsing; this can be further overridden to determine how each issue is parsed and shown.
* @param issue the issue to parse.
* @return a FileAnnotation with the data of the issue to show.
*/
public Issue parseIssue(final JSONObject issue) {

IssueBuilder issueBuilder = new IssueBuilder();

//file
String filename = parseFilename(issue);

//line
int start = parseStart(issue);

//type
String type = parseType(issue);

//category
String category = parseCategory(issue);

//message
String message = parseMessage(issue);

//priority
Priority priority = parsePriority(issue);

return new IssueBuilder()
.setFileName(filename)
.setLineStart(start)
.setType(type)
.setCategory(category)
.setMessage(message)
.setPriority(priority)
.build();
}

/**
* Parse function for filename.
* @param issue the object to parse.
* @return the filename.
*/
public String parseFilename(final JSONObject issue) {
String componentKey = issue.optString(ISSUE_COMPONENT);
return componentKey.substring(componentKey.lastIndexOf(':'));
}

/**
* Default parse for start. Override to change the default parsing.
* @param issue the object to parse.
* @return the start.
*/
public int parseStart(final JSONObject issue) {
return issue.optInt(ISSUE_LINE, -1);
}

/**
* Default parse for type. Override to change the default parsing.
* @param issue the object to parse.
* @return the type.
*/
public String parseType(final JSONObject issue) {
return issue.optString(ISSUE_TYPE, "");
}

/**
* Default parse for category. Override to change the default parsing.
* @param issue the object to parse.
* @return the filename.
*/
public String parseCategory(final JSONObject issue) {
return CATEGORY_SONARQUBE;
}

/**
* Default parse for message. Override to change the default parsing.
* @param issue the object to parse.
* @return the message.
*/
public String parseMessage(final JSONObject issue) {
return issue.optString(ISSUE_MESSAGE, "No message.");
}

/**
* Default parse for priority. Override to change the default parsing.
* @param issue the object to parse.
* @return the priority.
*/
public Priority parsePriority(final JSONObject issue) {
String severity = issue.optString(ISSUE_SEVERITY, null);
return severityToPriority(severity);
}

//UTILITIES
/**
* Find the module path inside the corresponding component.
* @param moduleKeyObject the object which contains the component key.
* @param componentKey the component key.
* @return the module path.
*/
protected String parseModulePath(final JSONObject moduleKeyObject, final String componentKey) {
String modulePath = "";
if (moduleKeyObject.has(componentKey)) {
String moduleKey = moduleKeyObject.getString(componentKey);
JSONObject moduleComponent = findComponentByKey(moduleKey);
if (moduleComponent != null && moduleComponent.has(COMPONENT_PATH)) {
modulePath = moduleComponent.getString(COMPONENT_PATH) + "/";
}
}
return modulePath;
}

/**
* Find the component in the components array which contains this key.
* @param key the key of the desired component.
* @return the desired JSONObject component, or null if it hasn't been found.
*/
protected JSONObject findComponentByKey (final String key) {
if (components != null && key != null) {
for (Object component : components) {
if (component instanceof JSONObject) {
JSONObject jsonComponent = (JSONObject)component;
if (key.equals(jsonComponent.optString(COMPONENT_KEY))) {
return (JSONObject)component;
}
}
}
}

return null;
}


/**
* Maps issue severity to warning priority:<br>
* <strong>HIGH:</strong> BLOCKER, CRITICAL<br>
* <strong>NORMAL:</strong> MAJOR<br>
* <strong>LOW:</strong> MINOR, INFO
*
* @param severity a String containing the SonarQube issue severity.
* @return a priority object corresponding to the passed severity.
*/
protected Priority severityToPriority (final String severity) {
Priority priority = Priority.NORMAL;
// Severity MAJOR is omitted as it corresponds with default Priority: NORMAL
if (severity != null) {
if (SEVERITY_BLOCKER.equals(severity) || SEVERITY_CRITICAL.equals(severity)) {
priority = Priority.HIGH;
} else if (SEVERITY_MINOR.equals(severity) || SEVERITY_INFO.equals(severity)) {
priority = Priority.LOW;
}
}
return priority;
}

}