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

Support Clean Code Taxonomy data #303

Merged
merged 5 commits into from
Aug 10, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,22 @@
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.sonar.api.SonarRuntime;
import org.sonar.api.batch.rule.Severity;
import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rules.CleanCodeAttribute;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.rule.RulesDefinition.NewRepository;
import org.sonar.api.server.rule.RulesDefinition.NewRule;
import org.sonar.api.utils.Version;

/**
* Creates external rule repository based on json file in the format <code>[ { "key": "...", "name": "..." }, ... ]</code>
Expand All @@ -55,21 +62,40 @@ public class ExternalRuleLoader {
private static final String DESCRIPTION_ONLY_URL = "See description of %s rule <code>%s</code> at the <a href=\"%s\">%s website</a>.";
private static final String DESCRIPTION_WITH_URL = "<p>%s</p> <p>See more at the <a href=\"%s\">%s website</a>.</p>";
private static final String DESCRIPTION_FALLBACK = "This is external rule <code>%s:%s</code>. No details are available.";
public static final Version API_VERSION_SUPPORTING_CLEAN_CODE_IMPACTS_AND_ATTRIBUTES = Version.create(10, 1);

private final String linterKey;
private final String linterName;
private final String languageKey;
private final boolean isCleanCodeImpactsAndAttributesSupported;

private Map<String, ExternalRule> rulesMap = new HashMap<>();

/**
* @deprecated Use the constructor that also provide the SonarRuntime argument to determine if you can use
* the new Clean Code attributes and impacts API.
* Then you should test "isCleanCodeImpactsAndAttributesSupported()" before using codeAttribute and codeImpacts.
*/
@Deprecated(since = "2.6")
public ExternalRuleLoader(String linterKey, String linterName, String pathToMetadata, String languageKey) {
this(linterKey, linterName, pathToMetadata, languageKey, null);
}

public ExternalRuleLoader(String linterKey, String linterName, String pathToMetadata, String languageKey, @Nullable SonarRuntime sonarRuntime) {
this.linterKey = linterKey;
this.linterName = linterName;
this.languageKey = languageKey;

isCleanCodeImpactsAndAttributesSupported = sonarRuntime != null &&
sonarRuntime.getApiVersion().isGreaterThanOrEqual(API_VERSION_SUPPORTING_CLEAN_CODE_IMPACTS_AND_ATTRIBUTES);

loadMetadataFile(pathToMetadata);
}

public boolean isCleanCodeImpactsAndAttributesSupported() {
return isCleanCodeImpactsAndAttributesSupported;
}

public void createExternalRuleRepository(org.sonar.api.server.rule.RulesDefinition.Context context) {
NewRepository externalRepo = context.createExternalRepository(linterKey, languageKey).setName(linterName);

Expand All @@ -78,14 +104,15 @@ public void createExternalRuleRepository(org.sonar.api.server.rule.RulesDefiniti
newRule.setHtmlDescription(rule.getDescription(linterKey, linterName));
newRule.setDebtRemediationFunction(newRule.debtRemediationFunctions().constantPerIssue(rule.constantDebtMinutes + "min"));
newRule.setType(rule.type);
newRule.setSeverity(rule.severity.name());
if (rule.codeAttribute != null && rule.codeImpacts != null) {
newRule.setCleanCodeAttribute(rule.codeAttribute);
rule.codeImpacts.forEach(newRule::addDefaultImpact);
}

if (rule.tags != null) {
newRule.setTags(rule.tags);
}

if (rule.severity != null) {
newRule.setSeverity(rule.severity);
}
}

externalRepo.done();
Expand All @@ -95,6 +122,10 @@ public Set<String> ruleKeys() {
return rulesMap.keySet();
}

/**
* If isCleanCodeImpactsAndAttributesSupported() == true then ruleType is deprecated and replaced by codeImpacts
*/
@Deprecated(since = "2.6")
public RuleType ruleType(String ruleKey) {
ExternalRule externalRule = rulesMap.get(ruleKey);
if (externalRule != null) {
Expand All @@ -104,15 +135,39 @@ public RuleType ruleType(String ruleKey) {
}
}

/**
* If isCleanCodeImpactsAndAttributesSupported() == true then ruleSeverity is deprecated and replaced by codeImpacts
*/
@Deprecated(since = "2.6")
public Severity ruleSeverity(String ruleKey) {
ExternalRule externalRule = rulesMap.get(ruleKey);
if (externalRule != null && externalRule.severity != null) {
return Severity.valueOf(externalRule.severity);
if (externalRule != null) {
return externalRule.severity;
} else {
return DEFAULT_SEVERITY;
}
}

@Nullable
public CleanCodeAttribute codeAttribute(String ruleKey) {
ExternalRule externalRule = rulesMap.get(ruleKey);
if (externalRule != null) {
return externalRule.codeAttribute;
} else {
return null;
}
}

@Nullable
public Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> codeImpacts(String ruleKey) {
ExternalRule externalRule = rulesMap.get(ruleKey);
if (externalRule != null) {
return externalRule.codeImpacts;
} else {
return null;
}
}

public Long ruleConstantDebtMinutes(String ruleKey) {
ExternalRule externalRule = rulesMap.get(ruleKey);
if (externalRule != null) {
Expand All @@ -128,7 +183,7 @@ private void loadMetadataFile(String pathToMetadata) {

List<Map<String, Object>> rules = new JsonParser().parseArray(inputStreamReader);
for (Map<String, Object> rule : rules) {
ExternalRule externalRule = new ExternalRule(rule);
ExternalRule externalRule = new ExternalRule(rule, isCleanCodeImpactsAndAttributesSupported);
rulesMap.put(externalRule.key, externalRule);
}

Expand All @@ -141,6 +196,7 @@ private static class ExternalRule {
final String key;
final String name;
final RuleType type;
final Severity severity;

@CheckForNull
final String url;
Expand All @@ -151,12 +207,15 @@ private static class ExternalRule {
@CheckForNull
final String[] tags;

final Long constantDebtMinutes;

@CheckForNull
final String severity;
final CleanCodeAttribute codeAttribute;

final Long constantDebtMinutes;
@CheckForNull
final Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> codeImpacts;

public ExternalRule(Map<String, Object> rule) {
public ExternalRule(Map<String, Object> rule, boolean isCleanCodeImpactsAndAttributesSupported) {
this.key = (String) rule.get("key");
this.name = (String) rule.get("name");
this.url = (String) rule.get("url");
Expand All @@ -168,13 +227,61 @@ public ExternalRule(Map<String, Object> rule) {
} else {
this.tags = null;
}
this.severity = (String) rule.get("severity");
String inputType = (String) rule.get("type");
if (inputType != null) {
type = RuleType.valueOf(inputType);
type = getType(rule);
severity = getSeverity(rule);
if (isCleanCodeImpactsAndAttributesSupported) {
codeAttribute = getCodeAttribute(rule);
codeImpacts = getCodeImpacts(rule);
} else {
type = DEFAULT_ISSUE_TYPE;
codeAttribute = null;
codeImpacts = null;
}
}

private static RuleType getType(Map<String, Object> rule) {
String strType = (String) rule.get("type");
if (strType != null) {
return RuleType.valueOf(strType);
} else {
return DEFAULT_ISSUE_TYPE;
}
}

private static Severity getSeverity(Map<String, Object> rule) {
String strSeverity = (String) rule.get("severity");
if (strSeverity != null) {
return Severity.valueOf(strSeverity);
} else {
return DEFAULT_SEVERITY;
}
}

@Nullable
private static CleanCodeAttribute getCodeAttribute(Map<String, Object> rule) {
JSONObject code = (JSONObject) rule.get("code");
if (code != null) {
String attribute = (String) code.get("attribute");
if (attribute != null) {
return CleanCodeAttribute.valueOf(attribute);
}
}
return null;
}

@Nullable
private static Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> getCodeImpacts(Map<String, Object> rule) {
JSONObject code = (JSONObject) rule.get("code");
if (code != null) {
JSONObject impacts = (JSONObject) code.get("impacts");
if (impacts != null) {
Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> map = new LinkedHashMap<>();
impacts.forEach(
(k, v) -> map.put(SoftwareQuality.valueOf((String) k),
org.sonar.api.issue.impact.Severity.valueOf((String) v)));
return map;
}
}
return null;
}

String getDescription(String linterKey, String linterName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@
import java.util.Optional;
import java.util.Set;
import org.sonar.api.SonarRuntime;
import org.sonar.api.issue.impact.Severity;
import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.RuleScope;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rules.CleanCodeAttribute;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.rule.RulesDefinition;
import org.sonar.api.server.rule.RulesDefinition.DebtRemediationFunctions;
Expand All @@ -53,7 +56,6 @@ public class RuleMetadataLoader {

private static final String INVALID_PROPERTY_MESSAGE = "Invalid property: %s";
private static final char RESOURCE_SEP = '/';
private static final String SECURITY_HOTSPOT = "SECURITY_HOTSPOT";
private final String resourceFolder;
private final Set<String> activatedByDefault;
private final JsonParser jsonParser;
Expand Down Expand Up @@ -114,7 +116,7 @@ private static NewRule addAnnotatedRule(NewRepository repository, Class<?> ruleC
throw new IllegalStateException("No Rule annotation was found on " + ruleClass.getName());
}
String ruleKey = ruleAnnotation.key();
if (ruleKey.length() == 0) {
if (ruleKey.isEmpty()) {
throw new IllegalStateException("Empty key");
}
new RulesDefinitionAnnotationLoader().load(repository, ruleClass);
Expand Down Expand Up @@ -142,7 +144,7 @@ private static void addDeprecatedRuleKey(NewRepository repository, NewRule rule,
}

private void addRuleByRuleKey(NewRepository repository, String ruleKey) {
if (ruleKey.length() == 0) {
if (ruleKey.isEmpty()) {
throw new IllegalStateException("Empty key");
}
NewRule rule = repository.createRule(ruleKey);
Expand All @@ -169,6 +171,13 @@ private void setMetadataFromJson(NewRule rule) {
rule.setSeverity(getUpperCaseString(ruleMetadata, "defaultSeverity"));
String type = getUpperCaseString(ruleMetadata, "type");
rule.setType(RuleType.valueOf(type));

if (isSupported(10, 1)) {
Object code = ruleMetadata.get("code");
if (code != null) {
setCodeAttributeFromJson(rule, (Map<String, Object>) code);
}
}
rule.setStatus(RuleStatus.valueOf(getUpperCaseString(ruleMetadata, "status")));
rule.setTags(getStringArray(ruleMetadata, "tags"));
getScopeIfPresent(ruleMetadata, "scope").ifPresent(rule::setScope);
Expand All @@ -195,6 +204,17 @@ Map<String, Object> getMetadata(String ruleKey) {
}
}

private static void setCodeAttributeFromJson(NewRule rule, Map<String, Object> code) {
String attribute = getString(code, "attribute");
rule.setCleanCodeAttribute(CleanCodeAttribute.valueOf(attribute));

Map<String, String> impacts = (Map<String, String>) code.get("impacts");
if (impacts == null || impacts.isEmpty()) {
throw new IllegalStateException(String.format(INVALID_PROPERTY_MESSAGE, "impacts") + " for rule: " + rule.key());
}
impacts.forEach((softwareQuality, severity) -> rule.addDefaultImpact(SoftwareQuality.valueOf(softwareQuality), Severity.valueOf(severity)));
}

private void setSecurityStandardsFromJson(NewRule rule, Map<String, Object> securityStandards) {
if (securityStandards.get("CWE") != null) {
rule.addCwe(getIntArray(securityStandards, "CWE"));
Expand Down Expand Up @@ -314,9 +334,4 @@ private static int[] getIntArray(Map<String, Object> map, String propertyName) {
return ((List<Number>) propertyValue).stream().mapToInt(Number::intValue).toArray();
}

boolean isSecurityHotspot(Map<String, Object> ruleMetadata) {
String type = getUpperCaseString(ruleMetadata, "type");
return SECURITY_HOTSPOT.equals(type);
}

}
Loading