001/* 002 * SonarQube 003 * Copyright (C) 2009-2017 SonarSource SA 004 * mailto:info AT sonarsource DOT com 005 * 006 * This program is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 3 of the License, or (at your option) any later version. 010 * 011 * This program is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public License 017 * along with this program; if not, write to the Free Software Foundation, 018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 019 */ 020package org.sonar.api.server.rule; 021 022import com.google.common.base.MoreObjects; 023import com.google.common.collect.ImmutableMap; 024import com.google.common.collect.ImmutableSortedSet; 025import com.google.common.collect.Sets; 026import java.io.IOException; 027import java.net.URL; 028import java.util.ArrayList; 029import java.util.Collection; 030import java.util.HashMap; 031import java.util.List; 032import java.util.Map; 033import java.util.Set; 034import javax.annotation.CheckForNull; 035import javax.annotation.Nullable; 036import javax.annotation.concurrent.Immutable; 037import org.apache.commons.io.IOUtils; 038import org.apache.commons.lang.StringUtils; 039import org.sonar.api.ExtensionPoint; 040import org.sonar.api.ce.ComputeEngineSide; 041import org.sonar.api.rule.RuleStatus; 042import org.sonar.api.rule.Severity; 043import org.sonar.api.rules.RuleType; 044import org.sonar.api.server.ServerSide; 045import org.sonar.api.server.debt.DebtRemediationFunction; 046import org.sonar.api.utils.log.Loggers; 047import org.sonarsource.api.sonarlint.SonarLintSide; 048 049import static com.google.common.base.Preconditions.checkArgument; 050import static com.google.common.base.Preconditions.checkState; 051import static java.lang.String.format; 052import static java.nio.charset.StandardCharsets.UTF_8; 053import static java.util.Collections.emptyList; 054import static java.util.Collections.unmodifiableList; 055import static java.util.Collections.unmodifiableMap; 056import static org.apache.commons.lang.StringUtils.defaultIfEmpty; 057import static org.apache.commons.lang.StringUtils.isEmpty; 058import static org.apache.commons.lang.StringUtils.trimToNull; 059 060/** 061 * Defines some coding rules of the same repository. For example the Java Findbugs plugin provides an implementation of 062 * this extension point in order to define the rules that it supports. 063 * <br> 064 * This interface replaces the deprecated class org.sonar.api.rules.RuleRepository. 065 * <br> 066 * <h3>How to use</h3> 067 * <pre> 068 * public class MyJsRulesDefinition implements RulesDefinition { 069 * 070 * {@literal @}Override 071 * public void define(Context context) { 072 * NewRepository repository = context.createRepository("my_js", "js").setName("My Javascript Analyzer"); 073 * 074 * // define a rule programmatically. Note that rules 075 * // could be loaded from files (JSON, XML, ...) 076 * NewRule x1Rule = repository.createRule("x1") 077 * .setName("No empty line") 078 * .setHtmlDescription("Generate an issue on empty lines") 079 * 080 * // optional tags 081 * .setTags("style", "stupid") 082 * 083 * // optional status. Default value is READY. 084 * .setStatus(RuleStatus.BETA) 085 * 086 * // default severity when the rule is activated on a Quality profile. Default value is MAJOR. 087 * .setSeverity(Severity.MINOR); 088 * 089 * // optional type for SonarQube Quality Model. Default is RulesDefinition.Type.CODE_SMELL. 090 * .setType(RulesDefinition.Type.BUG) 091 * 092 * x1Rule 093 * .setDebtRemediationFunction(x1Rule.debtRemediationFunctions().linearWithOffset("1h", "30min")); 094 * 095 * x1Rule.createParam("acceptWhitespace") 096 * .setDefaultValue("false") 097 * .setType(RuleParamType.BOOLEAN) 098 * .setDescription("Accept whitespaces on the line"); 099 * 100 * // don't forget to call done() to finalize the definition 101 * repository.done(); 102 * } 103 * } 104 * </pre> 105 * <br> 106 * If rules are declared in a XML file with the standard SonarQube format (see 107 * {@link org.sonar.api.server.rule.RulesDefinitionXmlLoader}), then it can be loaded by using : 108 * <br> 109 * <pre> 110 * public class MyJsRulesDefinition implements RulesDefinition { 111 * 112 * private final RulesDefinitionXmlLoader xmlLoader; 113 * 114 * public MyJsRulesDefinition(RulesDefinitionXmlLoader xmlLoader) { 115 * this.xmlLoader = xmlLoader; 116 * } 117 * 118 * {@literal @}Override 119 * public void define(Context context) { 120 * NewRepository repository = context.createRepository("my_js", "js").setName("My Javascript Analyzer"); 121 * // see javadoc of RulesDefinitionXmlLoader for the format 122 * xmlLoader.load(repository, getClass().getResourceAsStream("/path/to/rules.xml")); 123 * repository.done(); 124 * } 125 * } 126 * </pre> 127 * <br> 128 * In the above example, XML file must contain name and description of each rule. If it's not the case, then the 129 * (deprecated) English bundles can be used : 130 * <br> 131 * <pre> 132 * public class MyJsRulesDefinition implements RulesDefinition { 133 * 134 * private final RulesDefinitionXmlLoader xmlLoader; 135 * private final RulesDefinitionI18nLoader i18nLoader; 136 * 137 * public MyJsRulesDefinition(RulesDefinitionXmlLoader xmlLoader, RulesDefinitionI18nLoader i18nLoader) { 138 * this.xmlLoader = xmlLoader; 139 * this.i18nLoader = i18nLoader; 140 * } 141 * 142 * {@literal @}Override 143 * public void define(Context context) { 144 * NewRepository repository = context.createRepository("my_js", "js").setName("My Javascript Analyzer"); 145 * xmlLoader.load(repository, getClass().getResourceAsStream("/path/to/rules.xml"), "UTF-8"); 146 * i18nLoader.load(repository); 147 * repository.done(); 148 * } 149 * } 150 * </pre> 151 * 152 * @since 4.3 153 */ 154@ServerSide 155@ComputeEngineSide 156@SonarLintSide 157@ExtensionPoint 158public interface RulesDefinition { 159 160 /** 161 * Default sub-characteristics of technical debt model. See http://www.sqale.org 162 * @deprecated in 5.5. SQALE Quality Model is replaced by SonarQube Quality Model. 163 * See https://jira.sonarsource.com/browse/MMF-184 164 */ 165 @Deprecated 166 final class SubCharacteristics { 167 /** 168 * Related to characteristic REUSABILITY 169 */ 170 public static final String MODULARITY = "MODULARITY"; 171 172 /** 173 * Related to characteristic REUSABILITY 174 */ 175 public static final String TRANSPORTABILITY = "TRANSPORTABILITY"; 176 177 /** 178 * Related to characteristic PORTABILITY 179 */ 180 public static final String COMPILER_RELATED_PORTABILITY = "COMPILER_RELATED_PORTABILITY"; 181 182 /** 183 * Related to characteristic PORTABILITY 184 */ 185 public static final String HARDWARE_RELATED_PORTABILITY = "HARDWARE_RELATED_PORTABILITY"; 186 187 /** 188 * Related to characteristic PORTABILITY 189 */ 190 public static final String LANGUAGE_RELATED_PORTABILITY = "LANGUAGE_RELATED_PORTABILITY"; 191 192 /** 193 * Related to characteristic PORTABILITY 194 */ 195 public static final String OS_RELATED_PORTABILITY = "OS_RELATED_PORTABILITY"; 196 197 /** 198 * Related to characteristic PORTABILITY 199 */ 200 public static final String SOFTWARE_RELATED_PORTABILITY = "SOFTWARE_RELATED_PORTABILITY"; 201 202 /** 203 * Related to characteristic PORTABILITY 204 */ 205 public static final String TIME_ZONE_RELATED_PORTABILITY = "TIME_ZONE_RELATED_PORTABILITY"; 206 207 /** 208 * Related to characteristic MAINTAINABILITY 209 */ 210 public static final String READABILITY = "READABILITY"; 211 212 /** 213 * Related to characteristic MAINTAINABILITY 214 */ 215 public static final String UNDERSTANDABILITY = "UNDERSTANDABILITY"; 216 217 /** 218 * Related to characteristic SECURITY 219 */ 220 public static final String API_ABUSE = "API_ABUSE"; 221 222 /** 223 * Related to characteristic SECURITY 224 */ 225 public static final String ERRORS = "ERRORS"; 226 227 /** 228 * Related to characteristic SECURITY 229 */ 230 public static final String INPUT_VALIDATION_AND_REPRESENTATION = "INPUT_VALIDATION_AND_REPRESENTATION"; 231 232 /** 233 * Related to characteristic SECURITY 234 */ 235 public static final String SECURITY_FEATURES = "SECURITY_FEATURES"; 236 237 /** 238 * Related to characteristic EFFICIENCY 239 */ 240 public static final String CPU_EFFICIENCY = "CPU_EFFICIENCY"; 241 242 /** 243 * Related to characteristic EFFICIENCY 244 */ 245 public static final String MEMORY_EFFICIENCY = "MEMORY_EFFICIENCY"; 246 247 /** 248 * Related to characteristic EFFICIENCY 249 */ 250 public static final String NETWORK_USE = "NETWORK_USE"; 251 252 /** 253 * Related to characteristic CHANGEABILITY 254 */ 255 public static final String ARCHITECTURE_CHANGEABILITY = "ARCHITECTURE_CHANGEABILITY"; 256 257 /** 258 * Related to characteristic CHANGEABILITY 259 */ 260 public static final String DATA_CHANGEABILITY = "DATA_CHANGEABILITY"; 261 262 /** 263 * Related to characteristic CHANGEABILITY 264 */ 265 public static final String LOGIC_CHANGEABILITY = "LOGIC_CHANGEABILITY"; 266 267 /** 268 * Related to characteristic RELIABILITY 269 */ 270 public static final String ARCHITECTURE_RELIABILITY = "ARCHITECTURE_RELIABILITY"; 271 272 /** 273 * Related to characteristic RELIABILITY 274 */ 275 public static final String DATA_RELIABILITY = "DATA_RELIABILITY"; 276 277 /** 278 * Related to characteristic RELIABILITY 279 */ 280 public static final String EXCEPTION_HANDLING = "EXCEPTION_HANDLING"; 281 282 /** 283 * Related to characteristic RELIABILITY 284 */ 285 public static final String FAULT_TOLERANCE = "FAULT_TOLERANCE"; 286 287 /** 288 * Related to characteristic RELIABILITY 289 */ 290 public static final String INSTRUCTION_RELIABILITY = "INSTRUCTION_RELIABILITY"; 291 292 /** 293 * Related to characteristic RELIABILITY 294 */ 295 public static final String LOGIC_RELIABILITY = "LOGIC_RELIABILITY"; 296 297 /** 298 * Related to characteristic RELIABILITY 299 */ 300 public static final String RESOURCE_RELIABILITY = "RESOURCE_RELIABILITY"; 301 302 /** 303 * Related to characteristic RELIABILITY 304 */ 305 public static final String SYNCHRONIZATION_RELIABILITY = "SYNCHRONIZATION_RELIABILITY"; 306 307 /** 308 * Related to characteristic RELIABILITY 309 */ 310 public static final String UNIT_TESTS = "UNIT_TESTS"; 311 312 /** 313 * Related to characteristic TESTABILITY 314 */ 315 public static final String INTEGRATION_TESTABILITY = "INTEGRATION_TESTABILITY"; 316 317 /** 318 * Related to characteristic TESTABILITY 319 */ 320 public static final String UNIT_TESTABILITY = "UNIT_TESTABILITY"; 321 322 /** 323 * Related to characteristic ACCESSIBILITY 324 */ 325 public static final String USABILITY_ACCESSIBILITY = "USABILITY_ACCESSIBILITY"; 326 327 /** 328 * Related to characteristic ACCESSIBILITY 329 */ 330 public static final String USABILITY_COMPLIANCE = "USABILITY_COMPLIANCE"; 331 332 /** 333 * Related to characteristic ACCESSIBILITY 334 */ 335 public static final String USABILITY_EASE_OF_USE = "USABILITY_EASE_OF_USE"; 336 337 /** 338 * Related to characteristic REUSABILITY 339 */ 340 public static final String REUSABILITY_COMPLIANCE = "REUSABILITY_COMPLIANCE"; 341 342 /** 343 * Related to characteristic PORTABILITY 344 */ 345 public static final String PORTABILITY_COMPLIANCE = "PORTABILITY_COMPLIANCE"; 346 347 /** 348 * Related to characteristic MAINTAINABILITY 349 */ 350 public static final String MAINTAINABILITY_COMPLIANCE = "MAINTAINABILITY_COMPLIANCE"; 351 352 /** 353 * Related to characteristic SECURITY 354 */ 355 public static final String SECURITY_COMPLIANCE = "SECURITY_COMPLIANCE"; 356 357 /** 358 * Related to characteristic EFFICIENCY 359 */ 360 public static final String EFFICIENCY_COMPLIANCE = "EFFICIENCY_COMPLIANCE"; 361 362 /** 363 * Related to characteristic CHANGEABILITY 364 */ 365 public static final String CHANGEABILITY_COMPLIANCE = "CHANGEABILITY_COMPLIANCE"; 366 367 /** 368 * Related to characteristic RELIABILITY 369 */ 370 public static final String RELIABILITY_COMPLIANCE = "RELIABILITY_COMPLIANCE"; 371 372 /** 373 * Related to characteristic TESTABILITY 374 */ 375 public static final String TESTABILITY_COMPLIANCE = "TESTABILITY_COMPLIANCE"; 376 377 private SubCharacteristics() { 378 // only constants 379 } 380 } 381 382 /** 383 * Instantiated by core but not by plugins, except for their tests. 384 */ 385 class Context { 386 private final Map<String, Repository> repositoriesByKey = new HashMap<>(); 387 388 /** 389 * New builder for {@link org.sonar.api.server.rule.RulesDefinition.Repository}. 390 * <br> 391 * A plugin can add rules to a repository that is defined then executed by another plugin. For instance 392 * the FbContrib plugin contributes to the Findbugs plugin rules. In this case no need 393 * to execute {@link org.sonar.api.server.rule.RulesDefinition.NewRepository#setName(String)} 394 */ 395 public NewRepository createRepository(String key, String language) { 396 return new NewRepositoryImpl(this, key, language); 397 } 398 399 /** 400 * @deprecated since 5.2. Simply use {@link #createRepository(String, String)} 401 */ 402 @Deprecated 403 public NewRepository extendRepository(String key, String language) { 404 return createRepository(key, language); 405 } 406 407 @CheckForNull 408 public Repository repository(String key) { 409 return repositoriesByKey.get(key); 410 } 411 412 public List<Repository> repositories() { 413 return unmodifiableList(new ArrayList<>(repositoriesByKey.values())); 414 } 415 416 /** 417 * @deprecated returns empty list since 5.2. Concept of "extended repository" was misleading and not valuable. Simply declare 418 * repositories and use {@link #repositories()}. See http://jira.sonarsource.com/browse/SONAR-6709 419 */ 420 @Deprecated 421 public List<ExtendedRepository> extendedRepositories(String repositoryKey) { 422 return emptyList(); 423 } 424 425 /** 426 * @deprecated returns empty list since 5.2. Concept of "extended repository" was misleading and not valuable. Simply declare 427 * repositories and use {@link #repositories()}. See http://jira.sonarsource.com/browse/SONAR-6709 428 */ 429 @Deprecated 430 public List<ExtendedRepository> extendedRepositories() { 431 return emptyList(); 432 } 433 434 private void registerRepository(NewRepositoryImpl newRepository) { 435 Repository existing = repositoriesByKey.get(newRepository.key()); 436 if (existing != null) { 437 String existingLanguage = existing.language(); 438 checkState(existingLanguage.equals(newRepository.language), 439 "The rule repository '%s' must not be defined for two different languages: %s and %s", 440 newRepository.key, existingLanguage, newRepository.language); 441 } 442 repositoriesByKey.put(newRepository.key, new RepositoryImpl(newRepository, existing)); 443 } 444 } 445 446 interface NewExtendedRepository { 447 /** 448 * Create a rule with specified key. Max length of key is 200 characters. Key must be unique 449 * among the repository 450 * @throws IllegalArgumentException is key is not unique. Note a warning was logged up to version 5.4 (included) 451 */ 452 NewRule createRule(String ruleKey); 453 454 @CheckForNull 455 NewRule rule(String ruleKey); 456 457 Collection<NewRule> rules(); 458 459 String key(); 460 461 void done(); 462 } 463 464 interface NewRepository extends NewExtendedRepository { 465 NewRepository setName(String s); 466 } 467 468 class NewRepositoryImpl implements NewRepository { 469 private final Context context; 470 private final String key; 471 private String language; 472 private String name; 473 private final Map<String, NewRule> newRules = new HashMap<>(); 474 475 private NewRepositoryImpl(Context context, String key, String language) { 476 this.context = context; 477 this.key = this.name = key; 478 this.language = language; 479 } 480 481 @Override 482 public String key() { 483 return key; 484 } 485 486 @Override 487 public NewRepositoryImpl setName(@Nullable String s) { 488 if (StringUtils.isNotEmpty(s)) { 489 this.name = s; 490 } 491 return this; 492 } 493 494 @Override 495 public NewRule createRule(String ruleKey) { 496 checkArgument(!newRules.containsKey(ruleKey), "The rule '%s' of repository '%s' is declared several times", ruleKey, key); 497 NewRule newRule = new NewRule(key, ruleKey); 498 newRules.put(ruleKey, newRule); 499 return newRule; 500 } 501 502 @CheckForNull 503 @Override 504 public NewRule rule(String ruleKey) { 505 return newRules.get(ruleKey); 506 } 507 508 @Override 509 public Collection<NewRule> rules() { 510 return newRules.values(); 511 } 512 513 @Override 514 public void done() { 515 // note that some validations can be done here, for example for 516 // verifying that at least one rule is declared 517 518 context.registerRepository(this); 519 } 520 521 @Override 522 public String toString() { 523 return MoreObjects.toStringHelper(this) 524 .add("key", key) 525 .add("language", language) 526 .toString(); 527 } 528 } 529 530 interface ExtendedRepository { 531 String key(); 532 533 String language(); 534 535 @CheckForNull 536 Rule rule(String ruleKey); 537 538 List<Rule> rules(); 539 } 540 541 interface Repository extends ExtendedRepository { 542 String name(); 543 } 544 545 @Immutable 546 class RepositoryImpl implements Repository { 547 private final String key; 548 private final String language; 549 private final String name; 550 private final Map<String, Rule> rulesByKey; 551 552 private RepositoryImpl(NewRepositoryImpl newRepository, @Nullable Repository mergeInto) { 553 this.key = newRepository.key; 554 this.language = newRepository.language; 555 556 Map<String, Rule> ruleBuilder = new HashMap<>(); 557 if (mergeInto != null) { 558 if (!StringUtils.equals(newRepository.language, mergeInto.language()) || !StringUtils.equals(newRepository.key, mergeInto.key())) { 559 throw new IllegalArgumentException(format("Bug - language and key of the repositories to be merged should be the sames: %s and %s", newRepository, mergeInto)); 560 } 561 this.name = StringUtils.defaultIfBlank(mergeInto.name(), newRepository.name); 562 for (Rule rule : mergeInto.rules()) { 563 if (!newRepository.key().startsWith("common-") && ruleBuilder.containsKey(rule.key())) { 564 Loggers.get(getClass()).warn("The rule '{}' of repository '{}' is declared several times", rule.key(), mergeInto.key()); 565 } 566 ruleBuilder.put(rule.key(), rule); 567 } 568 } else { 569 this.name = newRepository.name; 570 } 571 for (NewRule newRule : newRepository.newRules.values()) { 572 newRule.validate(); 573 ruleBuilder.put(newRule.key, new Rule(this, newRule)); 574 } 575 this.rulesByKey = unmodifiableMap(ruleBuilder); 576 } 577 578 @Override 579 public String key() { 580 return key; 581 } 582 583 @Override 584 public String language() { 585 return language; 586 } 587 588 @Override 589 public String name() { 590 return name; 591 } 592 593 @Override 594 @CheckForNull 595 public Rule rule(String ruleKey) { 596 return rulesByKey.get(ruleKey); 597 } 598 599 @Override 600 public List<Rule> rules() { 601 return unmodifiableList(new ArrayList<>(rulesByKey.values())); 602 } 603 604 @Override 605 public boolean equals(Object o) { 606 if (this == o) { 607 return true; 608 } 609 if (o == null || getClass() != o.getClass()) { 610 return false; 611 } 612 RepositoryImpl that = (RepositoryImpl) o; 613 return key.equals(that.key); 614 } 615 616 @Override 617 public int hashCode() { 618 return key.hashCode(); 619 } 620 621 @Override 622 public String toString() { 623 return MoreObjects.toStringHelper(this) 624 .add("language", language) 625 .add("key", key) 626 .toString(); 627 } 628 } 629 630 /** 631 * Factory of {@link org.sonar.api.server.debt.DebtRemediationFunction}. 632 */ 633 interface DebtRemediationFunctions { 634 635 /** 636 * Shortcut for {@code create(Type.LINEAR, gap multiplier, null)}. 637 * @param gapMultiplier the duration to fix one issue. See {@link DebtRemediationFunction} for details about format. 638 * @see org.sonar.api.server.debt.DebtRemediationFunction.Type#LINEAR 639 */ 640 DebtRemediationFunction linear(String gapMultiplier); 641 642 /** 643 * Shortcut for {@code create(Type.LINEAR_OFFSET, gap multiplier, base effort)}. 644 * @param gapMultiplier duration to fix one point of complexity. See {@link DebtRemediationFunction} for details and format. 645 * @param baseEffort duration to make basic analysis. See {@link DebtRemediationFunction} for details and format. 646 * @see org.sonar.api.server.debt.DebtRemediationFunction.Type#LINEAR_OFFSET 647 */ 648 DebtRemediationFunction linearWithOffset(String gapMultiplier, String baseEffort); 649 650 /** 651 * Shortcut for {@code create(Type.CONSTANT_ISSUE, null, base effort)}. 652 * @param baseEffort cost per issue. See {@link DebtRemediationFunction} for details and format. 653 * @see org.sonar.api.server.debt.DebtRemediationFunction.Type#CONSTANT_ISSUE 654 */ 655 DebtRemediationFunction constantPerIssue(String baseEffort); 656 657 /** 658 * Flexible way to create a {@link DebtRemediationFunction}. An unchecked exception is thrown if 659 * coefficient and/or offset are not valid according to the given @{code type}. 660 * @since 5.3 661 */ 662 DebtRemediationFunction create(DebtRemediationFunction.Type type, @Nullable String gapMultiplier, @Nullable String baseEffort); 663 } 664 665 class NewRule { 666 private final String repoKey; 667 private final String key; 668 private RuleType type; 669 private String name; 670 private String htmlDescription; 671 private String markdownDescription; 672 private String internalKey; 673 private String severity = Severity.MAJOR; 674 private boolean template; 675 private RuleStatus status = RuleStatus.defaultStatus(); 676 private DebtRemediationFunction debtRemediationFunction; 677 private String gapDescription; 678 private final Set<String> tags = Sets.newTreeSet(); 679 private final Map<String, NewParam> paramsByKey = new HashMap<>(); 680 private final DebtRemediationFunctions functions; 681 private boolean activatedByDefault; 682 683 private NewRule(String repoKey, String key) { 684 this.repoKey = repoKey; 685 this.key = key; 686 this.functions = new DefaultDebtRemediationFunctions(repoKey, key); 687 } 688 689 public String key() { 690 return this.key; 691 } 692 693 /** 694 * Required rule name 695 */ 696 public NewRule setName(String s) { 697 this.name = trimToNull(s); 698 return this; 699 } 700 701 public NewRule setTemplate(boolean template) { 702 this.template = template; 703 return this; 704 } 705 706 /** 707 * Should this rule be enabled by default. For example in SonarLint standalone. 708 * @since 6.0 709 */ 710 public NewRule setActivatedByDefault(boolean activatedByDefault) { 711 this.activatedByDefault = activatedByDefault; 712 return this; 713 } 714 715 public NewRule setSeverity(String s) { 716 checkArgument(Severity.ALL.contains(s), "Severity of rule %s is not correct: %s", this, s); 717 this.severity = s; 718 return this; 719 } 720 721 /** 722 * The type as defined by the SonarQube Quality Model. 723 * <br> 724 * When a plugin does not define rule type, then it is deduced from 725 * tags: 726 * <ul> 727 * <li>if the rule has the "bug" tag then type is {@link RuleType#BUG}</li> 728 * <li>if the rule has the "security" tag then type is {@link RuleType#VULNERABILITY}</li> 729 * <li>if the rule has both tags "bug" and "security", then type is {@link RuleType#BUG}</li> 730 * <li>default type is {@link RuleType#CODE_SMELL}</li> 731 * </ul> 732 * Finally the "bug" and "security" tags are considered as reserved. They 733 * are silently removed from the final state of definition. 734 * @since 5.5 735 */ 736 public NewRule setType(RuleType t) { 737 this.type = t; 738 return this; 739 } 740 741 /** 742 * The optional description, in HTML format, has no max length. It's exclusive with markdown description 743 * (see {@link #setMarkdownDescription(String)}) 744 */ 745 public NewRule setHtmlDescription(@Nullable String s) { 746 checkState(markdownDescription == null, "Rule '%s' already has a Markdown description", this); 747 this.htmlDescription = trimToNull(s); 748 return this; 749 } 750 751 /** 752 * Load description from a file available in classpath. Example : <code>setHtmlDescription(getClass().getResource("/myrepo/Rule1234.html")</code> 753 */ 754 public NewRule setHtmlDescription(@Nullable URL classpathUrl) { 755 if (classpathUrl != null) { 756 try { 757 setHtmlDescription(IOUtils.toString(classpathUrl, UTF_8)); 758 } catch (IOException e) { 759 throw new IllegalStateException("Fail to read: " + classpathUrl, e); 760 } 761 } else { 762 this.htmlDescription = null; 763 } 764 return this; 765 } 766 767 /** 768 * The optional description, in a restricted Markdown format, has no max length. It's exclusive with HTML description 769 * (see {@link #setHtmlDescription(String)}) 770 */ 771 public NewRule setMarkdownDescription(@Nullable String s) { 772 checkState(htmlDescription == null, "Rule '%s' already has an HTML description", this); 773 this.markdownDescription = trimToNull(s); 774 return this; 775 } 776 777 /** 778 * Load description from a file available in classpath. Example : {@code setMarkdownDescription(getClass().getResource("/myrepo/Rule1234.md")} 779 */ 780 public NewRule setMarkdownDescription(@Nullable URL classpathUrl) { 781 if (classpathUrl != null) { 782 try { 783 setMarkdownDescription(IOUtils.toString(classpathUrl, UTF_8)); 784 } catch (IOException e) { 785 throw new IllegalStateException("Fail to read: " + classpathUrl, e); 786 } 787 } else { 788 this.markdownDescription = null; 789 } 790 return this; 791 } 792 793 /** 794 * Default value is {@link org.sonar.api.rule.RuleStatus#READY}. The value 795 * {@link org.sonar.api.rule.RuleStatus#REMOVED} is not accepted and raises an 796 * {@link java.lang.IllegalArgumentException}. 797 */ 798 public NewRule setStatus(RuleStatus status) { 799 checkArgument(RuleStatus.REMOVED != status, "Status 'REMOVED' is not accepted on rule '%s'", this); 800 this.status = status; 801 return this; 802 } 803 804 /** 805 * SQALE sub-characteristic. See http://www.sqale.org 806 * 807 * @see org.sonar.api.server.rule.RulesDefinition.SubCharacteristics for constant values 808 * @deprecated in 5.5. SQALE Quality Model is replaced by SonarQube Quality Model. This method does nothing. 809 * See https://jira.sonarsource.com/browse/MMF-184 810 * @see #setType(RuleType) 811 */ 812 public NewRule setDebtSubCharacteristic(@Nullable String s) { 813 return this; 814 } 815 816 /** 817 * Factory of {@link org.sonar.api.server.debt.DebtRemediationFunction} 818 */ 819 public DebtRemediationFunctions debtRemediationFunctions() { 820 return functions; 821 } 822 823 /** 824 * @see #debtRemediationFunctions() 825 */ 826 public NewRule setDebtRemediationFunction(@Nullable DebtRemediationFunction fn) { 827 this.debtRemediationFunction = fn; 828 return this; 829 } 830 831 /** 832 * @deprecated since 5.5, replaced by {@link #setGapDescription(String)} 833 */ 834 @Deprecated 835 public NewRule setEffortToFixDescription(@Nullable String s) { 836 return setGapDescription(s); 837 } 838 839 /** 840 * For rules that use LINEAR or LINEAR_OFFSET remediation functions, the meaning 841 * of the function parameter (= "gap") must be set. This description 842 * explains what 1 point of "gap" represents for the rule. 843 * <br> 844 * Example: for the "Insufficient condition coverage", this description for the 845 * remediation function gap multiplier/base effort would be something like 846 * "Effort to test one uncovered condition". 847 */ 848 public NewRule setGapDescription(@Nullable String s) { 849 this.gapDescription = s; 850 return this; 851 } 852 853 /** 854 * Create a parameter with given unique key. Max length of key is 128 characters. 855 */ 856 public NewParam createParam(String paramKey) { 857 checkArgument(!paramsByKey.containsKey(paramKey), "The parameter '%s' is declared several times on the rule %s", paramKey, this); 858 NewParam param = new NewParam(paramKey); 859 paramsByKey.put(paramKey, param); 860 return param; 861 } 862 863 @CheckForNull 864 public NewParam param(String paramKey) { 865 return paramsByKey.get(paramKey); 866 } 867 868 public Collection<NewParam> params() { 869 return paramsByKey.values(); 870 } 871 872 /** 873 * @see RuleTagFormat 874 */ 875 public NewRule addTags(String... list) { 876 for (String tag : list) { 877 RuleTagFormat.validate(tag); 878 tags.add(tag); 879 } 880 return this; 881 } 882 883 /** 884 * @see RuleTagFormat 885 */ 886 public NewRule setTags(String... list) { 887 tags.clear(); 888 addTags(list); 889 return this; 890 } 891 892 /** 893 * Optional key that can be used by the rule engine. Not displayed 894 * in webapp. For example the Java Checkstyle plugin feeds this field 895 * with the internal path ("Checker/TreeWalker/AnnotationUseStyle"). 896 */ 897 public NewRule setInternalKey(@Nullable String s) { 898 this.internalKey = s; 899 return this; 900 } 901 902 private void validate() { 903 if (isEmpty(name)) { 904 throw new IllegalStateException(format("Name of rule %s is empty", this)); 905 } 906 if (isEmpty(htmlDescription) && isEmpty(markdownDescription)) { 907 throw new IllegalStateException(format("One of HTML description or Markdown description must be defined for rule %s", this)); 908 } 909 } 910 911 @Override 912 public String toString() { 913 return format("[repository=%s, key=%s]", repoKey, key); 914 } 915 } 916 917 @Immutable 918 class Rule { 919 private final Repository repository; 920 private final String repoKey; 921 private final String key; 922 private final String name; 923 private final RuleType type; 924 private final String htmlDescription; 925 private final String markdownDescription; 926 private final String internalKey; 927 private final String severity; 928 private final boolean template; 929 private final DebtRemediationFunction debtRemediationFunction; 930 private final String gapDescription; 931 private final Set<String> tags; 932 private final Map<String, Param> params; 933 private final RuleStatus status; 934 private final boolean activatedByDefault; 935 936 private Rule(Repository repository, NewRule newRule) { 937 this.repository = repository; 938 this.repoKey = newRule.repoKey; 939 this.key = newRule.key; 940 this.name = newRule.name; 941 this.htmlDescription = newRule.htmlDescription; 942 this.markdownDescription = newRule.markdownDescription; 943 this.internalKey = newRule.internalKey; 944 this.severity = newRule.severity; 945 this.template = newRule.template; 946 this.status = newRule.status; 947 this.debtRemediationFunction = newRule.debtRemediationFunction; 948 this.gapDescription = newRule.gapDescription; 949 this.type = newRule.type == null ? RuleTagsToTypeConverter.convert(newRule.tags) : newRule.type; 950 this.tags = ImmutableSortedSet.copyOf(Sets.difference(newRule.tags, RuleTagsToTypeConverter.RESERVED_TAGS)); 951 ImmutableMap.Builder<String, Param> paramsBuilder = ImmutableMap.builder(); 952 for (NewParam newParam : newRule.paramsByKey.values()) { 953 paramsBuilder.put(newParam.key, new Param(newParam)); 954 } 955 this.params = paramsBuilder.build(); 956 this.activatedByDefault = newRule.activatedByDefault; 957 } 958 959 public Repository repository() { 960 return repository; 961 } 962 963 public String key() { 964 return key; 965 } 966 967 public String name() { 968 return name; 969 } 970 971 /** 972 * @since 5.5 973 * @see NewRule#setType(RuleType) 974 */ 975 public RuleType type() { 976 return type; 977 } 978 979 public String severity() { 980 return severity; 981 } 982 983 @CheckForNull 984 public String htmlDescription() { 985 return htmlDescription; 986 } 987 988 @CheckForNull 989 public String markdownDescription() { 990 return markdownDescription; 991 } 992 993 public boolean template() { 994 return template; 995 } 996 997 /** 998 * Should this rule be enabled by default. For example in SonarLint standalone. 999 * @since 6.0 1000 */ 1001 public boolean activatedByDefault() { 1002 return activatedByDefault; 1003 } 1004 1005 public RuleStatus status() { 1006 return status; 1007 } 1008 1009 /** 1010 * @deprecated in 5.5. SQALE Quality Model is replaced by SonarQube Quality Model. {@code null} is 1011 * always returned. See https://jira.sonarsource.com/browse/MMF-184 1012 * @see #type() 1013 */ 1014 @CheckForNull 1015 @Deprecated 1016 public String debtSubCharacteristic() { 1017 return null; 1018 } 1019 1020 @CheckForNull 1021 public DebtRemediationFunction debtRemediationFunction() { 1022 return debtRemediationFunction; 1023 } 1024 1025 /** 1026 * @deprecated since 5.5, replaced by {@link #gapDescription()} 1027 */ 1028 @Deprecated 1029 @CheckForNull 1030 public String effortToFixDescription() { 1031 return gapDescription(); 1032 } 1033 1034 @CheckForNull 1035 public String gapDescription() { 1036 return gapDescription; 1037 } 1038 1039 @CheckForNull 1040 public Param param(String key) { 1041 return params.get(key); 1042 } 1043 1044 public List<Param> params() { 1045 return unmodifiableList(new ArrayList<>(params.values())); 1046 } 1047 1048 public Set<String> tags() { 1049 return tags; 1050 } 1051 1052 /** 1053 * @see RulesDefinition.NewRule#setInternalKey(String) 1054 */ 1055 @CheckForNull 1056 public String internalKey() { 1057 return internalKey; 1058 } 1059 1060 @Override 1061 public boolean equals(Object o) { 1062 if (this == o) { 1063 return true; 1064 } 1065 if (o == null || getClass() != o.getClass()) { 1066 return false; 1067 } 1068 Rule other = (Rule) o; 1069 return key.equals(other.key) && repoKey.equals(other.repoKey); 1070 } 1071 1072 @Override 1073 public int hashCode() { 1074 int result = repoKey.hashCode(); 1075 result = 31 * result + key.hashCode(); 1076 return result; 1077 } 1078 1079 @Override 1080 public String toString() { 1081 return format("[repository=%s, key=%s]", repoKey, key); 1082 } 1083 } 1084 1085 class NewParam { 1086 private final String key; 1087 private String name; 1088 private String description; 1089 private String defaultValue; 1090 private RuleParamType type = RuleParamType.STRING; 1091 1092 private NewParam(String key) { 1093 this.key = this.name = key; 1094 } 1095 1096 public String key() { 1097 return key; 1098 } 1099 1100 public NewParam setName(@Nullable String s) { 1101 // name must never be null. 1102 this.name = StringUtils.defaultIfBlank(s, key); 1103 return this; 1104 } 1105 1106 public NewParam setType(RuleParamType t) { 1107 this.type = t; 1108 return this; 1109 } 1110 1111 /** 1112 * Plain-text description. Can be null. Max length is 4000 characters. 1113 */ 1114 public NewParam setDescription(@Nullable String s) { 1115 this.description = StringUtils.defaultIfBlank(s, null); 1116 return this; 1117 } 1118 1119 /** 1120 * Empty default value will be converted to null. Max length is 4000 characters. 1121 */ 1122 public NewParam setDefaultValue(@Nullable String s) { 1123 this.defaultValue = defaultIfEmpty(s, null); 1124 return this; 1125 } 1126 } 1127 1128 @Immutable 1129 class Param { 1130 private final String key; 1131 private final String name; 1132 private final String description; 1133 private final String defaultValue; 1134 private final RuleParamType type; 1135 1136 private Param(NewParam newParam) { 1137 this.key = newParam.key; 1138 this.name = newParam.name; 1139 this.description = newParam.description; 1140 this.defaultValue = newParam.defaultValue; 1141 this.type = newParam.type; 1142 } 1143 1144 public String key() { 1145 return key; 1146 } 1147 1148 public String name() { 1149 return name; 1150 } 1151 1152 @Nullable 1153 public String description() { 1154 return description; 1155 } 1156 1157 @Nullable 1158 public String defaultValue() { 1159 return defaultValue; 1160 } 1161 1162 public RuleParamType type() { 1163 return type; 1164 } 1165 1166 @Override 1167 public boolean equals(Object o) { 1168 if (this == o) { 1169 return true; 1170 } 1171 if (o == null || getClass() != o.getClass()) { 1172 return false; 1173 } 1174 Param that = (Param) o; 1175 return key.equals(that.key); 1176 } 1177 1178 @Override 1179 public int hashCode() { 1180 return key.hashCode(); 1181 } 1182 } 1183 1184 /** 1185 * This method is executed when server is started. 1186 */ 1187 void define(Context context); 1188 1189}