aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.DS_Storebin8196 -> 0 bytes
-rw-r--r--.gitignore2
-rw-r--r--its/plugin/projects/external-report-project/report.json55
-rw-r--r--its/plugin/projects/external-report-project/src/file1.css111
-rw-r--r--its/plugin/projects/external-report-project/src/file2.css114
-rw-r--r--its/plugin/projects/external-report-project/stylelintconfig.json162
-rw-r--r--its/plugin/src/test/java/org/sonar/css/its/StylelintReportTest.java62
-rw-r--r--its/plugin/src/test/java/org/sonar/css/its/Tests.java4
-rw-r--r--pom.xml2
-rw-r--r--sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssPlugin.java24
-rw-r--r--sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssRuleSensor.java23
-rw-r--r--sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssRules.java25
-rw-r--r--sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssRulesDefinition.java10
-rw-r--r--sonar-css-plugin/src/main/java/org/sonar/css/plugin/StylelintReport.java38
-rw-r--r--sonar-css-plugin/src/main/java/org/sonar/css/plugin/StylelintReportSensor.java219
-rw-r--r--sonar-css-plugin/src/main/resources/org/sonar/l10n/css/rules/stylelint/rules.json852
-rw-r--r--sonar-css-plugin/src/test/java/org/sonar/css/plugin/CssPluginTest.java2
-rw-r--r--sonar-css-plugin/src/test/java/org/sonar/css/plugin/CssRuleSensorTest.java1
-rw-r--r--sonar-css-plugin/src/test/java/org/sonar/css/plugin/CssRulesDefinitionTest.java27
-rw-r--r--sonar-css-plugin/src/test/java/org/sonar/css/plugin/StylelintReportSensorTest.java212
-rw-r--r--sonar-css-plugin/src/test/resources/stylelint-report/file.css1
-rw-r--r--sonar-css-plugin/src/test/resources/stylelint-report/invalid-file.json22
-rw-r--r--sonar-css-plugin/src/test/resources/stylelint-report/report.json17
23 files changed, 1948 insertions, 37 deletions
diff --git a/.DS_Store b/.DS_Store
deleted file mode 100644
index ec71b6d..0000000
--- a/.DS_Store
+++ /dev/null
Binary files differ
diff --git a/.gitignore b/.gitignore
index e02da25..469e759 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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 {
diff --git a/pom.xml b/pom.xml
index eed37f0..9ee3a0c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -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)"
+ }
+ ]
+ }
+]