package com.pablissimo.sonar;
import com.pablissimo.sonar.model.TsLintRule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.config.Settings;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.debt.DebtRemediationFunction;
import org.sonar.api.server.rule.RulesDefinition;
import java.io.*;
import java.nio.charset.Charset;
import java.util.*;
public class TsRulesDefinition implements RulesDefinition {
private static final Logger LOG = LoggerFactory.getLogger(TsRulesDefinition.class);
public static final String REPOSITORY_NAME = "tslint";
public static final String DEFAULT_RULE_SEVERITY = Severity.defaultSeverity();
public static final String DEFAULT_RULE_DESCRIPTION = "No description provided for this TsLint rule";
public static final String DEFAULT_RULE_DEBT_SCALAR = "0min";
public static final String DEFAULT_RULE_DEBT_OFFSET = "0min";
public static final String DEFAULT_RULE_DEBT_TYPE = RuleType.CODE_SMELL.name();
private static final String CORE_RULES_CONFIG_RESOURCE_PATH = "/tslint/tslint-rules.properties";
/** The SonarQube rule that will contain all unknown TsLint issues. */
public static final TsLintRule TSLINT_UNKNOWN_RULE = new TsLintRule(
"tslint-issue",
Severity.MAJOR,
"tslint issues that are not yet known to the plugin",
"No description for TsLint rule");
private List<TsLintRule> tslintCoreRules = new ArrayList<>();
private List<TsLintRule> tslintRules = new ArrayList<>();
private final Settings settings;
public TsRulesDefinition() {
this(null);
}
public TsRulesDefinition(Settings settings) {
this.settings = settings;
loadCoreRules();
loadCustomRules();
}
private void loadCoreRules() {
InputStream coreRulesStream = TsRulesDefinition.class.getResourceAsStream(CORE_RULES_CONFIG_RESOURCE_PATH);
loadRules(coreRulesStream, tslintCoreRules);
}
private void loadCustomRules() {
if (this.settings == null)
return;
if (settings.getBoolean(TypeScriptPlugin.SETTING_TS_LINT_DISALLOW_CUSTOM_RULES)) {
LOG.info("Usage of custom rules is inhibited");
return;
}
List<String> configKeys = settings.getKeysStartingWith(TypeScriptPlugin.SETTING_TS_RULE_CONFIGS);
for (String cfgKey : configKeys) {
if (!cfgKey.endsWith("config")) {
continue;
}
String rulesConfig = settings.getString(cfgKey);
if (rulesConfig != null) {
InputStream rulesConfigStream = new ByteArrayInputStream(rulesConfig.getBytes(Charset.defaultCharset()));
loadRules(rulesConfigStream, tslintRules);
}
}
}
public static void loadRules(InputStream stream, List<TsLintRule> rulesCollection) {
Properties properties = new Properties();
try {
properties.load(stream);
} catch (IOException e) {
LOG.error("Error while loading TsLint rules", e);
}
for(String propKey : properties.stringPropertyNames()) {
if (propKey.contains(".")) {
continue;
}
String ruleEnabled = properties.getProperty(propKey);
if (!"true".equals(ruleEnabled)) {
continue;
}
String ruleId = propKey;
String ruleName = properties.getProperty(propKey + ".name", ruleId.replace("-", " "));
String ruleSeverity = properties.getProperty(propKey + ".severity", DEFAULT_RULE_SEVERITY);
String ruleDescription = properties.getProperty(propKey + ".description", DEFAULT_RULE_DESCRIPTION);
String debtRemediationFunction = properties.getProperty(propKey + ".debtFunc", null);
String debtRemediationScalar = properties.getProperty(propKey + ".debtScalar", DEFAULT_RULE_DEBT_SCALAR);
String debtRemediationOffset = properties.getProperty(propKey + ".debtOffset", DEFAULT_RULE_DEBT_OFFSET);
String debtType = properties.getProperty(propKey + ".debtType", DEFAULT_RULE_DEBT_TYPE);
TsLintRule tsRule = null;
// try to apply the specified debt remediation function
if (debtRemediationFunction != null) {
DebtRemediationFunction.Type debtRemediationFunctionEnum = DebtRemediationFunction.Type.valueOf(debtRemediationFunction);
tsRule = new TsLintRule(
ruleId,
ruleSeverity,
ruleName,
ruleDescription,
debtRemediationFunctionEnum,
debtRemediationScalar,
debtRemediationOffset,
debtType
);
}
// no debt remediation function specified
if (tsRule == null) {
tsRule = new TsLintRule(
ruleId,
ruleSeverity,
ruleName,
ruleDescription
);
}
rulesCollection.add(tsRule);
}
Collections.sort(rulesCollection, (TsLintRule r1, TsLintRule r2) -> r1.key.compareTo(r2.key));
}
private void createRule(NewRepository repository, TsLintRule tsRule) {
NewRule sonarRule =
repository
.createRule(tsRule.key)
.setName(tsRule.name)
.setSeverity(tsRule.severity)
.setHtmlDescription(tsRule.htmlDescription)
.setStatus(RuleStatus.READY);
if (tsRule.hasDebtRemediation) {
DebtRemediationFunction debtRemediationFn = null;
DebtRemediationFunctions funcs = sonarRule.debtRemediationFunctions();
switch (tsRule.debtRemediationFunction)
{
case LINEAR:
debtRemediationFn = funcs.linear(tsRule.debtRemediationScalar);
break;
case LINEAR_OFFSET:
debtRemediationFn = funcs.linearWithOffset(tsRule.debtRemediationScalar, tsRule.debtRemediationOffset);
break;
case CONSTANT_ISSUE:
debtRemediationFn = funcs.constantPerIssue(tsRule.debtRemediationScalar);
break;
}
sonarRule.setDebtRemediationFunction(debtRemediationFn);
}
RuleType type = null;
if (tsRule.debtType != null && RuleType.names().contains(tsRule.debtType)) {
// Try and parse it as a new-style rule type (since 5.5 SQALE's been replaced
// with something simpler, and there's really only three buckets)
type = RuleType.valueOf(tsRule.debtType);
}
if (type == null) {
type = RuleType.CODE_SMELL;
}
sonarRule.setType(type);
}
@Override
public void define(Context context) {
NewRepository repository =
context
.createRepository(REPOSITORY_NAME, TypeScriptLanguage.LANGUAGE_KEY)
.setName("TsLint Analyzer");
createRule(repository, TSLINT_UNKNOWN_RULE);
// add the TsLint builtin core rules
for (TsLintRule coreRule : tslintCoreRules) {
createRule(repository, coreRule);
}
// add additional custom TsLint rules
for (TsLintRule customRule : tslintRules) {
createRule(repository, customRule);
}
repository.done();
}
public List<TsLintRule> getCoreRules() {
return tslintCoreRules;
}
public List<TsLintRule> getRules() {
return tslintRules;
}
}