private static DebtRemediationFunction remediationFunction(DebtRemediationFunction.Type function, @Nullable String coefficient, @Nullable String offset, RulesDefinition.DebtRemediationFunctions functions, String repoKey, String ruleKey) { if (DebtRemediationFunction.Type.LINEAR.equals(function) && coefficient != null) { return functions.linear(coefficient); } else if (DebtRemediationFunction.Type.CONSTANT_ISSUE.equals(function) && offset != null) { return functions.constantPerIssue(offset); } else if (DebtRemediationFunction.Type.LINEAR_OFFSET.equals(function) && coefficient != null && offset != null) { return functions.linearWithOffset(coefficient, offset); } else { throw new IllegalArgumentException(String.format("Debt definition on rule '%s:%s' is invalid", repoKey, ruleKey)); } }
private static void fillRemediationFunction(RulesDefinition.NewRule rule, @Nullable String debtRemediationFunction, @Nullable String functionOffset, @Nullable String functionCoeff) { if (isNotBlank(debtRemediationFunction)) { DebtRemediationFunction.Type functionType = DebtRemediationFunction.Type.valueOf(debtRemediationFunction); rule.setDebtRemediationFunction(rule.debtRemediationFunctions().create(functionType, functionCoeff, functionOffset)); } }
.setHtmlDescription("Search for a given tag in Xoo files"); hasTag .setDebtRemediationFunction(hasTag.debtRemediationFunctions().constantPerIssue("2min")); hasTag.createParam("tag") .setDefaultValue("xoo") .setHtmlDescription("Generate an issue on each line of a file. It requires the metric \"lines\"."); oneIssuePerLine .setDebtRemediationFunction(oneIssuePerLine.debtRemediationFunctions().linear("1min")) .setGapDescription("It takes about 1 minute to an experienced software craftsman to remove a line of code"); oneIssuePerFile.setDebtRemediationFunction(oneIssuePerFile.debtRemediationFunctions().linear(TEN_MIN)); oneIssuePerTestFile.setDebtRemediationFunction(oneIssuePerTestFile.debtRemediationFunctions().linear(TEN_MIN)); oneIssuePerDirectory.setDebtRemediationFunction(oneIssuePerDirectory.debtRemediationFunctions().linear(TEN_MIN)); oneDayDebtPerFile.setDebtRemediationFunction(oneDayDebtPerFile.debtRemediationFunctions().linear("1d")); .setDebtRemediationFunction(oneIssuePerModule.debtRemediationFunctions().linearWithOffset("25min", "1h")) .setGapDescription("A certified architect will need roughly half an hour to start working on removal of modules, " + "then it's about one hour per module."); .setType(RuleType.BUG); oneBugIssuePerLine .setDebtRemediationFunction(oneBugIssuePerLine.debtRemediationFunctions().linear("5min")); .setType(RuleType.VULNERABILITY); oneVulnerabilityIssuePerModule
debtRemediationFn = funcs.linear(tsRule.debtRemediationScalar); break; debtRemediationFn = funcs.linearWithOffset(tsRule.debtRemediationScalar, tsRule.debtRemediationOffset); break; debtRemediationFn = funcs.constantPerIssue(tsRule.debtRemediationScalar); break;
@SuppressWarnings("rawtypes") @Override public void define(Context context) { NewRepository repository = context.createRepository(Constants.STD_REPOSITORY_KEY, Constants.LANGUAGE_KEY).setName(REPOSITORY_NAME); AnnotationBasedRulesDefinition annotationLoader = new AnnotationBasedRulesDefinition(repository, Constants.LANGUAGE_KEY); annotationLoader.addRuleClasses(false, Arrays.<Class> asList(BasicChecksRegistration.ppCheckClasses())); // Manually created rules for compiler warnings createWarningRule(repository, COMPILER_WARNING_RULEKEY, "Compiler warnings", "2h", Priority.MINOR); createWarningRule(repository, COMPILER_WARNING_214_RULEKEY, "TRANSACTION keyword given within actual transaction level", "3h"); createWarningRule(repository, COMPILER_WARNING_2965_RULEKEY, "Invalid use of nonconstant elements in preprocessor expression", "30min", Priority.BLOCKER); createWarningRule(repository, COMPILER_WARNING_4788_RULEKEY, "Translation exceeds allocated length", "30min", Priority.CRITICAL, new String[] {COMPILER_WARNING_TAG, "tranman"}); createWarningRule(repository, COMPILER_WARNING_12115_RULEKEY, "Expression evaluates to a constant", "1h"); createWarningRule(repository, COMPILER_WARNING_14786_RULEKEY, "Table and field names must appear as they are in the schema", "20min", Priority.CRITICAL); createWarningRule(repository, COMPILER_WARNING_14789_RULEKEY, "Fields must be qualified with table name", "15min", Priority.MAJOR); createWarningRule(repository, COMPILER_WARNING_18494_RULEKEY, "Abbreviated keywords are not authorized", "5min", Priority.INFO); createWarningRule(repository, COMPILER_WARNING_15090_RULEKEY, "Dead code", "4h"); // Manually created rule for proparse errors NewRule proparseRule = repository.createRule(PROPARSE_ERROR_RULEKEY).setName("Proparse error").setSeverity( Priority.INFO.name()); proparseRule.setDebtRemediationFunction(proparseRule.debtRemediationFunctions().constantPerIssue("3h")); proparseRule.setType(RuleType.BUG); proparseRule.setHtmlDescription(getClass().getResource(String.format(HTML_DOC_PATH, Constants.LANGUAGE_KEY, Constants.STD_REPOSITORY_KEY, proparseRule.key()))); repository.done(); }
@Override public void define(Context context) { NewRepository repository = context.createRepository(XOO_REPOSITORY, Xoo.KEY).setName("Xoo"); // Load checks new RulesDefinitionAnnotationLoader().load(repository, Check.ALL); // define a single rule programmatically. Note that rules // can be loaded from JSON or XML files too. NewRule x1Rule = repository.createRule("x1") .setName("No empty line") .setMarkdownDescription("Generate an issue on *empty* lines of Xoo source files") // optional tags .setTags("style", "security") // optional status. Default value is READY. .setStatus(RuleStatus.BETA) // default severity when the rule is activated on a Quality profile. Default value is MAJOR. .setSeverity(Severity.MINOR); // debt-related information x1Rule .setDebtSubCharacteristic(SubCharacteristics.INTEGRATION_TESTABILITY) .setDebtRemediationFunction(x1Rule.debtRemediationFunctions().linearWithOffset("1h", "30min")) .setEffortToFixDescription("Effort to fix issue on one line"); x1Rule.createParam("acceptWhitespace") .setDefaultValue("false") .setType(RuleParamType.BOOLEAN) .setDescription("= Accept whitespace (``\\s|\\t``) on the line\nThis property is available so that a line containing only whitespace is not considered empty.\n" + "== Example with property set to ``false``\n``xoo\n <- One issue here\n<- And one here\n``\n\n" + "== Example with property set to ``true``\n``xoo\n <- No issue here\n<- But one here\n``\n"); // don't forget to call done() to finalize the definition repository.done(); }
private void setupSqaleModel(NewRule rule, Class<?> ruleClass) { SqaleSubCharacteristic subChar = getSqaleSubCharAnnotation(ruleClass); if (subChar != null) { rule.setDebtSubCharacteristic(subChar.value()); } SqaleConstantRemediation constant = AnnotationUtils.getAnnotation(ruleClass, SqaleConstantRemediation.class); SqaleLinearRemediation linear = AnnotationUtils.getAnnotation(ruleClass, SqaleLinearRemediation.class); SqaleLinearWithOffsetRemediation linearWithOffset = AnnotationUtils.getAnnotation(ruleClass, SqaleLinearWithOffsetRemediation.class); Set<Annotation> remediations = Sets.newHashSet(constant, linear, linearWithOffset); if (Iterables.size(Iterables.filter(remediations, Predicates.notNull())) > 1) { throw new IllegalArgumentException("Found more than one SQALE remediation annotations on " + ruleClass); } if (constant != null) { rule.setDebtRemediationFunction(rule.debtRemediationFunctions().constantPerIssue(constant.value())); } if (linear != null) { rule.setDebtRemediationFunction(rule.debtRemediationFunctions().linear(linear.coeff())); rule.setEffortToFixDescription(linear.effortToFixDescription()); } if (linearWithOffset != null) { rule.setDebtRemediationFunction( rule.debtRemediationFunctions().linearWithOffset(linearWithOffset.coeff(), linearWithOffset.offset())); rule.setEffortToFixDescription(linearWithOffset.effortToFixDescription()); } }
private void setupSqaleModel(NewRule rule, Class<?> ruleClass) { SqaleConstantRemediation constant = AnnotationUtils.getAnnotation(ruleClass, SqaleConstantRemediation.class); SqaleLinearRemediation linear = AnnotationUtils.getAnnotation(ruleClass, SqaleLinearRemediation.class); SqaleLinearWithOffsetRemediation linearWithOffset = AnnotationUtils.getAnnotation(ruleClass, SqaleLinearWithOffsetRemediation.class); Set<Annotation> remediations = Sets.newHashSet(constant, linear, linearWithOffset); if (Iterables.size(Iterables.filter(remediations, Predicates.notNull())) > 1) { throw new IllegalArgumentException("Found more than one SQALE remediation annotations on " + ruleClass); } if (constant != null) { rule.setDebtRemediationFunction(rule.debtRemediationFunctions().constantPerIssue(constant.value())); } if (linear != null) { rule.setDebtRemediationFunction(rule.debtRemediationFunctions().linear(linear.coeff())); rule.setGapDescription(linear.effortToFixDescription()); } if (linearWithOffset != null) { rule.setDebtRemediationFunction( rule.debtRemediationFunctions().linearWithOffset(linearWithOffset.coeff(), linearWithOffset.offset())); rule.setGapDescription(linearWithOffset.effortToFixDescription()); } }
private void setupSqaleModel(NewRule rule, Class<?> ruleClass) { SqaleConstantRemediation constant = AnnotationUtils.getAnnotation(ruleClass, SqaleConstantRemediation.class); SqaleLinearRemediation linear = AnnotationUtils.getAnnotation(ruleClass, SqaleLinearRemediation.class); SqaleLinearWithOffsetRemediation linearWithOffset = AnnotationUtils.getAnnotation(ruleClass, SqaleLinearWithOffsetRemediation.class); Set<Annotation> remediations = Sets.newHashSet(constant, linear, linearWithOffset); if (Iterables.size(Iterables.filter(remediations, Predicates.notNull())) > 1) { throw new IllegalArgumentException("Found more than one SQALE remediation annotations on " + ruleClass); } if (constant != null) { rule.setDebtRemediationFunction(rule.debtRemediationFunctions().constantPerIssue(constant.value())); } if (linear != null) { rule.setDebtRemediationFunction(rule.debtRemediationFunctions().linear(linear.coeff())); rule.setGapDescription(linear.effortToFixDescription()); } if (linearWithOffset != null) { rule.setDebtRemediationFunction( rule.debtRemediationFunctions().linearWithOffset(linearWithOffset.coeff(), linearWithOffset.offset())); rule.setGapDescription(linearWithOffset.effortToFixDescription()); } }
/** * IMPORTANT This method should not be used when SonarQube runtime version is less than 7.2 * because it would trigger calls to {@link org.sonar.api.server.rule.RulesDefinition.Context#createExternalRepository} * which was added in SonarQube 7.2. */ public void createExternalRuleRepository(org.sonar.api.server.rule.RulesDefinition.Context context) { NewRepository externalRepo = context.createExternalRepository(linterKey, languageKey).setName(linterName); for (ExternalRule rule : rulesMap.values()) { NewRule newRule = externalRepo.createRule(rule.key).setName(rule.name); newRule.setHtmlDescription(rule.getDescription(linterKey, linterName)); newRule.setDebtRemediationFunction(newRule.debtRemediationFunctions().constantPerIssue(rule.constantDebtMinutes + "min")); newRule.setType(rule.type); if (rule.tags != null) { newRule.setTags(rule.tags); } if (rule.severity != null) { newRule.setSeverity(rule.severity); } } externalRepo.done(); }
private static void setRemediationFromJson(NewRule rule, Map<String, Object> remediation) { String func = getString(remediation, "func"); DebtRemediationFunctions remediationBuilder = rule.debtRemediationFunctions(); if (func.startsWith("Constant")) { String constantCost = getString(remediation, "constantCost"); rule.setDebtRemediationFunction(remediationBuilder.constantPerIssue(constantCost.replace("mn", "min"))); } else if ("Linear".equals(func)) { String linearFactor = getString(remediation, "linearFactor"); rule.setDebtRemediationFunction(remediationBuilder.linear(linearFactor.replace("mn", "min"))); } else { String linearFactor = getString(remediation, "linearFactor"); String linearOffset = getString(remediation, "linearOffset"); rule.setDebtRemediationFunction(remediationBuilder.linearWithOffset( linearFactor.replace("mn", "min"), linearOffset.replace("mn", "min"))); } if (remediation.get("linearDesc") != null) { rule.setGapDescription(getString(remediation, "linearDesc")); } }
@Override public void define(Context context) { NewRepository repository = context.createRepository(REPOSITORY, JAVA_LANGUAGE).setName("My Custom Java Analyzer"); NewRule x1Rule = repository.createRule(RULE_ON_LINE_1.rule()) .setName("Stupid rule") .setHtmlDescription("Generates an issue on every line 1 of Java files") // optional tags .setTags("style", "stupid") // optional status. Default value is READY. .setStatus(RuleStatus.BETA) // default severity when the rule is activated on a Quality profile. Default value is MAJOR. .setSeverity(Severity.MINOR); x1Rule.setDebtRemediationFunction(x1Rule.debtRemediationFunctions().linearWithOffset("1h", "30min")); // don't forget to call done() to finalize the definition repository.done(); } }
public static void createExternalRuleRepository(Context context, String linterId, String linterName) { NewRepository externalRepo = context.createExternalRepository(linterId, GoLanguage.KEY).setName(linterName); String pathToRulesMeta = "org/sonar/l10n/go/rules/" + linterId + "/rules.json"; try (InputStreamReader inputStreamReader = new InputStreamReader(AbstractReportSensor.class.getClassLoader().getResourceAsStream(pathToRulesMeta), StandardCharsets.UTF_8)) { ExternalRule[] rules = new Gson().fromJson(inputStreamReader, ExternalRule[].class); for (ExternalRule rule : rules) { NewRule newRule = externalRepo.createRule(rule.key).setName(rule.name); newRule.setHtmlDescription(rule.description); newRule.setDebtRemediationFunction(newRule.debtRemediationFunctions().constantPerIssue(DEFAULT_REMEDIATION_COST + "min")); if (linterId.equals(GoVetReportSensor.LINTER_ID)) { newRule.setType(RuleType.BUG); } } } catch (IOException e) { throw new IllegalStateException("Can't read resource: " + pathToRulesMeta, e); } externalRepo.done(); }
@Override public void define(Context context) { NewRepository repository = context.createRepository(repositoryKey(), "php").setName("MyCompany Custom Repository"); // Load rule meta data from annotations RulesDefinitionAnnotationLoader annotationLoader = new RulesDefinitionAnnotationLoader(); checkClasses().forEach(ruleClass -> annotationLoader.load(repository, ruleClass)); // Optionally override html description from annotation with content from html files repository.rules().forEach(rule -> rule.setHtmlDescription(loadResource("/org/sonar/l10n/php/rules/custom/" + rule.key() + ".html"))); // Optionally define remediation costs Map<String, String> remediationCosts = new HashMap<>(); remediationCosts.put(ForbiddenFunctionUseCheck.KEY, "5min"); remediationCosts.put(OtherForbiddenFunctionUseCheck.KEY, "5min"); repository.rules().forEach(rule -> rule.setDebtRemediationFunction( rule.debtRemediationFunctions().constantPerIssue(remediationCosts.get(rule.key())))); repository.done(); }
private static void defineCommentDensityRule(RulesDefinition.NewRepository repo) { RulesDefinition.NewRule rule = repo.createRule(CommonRuleKeys.INSUFFICIENT_COMMENT_DENSITY); rule.setName("Source files should have a sufficient density of comment lines") .addTags("convention") .setHtmlDescription("An issue is created on a file as soon as the density of comment lines on this file is less than the required threshold. " + "The number of comment lines to be written in order to reach the required threshold is provided by each issue message.") .setDebtRemediationFunction(rule.debtRemediationFunctions().linear("2min")) .setGapDescription("number of lines required to meet minimum density") .setSeverity(Severity.MAJOR); rule.createParam(CommonRuleKeys.INSUFFICIENT_COMMENT_DENSITY_PROPERTY) .setName("The minimum required comment density") .setDefaultValue("25") .setType(RuleParamType.FLOAT); }
private static void defineLineCoverageRule(RulesDefinition.NewRepository repo) { RulesDefinition.NewRule rule = repo.createRule(CommonRuleKeys.INSUFFICIENT_LINE_COVERAGE); rule.setName("Lines should have sufficient coverage by tests") .addTags("bad-practice") .setHtmlDescription("An issue is created on a file as soon as the line coverage on this file is less than the required threshold. " + "It gives the number of lines to be covered in order to reach the required threshold.") .setDebtRemediationFunction(rule.debtRemediationFunctions().linear("2min")) .setGapDescription("number of lines under the coverage threshold") .setSeverity(Severity.MAJOR); rule.createParam(CommonRuleKeys.INSUFFICIENT_LINE_COVERAGE_PROPERTY) .setName("The minimum required line coverage ratio") .setDefaultValue("65") .setType(RuleParamType.FLOAT); }
private static void defineBranchCoverageRule(RulesDefinition.NewRepository repo) { RulesDefinition.NewRule rule = repo.createRule(CommonRuleKeys.INSUFFICIENT_BRANCH_COVERAGE); rule.setName("Branches should have sufficient coverage by tests") .addTags("bad-practice") .setHtmlDescription("An issue is created on a file as soon as the branch coverage on this file is less than the required threshold." + "It gives the number of branches to be covered in order to reach the required threshold.") .setDebtRemediationFunction(rule.debtRemediationFunctions().linear("5min")) .setGapDescription("number of uncovered conditions") .setSeverity(Severity.MAJOR); rule.createParam(CommonRuleKeys.INSUFFICIENT_BRANCH_COVERAGE_PROPERTY) .setName("The minimum required branch coverage ratio") .setDefaultValue("65") .setType(RuleParamType.FLOAT); }
private void setTechnicalDebt(RulesDefinition.NewRule rule, Metadata metadataAnnotation) { String technicalDebt = metadataAnnotation.technicalDebt(); if (StringUtils.isNotEmpty(technicalDebt)) { DebtRemediationFunctions remediationFunction = rule.debtRemediationFunctions(); rule.setDebtRemediationFunction(remediationFunction.constantPerIssue(technicalDebt)); } }
private void createWarningRule(NewRepository repository, String ruleKey, String name, String remediationCost, Priority priority, String[] tags) { NewRule warning = repository.createRule(ruleKey).setName(name).setSeverity(priority.name()); warning.setTags(tags); warning.setDebtRemediationFunction(warning.debtRemediationFunctions().constantPerIssue(remediationCost)); warning.setType(RuleType.CODE_SMELL); warning.setHtmlDescription(getClass().getResource( String.format(HTML_DOC_PATH, Constants.LANGUAGE_KEY, Constants.STD_REPOSITORY_KEY, warning.key()))); } }
@Override public void define(Context context) { RulesDefinition.NewRepository repository = context.createRepository(repositoryKey(), "js") .setName("MyCompany Custom Repository"); // this will load metadata from @Rule annotation new RulesDefinitionAnnotationLoader().load(repository, checkClasses().toArray(new Class[] {})); NewRule rule = repository.rule("S1"); // description is loaded from html file in resources directory rule.setHtmlDescription(loadRuleDescription(rule.key())); // remediation function sets how much rule violation contributes to technical debt rule.setDebtRemediationFunction(rule.debtRemediationFunctions().linear("5min")); repository.done(); }