diff options
23 files changed, 1948 insertions, 37 deletions
| diff --git a/.DS_Store b/.DS_StoreBinary files differ deleted file mode 100644 index ec71b6d..0000000 --- a/.DS_Store +++ /dev/null @@ -1,5 +1,5 @@  # ---- Mac OS X -.DS_Store? +.DS_Store  Icon?  # Thumbnails  ._* diff --git a/its/plugin/projects/external-report-project/report.json b/its/plugin/projects/external-report-project/report.json new file mode 100644 index 0000000..2681dac --- /dev/null +++ b/its/plugin/projects/external-report-project/report.json @@ -0,0 +1,55 @@ +[ +  { +    "source": "src/file1.css", +    "deprecations": [], +    "invalidOptionWarnings": [], +    "parseErrors": [], +    "errored": true, +    "warnings": [ +      { +        "line": 111, +        "column": 1, +        "rule": "no-missing-end-of-source-newline", +        "severity": "error", +        "text": "Unexpected missing end-of-source newline (no-missing-end-of-source-newline)" +      }, +      { +        "line": 81, +        "column": 1, +        "rule": "rule-empty-line-before", +        "severity": "error", +        "text": "Expected empty line before rule (rule-empty-line-before)" +      }, +      { +        "line": 55, +        "column": 20, +        "rule": "selector-pseudo-element-colon-notation", +        "severity": "error", +        "text": "Expected double colon pseudo-element notation (selector-pseudo-element-colon-notation)" +      } +    ] +  }, +  { +    "source": "src/file2.css", +    "deprecations": [], +    "invalidOptionWarnings": [], +    "parseErrors": [], +    "errored": true, +    "warnings": [ +      { +        "line": 58, +        "column": 28, +        "rule": "block-no-empty", +        "severity": "error", +        "text": "Unexpected empty block (block-no-empty)" +      }, +      { +        "line": 114, +        "column": 1, +        "rule": "no-missing-end-of-source-newline", +        "severity": "error", +        "text": "Unexpected missing end-of-source newline (no-missing-end-of-source-newline)" +      } +    ] +  } +] diff --git a/its/plugin/projects/external-report-project/src/file1.css b/its/plugin/projects/external-report-project/src/file1.css new file mode 100644 index 0000000..329fa57 --- /dev/null +++ b/its/plugin/projects/external-report-project/src/file1.css @@ -0,0 +1,111 @@ +.navbar-search { +  position: relative; +  padding: calc((var(--globalNavHeight) - var(--globalNavContentHeight)) / 2) 0; +} + +.navbar-search .search-box, +.navbar-search .search-box-input { +  width: 26vw; +  max-width: 310px; +  min-width: 260px; +  height: var(--globalNavContentHeight); +} + +.navbar-search .search-box-input { +  border-color: #fff; +} + +.navbar-search .search-box-note { +  line-height: calc(var(--globalNavContentHeight) - 2px); +} + +.navbar-search .search-box-magnifier, +.navbar-search .search-box-clear { +  top: calc((var(--globalNavContentHeight) - 16px) / 2); +} + +.navbar-search-input { +  vertical-align: middle; +  width: 310px; +  margin-top: 3px; +  margin-bottom: 3px; +  padding-left: 26px !important; +} + +.navbar-search-input-hint { +  position: absolute; +  top: 1px; +  right: 27px; +  line-height: var(--controlHeight); +  font-size: var(--smallFontSize); +  color: var(--secondFontColor); +} + +.navbar-search-icon { +  position: relative; +  z-index: var(--aboveNormalZIndex); +  vertical-align: middle; +  width: 16px; +  margin-left: 4px; +  margin-right: -20px; +  background-color: #fff; +  color: var(--secondFontColor); +} + +.navbar-search-icon:before { +  font-size: var(--mediumFontSize); +} + +.navbar-search-item-link { +  display: flex !important; +} + +.navbar-search-item-match { +  flex-grow: 5; +  overflow: hidden; +  text-overflow: ellipsis; +} + +.navbar-search-item-right { +  flex-grow: 1; +  padding-left: 10px; +  text-align: right; +} + +.navbar-search-item-icons { +  position: relative; +  flex-shrink: 0; +  width: 16px; +  height: 16px; +} +.navbar-search-item-icons > * { +  position: absolute; +  z-index: 5; +  top: 0; +  left: 0; +} + +.navbar-search-item-icons > .icon-outline, +.navbar-search-item-icons > .icon-clock { +  z-index: 6; +  top: -4px; +  left: -5px; +} + +.navbar-search-no-results { +  margin-top: 4px; +  padding: 5px 10px; +} + +.global-navbar-search-dropdown { +  top: 100% !important; +  max-height: 80vh; +  width: 440px; +  padding: 0 !important; +  overflow-y: auto; +  overflow-x: hidden; +} + +.global-navbar-search-dropdown .dropdown-bottom-hint { +  margin-bottom: 0; +}
\ No newline at end of file diff --git a/its/plugin/projects/external-report-project/src/file2.css b/its/plugin/projects/external-report-project/src/file2.css new file mode 100644 index 0000000..ff1d2e5 --- /dev/null +++ b/its/plugin/projects/external-report-project/src/file2.css @@ -0,0 +1,114 @@ +.date-input-control { +  position: relative; +  display: inline-block; +  cursor: pointer; +} + +.date-input-control-input { +  width: 130px; +  padding-left: var(--controlHeight) !important; +  cursor: pointer; +} + +.date-input-control-input.is-filled { +  padding-right: 16px !important; +} + +.date-input-control-icon { +  position: absolute; +  top: 4px; +  left: 4px; +} + +.date-input-control-icon path { +  fill: var(--gray80); +  transition: fill 0.3s ease; +} + +.date-input-control-input:focus + .date-input-control-icon path { +  fill: var(--blue); +} + +.date-input-control-reset { +  position: absolute; +  top: 4px; +  right: 4px; +  border: none; +} + +.date-input-calendar { +  position: absolute; +  z-index: var(--dropdownMenuZIndex); +  top: 100%; +  left: 0; +  border: 1px solid var(--barBorderColor); +  background-color: #fff; +  box-shadow: var(--defaultShadow); +} + +.date-input-calendar-nav { +  display: flex; +  justify-content: space-between; +  align-items: center; +  padding-top: var(--gridSize); +  padding-left: var(--gridSize); +  padding-right: var(--gridSize); +} + +.date-input-calender-month { +} + +.date-input-calender-month-select { +  width: 70px; +} + +.button.boolean-toggle { +  display: inline-block; +  vertical-align: middle; +  width: 48px; +  height: var(--controlHeight); +  padding: 1px; +  border: 1px solid var(--gray80); +  border-radius: var(--controlHeight); +  box-sizing: border-box; +  background-color: #fff; +  cursor: pointer; +  transition: all 0.3s ease; +} + +.button.boolean-toggle:hover { +  background-color: #fff; +} + +.button.boolean-toggle:focus { +  border-color: var(--blue); +  background-color: #f6f6f6; +} + +.boolean-toggle-handle { +  width: 20px; +  height: 20px; +  border: 1px solid var(--gray80); +  border-radius: 22px; +  box-sizing: border-box; +  background-color: #f6f6f6; +  transition: transform 0.3s cubic-bezier(0.87, -0.41, 0.19, 1.44), border 0.3s ease; +} + +.button.boolean-toggle-on { +  border-color: var(--darkBlue); +  background-color: var(--darkBlue); +} + +.button.boolean-toggle-on:hover { +  background-color: var(--darkBlue); +} + +.button.boolean-toggle-on:focus { +  background-color: var(--darkBlue); +} + +.button.boolean-toggle-on .boolean-toggle-handle { +  border-color: #f6f6f6; +  transform: translateX(var(--controlHeight)); +}
\ No newline at end of file diff --git a/its/plugin/projects/external-report-project/stylelintconfig.json b/its/plugin/projects/external-report-project/stylelintconfig.json new file mode 100644 index 0000000..4a0997a --- /dev/null +++ b/its/plugin/projects/external-report-project/stylelintconfig.json @@ -0,0 +1,162 @@ +{ +  "rules": { +    "at-rule-no-unknown": true, +    "block-no-empty": true, +    "color-no-invalid-hex": true, +    "comment-no-empty": true, +    "declaration-block-no-duplicate-properties": [ +      true, +      { +        "ignore": [ +          "consecutive-duplicates-with-different-values" +        ] +      } +    ], +    "declaration-block-no-shorthand-property-overrides": true, +    "font-family-no-duplicate-names": true, +    "font-family-no-missing-generic-family-keyword": true, +    "function-calc-no-unspaced-operator": true, +    "function-linear-gradient-no-nonstandard-direction": true, +    "keyframe-declaration-no-important": true, +    "media-feature-name-no-unknown": true, +    "no-descending-specificity": true, +    "no-duplicate-at-import-rules": true, +    "no-duplicate-selectors": true, +    "no-empty-source": true, +    "no-extra-semicolons": true, +    "no-invalid-double-slash-comments": true, +    "property-no-unknown": true, +    "selector-pseudo-class-no-unknown": true, +    "selector-pseudo-element-no-unknown": true, +    "selector-type-no-unknown": true, +    "string-no-newline": true, +    "unit-no-unknown": true, +    "at-rule-empty-line-before": [ +      "always", +      { +        "except": [ +          "blockless-after-same-name-blockless", +          "first-nested" +        ], +        "ignore": [ +          "after-comment" +        ] +      } +    ], +    "at-rule-name-case": "lower", +    "at-rule-name-space-after": "always-single-line", +    "at-rule-semicolon-newline-after": "always", +    "block-closing-brace-empty-line-before": "never", +    "block-closing-brace-newline-after": "always", +    "block-closing-brace-newline-before": "always-multi-line", +    "block-closing-brace-space-before": "always-single-line", +    "block-opening-brace-newline-after": "always-multi-line", +    "block-opening-brace-space-after": "always-single-line", +    "block-opening-brace-space-before": "always", +    "color-hex-case": "lower", +    "color-hex-length": "short", +    "comment-empty-line-before": [ +      "always", +      { +        "except": [ +          "first-nested" +        ], +        "ignore": [ +          "stylelint-commands" +        ] +      } +    ], +    "comment-whitespace-inside": "always", +    "custom-property-empty-line-before": [ +      "always", +      { +        "except": [ +          "after-custom-property", +          "first-nested" +        ], +        "ignore": [ +          "after-comment", +          "inside-single-line-block" +        ] +      } +    ], +    "declaration-bang-space-after": "never", +    "declaration-bang-space-before": "always", +    "declaration-block-semicolon-newline-after": "always-multi-line", +    "declaration-block-semicolon-space-after": "always-single-line", +    "declaration-block-semicolon-space-before": "never", +    "declaration-block-single-line-max-declarations": 1, +    "declaration-block-trailing-semicolon": "always", +    "declaration-colon-newline-after": "always-multi-line", +    "declaration-colon-space-after": "always-single-line", +    "declaration-colon-space-before": "never", +    "declaration-empty-line-before": [ +      "always", +      { +        "except": [ +          "after-declaration", +          "first-nested" +        ], +        "ignore": [ +          "after-comment", +          "inside-single-line-block" +        ] +      } +    ], +    "function-comma-newline-after": "always-multi-line", +    "function-comma-space-after": "always-single-line", +    "function-comma-space-before": "never", +    "function-max-empty-lines": 0, +    "function-name-case": "lower", +    "function-parentheses-newline-inside": "always-multi-line", +    "function-parentheses-space-inside": "never-single-line", +    "function-whitespace-after": "always", +    "indentation": 2, +    "length-zero-no-unit": true, +    "max-empty-lines": 1, +    "media-feature-colon-space-after": "always", +    "media-feature-colon-space-before": "never", +    "media-feature-name-case": "lower", +    "media-feature-parentheses-space-inside": "never", +    "media-feature-range-operator-space-after": "always", +    "media-feature-range-operator-space-before": "always", +    "media-query-list-comma-newline-after": "always-multi-line", +    "media-query-list-comma-space-after": "always-single-line", +    "media-query-list-comma-space-before": "never", +    "no-eol-whitespace": true, +    "no-missing-end-of-source-newline": true, +    "number-leading-zero": "always", +    "number-no-trailing-zeros": true, +    "property-case": "lower", +    "rule-empty-line-before": [ +      "always-multi-line", +      { +        "except": [ +          "first-nested" +        ], +        "ignore": [ +          "after-comment" +        ] +      } +    ], +    "selector-attribute-brackets-space-inside": "never", +    "selector-attribute-operator-space-after": "never", +    "selector-attribute-operator-space-before": "never", +    "selector-combinator-space-after": "always", +    "selector-combinator-space-before": "always", +    "selector-descendant-combinator-no-non-space": true, +    "selector-list-comma-newline-after": "always", +    "selector-list-comma-space-before": "never", +    "selector-max-empty-lines": 0, +    "selector-pseudo-class-case": "lower", +    "selector-pseudo-class-parentheses-space-inside": "never", +    "selector-pseudo-element-case": "lower", +    "selector-pseudo-element-colon-notation": "double", +    "selector-type-case": "lower", +    "unit-case": "lower", +    "value-list-comma-newline-after": "always-multi-line", +    "value-list-comma-space-after": "always-single-line", +    "value-list-comma-space-before": "never", +    "value-list-max-empty-lines": 0 +  } +} diff --git a/its/plugin/src/test/java/org/sonar/css/its/StylelintReportTest.java b/its/plugin/src/test/java/org/sonar/css/its/StylelintReportTest.java new file mode 100644 index 0000000..b10f7a4 --- /dev/null +++ b/its/plugin/src/test/java/org/sonar/css/its/StylelintReportTest.java @@ -0,0 +1,62 @@ +/* + * SonarCSS + * Copyright (C) 2018-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. + */ +package org.sonar.css.its; + +import com.sonar.orchestrator.Orchestrator; +import java.util.Collections; +import java.util.List; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.ws.Issues.Issue; +import org.sonarqube.ws.client.issues.SearchRequest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.css.its.Tests.newWsClient; + +public class StylelintReportTest { + +  private static String PROJECT_KEY = "external-report-project"; + +  @ClassRule +  public static Orchestrator orchestrator = Tests.ORCHESTRATOR; + +  @BeforeClass +  public static void prepare() { +    orchestrator.executeBuild(Tests.createScanner(PROJECT_KEY).setProperty("sonar.css.stylelint.reportPaths", "report.json")); +  } + +  @Test +  public void test() { +    if (orchestrator.getServer().version().isGreaterThanOrEquals(7, 2)) { +      SearchRequest request = new SearchRequest(); +      request.setComponentKeys(Collections.singletonList(PROJECT_KEY)); +      List<Issue> issuesList = newWsClient().issues().search(request).getIssuesList(); + +      assertThat(issuesList).extracting("line").containsExactlyInAnyOrder(111, 81, 55, 58, 114); +      assertThat(issuesList).extracting("rule").containsExactlyInAnyOrder( +        "external_stylelint:no-missing-end-of-source-newline", +        "external_stylelint:no-missing-end-of-source-newline", +        "external_stylelint:rule-empty-line-before", +        "external_stylelint:selector-pseudo-element-colon-notation", +        "external_stylelint:block-no-empty"); +    } +  } +} diff --git a/its/plugin/src/test/java/org/sonar/css/its/Tests.java b/its/plugin/src/test/java/org/sonar/css/its/Tests.java index 31b2f78..d94a633 100644 --- a/its/plugin/src/test/java/org/sonar/css/its/Tests.java +++ b/its/plugin/src/test/java/org/sonar/css/its/Tests.java @@ -29,6 +29,7 @@ import java.util.List;  import org.junit.ClassRule;  import org.junit.runner.RunWith;  import org.junit.runners.Suite; +import org.sonar.css.plugin.StylelintReport;  import org.sonarqube.ws.Measures.ComponentWsResponse;  import org.sonarqube.ws.Measures.Measure;  import org.sonarqube.ws.client.HttpConnector; @@ -39,7 +40,8 @@ import org.sonarqube.ws.client.measures.ComponentRequest;  @RunWith(Suite.class)  @Suite.SuiteClasses({    MetricsTest.class, -  IssuesTest.class +  IssuesTest.class, +  StylelintReportTest.class  })  public class Tests { @@ -76,7 +76,7 @@          <junit.version>4.12</junit.version>          <sonar.version>7.2</sonar.version>          <sonar.min.version>6.7</sonar.min.version> -        <sonar-orchestrator.version>3.17.0.1491</sonar-orchestrator.version> +        <sonar-orchestrator.version>3.19.0.1641</sonar-orchestrator.version>          <sonarlint.version>3.1.0.1376</sonarlint.version>          <jsr305.version>3.0.2</jsr305.version>          <mockito.version>2.18.3</mockito.version> diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssPlugin.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssPlugin.java index b4f78f7..c35836f 100644 --- a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssPlugin.java +++ b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssPlugin.java @@ -22,6 +22,7 @@ package org.sonar.css.plugin;  import org.sonar.api.Plugin;  import org.sonar.api.config.PropertyDefinition;  import org.sonar.api.resources.Qualifiers; +import org.sonar.api.utils.Version;  import org.sonar.css.plugin.bundle.CssBundleHandler;  public class CssPlugin implements Plugin { @@ -29,19 +30,26 @@ public class CssPlugin implements Plugin {    static final String FILE_SUFFIXES_KEY = "sonar.css.file.suffixes";    public static final String FILE_SUFFIXES_DEFVALUE = ".css,.less,.scss"; +  public static final String STYLELINT_REPORT_PATHS = "sonar.css.stylelint.reportPaths"; +  public static final String STYLELINT_REPORT_PATHS_DEFAULT_VALUE = ""; +    private static final String CSS_CATEGORY = "CSS"; +  private static final String LINTER_SUBCATEGORY = "Popular Rule Engines";    private static final String GENERAL_SUBCATEGORY = "General";    @Override    public void define(Context context) { +    boolean externalIssuesSupported = context.getSonarQubeVersion().isGreaterThanOrEqual(Version.create(7, 2)); +      context.addExtensions(        MetricSensor.class,        CssLanguage.class,        SonarWayProfile.class, -      CssRulesDefinition.class, +      new CssRulesDefinition(externalIssuesSupported),        CssBundleHandler.class,        CssRuleSensor.class,        StylelintCommandProvider.class, +      StylelintReportSensor.class,        PropertyDefinition.builder(FILE_SUFFIXES_KEY)          .defaultValue(FILE_SUFFIXES_DEFVALUE) @@ -53,5 +61,19 @@ public class CssPlugin implements Plugin {          .multiValues(true)          .build()      ); + + +    if (externalIssuesSupported) { +      context.addExtension( +        PropertyDefinition.builder(STYLELINT_REPORT_PATHS) +          .defaultValue(STYLELINT_REPORT_PATHS_DEFAULT_VALUE) +          .name("Stylelint Report Files") +          .description("Paths (absolute or relative) to the JSON files with stylelint issues.") +          .onQualifiers(Qualifiers.MODULE, Qualifiers.PROJECT) +          .subCategory(LINTER_SUBCATEGORY) +          .category(CSS_CATEGORY) +          .multiValues(true) +          .build()); +    }    }  } diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssRuleSensor.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssRuleSensor.java index 0601839..4664921 100644 --- a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssRuleSensor.java +++ b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssRuleSensor.java @@ -36,7 +36,10 @@ import org.sonar.api.batch.sensor.SensorContext;  import org.sonar.api.batch.sensor.SensorDescriptor;  import org.sonar.api.batch.sensor.issue.NewIssue;  import org.sonar.api.batch.sensor.issue.NewIssueLocation; +import org.sonar.api.rule.RuleKey;  import org.sonar.css.plugin.CssRules.StylelintConfig; +import org.sonar.css.plugin.StylelintReport.Issue; +import org.sonar.css.plugin.StylelintReport.IssuesPerFile;  import org.sonar.css.plugin.bundle.BundleHandler;  public class CssRuleSensor implements Sensor { @@ -55,8 +58,7 @@ public class CssRuleSensor implements Sensor {    public void describe(SensorDescriptor descriptor) {      descriptor        .onlyOnLanguage(CssLanguage.KEY) -      .name("SonarCSS Rules") -      .onlyOnFileType(InputFile.Type.MAIN); +      .name("SonarCSS Rules");    }    @Override @@ -107,24 +109,17 @@ public class CssRuleSensor implements Sensor {              .at(inputFile.selectLine(issue.line))              .message(issue.text); +          RuleKey ruleKey = cssRules.getActiveSonarKey(issue.rule); +          if (ruleKey == null) { +            throw new IllegalStateException("Unknown stylelint rule or rule not enabled " + issue.rule); +          }            sonarIssue              .at(location) -            .forRule(cssRules.getSonarKey(issue.rule)) +            .forRule(ruleKey)              .save();          }        }      }    } -  static class IssuesPerFile { -    String source; -    Issue[] warnings; -  } - -  static class Issue { -    int line; -    String rule; -    String text; -  } -  } diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssRules.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssRules.java index 92d59aa..0db8f90 100644 --- a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssRules.java +++ b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssRules.java @@ -19,25 +19,25 @@   */  package org.sonar.css.plugin; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable;  import org.sonar.api.batch.rule.CheckFactory;  import org.sonar.api.batch.rule.Checks;  import org.sonar.api.rule.RuleKey; +import org.sonar.css.plugin.rules.ColorNoInvalidHex;  import org.sonar.css.plugin.rules.CommentNoEmpty;  import org.sonar.css.plugin.rules.CssRule;  import org.sonar.css.plugin.rules.DeclarationBlockNoDuplicateProperties;  import org.sonar.css.plugin.rules.KeyframeDeclarationNoImportant;  import org.sonar.css.plugin.rules.NoEmptySource; -import org.sonar.css.plugin.rules.ColorNoInvalidHex;  import org.sonar.css.plugin.rules.StringNoNewline;  import org.sonar.css.plugin.rules.UnitNoUnknown; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -  public class CssRules {    private final Map<String, RuleKey> stylelintKeyToRuleKey; @@ -66,12 +66,9 @@ public class CssRules {      ));    } -  public RuleKey getSonarKey(String stylelintKey) { -    RuleKey ruleKey = stylelintKeyToRuleKey.get(stylelintKey); -    if (ruleKey == null) { -      throw new IllegalStateException("Unknown stylelint rule or rule not enabled " + stylelintKey); -    } -    return ruleKey; +  @Nullable +  public RuleKey getActiveSonarKey(String stylelintKey) { +    return stylelintKeyToRuleKey.get(stylelintKey);    }    public StylelintConfig getConfig() { diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssRulesDefinition.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssRulesDefinition.java index a338d7f..203dcb8 100644 --- a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssRulesDefinition.java +++ b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssRulesDefinition.java @@ -31,6 +31,12 @@ public class CssRulesDefinition implements RulesDefinition {    public static final String RESOURCE_FOLDER = "org/sonar/l10n/css/rules/css"; +  private boolean externalIssuesSupported; + +  public CssRulesDefinition(boolean externalIssuesSupported) { +    this.externalIssuesSupported = externalIssuesSupported; +  } +    @Override    public void define(Context context) {      NewRepository repository = context @@ -40,5 +46,9 @@ public class CssRulesDefinition implements RulesDefinition {      RuleMetadataLoader ruleMetadataLoader = new RuleMetadataLoader(RESOURCE_FOLDER, PROFILE_PATH);      ruleMetadataLoader.addRulesByAnnotatedClass(repository, CssRules.getRuleClasses());      repository.done(); + +    if (externalIssuesSupported) { +      StylelintReportSensor.createExternalRuleRepository(context); +    }    }  } diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/StylelintReport.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/StylelintReport.java new file mode 100644 index 0000000..723b698 --- /dev/null +++ b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/StylelintReport.java @@ -0,0 +1,38 @@ +/* + * SonarCSS + * Copyright (C) 2018-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. + */ +package org.sonar.css.plugin; + +public class StylelintReport { + +  private StylelintReport(){ +  } + +  static class IssuesPerFile { +    String source; +    Issue[] warnings; +  } + +  static class Issue { +    int line; +    String rule; +    String text; +  } + +} diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/StylelintReportSensor.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/StylelintReportSensor.java new file mode 100644 index 0000000..a2225b3 --- /dev/null +++ b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/StylelintReportSensor.java @@ -0,0 +1,219 @@ +/* + * SonarCSS + * Copyright (C) 2018-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. + */ +package org.sonar.css.plugin; + +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import javax.annotation.Nullable; +import org.sonar.api.batch.fs.FilePredicates; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.rule.CheckFactory; +import org.sonar.api.batch.rule.Severity; +import org.sonar.api.batch.sensor.Sensor; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.SensorDescriptor; +import org.sonar.api.batch.sensor.issue.NewExternalIssue; +import org.sonar.api.batch.sensor.issue.NewIssueLocation; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.rules.RuleType; +import org.sonar.api.server.rule.RulesDefinition.Context; +import org.sonar.api.server.rule.RulesDefinition.NewRepository; +import org.sonar.api.server.rule.RulesDefinition.NewRule; +import org.sonar.api.utils.Version; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.css.plugin.StylelintReport.Issue; +import org.sonar.css.plugin.StylelintReport.IssuesPerFile; + +public class StylelintReportSensor implements Sensor { + +  private static final Logger LOG = Loggers.get(StylelintReportSensor.class); + +  private static final String REPOSITORY = "stylelint"; + +  private static final long DEFAULT_REMEDIATION_COST = 5L; +  private static final Severity DEFAULT_SEVERITY = Severity.MAJOR; +  private static final String FILE_EXCEPTION_MESSAGE = "No issues information will be saved as the report file can't be read."; + +  private static final Set<String> BUG_RULES = new HashSet<>(Arrays.asList( +    "selector-type-no-unknown", +    "no-invalid-double-slash-comments", +    "no-descending-specificity", +    "at-rule-no-unknown", +    "selector-type-no-unknown", +    "selector-pseudo-element-no-unknown", +    "selector-pseudo-class-no-unknown", +    "declaration-block-no-shorthand-property-overrides", +    "declaration-block-no-duplicate-properties", +    "keyframe-declaration-no-important", +    "property-no-unknown", +    "unit-no-unknown", +    "function-linear-gradient-no-nonstandard-direction", +    "function-calc-no-unspaced-operator", +    "font-family-no-missing-generic-family-keyword", +    "color-no-invalid-hex" +  )); + +  private final CssRules cssRules; + +  public StylelintReportSensor(CheckFactory checkFactory) { +    cssRules = new CssRules(checkFactory); +  } + +  @Override +  public void describe(SensorDescriptor descriptor) { +    descriptor +      .onlyOnLanguage(CssLanguage.KEY) +      .name("Import of stylelint issues"); +  } + +  @Override +  public void execute(SensorContext context) { +    boolean externalIssuesSupported = context.getSonarQubeVersion().isGreaterThanOrEqual(Version.create(7, 2)); +    String[] reportPaths = context.config().getStringArray(CssPlugin.STYLELINT_REPORT_PATHS); + +    if (reportPaths.length == 0) { +      return; +    } + +    if (!externalIssuesSupported) { +      LOG.error("Import of external issues requires SonarQube 7.2 or greater."); +      return; +    } + +    for (String reportPath : reportPaths) { +      File report = getIOFile(context.fileSystem().baseDir(), reportPath); +      importReport(report, context); +    } +  } + +  private void importReport(File report, SensorContext context) { +    LOG.info("Importing {}", report.getAbsoluteFile()); + +    try (InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream(report), StandardCharsets.UTF_8)) { +      IssuesPerFile[] issues = new Gson().fromJson(inputStreamReader, IssuesPerFile[].class); +      for (IssuesPerFile issuesPerFile : issues) { +        InputFile inputFile = getInputFile(context, issuesPerFile.source); +        if (inputFile != null) { +          for (Issue issue : issuesPerFile.warnings) { +            saveStylelintIssue(context, issue, inputFile); +          } +        } +      } +    } catch (IOException e) { +      LOG.error(FILE_EXCEPTION_MESSAGE, e); +    } catch (JsonSyntaxException e) { +      LOG.error("Failed to parse json stylelint report", e); +    } +  } + +  @Nullable +  private static InputFile getInputFile(SensorContext context, String fileName) { +    FilePredicates predicates = context.fileSystem().predicates(); +    InputFile inputFile = context.fileSystem().inputFile(predicates.or(predicates.hasRelativePath(fileName), predicates.hasAbsolutePath(fileName))); +    if (inputFile == null) { +      LOG.warn("No input file found for {}. No stylelint issues will be imported on this file.", fileName); +      return null; +    } +    return inputFile; +  } + +  private void saveStylelintIssue(SensorContext context, Issue issue, InputFile inputFile) { +    String stylelintKey = issue.rule; + +    RuleKey sonarKey = cssRules.getActiveSonarKey(stylelintKey); +    if (sonarKey != null) { +      String message = "Stylelint issue for rule '{}' is skipped because this rule is activated in your SonarQube profile for CSS (rule key in SQ {})"; +      LOG.debug(message, stylelintKey, sonarKey.toString()); +      return; +    } + +    NewExternalIssue newExternalIssue = context.newExternalIssue(); + +    NewIssueLocation primaryLocation = newExternalIssue.newLocation() +      .message(issue.text) +      .on(inputFile) +      .at(inputFile.selectLine(issue.line)); + +    newExternalIssue +      .at(primaryLocation) +      .forRule(RuleKey.of(REPOSITORY, stylelintKey)) +      .type(ruleType(stylelintKey)) +      .severity(DEFAULT_SEVERITY) +      .remediationEffortMinutes(DEFAULT_REMEDIATION_COST) +      .save(); +  } + +  private static RuleType ruleType(String stylelintKey) { +    return BUG_RULES.contains(stylelintKey) +      ? RuleType.BUG +      : RuleType.CODE_SMELL; +  } + +  /** +   * Returns a java.io.File for the given path. +   * If path is not absolute, returns a File with module base directory as parent path. +   */ +  private static File getIOFile(File baseDir, String path) { +    File file = new File(path); +    if (!file.isAbsolute()) { +      file = new File(baseDir, path); +    } + +    return file; +  } + +  static void createExternalRuleRepository(Context context) { +    NewRepository externalRepo = context.createExternalRepository(REPOSITORY, CssLanguage.KEY).setName(REPOSITORY); +    String pathToRulesMeta = "org/sonar/l10n/css/rules/" + REPOSITORY + "/rules.json"; +    String description = "See the description of %s rule <code>%s</code> at <a href=\"%s\">%s website</a>."; + +    try (InputStreamReader inputStreamReader = new InputStreamReader(StylelintReportSensor.class.getClassLoader().getResourceAsStream(pathToRulesMeta), StandardCharsets.UTF_8)) { +      ExternalRule[] rules = new Gson().fromJson(inputStreamReader, ExternalRule[].class); +      for (ExternalRule rule : rules) { +        NewRule newRule = externalRepo.createRule(rule.key).setName(rule.name); +        newRule.setHtmlDescription(String.format(description, REPOSITORY, rule.key, rule.url, REPOSITORY)); +        newRule.setDebtRemediationFunction(newRule.debtRemediationFunctions().constantPerIssue(DEFAULT_REMEDIATION_COST + "min")); +        if (BUG_RULES.contains(rule.key)) { +          newRule.setType(RuleType.BUG); +        } +      } + +    } catch (IOException e) { +      throw new IllegalStateException("Can't read resource: " + pathToRulesMeta, e); +    } + +    externalRepo.done(); +  } + +  private static class ExternalRule { +    String url; +    String key; +    String name; +  } +} diff --git a/sonar-css-plugin/src/main/resources/org/sonar/l10n/css/rules/stylelint/rules.json b/sonar-css-plugin/src/main/resources/org/sonar/l10n/css/rules/stylelint/rules.json new file mode 100644 index 0000000..4cd1ee0 --- /dev/null +++ b/sonar-css-plugin/src/main/resources/org/sonar/l10n/css/rules/stylelint/rules.json @@ -0,0 +1,852 @@ +[ +  { +    "key": "color-no-invalid-hex", +    "url": "https://stylelint.io/user-guide/rules/color-no-invalid-hex/", +    "name": "Disallow invalid hex colors" +  }, +  { +    "key": "font-family-no-duplicate-names", +    "url": "https://stylelint.io/user-guide/rules/font-family-no-duplicate-names/", +    "name": "Disallow duplicate font family names" +  }, +  { +    "key": "font-family-no-missing-generic-family-keyword", +    "url": "https://stylelint.io/user-guide/rules/font-family-no-missing-generic-family-keyword/", +    "name": "Disallow missing generic families in lists of font family names" +  }, +  { +    "key": "function-calc-no-unspaced-operator", +    "url": "https://stylelint.io/user-guide/rules/function-calc-no-unspaced-operator/", +    "name": "Disallow an unspaced operator within `calc` functions" +  }, +  { +    "key": "function-linear-gradient-no-nonstandard-direction", +    "url": "https://stylelint.io/user-guide/rules/function-linear-gradient-no-nonstandard-direction/", +    "name": "Disallow direction values in `linear-gradient()` calls that are not valid according to the [standard syntax](https://developer.mozilla.org/en-US/docs/Web/CSS/linear-gradient#Syntax)" +  }, +  { +    "key": "string-no-newline", +    "url": "https://stylelint.io/user-guide/rules/string-no-newline/", +    "name": "Disallow (unescaped) newlines in strings" +  }, +  { +    "key": "unit-no-unknown", +    "url": "https://stylelint.io/user-guide/rules/unit-no-unknown/", +    "name": "Disallow unknown units" +  }, +  { +    "key": "property-no-unknown", +    "url": "https://stylelint.io/user-guide/rules/property-no-unknown/", +    "name": "Disallow unknown properties" +  }, +  { +    "key": "keyframe-declaration-no-important", +    "url": "https://stylelint.io/user-guide/rules/keyframe-declaration-no-important/", +    "name": "Disallow `!important` within keyframe declarations" +  }, +  { +    "key": "declaration-block-no-duplicate-properties", +    "url": "https://stylelint.io/user-guide/rules/declaration-block-no-duplicate-properties/", +    "name": "Disallow duplicate properties within declaration blocks" +  }, +  { +    "key": "declaration-block-no-shorthand-property-overrides", +    "url": "https://stylelint.io/user-guide/rules/declaration-block-no-shorthand-property-overrides/", +    "name": "Disallow shorthand properties that override related longhand properties within declaration blocks" +  }, +  { +    "key": "block-no-empty", +    "url": "https://stylelint.io/user-guide/rules/block-no-empty/", +    "name": "Disallow empty blocks" +  }, +  { +    "key": "selector-pseudo-class-no-unknown", +    "url": "https://stylelint.io/user-guide/rules/selector-pseudo-class-no-unknown/", +    "name": "Disallow unknown pseudo-class selectors" +  }, +  { +    "key": "selector-pseudo-element-no-unknown", +    "url": "https://stylelint.io/user-guide/rules/selector-pseudo-element-no-unknown/", +    "name": "Disallow unknown pseudo-element selectors" +  }, +  { +    "key": "selector-type-no-unknown", +    "url": "https://stylelint.io/user-guide/rules/selector-type-no-unknown/", +    "name": "Disallow unknown type selectors" +  }, +  { +    "key": "media-feature-name-no-unknown", +    "url": "https://stylelint.io/user-guide/rules/media-feature-name-no-unknown/", +    "name": "Disallow unknown media feature names" +  }, +  { +    "key": "at-rule-no-unknown", +    "url": "https://stylelint.io/user-guide/rules/at-rule-no-unknown/", +    "name": "Disallow unknown at-rules" +  }, +  { +    "key": "comment-no-empty", +    "url": "https://stylelint.io/user-guide/rules/comment-no-empty/", +    "name": " Disallow empty comments" +  }, +  { +    "key": "no-descending-specificity", +    "url": "https://stylelint.io/user-guide/rules/no-descending-specificity/", +    "name": "Disallow selectors of lower specificity from coming after overriding selectors of higher specificity" +  }, +  { +    "key": "no-duplicate-at-import-rules", +    "url": "https://stylelint.io/user-guide/rules/no-duplicate-at-import-rules/", +    "name": "Disallow duplicate `@import` rules within a stylesheet" +  }, +  { +    "key": "no-duplicate-selectors", +    "url": "https://stylelint.io/user-guide/rules/no-duplicate-selectors/", +    "name": "Disallow duplicate selectors" +  }, +  { +    "key": "no-empty-source", +    "url": "https://stylelint.io/user-guide/rules/no-empty-source/", +    "name": "Disallow empty sources" +  }, +  { +    "key": "no-extra-semicolons", +    "url": "https://stylelint.io/user-guide/rules/no-extra-semicolons/", +    "name": "Disallow extra semicolons" +  }, +  { +    "key": "no-invalid-double-slash-comments", +    "url": "https://stylelint.io/user-guide/rules/no-invalid-double-slash-comments/", +    "name": "Disallow double-slash comments (`//...`) which are not supported by CSS" +  }, +  { +    "key": "color-named", +    "url": "https://stylelint.io/user-guide/rules/color-named/", +    "name": "Require (where possible) or disallow named colors" +  }, +  { +    "key": "color-no-hex", +    "url": "https://stylelint.io/user-guide/rules/color-no-hex/", +    "name": "Disallow hex colors" +  }, +  { +    "key": "function-blacklist", +    "url": "https://stylelint.io/user-guide/rules/function-blacklist/", +    "name": "Specify a blacklist of disallowed functions" +  }, +  { +    "key": "function-url-no-scheme-relative", +    "url": "https://stylelint.io/user-guide/rules/function-url-no-scheme-relative/", +    "name": "Disallow scheme-relative urls" +  }, +  { +    "key": "function-url-scheme-blacklist", +    "url": "https://stylelint.io/user-guide/rules/function-url-scheme-blacklist/", +    "name": "Specify a blacklist of disallowed url schemes" +  }, +  { +    "key": "function-url-scheme-whitelist", +    "url": "https://stylelint.io/user-guide/rules/function-url-scheme-whitelist/", +    "name": "Specify a whitelist of allowed url schemes" +  }, +  { +    "key": "function-whitelist", +    "url": "https://stylelint.io/user-guide/rules/function-whitelist/", +    "name": "Specify a whitelist of allowed functions" +  }, +  { +    "key": "keyframes-name-pattern", +    "url": "https://stylelint.io/user-guide/rules/keyframes-name-pattern/", +    "name": "Specify a pattern for keyframe names" +  }, +  { +    "key": "number-max-precision", +    "url": "https://stylelint.io/user-guide/rules/number-max-precision/", +    "name": "Limit the number of decimal places allowed in numbers" +  }, +  { +    "key": "time-min-milliseconds", +    "url": "https://stylelint.io/user-guide/rules/time-min-milliseconds/", +    "name": "Specify the minimum number of milliseconds for time values" +  }, +  { +    "key": "unit-blacklist", +    "url": "https://stylelint.io/user-guide/rules/unit-blacklist/", +    "name": "Specify a blacklist of disallowed units" +  }, +  { +    "key": "unit-whitelist", +    "url": "https://stylelint.io/user-guide/rules/unit-whitelist/", +    "name": "Specify a whitelist of allowed units" +  }, +  { +    "key": "shorthand-property-no-redundant-values", +    "url": "https://stylelint.io/user-guide/rules/shorthand-property-no-redundant-values/", +    "name": "Disallow redundant values in shorthand properties" +  }, +  { +    "key": "value-no-vendor-prefix", +    "url": "https://stylelint.io/user-guide/rules/value-no-vendor-prefix/", +    "name": "Disallow vendor prefixes for values" +  }, +  { +    "key": "custom-property-pattern", +    "url": "https://stylelint.io/user-guide/rules/custom-property-pattern/", +    "name": "Specify a pattern for custom properties" +  }, +  { +    "key": "property-blacklist", +    "url": "https://stylelint.io/user-guide/rules/property-blacklist/", +    "name": "Specify a blacklist of disallowed properties" +  }, +  { +    "key": "property-no-vendor-prefix", +    "url": "https://stylelint.io/user-guide/rules/property-no-vendor-prefix/", +    "name": "Disallow vendor prefixes for properties" +  }, +  { +    "key": "property-whitelist", +    "url": "https://stylelint.io/user-guide/rules/property-whitelist/", +    "name": "Specify a whitelist of allowed properties" +  }, +  { +    "key": "declaration-block-no-redundant-longhand-properties", +    "url": "https://stylelint.io/user-guide/rules/declaration-block-no-redundant-longhand-properties/", +    "name": "Disallow longhand properties that can be combined into one shorthand property" +  }, +  { +    "key": "declaration-no-important", +    "url": "https://stylelint.io/user-guide/rules/declaration-no-important/", +    "name": "Disallow `!important` within declarations" +  }, +  { +    "key": "declaration-property-unit-blacklist", +    "url": "https://stylelint.io/user-guide/rules/declaration-property-unit-blacklist/", +    "name": "Specify a blacklist of disallowed property and unit pairs within declarations" +  }, +  { +    "key": "declaration-property-unit-whitelist", +    "url": "https://stylelint.io/user-guide/rules/declaration-property-unit-whitelist/", +    "name": "Specify a whitelist of allowed property and unit pairs within declarations" +  }, +  { +    "key": "declaration-property-value-blacklist", +    "url": "https://stylelint.io/user-guide/rules/declaration-property-value-blacklist/", +    "name": "Specify a blacklist of disallowed property and value pairs within declarations" +  }, +  { +    "key": "declaration-property-value-whitelist", +    "url": "https://stylelint.io/user-guide/rules/declaration-property-value-whitelist/", +    "name": "Specify a whitelist of allowed property and value pairs within declarations" +  }, +  { +    "key": "declaration-block-single-line-max-declarations", +    "url": "https://stylelint.io/user-guide/rules/declaration-block-single-line-max-declarations/", +    "name": "Limit the number of declaration within single line declaration blocks" +  }, +  { +    "key": "selector-attribute-operator-blacklist", +    "url": "https://stylelint.io/user-guide/rules/selector-attribute-operator-blacklist/", +    "name": "Specify a blacklist of disallowed attribute operators" +  }, +  { +    "key": "selector-attribute-operator-whitelist", +    "url": "https://stylelint.io/user-guide/rules/selector-attribute-operator-whitelist/", +    "name": "Specify a whitelist of allowed attribute operators" +  }, +  { +    "key": "selector-class-pattern", +    "url": "https://stylelint.io/user-guide/rules/selector-class-pattern/", +    "name": "Specify a pattern for class selectors" +  }, +  { +    "key": "selector-combinator-blacklist", +    "url": "https://stylelint.io/user-guide/rules/selector-combinator-blacklist/", +    "name": "Specify a blacklist of disallowed combinators" +  }, +  { +    "key": "selector-combinator-whitelist", +    "url": "https://stylelint.io/user-guide/rules/selector-combinator-whitelist/", +    "name": "Specify a whitelist of allowed combinators" +  }, +  { +    "key": "selector-id-pattern", +    "url": "https://stylelint.io/user-guide/rules/selector-id-pattern/", +    "name": "Specify a pattern for id selectors" +  }, +  { +    "key": "selector-max-attribute", +    "url": "https://stylelint.io/user-guide/rules/selector-max-attribute/", +    "name": "Limit the number of attribute selectors in a selector" +  }, +  { +    "key": "selector-max-class", +    "url": "https://stylelint.io/user-guide/rules/selector-max-class/", +    "name": "Limit the number of classes in a selector" +  }, +  { +    "key": "selector-max-combinators", +    "url": "https://stylelint.io/user-guide/rules/selector-max-combinators/", +    "name": "Limit the number of combinators in a selector" +  }, +  { +    "key": "selector-max-compound-selectors", +    "url": "https://stylelint.io/user-guide/rules/selector-max-compound-selectors/", +    "name": "Limit the number of compound selectors in a selector" +  }, +  { +    "key": "selector-max-empty-lines", +    "url": "https://stylelint.io/user-guide/rules/selector-max-empty-lines/", +    "name": "Limit the number of adjacent empty lines within selectors" +  }, +  { +    "key": "selector-max-id", +    "url": "https://stylelint.io/user-guide/rules/selector-max-id/", +    "name": "Limit the number of id selectors in a selector" +  }, +  { +    "key": "selector-max-pseudo-class", +    "url": "https://stylelint.io/user-guide/rules/selector-max-pseudo-class/", +    "name": "Limit the number of pseudo-classes in a selector" +  }, +  { +    "key": "selector-max-specificity", +    "url": "https://stylelint.io/user-guide/rules/selector-max-specificity/", +    "name": "Limit the specificity of selectors" +  }, +  { +    "key": "selector-max-type", +    "url": "https://stylelint.io/user-guide/rules/selector-max-type/", +    "name": "Limit the number of type in a selector" +  }, +  { +    "key": "selector-max-universal", +    "url": "https://stylelint.io/user-guide/rules/selector-max-universal/", +    "name": "Limit the number of universal selectors in a selector" +  }, +  { +    "key": "selector-nested-pattern", +    "url": "https://stylelint.io/user-guide/rules/selector-nested-pattern/", +    "name": "Specify a pattern for the selectors of rules nested within rules" +  }, +  { +    "key": "selector-no-qualifying-type", +    "url": "https://stylelint.io/user-guide/rules/selector-no-qualifying-type/", +    "name": "Disallow qualifying a selector by type" +  }, +  { +    "key": "selector-no-vendor-prefix", +    "url": "https://stylelint.io/user-guide/rules/selector-no-vendor-prefix/", +    "name": "Disallow vendor prefixes for selectors" +  }, +  { +    "key": "selector-pseudo-class-blacklist", +    "url": "https://stylelint.io/user-guide/rules/selector-pseudo-class-blacklist/", +    "name": "Specify a blacklist of disallowed pseudo-class selectors" +  }, +  { +    "key": "selector-pseudo-class-whitelist", +    "url": "https://stylelint.io/user-guide/rules/selector-pseudo-class-whitelist/", +    "name": "Specify a whitelist of allowed pseudo-class selectors" +  }, +  { +    "key": "selector-pseudo-element-blacklist", +    "url": "https://stylelint.io/user-guide/rules/selector-pseudo-element-blacklist/", +    "name": "Specify a blacklist of disallowed pseudo-element selectors" +  }, +  { +    "key": "selector-pseudo-element-whitelist", +    "url": "https://stylelint.io/user-guide/rules/selector-pseudo-element-whitelist/", +    "name": "Specify a whitelist of allowed pseudo-element selectors" +  }, +  { +    "key": "media-feature-name-blacklist", +    "url": "https://stylelint.io/user-guide/rules/media-feature-name-blacklist/", +    "name": "Specify a blacklist of disallowed media feature names" +  }, +  { +    "key": "media-feature-name-no-vendor-prefix", +    "url": "https://stylelint.io/user-guide/rules/media-feature-name-no-vendor-prefix/", +    "name": "Disallow vendor prefixes for media feature names" +  }, +  { +    "key": "media-feature-name-value-whitelist", +    "url": "https://stylelint.io/user-guide/rules/media-feature-name-value-whitelist/", +    "name": "Specify a whitelist of allowed media feature name and value pairs" +  }, +  { +    "key": "media-feature-name-whitelist", +    "url": "https://stylelint.io/user-guide/rules/media-feature-name-whitelist/", +    "name": "Specify a whitelist of allowed media feature names" +  }, +  { +    "key": "custom-media-pattern", +    "url": "https://stylelint.io/user-guide/rules/custom-media-pattern/", +    "name": "Specify a pattern for custom media query names" +  }, +  { +    "key": "at-rule-blacklist", +    "url": "https://stylelint.io/user-guide/rules/at-rule-blacklist/", +    "name": "Specify a blacklist of disallowed at-rules" +  }, +  { +    "key": "at-rule-no-vendor-prefix", +    "url": "https://stylelint.io/user-guide/rules/at-rule-no-vendor-prefix/", +    "name": "Disallow vendor prefixes for at-rules" +  }, +  { +    "key": "at-rule-whitelist", +    "url": "https://stylelint.io/user-guide/rules/at-rule-whitelist/", +    "name": "Specify a whitelist of allowed at-rules" +  }, +  { +    "key": "comment-word-blacklist", +    "url": "https://stylelint.io/user-guide/rules/comment-word-blacklist/", +    "name": "Specify a blacklist of disallowed words within comments" +  }, +  { +    "key": "max-nesting-depth", +    "url": "https://stylelint.io/user-guide/rules/max-nesting-depth/", +    "name": "Limit the depth of nesting" +  }, +  { +    "key": "no-unknown-animations", +    "url": "https://stylelint.io/user-guide/rules/no-unknown-animations/", +    "name": "Disallow unknown animations" +  }, +  { +    "key": "color-hex-case", +    "url": "https://stylelint.io/user-guide/rules/color-hex-case/", +    "name": "Specify lowercase or uppercase for hex colors" +  }, +  { +    "key": "color-hex-length", +    "url": "https://stylelint.io/user-guide/rules/color-hex-length/", +    "name": "Specify short or long notation for hex colors" +  }, +  { +    "key": "font-family-name-quotes", +    "url": "https://stylelint.io/user-guide/rules/font-family-name-quotes/", +    "name": "Specify whether or not quotation marks should be used around font family names" +  }, +  { +    "key": "font-weight-notation", +    "url": "https://stylelint.io/user-guide/rules/font-weight-notation/", +    "name": "Require numeric or named (where possible) `font-weight` values" +  }, +  { +    "key": "function-comma-newline-after", +    "url": "https://stylelint.io/user-guide/rules/function-comma-newline-after/", +    "name": "Require a newline or disallow whitespace after the commas of functions" +  }, +  { +    "key": "function-comma-newline-before", +    "url": "https://stylelint.io/user-guide/rules/function-comma-newline-before/", +    "name": "Require a newline or disallow whitespace before the commas of functions" +  }, +  { +    "key": "function-comma-space-after", +    "url": "https://stylelint.io/user-guide/rules/function-comma-space-after/", +    "name": "Require a single space or disallow whitespace after the commas of functions" +  }, +  { +    "key": "function-comma-space-before", +    "url": "https://stylelint.io/user-guide/rules/function-comma-space-before/", +    "name": "Require a single space or disallow whitespace before the commas of functions" +  }, +  { +    "key": "function-max-empty-lines", +    "url": "https://stylelint.io/user-guide/rules/function-max-empty-lines/", +    "name": "Limit the number of adjacent empty lines within functions" +  }, +  { +    "key": "function-name-case", +    "url": "https://stylelint.io/user-guide/rules/function-name-case/", +    "name": "Specify lowercase or uppercase for function names" +  }, +  { +    "key": "function-parentheses-newline-inside", +    "url": "https://stylelint.io/user-guide/rules/function-parentheses-newline-inside/", +    "name": "Require a newline or disallow whitespace on the inside of the parentheses of functions" +  }, +  { +    "key": "function-parentheses-space-inside", +    "url": "https://stylelint.io/user-guide/rules/function-parentheses-space-inside/", +    "name": "Require a single space or disallow whitespace on the inside of the parentheses of functions" +  }, +  { +    "key": "function-url-quotes", +    "url": "https://stylelint.io/user-guide/rules/function-url-quotes/", +    "name": "Require or disallow quotes for urls" +  }, +  { +    "key": "function-whitespace-after", +    "url": "https://stylelint.io/user-guide/rules/function-whitespace-after/", +    "name": "Require or disallow whitespace after functions" +  }, +  { +    "key": "number-leading-zero", +    "url": "https://stylelint.io/user-guide/rules/number-leading-zero/", +    "name": "Require or disallow a leading zero for fractional numbers less than 1" +  }, +  { +    "key": "number-no-trailing-zeros", +    "url": "https://stylelint.io/user-guide/rules/number-no-trailing-zeros/", +    "name": "Disallow trailing zeros in numbers" +  }, +  { +    "key": "string-quotes", +    "url": "https://stylelint.io/user-guide/rules/string-quotes/", +    "name": "Specify single or double quotes around strings" +  }, +  { +    "key": "length-zero-no-unit", +    "url": "https://stylelint.io/user-guide/rules/length-zero-no-unit/", +    "name": "Disallow units for zero lengths" +  }, +  { +    "key": "unit-case", +    "url": "https://stylelint.io/user-guide/rules/unit-case/", +    "name": "Specify lowercase or uppercase for units" +  }, +  { +    "key": "value-keyword-case", +    "url": "https://stylelint.io/user-guide/rules/value-keyword-case/", +    "name": "Specify lowercase or uppercase for keywords values" +  }, +  { +    "key": "value-list-comma-newline-after", +    "url": "https://stylelint.io/user-guide/rules/value-list-comma-newline-after/", +    "name": "Require a newline or disallow whitespace after the commas of value lists" +  }, +  { +    "key": "value-list-comma-newline-before", +    "url": "https://stylelint.io/user-guide/rules/value-list-comma-newline-before/", +    "name": "Require a newline or disallow whitespace before the commas of value lists" +  }, +  { +    "key": "value-list-comma-space-after", +    "url": "https://stylelint.io/user-guide/rules/value-list-comma-space-after/", +    "name": "Require a single space or disallow whitespace after the commas of value lists" +  }, +  { +    "key": "value-list-comma-space-before", +    "url": "https://stylelint.io/user-guide/rules/value-list-comma-space-before/", +    "name": "Require a single space or disallow whitespace before the commas of value lists" +  }, +  { +    "key": "value-list-max-empty-lines", +    "url": "https://stylelint.io/user-guide/rules/value-list-max-empty-lines/", +    "name": "Limit the number of adjacent empty lines within value lists" +  }, +  { +    "key": "custom-property-empty-line-before", +    "url": "https://stylelint.io/user-guide/rules/custom-property-empty-line-before/", +    "name": "Require or disallow an empty line before custom properties" +  }, +  { +    "key": "property-case", +    "url": "https://stylelint.io/user-guide/rules/property-case/", +    "name": "Specify lowercase or uppercase for properties" +  }, +  { +    "key": "declaration-bang-space-after", +    "url": "https://stylelint.io/user-guide/rules/declaration-bang-space-after/", +    "name": "Require a single space or disallow whitespace after the bang of declarations" +  }, +  { +    "key": "declaration-bang-space-before", +    "url": "https://stylelint.io/user-guide/rules/declaration-bang-space-before/", +    "name": "Require a single space or disallow whitespace before the bang of declarations" +  }, +  { +    "key": "declaration-colon-newline-after", +    "url": "https://stylelint.io/user-guide/rules/declaration-colon-newline-after/", +    "name": "Require a newline or disallow whitespace after the colon of declarations" +  }, +  { +    "key": "declaration-colon-space-after", +    "url": "https://stylelint.io/user-guide/rules/declaration-colon-space-after/", +    "name": "Require a single space or disallow whitespace after the colon of declarations" +  }, +  { +    "key": "declaration-colon-space-before", +    "url": "https://stylelint.io/user-guide/rules/declaration-colon-space-before/", +    "name": "Require a single space or disallow whitespace before the colon of declarations" +  }, +  { +    "key": "declaration-empty-line-before", +    "url": "https://stylelint.io/user-guide/rules/declaration-empty-line-before/", +    "name": "Require or disallow an empty line before declarations" +  }, +  { +    "key": "declaration-block-semicolon-newline-after", +    "url": "https://stylelint.io/user-guide/rules/declaration-block-semicolon-newline-after/", +    "name": "Require a newline or disallow whitespace after the semicolons of declaration blocks" +  }, +  { +    "key": "declaration-block-semicolon-newline-before", +    "url": "https://stylelint.io/user-guide/rules/declaration-block-semicolon-newline-before/", +    "name": "Require a newline or disallow whitespace before the semicolons of declaration blocks" +  }, +  { +    "key": "declaration-block-semicolon-space-after", +    "url": "https://stylelint.io/user-guide/rules/declaration-block-semicolon-space-after/", +    "name": "Require a single space or disallow whitespace after the semicolons of declaration blocks" +  }, +  { +    "key": "declaration-block-semicolon-space-before", +    "url": "https://stylelint.io/user-guide/rules/declaration-block-semicolon-space-before/", +    "name": "Require a single space or disallow whitespace before the semicolons of declaration blocks" +  }, +  { +    "key": "declaration-block-trailing-semicolon", +    "url": "https://stylelint.io/user-guide/rules/declaration-block-trailing-semicolon/", +    "name": "Require or disallow a trailing semicolon within declaration blocks" +  }, +  { +    "key": "block-closing-brace-empty-line-before", +    "url": "https://stylelint.io/user-guide/rules/block-closing-brace-empty-line-before/", +    "name": "Require or disallow an empty line before the closing brace of blocks" +  }, +  { +    "key": "block-closing-brace-newline-after", +    "url": "https://stylelint.io/user-guide/rules/block-closing-brace-newline-after/", +    "name": "Require a newline or disallow whitespace after the closing brace of blocks" +  }, +  { +    "key": "block-closing-brace-newline-before", +    "url": "https://stylelint.io/user-guide/rules/block-closing-brace-newline-before/", +    "name": "Require a newline or disallow whitespace before the closing brace of blocks" +  }, +  { +    "key": "block-closing-brace-space-after", +    "url": "https://stylelint.io/user-guide/rules/block-closing-brace-space-after/", +    "name": "Require a single space or disallow whitespace after the closing brace of blocks" +  }, +  { +    "key": "block-closing-brace-space-before", +    "url": "https://stylelint.io/user-guide/rules/block-closing-brace-space-before/", +    "name": "Require a single space or disallow whitespace before the closing brace of blocks" +  }, +  { +    "key": "block-opening-brace-newline-after", +    "url": "https://stylelint.io/user-guide/rules/block-opening-brace-newline-after/", +    "name": "Require a newline after the opening brace of blocks" +  }, +  { +    "key": "block-opening-brace-newline-before", +    "url": "https://stylelint.io/user-guide/rules/block-opening-brace-newline-before/", +    "name": "Require a newline or disallow whitespace before the opening brace of blocks" +  }, +  { +    "key": "block-opening-brace-space-after", +    "url": "https://stylelint.io/user-guide/rules/block-opening-brace-space-after/", +    "name": "Require a single space or disallow whitespace after the opening brace of blocks" +  }, +  { +    "key": "block-opening-brace-space-before", +    "url": "https://stylelint.io/user-guide/rules/block-opening-brace-space-before/", +    "name": "Require a single space or disallow whitespace before the opening brace of blocks" +  }, +  { +    "key": "selector-attribute-brackets-space-inside", +    "url": "https://stylelint.io/user-guide/rules/selector-attribute-brackets-space-inside/", +    "name": "Require a single space or disallow whitespace on the inside of the brackets within attribute selectors" +  }, +  { +    "key": "selector-attribute-operator-space-after", +    "url": "https://stylelint.io/user-guide/rules/selector-attribute-operator-space-after/", +    "name": "Require a single space or disallow whitespace after operators within attribute selectors" +  }, +  { +    "key": "selector-attribute-operator-space-before", +    "url": "https://stylelint.io/user-guide/rules/selector-attribute-operator-space-before/", +    "name": "Require a single space or disallow whitespace before operators within attribute selectors" +  }, +  { +    "key": "selector-attribute-quotes", +    "url": "https://stylelint.io/user-guide/rules/selector-attribute-quotes/", +    "name": "Require or disallow quotes for attribute values" +  }, +  { +    "key": "selector-combinator-space-after", +    "url": "https://stylelint.io/user-guide/rules/selector-combinator-space-after/", +    "name": "Require a single space or disallow whitespace after the combinators of selectors" +  }, +  { +    "key": "selector-combinator-space-before", +    "url": "https://stylelint.io/user-guide/rules/selector-combinator-space-before/", +    "name": "Require a single space or disallow whitespace before the combinators of selectors" +  }, +  { +    "key": "selector-descendant-combinator-no-non-space", +    "url": "https://stylelint.io/user-guide/rules/selector-descendant-combinator-no-non-space/", +    "name": "Disallow non-space characters for descendant combinators of selectors" +  }, +  { +    "key": "selector-pseudo-class-case", +    "url": "https://stylelint.io/user-guide/rules/selector-pseudo-class-case/", +    "name": "Specify lowercase or uppercase for pseudo-class selectors" +  }, +  { +    "key": "selector-pseudo-class-parentheses-space-inside", +    "url": "https://stylelint.io/user-guide/rules/selector-pseudo-class-parentheses-space-inside/", +    "name": "Require a single space or disallow whitespace on the inside of the parentheses within pseudo-class selectors" +  }, +  { +    "key": "selector-pseudo-element-case", +    "url": "https://stylelint.io/user-guide/rules/selector-pseudo-element-case/", +    "name": "Specify lowercase or uppercase for pseudo-element selectors" +  }, +  { +    "key": "selector-pseudo-element-colon-notation", +    "url": "https://stylelint.io/user-guide/rules/selector-pseudo-element-colon-notation/", +    "name": "Specify single or double colon notation for applicable pseudo-elements" +  }, +  { +    "key": "selector-type-case", +    "url": "https://stylelint.io/user-guide/rules/selector-type-case/", +    "name": "Specify lowercase or uppercase for type selector" +  }, +  { +    "key": "selector-list-comma-newline-after", +    "url": "https://stylelint.io/user-guide/rules/selector-list-comma-newline-after/", +    "name": "Require a newline or disallow whitespace after the commas of selector lists" +  }, +  { +    "key": "selector-list-comma-newline-before", +    "url": "https://stylelint.io/user-guide/rules/selector-list-comma-newline-before/", +    "name": "Require a newline or disallow whitespace before the commas of selector lists" +  }, +  { +    "key": "selector-list-comma-space-after", +    "url": "https://stylelint.io/user-guide/rules/selector-list-comma-space-after/", +    "name": "Require a single space or disallow whitespace after the commas of selector lists" +  }, +  { +    "key": "selector-list-comma-space-before", +    "url": "https://stylelint.io/user-guide/rules/selector-list-comma-space-before/", +    "name": "Require a single space or disallow whitespace before the commas of selector lists" +  }, +  { +    "key": "rule-empty-line-before", +    "url": "https://stylelint.io/user-guide/rules/rule-empty-line-before/", +    "name": "Require or disallow an empty line before rules" +  }, +  { +    "key": "media-feature-colon-space-after", +    "url": "https://stylelint.io/user-guide/rules/media-feature-colon-space-after/", +    "name": "Require a single space or disallow whitespace after the colon in media features" +  }, +  { +    "key": "media-feature-colon-space-before", +    "url": "https://stylelint.io/user-guide/rules/media-feature-colon-space-before/", +    "name": "Require a single space or disallow whitespace before the colon in media features" +  }, +  { +    "key": "media-feature-name-case", +    "url": "https://stylelint.io/user-guide/rules/media-feature-name-case/", +    "name": "Specify lowercase or uppercase for media feature names" +  }, +  { +    "key": "media-feature-parentheses-space-inside", +    "url": "https://stylelint.io/user-guide/rules/media-feature-parentheses-space-inside/", +    "name": "Require a single space or disallow whitespace on the inside of the parentheses within media features" +  }, +  { +    "key": "media-feature-range-operator-space-after", +    "url": "https://stylelint.io/user-guide/rules/media-feature-range-operator-space-after/", +    "name": "Require a single space or disallow whitespace after the range operator in media features" +  }, +  { +    "key": "media-feature-range-operator-space-before", +    "url": "https://stylelint.io/user-guide/rules/media-feature-range-operator-space-before/", +    "name": "Require a single space or disallow whitespace before the range operator in media features" +  }, +  { +    "key": "media-query-list-comma-newline-after", +    "url": "https://stylelint.io/user-guide/rules/media-query-list-comma-newline-after/", +    "name": "Require a newline or disallow whitespace after the commas of media query lists" +  }, +  { +    "key": "media-query-list-comma-newline-before", +    "url": "https://stylelint.io/user-guide/rules/media-query-list-comma-newline-before/", +    "name": "Require a newline or disallow whitespace before the commas of media query lists" +  }, +  { +    "key": "media-query-list-comma-space-after", +    "url": "https://stylelint.io/user-guide/rules/media-query-list-comma-space-after/", +    "name": "Require a single space or disallow whitespace after the commas of media query lists" +  }, +  { +    "key": "media-query-list-comma-space-before", +    "url": "https://stylelint.io/user-guide/rules/media-query-list-comma-space-before/", +    "name": "Require a single space or disallow whitespace before the commas of media query lists" +  }, +  { +    "key": "at-rule-empty-line-before", +    "url": "https://stylelint.io/user-guide/rules/at-rule-empty-line-before/", +    "name": "Require or disallow an empty line before at-rules" +  }, +  { +    "key": "at-rule-name-case", +    "url": "https://stylelint.io/user-guide/rules/at-rule-name-case/", +    "name": "Specify lowercase or uppercase for at-rules names" +  }, +  { +    "key": "at-rule-name-newline-after", +    "url": "https://stylelint.io/user-guide/rules/at-rule-name-newline-after/", +    "name": "Require a newline after at-rule names" +  }, +  { +    "key": "at-rule-name-space-after", +    "url": "https://stylelint.io/user-guide/rules/at-rule-name-space-after/", +    "name": "Require a single space after at-rule names" +  }, +  { +    "key": "at-rule-semicolon-newline-after", +    "url": "https://stylelint.io/user-guide/rules/at-rule-semicolon-newline-after/", +    "name": "Require a newline after the semicolon of at-rules" +  }, +  { +    "key": "at-rule-semicolon-space-before", +    "url": "https://stylelint.io/user-guide/rules/at-rule-semicolon-space-before/", +    "name": "Require a single space or disallow whitespace before the semicolons of at rules" +  }, +  { +    "key": "comment-empty-line-before", +    "url": "https://stylelint.io/user-guide/rules/comment-empty-line-before/", +    "name": "Require or disallow an empty line before comments" +  }, +  { +    "key": "comment-whitespace-inside", +    "url": "https://stylelint.io/user-guide/rules/comment-whitespace-inside/", +    "name": "Require or disallow whitespace on the inside of comment markers" +  }, +  { +    "key": "indentation", +    "url": "https://stylelint.io/user-guide/rules/indentation/", +    "name": "Specify indentation" +  }, +  { +    "key": "max-empty-lines", +    "url": "https://stylelint.io/user-guide/rules/max-empty-lines/", +    "name": "Limit the number of adjacent empty lines" +  }, +  { +    "key": "max-line-length", +    "url": "https://stylelint.io/user-guide/rules/max-line-length/", +    "name": "Limit the length of a line" +  }, +  { +    "key": "no-eol-whitespace", +    "url": "https://stylelint.io/user-guide/rules/no-eol-whitespace/", +    "name": "Disallow end-of-line whitespace" +  }, +  { +    "key": "no-missing-end-of-source-newline", +    "url": "https://stylelint.io/user-guide/rules/no-missing-end-of-source-newline/", +    "name": "Disallow missing end-of-source newlines" +  } +] diff --git a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/CssPluginTest.java b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/CssPluginTest.java index 1cbade9..c5d9bbd 100644 --- a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/CssPluginTest.java +++ b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/CssPluginTest.java @@ -36,6 +36,6 @@ public class CssPluginTest {      Plugin.Context context = new Plugin.Context(runtime);      Plugin underTest = new CssPlugin();      underTest.define(context); -    assertThat(context.getExtensions()).hasSize(8); +    assertThat(context.getExtensions()).hasSize(9);    }  } diff --git a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/CssRuleSensorTest.java b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/CssRuleSensorTest.java index f86e24f..471c4ef 100644 --- a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/CssRuleSensorTest.java +++ b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/CssRuleSensorTest.java @@ -60,7 +60,6 @@ public class CssRuleSensorTest {      sensor.describe(sensorDescriptor);      assertThat(sensorDescriptor.name()).isEqualTo("SonarCSS Rules");      assertThat(sensorDescriptor.languages()).containsOnly("css"); -    assertThat(sensorDescriptor.type()).isEqualTo(Type.MAIN);    }    @Test diff --git a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/CssRulesDefinitionTest.java b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/CssRulesDefinitionTest.java index 7dffb35..167aeef 100644 --- a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/CssRulesDefinitionTest.java +++ b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/CssRulesDefinitionTest.java @@ -27,14 +27,35 @@ import static org.assertj.core.api.Assertions.assertThat;  public class CssRulesDefinitionTest {    @Test -  public void test() { -    CssRulesDefinition rulesDefinition = new CssRulesDefinition(); +  public void test_with_external_rules() { +    CssRulesDefinition rulesDefinition = new CssRulesDefinition(true); +    RulesDefinition.Context context = new RulesDefinition.Context(); +    rulesDefinition.define(context); + +    assertThat(context.repositories()).hasSize(2); +    RulesDefinition.Repository mainRepository = context.repository("css"); +    RulesDefinition.Repository externalRepository = context.repository("external_stylelint"); + +    assertThat(externalRepository.name()).isEqualTo("stylelint"); +    assertThat(externalRepository.language()).isEqualTo("css"); +    assertThat(externalRepository.isExternal()).isEqualTo(true); +    assertThat(externalRepository.rules()).hasSize(170); + +    assertThat(mainRepository.name()).isEqualTo("SonarAnalyzer"); +    assertThat(mainRepository.language()).isEqualTo("css"); +    assertThat(mainRepository.isExternal()).isEqualTo(false); +    assertThat(mainRepository.rules()).hasSize(CssRules.getRuleClasses().size()); +  } + +  @Test +  public void test_no_external_rules() throws Exception { +    CssRulesDefinition rulesDefinition = new CssRulesDefinition(false);      RulesDefinition.Context context = new RulesDefinition.Context();      rulesDefinition.define(context); -    RulesDefinition.Repository repository = context.repository("css");      assertThat(context.repositories()).hasSize(1); +    RulesDefinition.Repository repository = context.repository("css");      assertThat(repository.name()).isEqualTo("SonarAnalyzer");      assertThat(repository.language()).isEqualTo("css");      assertThat(repository.isExternal()).isEqualTo(false); diff --git a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/StylelintReportSensorTest.java b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/StylelintReportSensorTest.java new file mode 100644 index 0000000..bbbc8d8 --- /dev/null +++ b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/StylelintReportSensorTest.java @@ -0,0 +1,212 @@ +/* + * SonarCSS + * Copyright (C) 2018-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. + */ +package org.sonar.css.plugin; + +import java.io.File; +import java.io.FileWriter; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.Iterator; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.SonarQubeSide; +import org.sonar.api.SonarRuntime; +import org.sonar.api.batch.fs.InputFile.Type; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.fs.internal.TestInputFileBuilder; +import org.sonar.api.batch.rule.CheckFactory; +import org.sonar.api.batch.rule.Severity; +import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor; +import org.sonar.api.batch.sensor.internal.SensorContextTester; +import org.sonar.api.batch.sensor.issue.ExternalIssue; +import org.sonar.api.internal.SonarRuntimeImpl; +import org.sonar.api.rules.RuleType; +import org.sonar.api.utils.Version; +import org.sonar.api.utils.log.LogTester; +import org.sonar.api.utils.log.LoggerLevel; + +import static org.assertj.core.api.Assertions.assertThat; + +public class StylelintReportSensorTest { + +  @Rule +  public TemporaryFolder tmpDir = new TemporaryFolder(); + +  @Rule +  public final LogTester logTester = new LogTester(); + +  private static final File BASE_DIR = new File("src/test/resources/stylelint-report/").getAbsoluteFile(); +  private static final String CONTENT = ".foo {\n}"; + +  private SensorContextTester context = SensorContextTester.create(BASE_DIR); +  private static final CheckFactory EMPTY_CHECK_FACTORY = new CheckFactory(new TestActiveRules()); +  private static final CheckFactory CHECK_FACTORY_WITH_RULE = new CheckFactory(new TestActiveRules("S4647")); + +  private StylelintReportSensor stylelintReportSensor = new StylelintReportSensor(EMPTY_CHECK_FACTORY); +  private DefaultInputFile inputFile = createInputFile(context, CONTENT, "file.css"); + +  @Before +  public void setUp() throws Exception { +    context.setRuntime(getRuntime(7, 2)); +    context.fileSystem().add(inputFile); +  } + +  @Test +  public void should_add_issues_from_report() throws Exception { +    setReport("report.json"); +    stylelintReportSensor.execute(context); + +    Collection<ExternalIssue> externalIssues = context.allExternalIssues(); +    assertThat(externalIssues).hasSize(2); +    Iterator<ExternalIssue> iterator = externalIssues.iterator(); +    ExternalIssue first = iterator.next(); +    ExternalIssue second = iterator.next(); + +    assertThat(first.type()).isEqualTo(RuleType.BUG); +    assertThat(second.type()).isEqualTo(RuleType.CODE_SMELL); + +    assertThat(first.remediationEffort()).isEqualTo(5); +    assertThat(first.severity()).isEqualTo(Severity.MAJOR); +    assertThat(first.primaryLocation().message()).isEqualTo("external issue message (color-no-invalid-hex)"); +    assertThat(first.primaryLocation().textRange().start().line()).isEqualTo(1); +  } + +  @Test +  public void should_support_absolute_file_paths_in_report() throws Exception { +    String report = "[\n" + +      "  {\n" + +      "    \"source\": \"%s\",\n" + +      "    \"warnings\": [\n" + +      "      {\n" + +      "        \"line\": 1,\n" + +      "        \"rule\": \"color\\-no\\-invalid-hex\",\n" + +      "        \"text\": \"external issue message\"\n" + +      "      }\n" + +      "    ]\n" + +      "  }\n" + +      "]\n"; + +    File reportFile = tmpDir.newFile(); +    FileWriter writer = new FileWriter(reportFile); +    writer.write(String.format(report, inputFile.absolutePath())); +    writer.close(); + +    setReport(reportFile.getAbsolutePath()); +    stylelintReportSensor.execute(context); + +    assertThat(context.allExternalIssues()).hasSize(1); +  } + +  @Test +  public void should_skip_duplicates() throws Exception { +    // when in SQ CSS profile there is an activated rule matching to an external issue, +    // that external issue is ignored +    setReport("report.json"); +    new StylelintReportSensor(CHECK_FACTORY_WITH_RULE).execute(context); + +    Collection<ExternalIssue> externalIssues = context.allExternalIssues(); +    assertThat(externalIssues).hasSize(1); +    assertThat(externalIssues.iterator().next().primaryLocation().message()).isEqualTo("external issue message (comment-no-empty)"); + +    assertThat(logTester.logs(LoggerLevel.DEBUG)) +      .contains("Stylelint issue for rule 'color-no-invalid-hex' is skipped because this rule is activated in your SonarQube profile for CSS (rule key in SQ css:S4647)"); +  } + +  @Test +  public void should_ignore_report_on_older_sonarqube() throws Exception { +    context.setRuntime(getRuntime(7, 1)); +    setReport("report.json"); +    stylelintReportSensor.execute(context); + +    assertThat(context.allExternalIssues()).isEmpty(); +    assertThat(logTester.logs(LoggerLevel.ERROR)).contains("Import of external issues requires SonarQube 7.2 or greater."); +  } + +  @Test +  public void should_do_nothing_when_no_report() throws Exception { +    setReport(""); +    stylelintReportSensor.execute(context); + +    assertThat(context.allExternalIssues()).isEmpty(); +  } + +  @Test +  public void should_log_when_not_existing_report_file() throws Exception { +    setReport("not-exist.json"); +    stylelintReportSensor.execute(context); + +    assertThat(context.allExternalIssues()).isEmpty(); +    assertThat(logTester.logs(LoggerLevel.ERROR)).contains("No issues information will be saved as the report file can't be read."); +  } + +  @Test +  public void should_log_when_not_found_input_file() throws Exception { +    setReport("invalid-file.json"); +    stylelintReportSensor.execute(context); + +    assertThat(context.allExternalIssues()).hasSize(1); +    assertThat(logTester.logs(LoggerLevel.WARN)).contains("No input file found for not-exist.css. No stylelint issues will be imported on this file."); +  } + +  @Test +  public void should_accept_absolute_path_to_report() throws Exception { +    setReport(new File(BASE_DIR, "report.json").getAbsolutePath()); +    stylelintReportSensor.execute(context); +    assertThat(context.allExternalIssues()).hasSize(2); +  } + +  @Test +  public void should_accept_several_reports() throws Exception { +    setReport("report.json, invalid-file.json"); +    stylelintReportSensor.execute(context); +    assertThat(context.allExternalIssues()).hasSize(3); +  } + +  @Test +  public void test_descriptor() throws Exception { +    DefaultSensorDescriptor sensorDescriptor = new DefaultSensorDescriptor(); +    stylelintReportSensor.describe(sensorDescriptor); +    assertThat(sensorDescriptor.name()).isEqualTo("Import of stylelint issues"); +    assertThat(sensorDescriptor.languages()).containsOnly(CssLanguage.KEY); +  } + +  private void setReport(String reportFileName) { +    context.settings().setProperty(CssPlugin.STYLELINT_REPORT_PATHS, reportFileName); +  } + +  private SonarRuntime getRuntime(int major, int minor) { +    return SonarRuntimeImpl.forSonarQube(Version.create(major, minor), SonarQubeSide.SERVER); +  } + +  private static DefaultInputFile createInputFile(SensorContextTester sensorContext, String content, String relativePath) { +    DefaultInputFile testInputFile = new TestInputFileBuilder("moduleKey", relativePath) +      .setModuleBaseDir(sensorContext.fileSystem().baseDirPath()) +      .setType(Type.MAIN) +      .setLanguage(CssLanguage.KEY) +      .setCharset(StandardCharsets.UTF_8) +      .setContents(content) +      .build(); + +    sensorContext.fileSystem().add(testInputFile); +    return testInputFile; +  } +} diff --git a/sonar-css-plugin/src/test/resources/stylelint-report/file.css b/sonar-css-plugin/src/test/resources/stylelint-report/file.css new file mode 100644 index 0000000..59aa5dd --- /dev/null +++ b/sonar-css-plugin/src/test/resources/stylelint-report/file.css @@ -0,0 +1 @@ +File content is set in test file diff --git a/sonar-css-plugin/src/test/resources/stylelint-report/invalid-file.json b/sonar-css-plugin/src/test/resources/stylelint-report/invalid-file.json new file mode 100644 index 0000000..0dc724c --- /dev/null +++ b/sonar-css-plugin/src/test/resources/stylelint-report/invalid-file.json @@ -0,0 +1,22 @@ +[ +  { +    "source": "not-exist.css", +    "warnings": [ +      { +        "line": 1, +        "rule": "color-no-invalid-hex", +        "text": "external issue message" +      } +    ] +  }, +  { +    "source": "file.css", +    "warnings": [ +      { +        "line": 1, +        "rule": "comment-no-empty", +        "text": "external issue message (comment-no-empty)" +      } +    ] +  } +] diff --git a/sonar-css-plugin/src/test/resources/stylelint-report/report.json b/sonar-css-plugin/src/test/resources/stylelint-report/report.json new file mode 100644 index 0000000..6e758b3 --- /dev/null +++ b/sonar-css-plugin/src/test/resources/stylelint-report/report.json @@ -0,0 +1,17 @@ +[ +  { +    "source": "file.css", +    "warnings": [ +      { +        "line": 1, +        "rule": "color-no-invalid-hex", +        "text": "external issue message (color-no-invalid-hex)" +      }, +      { +        "line": 1, +        "rule": "comment-no-empty", +        "text": "external issue message (comment-no-empty)" +      } +    ] +  } +] | 
