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