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