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