aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-css-plugin/src/main/java
diff options
context:
space:
mode:
authorElena Vilchik2018-06-22 18:03:05 +0200
committerAmaury Levé2018-06-22 18:03:05 +0200
commit88cc2bbfbda30d8ba8148dd4a7d0392a3171fe50 (patch)
tree9fbe1cd5e45c63c1958e63fd0189d761b2afd563 /sonar-css-plugin/src/main/java
parent7385260825c79419a920bdf835e925cef916b404 (diff)
downloadsonar-css-88cc2bbfbda30d8ba8148dd4a7d0392a3171fe50.tar.bz2
Support import of external stylelint issues report (#65)
Diffstat (limited to 'sonar-css-plugin/src/main/java')
-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
6 files changed, 310 insertions, 29 deletions
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;
+ }
+}