diff options
| author | Elena Vilchik | 2018-06-22 18:03:05 +0200 | 
|---|---|---|
| committer | Amaury Levé | 2018-06-22 18:03:05 +0200 | 
| commit | 88cc2bbfbda30d8ba8148dd4a7d0392a3171fe50 (patch) | |
| tree | 9fbe1cd5e45c63c1958e63fd0189d761b2afd563 /sonar-css-plugin/src/main/java | |
| parent | 7385260825c79419a920bdf835e925cef916b404 (diff) | |
| download | sonar-css-88cc2bbfbda30d8ba8148dd4a7d0392a3171fe50.tar.bz2 | |
Support import of external stylelint issues report (#65)
Diffstat (limited to 'sonar-css-plugin/src/main/java')
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; +  } +}  | 
