You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

RulesDefinition.java 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2021 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.api.server.rule;
  21. import java.net.URL;
  22. import java.util.ArrayList;
  23. import java.util.Collection;
  24. import java.util.HashMap;
  25. import java.util.List;
  26. import java.util.Map;
  27. import java.util.Set;
  28. import javax.annotation.CheckForNull;
  29. import javax.annotation.Nullable;
  30. import javax.annotation.concurrent.Immutable;
  31. import org.sonar.api.ExtensionPoint;
  32. import org.sonar.api.ce.ComputeEngineSide;
  33. import org.sonar.api.rule.RuleKey;
  34. import org.sonar.api.rule.RuleScope;
  35. import org.sonar.api.rule.RuleStatus;
  36. import org.sonar.api.rules.RuleType;
  37. import org.sonar.api.server.ServerSide;
  38. import org.sonar.api.server.debt.DebtRemediationFunction;
  39. import org.sonar.api.server.rule.internal.DefaultNewRepository;
  40. import org.sonar.api.server.rule.internal.DefaultRepository;
  41. import org.sonarsource.api.sonarlint.SonarLintSide;
  42. import static java.util.Collections.unmodifiableList;
  43. import static org.sonar.api.utils.Preconditions.checkState;
  44. /**
  45. * Defines some coding rules of the same repository. For example the Java Findbugs plugin provides an implementation of
  46. * this extension point in order to define the rules that it supports.
  47. * <br>
  48. * This interface replaces the deprecated class org.sonar.api.rules.RuleRepository.
  49. * <br>
  50. * <h3>How to use</h3>
  51. * <pre>
  52. * public class MyJsRulesDefinition implements RulesDefinition {
  53. * {@literal @}Override
  54. * public void define(Context context) {
  55. * NewRepository repository = context.createRepository("my_js", "js").setName("My Javascript Analyzer");
  56. * // define a rule programmatically. Note that rules
  57. * // could be loaded from files (JSON, XML, ...)
  58. * NewRule x1Rule = repository.createRule("x1")
  59. * .setName("No empty line")
  60. * .setHtmlDescription("Generate an issue on empty lines")
  61. * // optional tags
  62. * .setTags("style", "stupid")
  63. * // optional status. Default value is READY.
  64. * .setStatus(RuleStatus.BETA)
  65. * // default severity when the rule is activated on a Quality profile. Default value is MAJOR.
  66. * .setSeverity(Severity.MINOR);
  67. * // optional type for SonarQube Quality Model. Default is RulesDefinition.Type.CODE_SMELL.
  68. * .setType(RulesDefinition.Type.BUG)
  69. * x1Rule
  70. * .setDebtRemediationFunction(x1Rule.debtRemediationFunctions().linearWithOffset("1h", "30min"));
  71. * x1Rule.createParam("acceptWhitespace")
  72. * .setDefaultValue("false")
  73. * .setType(RuleParamType.BOOLEAN)
  74. * .setDescription("Accept whitespaces on the line");
  75. * // don't forget to call done() to finalize the definition
  76. * repository.done();
  77. * }
  78. * }
  79. * </pre>
  80. * <br>
  81. * If rules are declared in a XML file with the standard SonarQube format (see
  82. * {@link org.sonar.api.server.rule.RulesDefinitionXmlLoader}), then it can be loaded by using :
  83. * <br>
  84. * <pre>
  85. * public class MyJsRulesDefinition implements RulesDefinition {
  86. * private final RulesDefinitionXmlLoader xmlLoader;
  87. * public MyJsRulesDefinition(RulesDefinitionXmlLoader xmlLoader) {
  88. * this.xmlLoader = xmlLoader;
  89. * }
  90. * {@literal @}Override
  91. * public void define(Context context) {
  92. * NewRepository repository = context.createRepository("my_js", "js").setName("My Javascript Analyzer");
  93. * // see javadoc of RulesDefinitionXmlLoader for the format
  94. * xmlLoader.load(repository, getClass().getResourceAsStream("/path/to/rules.xml"));
  95. * repository.done();
  96. * }
  97. * }
  98. * </pre>
  99. * <br>
  100. * In the above example, XML file must contain name and description of each rule. If it's not the case, then the
  101. * (deprecated) English bundles can be used :
  102. * <br>
  103. * <pre>
  104. * public class MyJsRulesDefinition implements RulesDefinition {
  105. * private final RulesDefinitionXmlLoader xmlLoader;
  106. * private final RulesDefinitionI18nLoader i18nLoader;
  107. * public MyJsRulesDefinition(RulesDefinitionXmlLoader xmlLoader, RulesDefinitionI18nLoader i18nLoader) {
  108. * this.xmlLoader = xmlLoader;
  109. * this.i18nLoader = i18nLoader;
  110. * }
  111. * {@literal @}Override
  112. * public void define(Context context) {
  113. * NewRepository repository = context.createRepository("my_js", "js").setName("My Javascript Analyzer");
  114. * xmlLoader.load(repository, getClass().getResourceAsStream("/path/to/rules.xml"), "UTF-8");
  115. * i18nLoader.load(repository);
  116. * repository.done();
  117. * }
  118. * }
  119. * </pre>
  120. *
  121. * @since 4.3
  122. */
  123. @ServerSide
  124. @ComputeEngineSide
  125. @SonarLintSide
  126. @ExtensionPoint
  127. public interface RulesDefinition {
  128. /**
  129. * This implementation will be removed as soon as analyzers stop instantiating it.
  130. * Use RulesDefinitionContext in sonar-plugin-api-impl.
  131. */
  132. class Context extends AbstractContext {
  133. private final Map<String, Repository> repositoriesByKey = new HashMap<>();
  134. private String currentPluginKey = null;
  135. @Override
  136. public RulesDefinition.NewRepository createRepository(String key, String language) {
  137. return new DefaultNewRepository(this, key, language, false);
  138. }
  139. @Override
  140. public RulesDefinition.NewRepository createExternalRepository(String engineId, String language) {
  141. return new DefaultNewRepository(this, RuleKey.EXTERNAL_RULE_REPO_PREFIX + engineId, language, true);
  142. }
  143. @Override
  144. @CheckForNull
  145. public RulesDefinition.Repository repository(String key) {
  146. return repositoriesByKey.get(key);
  147. }
  148. @Override
  149. public List<RulesDefinition.Repository> repositories() {
  150. return unmodifiableList(new ArrayList<>(repositoriesByKey.values()));
  151. }
  152. public void registerRepository(DefaultNewRepository newRepository) {
  153. RulesDefinition.Repository existing = repositoriesByKey.get(newRepository.key());
  154. if (existing != null) {
  155. String existingLanguage = existing.language();
  156. checkState(existingLanguage.equals(newRepository.language()),
  157. "The rule repository '%s' must not be defined for two different languages: %s and %s",
  158. newRepository.key(), existingLanguage, newRepository.language());
  159. }
  160. repositoriesByKey.put(newRepository.key(), new DefaultRepository(newRepository, existing));
  161. }
  162. public String currentPluginKey() {
  163. return currentPluginKey;
  164. }
  165. @Override
  166. public void setCurrentPluginKey(@Nullable String pluginKey) {
  167. this.currentPluginKey = pluginKey;
  168. }
  169. }
  170. /**
  171. * Instantiated by core but not by plugins, except for their tests.
  172. */
  173. abstract class AbstractContext {
  174. /*
  175. * New builder for {@link org.sonar.api.server.rule.RulesDefinition.Repository}.
  176. * <br>
  177. * A plugin can add rules to a repository that is defined then executed by another plugin. For instance
  178. * the FbContrib plugin contributes to the Findbugs plugin rules. In this case no need
  179. * to execute {@link org.sonar.api.server.rule.RulesDefinition.NewRepository#setName(String)}
  180. */
  181. public abstract NewRepository createRepository(String key, String language);
  182. /**
  183. * Creates a repository of rules from external rule engines.
  184. * The repository key will be "external_[engineId]".
  185. *
  186. * @since 7.2
  187. */
  188. public abstract NewRepository createExternalRepository(String engineId, String language);
  189. @CheckForNull
  190. public abstract Repository repository(String key);
  191. public abstract List<Repository> repositories();
  192. public abstract void setCurrentPluginKey(@Nullable String pluginKey);
  193. }
  194. interface NewExtendedRepository {
  195. /**
  196. * Create a rule with specified key. Max length of key is 200 characters. Key must be unique
  197. * among the repository
  198. *
  199. * @throws IllegalArgumentException is key is not unique. Note a warning was logged up to version 5.4 (included)
  200. */
  201. NewRule createRule(String ruleKey);
  202. @CheckForNull
  203. NewRule rule(String ruleKey);
  204. Collection<NewRule> rules();
  205. String key();
  206. void done();
  207. }
  208. interface NewRepository extends NewExtendedRepository {
  209. NewRepository setName(String s);
  210. /**
  211. * @since 7.2
  212. */
  213. boolean isExternal();
  214. }
  215. enum OwaspTop10 {
  216. A1, A2, A3, A4, A5, A6, A7, A8, A9, A10
  217. }
  218. interface ExtendedRepository {
  219. String key();
  220. String language();
  221. @CheckForNull
  222. Rule rule(String ruleKey);
  223. List<Rule> rules();
  224. }
  225. interface Repository extends ExtendedRepository {
  226. String name();
  227. /**
  228. * @since 7.2
  229. */
  230. boolean isExternal();
  231. }
  232. /**
  233. * Factory of {@link org.sonar.api.server.debt.DebtRemediationFunction}.
  234. */
  235. interface DebtRemediationFunctions {
  236. /**
  237. * Shortcut for {@code create(Type.LINEAR, gap multiplier, null)}.
  238. *
  239. * @param gapMultiplier the duration to fix one issue. See {@link DebtRemediationFunction} for details about format.
  240. * @see org.sonar.api.server.debt.DebtRemediationFunction.Type#LINEAR
  241. */
  242. DebtRemediationFunction linear(String gapMultiplier);
  243. /**
  244. * Shortcut for {@code create(Type.LINEAR_OFFSET, gap multiplier, base effort)}.
  245. *
  246. * @param gapMultiplier duration to fix one point of complexity. See {@link DebtRemediationFunction} for details and format.
  247. * @param baseEffort duration to make basic analysis. See {@link DebtRemediationFunction} for details and format.
  248. * @see org.sonar.api.server.debt.DebtRemediationFunction.Type#LINEAR_OFFSET
  249. */
  250. DebtRemediationFunction linearWithOffset(String gapMultiplier, String baseEffort);
  251. /**
  252. * Shortcut for {@code create(Type.CONSTANT_ISSUE, null, base effort)}.
  253. *
  254. * @param baseEffort cost per issue. See {@link DebtRemediationFunction} for details and format.
  255. * @see org.sonar.api.server.debt.DebtRemediationFunction.Type#CONSTANT_ISSUE
  256. */
  257. DebtRemediationFunction constantPerIssue(String baseEffort);
  258. /**
  259. * Flexible way to create a {@link DebtRemediationFunction}. An unchecked exception is thrown if
  260. * coefficient and/or offset are not valid according to the given @{code type}.
  261. *
  262. * @since 5.3
  263. */
  264. DebtRemediationFunction create(DebtRemediationFunction.Type type, @Nullable String gapMultiplier, @Nullable String baseEffort);
  265. }
  266. abstract class NewRule {
  267. public abstract String key();
  268. /**
  269. * @since 7.1
  270. */
  271. @CheckForNull
  272. public abstract RuleScope scope();
  273. /**
  274. * @since 7.1
  275. */
  276. public abstract NewRule setScope(RuleScope scope);
  277. /**
  278. * Required rule name
  279. */
  280. public abstract NewRule setName(String s);
  281. public abstract NewRule setTemplate(boolean template);
  282. /**
  283. * Should this rule be enabled by default. For example in SonarLint standalone.
  284. *
  285. * @since 6.0
  286. */
  287. public abstract NewRule setActivatedByDefault(boolean activatedByDefault);
  288. public abstract NewRule setSeverity(String s);
  289. /**
  290. * The type as defined by the SonarQube Quality Model.
  291. * <br>
  292. * When a plugin does not define rule type, then it is deduced from
  293. * tags:
  294. * <ul>
  295. * <li>if the rule has the "bug" tag then type is {@link RuleType#BUG}</li>
  296. * <li>if the rule has the "security" tag then type is {@link RuleType#VULNERABILITY}</li>
  297. * <li>if the rule has both tags "bug" and "security", then type is {@link RuleType#BUG}</li>
  298. * <li>default type is {@link RuleType#CODE_SMELL}</li>
  299. * </ul>
  300. * Finally the "bug" and "security" tags are considered as reserved. They
  301. * are silently removed from the final state of definition.
  302. *
  303. * @since 5.5
  304. */
  305. public abstract NewRule setType(RuleType t);
  306. /**
  307. * The optional description, in HTML format, has no max length. It's exclusive with markdown description
  308. * (see {@link #setMarkdownDescription(String)})
  309. */
  310. public abstract NewRule setHtmlDescription(@Nullable String s);
  311. /**
  312. * Load description from a file available in classpath. Example : <code>setHtmlDescription(getClass().getResource("/myrepo/Rule1234.html")</code>
  313. */
  314. public abstract NewRule setHtmlDescription(@Nullable URL classpathUrl);
  315. /**
  316. * The optional description, in a restricted Markdown format, has no max length. It's exclusive with HTML description
  317. * (see {@link #setHtmlDescription(String)})
  318. */
  319. public abstract NewRule setMarkdownDescription(@Nullable String s);
  320. /**
  321. * Load description from a file available in classpath. Example : {@code setMarkdownDescription(getClass().getResource("/myrepo/Rule1234.md")}
  322. */
  323. public abstract NewRule setMarkdownDescription(@Nullable URL classpathUrl);
  324. /**
  325. * Default value is {@link org.sonar.api.rule.RuleStatus#READY}. The value
  326. * {@link org.sonar.api.rule.RuleStatus#REMOVED} is not accepted and raises an
  327. * {@link java.lang.IllegalArgumentException}.
  328. */
  329. public abstract NewRule setStatus(RuleStatus status);
  330. /**
  331. * Factory of {@link org.sonar.api.server.debt.DebtRemediationFunction}
  332. */
  333. public abstract DebtRemediationFunctions debtRemediationFunctions();
  334. /**
  335. * @see #debtRemediationFunctions()
  336. */
  337. public abstract NewRule setDebtRemediationFunction(@Nullable DebtRemediationFunction fn);
  338. /**
  339. * For rules that use LINEAR or LINEAR_OFFSET remediation functions, the meaning
  340. * of the function parameter (= "gap") must be set. This description
  341. * explains what 1 point of "gap" represents for the rule.
  342. * <br>
  343. * Example: for the "Insufficient condition coverage", this description for the
  344. * remediation function gap multiplier/base effort would be something like
  345. * "Effort to test one uncovered condition".
  346. */
  347. public abstract NewRule setGapDescription(@Nullable String s);
  348. /**
  349. * Create a parameter with given unique key. Max length of key is 128 characters.
  350. */
  351. public abstract NewParam createParam(String paramKey);
  352. @CheckForNull
  353. public abstract NewParam param(String paramKey);
  354. public abstract Collection<NewParam> params();
  355. /**
  356. * @see RuleTagFormat
  357. */
  358. public abstract NewRule addTags(String... list);
  359. /**
  360. * @see RuleTagFormat
  361. */
  362. public abstract NewRule setTags(String... list);
  363. /**
  364. * @since 7.3
  365. */
  366. public abstract NewRule addOwaspTop10(OwaspTop10... standards);
  367. /**
  368. * @since 7.3
  369. */
  370. public abstract NewRule addCwe(int... nums);
  371. /**
  372. * Optional key that can be used by the rule engine. Not displayed
  373. * in webapp. For example the Java Checkstyle plugin feeds this field
  374. * with the internal path ("Checker/TreeWalker/AnnotationUseStyle").
  375. */
  376. public abstract NewRule setInternalKey(@Nullable String s);
  377. /**
  378. * Register a repository and key under which this rule used to be known
  379. * (see {@link Rule#deprecatedRuleKeys} for details).
  380. * <p>
  381. * Deprecated keys should be added with this method in order, oldest first, for documentation purpose.
  382. *
  383. * @throws IllegalArgumentException if {@code repository} or {@code key} is {@code null} or empty.
  384. * @see Rule#deprecatedRuleKeys
  385. * @since 7.1
  386. */
  387. public abstract NewRule addDeprecatedRuleKey(String repository, String key);
  388. }
  389. @Immutable
  390. abstract class Rule {
  391. public abstract Repository repository();
  392. /**
  393. * @since 6.6 the plugin the rule was declared in
  394. */
  395. @CheckForNull
  396. public abstract String pluginKey();
  397. public abstract String key();
  398. public abstract String name();
  399. /**
  400. * @since 7.1
  401. */
  402. public abstract RuleScope scope();
  403. /**
  404. * @see NewRule#setType(RuleType)
  405. * @since 5.5
  406. */
  407. public abstract RuleType type();
  408. public abstract String severity();
  409. @CheckForNull
  410. public abstract String htmlDescription();
  411. @CheckForNull
  412. public abstract String markdownDescription();
  413. public abstract boolean template();
  414. /**
  415. * Should this rule be enabled by default. For example in SonarLint standalone.
  416. *
  417. * @since 6.0
  418. */
  419. public abstract boolean activatedByDefault();
  420. public abstract RuleStatus status();
  421. @CheckForNull
  422. public abstract DebtRemediationFunction debtRemediationFunction();
  423. @CheckForNull
  424. public abstract String gapDescription();
  425. @CheckForNull
  426. public abstract Param param(String key);
  427. public abstract List<Param> params();
  428. public abstract Set<String> tags();
  429. public abstract Set<String> securityStandards();
  430. /**
  431. * Deprecated rules keys for this rule.
  432. * <p>
  433. * If you want to rename the key of a rule or change its repository or both, register the rule's previous repository
  434. * and key (see {@link NewRule#addDeprecatedRuleKey(String, String) addDeprecatedRuleKey}). This will allow
  435. * SonarQube to support "issue re-keying" for this rule.
  436. * <p>
  437. * If the repository and/or key of an existing rule is changed without declaring deprecated keys, existing issues
  438. * for this rule, created under the rule's previous repository and/or key, will be closed and new ones will be
  439. * created under the issue's new repository and/or key.
  440. * <p>
  441. * Several deprecated keys can be provided to allow SonarQube to support several key (and/or repository) changes
  442. * across multiple versions of a plugin.
  443. * <br>
  444. * Consider the following use case scenario:
  445. * <ul>
  446. * <li>Rule {@code Foo:A} is defined in version 1 of the plugin
  447. * <pre>
  448. * NewRepository newRepository = context.createRepository("Foo", "my_language");
  449. * NewRule r = newRepository.createRule("A");
  450. * </pre>
  451. * </li>
  452. * <li>Rule's key is renamed to B in version 2 of the plugin
  453. * <pre>
  454. * NewRepository newRepository = context.createRepository("Foo", "my_language");
  455. * NewRule r = newRepository.createRule("B")
  456. * .addDeprecatedRuleKey("Foo", "A");
  457. * </pre>
  458. * </li>
  459. * <li>All rules, including {@code Foo:B}, are moved to a new repository Bar in version 3 of the plugin
  460. * <pre>
  461. * NewRepository newRepository = context.createRepository("Bar", "my_language");
  462. * NewRule r = newRepository.createRule("B")
  463. * .addDeprecatedRuleKey("Foo", "A")
  464. * .addDeprecatedRuleKey("Bar", "B");
  465. * </pre>
  466. * </li>
  467. * </ul>
  468. * With all deprecated keys defined in version 3 of the plugin, SonarQube will be able to support "issue re-keying"
  469. * for this rule in all cases:
  470. * <ul>
  471. * <li>plugin upgrade from v1 to v2,</li>
  472. * <li>plugin upgrade from v2 to v3</li>
  473. * <li>AND plugin upgrade from v1 to v3</li>
  474. * </ul>
  475. * <p>
  476. * Finally, repository/key pairs must be unique across all rules and their deprecated keys.
  477. * <br>
  478. * This implies that no rule can use the same repository and key as the deprecated key of another rule. This
  479. * uniqueness applies across plugins.
  480. * <p>
  481. * Note that, even though this method returns a {@code Set}, its elements are ordered according to calls to
  482. * {@link NewRule#addDeprecatedRuleKey(String, String) addDeprecatedRuleKey}. This allows to describe the history
  483. * of a rule's repositories and keys over time. Oldest repository and key must be specified first.
  484. *
  485. * @see NewRule#addDeprecatedRuleKey(String, String)
  486. * @since 7.1
  487. */
  488. public abstract Set<RuleKey> deprecatedRuleKeys();
  489. /**
  490. * @see RulesDefinition.NewRule#setInternalKey(String)
  491. */
  492. @CheckForNull
  493. public abstract String internalKey();
  494. }
  495. abstract class NewParam {
  496. public abstract String key();
  497. public abstract NewParam setName(@Nullable String s);
  498. public abstract NewParam setType(RuleParamType t);
  499. /**
  500. * Plain-text description. Can be null. Max length is 4000 characters.
  501. */
  502. public abstract NewParam setDescription(@Nullable String s);
  503. /**
  504. * Empty default value will be converted to null. Max length is 4000 characters.
  505. */
  506. public abstract NewParam setDefaultValue(@Nullable String s);
  507. }
  508. @Immutable
  509. interface Param {
  510. String key();
  511. String name();
  512. @Nullable
  513. String description();
  514. @Nullable
  515. String defaultValue();
  516. RuleParamType type();
  517. }
  518. /**
  519. * This method is executed when server is started.
  520. */
  521. void define(Context context);
  522. }