diff options
6 files changed, 223 insertions, 16 deletions
| diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/AnalysisWarningsWrapper.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/AnalysisWarningsWrapper.java new file mode 100644 index 0000000..9abc96e --- /dev/null +++ b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/AnalysisWarningsWrapper.java @@ -0,0 +1,43 @@ +/* + * 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 org.sonar.api.batch.InstantiationStrategy; +import org.sonar.api.batch.ScannerSide; +import org.sonar.api.notifications.AnalysisWarnings; + +/** + * Wrap an AnalysisWarnings instance, available since SQ API 7.4. + * Do not load this class on older runtimes. + * Drop this class when the minimum supported version of SonarQube API reaches 7.4. + */ +@ScannerSide +@InstantiationStrategy("PER_BATCH") +public class AnalysisWarningsWrapper { +  private final AnalysisWarnings analysisWarnings; + +  public AnalysisWarningsWrapper(AnalysisWarnings analysisWarnings) { +    this.analysisWarnings = analysisWarnings; +  } + +  public void addUnique(String text) { +    this.analysisWarnings.addUnique(text); +  } +} 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 323f628..93cba33 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 @@ -20,6 +20,8 @@  package org.sonar.css.plugin;  import org.sonar.api.Plugin; +import org.sonar.api.SonarProduct; +import org.sonar.api.SonarRuntime;  import org.sonar.api.config.PropertyDefinition;  import org.sonar.api.resources.Qualifiers;  import org.sonar.api.utils.Version; @@ -27,6 +29,8 @@ import org.sonar.css.plugin.bundle.CssBundleHandler;  public class CssPlugin implements Plugin { +  private static final Version ANALYSIS_WARNINGS_MIN_SUPPORTED_SQ_VERSION = Version.create(7, 4); +    static final String FILE_SUFFIXES_KEY = "sonar.css.file.suffixes";    public static final String FILE_SUFFIXES_DEFVALUE = ".css,.less,.scss"; @@ -86,5 +90,17 @@ public class CssPlugin implements Plugin {            .multiValues(true)            .build());      } + +    if (isAnalysisWarningsSupported(context.getRuntime())) { +      context.addExtension(AnalysisWarningsWrapper.class); +    } +  } + +  /** +   * Drop this and related when the minimum supported version of SonarQube API reaches 7.4. +   */ +  private static boolean isAnalysisWarningsSupported(SonarRuntime runtime) { +    return runtime.getApiVersion().isGreaterThanOrEqual(ANALYSIS_WARNINGS_MIN_SUPPORTED_SQ_VERSION) +      && runtime.getProduct() != SonarProduct.SONARLINT;    }  } 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 0e4f5fc..fa078fb 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 @@ -30,6 +30,7 @@ import java.nio.file.Paths;  import java.util.Collections;  import java.util.regex.Matcher;  import java.util.regex.Pattern; +import javax.annotation.Nullable;  import org.apache.commons.io.IOUtils;  import org.sonar.api.batch.fs.FileSystem;  import org.sonar.api.batch.fs.InputFile; @@ -51,18 +52,29 @@ public class CssRuleSensor implements Sensor {    private static final Logger LOG = Loggers.get(CssRuleSensor.class);    private static final int MIN_NODE_VERSION = 6; +  private static final String WARNING_PREFIX = "CSS files were not analyzed. ";    private final BundleHandler bundleHandler;    private final CssRules cssRules;    private final LinterCommandProvider linterCommandProvider; +  @Nullable +  private final AnalysisWarningsWrapper analysisWarnings;    private final ExternalProcessStreamConsumer externalProcessStreamConsumer = new ExternalProcessStreamConsumer();    public CssRuleSensor(BundleHandler bundleHandler,                         CheckFactory checkFactory, -                       LinterCommandProvider linterCommandProvider) { +                       LinterCommandProvider linterCommandProvider, +                       @Nullable AnalysisWarningsWrapper analysisWarnings) {      this.bundleHandler = bundleHandler;      this.linterCommandProvider = linterCommandProvider;      this.cssRules = new CssRules(checkFactory); +    this.analysisWarnings = analysisWarnings; +  } + +  public CssRuleSensor(BundleHandler bundleHandler, +                       CheckFactory checkFactory, +                       LinterCommandProvider linterCommandProvider) { +    this(bundleHandler, checkFactory, linterCommandProvider, null);    }    @Override @@ -127,6 +139,9 @@ public class CssRuleSensor implements Sensor {        version = IOUtils.toString(process.getInputStream(), StandardCharsets.UTF_8).trim();      } catch (Exception e) {        LOG.error("Failed to get Node.js version. " + messageSuffix, e); +      if (analysisWarnings != null) { +        analysisWarnings.addUnique(WARNING_PREFIX + "Node.js version could not be detected using command: " + nodeExecutable + " -v"); +      }        return false;      } @@ -135,13 +150,19 @@ public class CssRuleSensor implements Sensor {      if (versionMatcher.matches()) {        int major = Integer.parseInt(versionMatcher.group(1));        if (major < MIN_NODE_VERSION) { -        String message = String.format("Only Node.js v%s or later is supported, got %s. %s", MIN_NODE_VERSION, version, messageSuffix); -        LOG.error(message); +        String message = String.format("Only Node.js v%s or later is supported, got %s.", MIN_NODE_VERSION, version); +        LOG.error(message + ' ' + messageSuffix); +        if (analysisWarnings != null) { +          analysisWarnings.addUnique(WARNING_PREFIX + message); +        }          return false;        }      } else { -      String message = String.format("Failed to parse Node.js version, got '%s'. %s", version, messageSuffix); -      LOG.error(message); +      String message = String.format("Failed to parse Node.js version, got '%s'.", version); +      LOG.error(message + ' ' + messageSuffix); +      if (analysisWarnings != null) { +        analysisWarnings.addUnique(WARNING_PREFIX + message); +      }        return false;      } diff --git a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/AnalysisWarningsWrapperTest.java b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/AnalysisWarningsWrapperTest.java new file mode 100644 index 0000000..3e811f7 --- /dev/null +++ b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/AnalysisWarningsWrapperTest.java @@ -0,0 +1,39 @@ +/* + * 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 org.junit.Test; +import org.sonar.api.notifications.AnalysisWarnings; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class AnalysisWarningsWrapperTest { +  @Test +  public void delegate_to_analysisWarnings() { +    AnalysisWarnings analysisWarnings = mock(AnalysisWarnings.class); + +    AnalysisWarningsWrapper wrapper = new AnalysisWarningsWrapper(analysisWarnings); + +    String warning = "some warning"; +    wrapper.addUnique(warning); +    verify(analysisWarnings).addUnique(warning); +  } +} 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 21a944a..5fb2fd7 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 @@ -47,4 +47,22 @@ public class CssPluginTest {      underTest.define(context);      assertThat(context.getExtensions()).hasSize(12);    } + +  @Test +  public void count_extensions_7_4() { +    SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.create(7, 4), SonarQubeSide.SCANNER); +    Plugin.Context context = new Plugin.Context(runtime); +    Plugin underTest = new CssPlugin(); +    underTest.define(context); +    assertThat(context.getExtensions()).hasSize(13); +  } + +  @Test +  public void count_extensions_7_4_sonarlint() { +    SonarRuntime runtime = SonarRuntimeImpl.forSonarLint(Version.create(7, 4)); +    Plugin.Context context = new Plugin.Context(runtime); +    Plugin underTest = new CssPlugin(); +    underTest.define(context); +    assertThat(context.getExtensions()).hasSize(12); +  }  } 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 782b10f..9a45938 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 @@ -27,6 +27,7 @@ import java.nio.file.Files;  import java.nio.file.Path;  import java.nio.file.Paths;  import java.util.concurrent.TimeUnit; +import javax.annotation.Nullable;  import org.awaitility.Awaitility;  import org.junit.Before;  import org.junit.Rule; @@ -49,6 +50,11 @@ import org.sonar.css.plugin.bundle.CssBundleHandler;  import static org.assertj.core.api.Assertions.assertThat;  import static org.awaitility.Awaitility.await; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.matches; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions;  public class CssRuleSensorTest { @@ -67,6 +73,7 @@ public class CssRuleSensorTest {    private SensorContextTester context = SensorContextTester.create(BASE_DIR);    private DefaultInputFile inputFile = createInputFile(context, "some css content\n on 2 lines", "dir/file.css"); +  private AnalysisWarningsWrapper analysisWarnings = mock(AnalysisWarningsWrapper.class);    @Before    public void setUp() { @@ -76,7 +83,7 @@ public class CssRuleSensorTest {    @Test    public void test_descriptor() { -    CssRuleSensor sensor = new CssRuleSensor(new CssBundleHandler(), checkFactory, new StylelintCommandProvider()); +    CssRuleSensor sensor = new CssRuleSensor(new CssBundleHandler(), checkFactory, new StylelintCommandProvider(), analysisWarnings);      DefaultSensorDescriptor sensorDescriptor = new DefaultSensorDescriptor();      sensor.describe(sensorDescriptor);      assertThat(sensorDescriptor.name()).isEqualTo("SonarCSS Rules"); @@ -86,7 +93,7 @@ public class CssRuleSensorTest {    @Test    public void test_execute() throws IOException {      TestLinterCommandProvider commandProvider = getCommandProvider(); -    CssRuleSensor sensor = new CssRuleSensor(new TestBundleHandler(), checkFactory, commandProvider); +    CssRuleSensor sensor = createCssRuleSensor(commandProvider);      sensor.execute(context);      assertThat(context.allIssues()).hasSize(1); @@ -95,45 +102,100 @@ public class CssRuleSensorTest {      Path configPath = Paths.get(context.fileSystem().workDir().getAbsolutePath(), "testconfig.json");      assertThat(Files.readAllLines(configPath)).containsOnly("{\"rules\":{\"color-no-invalid-hex\":true,\"declaration-block-no-duplicate-properties\":[true,{\"ignore\":[\"consecutive-duplicates-with-different-values\"]}]}}"); +    verifyZeroInteractions(analysisWarnings);    }    @Test    public void test_invalid_node() {      TestLinterCommandProvider commandProvider = getCommandProvider();      commandProvider.nodeExecutable += " " + TestLinterCommandProvider.resourceScript("/executables/invalidNodeVersion.js"); -    CssRuleSensor sensor = new CssRuleSensor(new TestBundleHandler(), checkFactory, commandProvider); +    CssRuleSensor sensor = createCssRuleSensor(commandProvider);      sensor.execute(context);      assertThat(context.allIssues()).hasSize(0);      assertThat(logTester.logs(LoggerLevel.ERROR)).contains("Failed to parse Node.js version, got 'Invalid version'. No CSS files will be analyzed."); +    verifyZeroInteractions(analysisWarnings);    }    @Test    public void test_no_node() {      TestLinterCommandProvider commandProvider = getCommandProvider();      commandProvider.nodeExecutable = TestLinterCommandProvider.resourceScript("/executables/invalidNodeVersion.js"); -    CssRuleSensor sensor = new CssRuleSensor(new TestBundleHandler(), checkFactory, commandProvider); +    CssRuleSensor sensor = createCssRuleSensor(commandProvider);      sensor.execute(context);      assertThat(context.allIssues()).hasSize(0);      assertThat(logTester.logs(LoggerLevel.ERROR)).contains("Failed to get Node.js version. No CSS files will be analyzed."); +    verifyZeroInteractions(analysisWarnings);    }    @Test    public void test_old_node() {      TestLinterCommandProvider commandProvider = getCommandProvider();      commandProvider.nodeExecutable += " " + TestLinterCommandProvider.resourceScript("/executables/oldNodeVersion.js"); -    CssRuleSensor sensor = new CssRuleSensor(new TestBundleHandler(), checkFactory, commandProvider); +    CssRuleSensor sensor = createCssRuleSensor(commandProvider);      sensor.execute(context);      assertThat(context.allIssues()).hasSize(0);      assertThat(logTester.logs(LoggerLevel.ERROR)).contains("Only Node.js v6 or later is supported, got 3.2.1. No CSS files will be analyzed."); +    verifyZeroInteractions(analysisWarnings); +  } + +  @Test +  public void test_execute_with_analysisWarnings() throws IOException { +    TestLinterCommandProvider commandProvider = getCommandProvider(); +    CssRuleSensor sensor = createCssRuleSensor(commandProvider, analysisWarnings); +    sensor.execute(context); + +    assertThat(context.allIssues()).hasSize(1); +    Issue issue = context.allIssues().iterator().next(); +    assertThat(issue.primaryLocation().message()).isEqualTo("some message"); + +    Path configPath = Paths.get(context.fileSystem().workDir().getAbsolutePath(), "testconfig.json"); +    assertThat(Files.readAllLines(configPath)).containsOnly("{\"rules\":{\"color-no-invalid-hex\":true,\"declaration-block-no-duplicate-properties\":[true,{\"ignore\":[\"consecutive-duplicates-with-different-values\"]}]}}"); +    verifyZeroInteractions(analysisWarnings); +  } + +  @Test +  public void test_invalid_node_with_analysisWarnings() { +    TestLinterCommandProvider commandProvider = getCommandProvider(); +    commandProvider.nodeExecutable += " " + TestLinterCommandProvider.resourceScript("/executables/invalidNodeVersion.js"); +    CssRuleSensor sensor = createCssRuleSensor(commandProvider, analysisWarnings); +    sensor.execute(context); + +    assertThat(context.allIssues()).hasSize(0); +    assertThat(logTester.logs(LoggerLevel.ERROR)).contains("Failed to parse Node.js version, got 'Invalid version'. No CSS files will be analyzed."); +    verify(analysisWarnings).addUnique(eq("CSS files were not analyzed. Failed to parse Node.js version, got 'Invalid version'.")); +  } + +  @Test +  public void test_no_node_with_analysisWarnings() { +    TestLinterCommandProvider commandProvider = getCommandProvider(); +    commandProvider.nodeExecutable = TestLinterCommandProvider.resourceScript("/executables/invalidNodeVersion.js"); +    CssRuleSensor sensor = createCssRuleSensor(commandProvider, analysisWarnings); +    sensor.execute(context); + +    assertThat(context.allIssues()).hasSize(0); +    assertThat(logTester.logs(LoggerLevel.ERROR)).contains("Failed to get Node.js version. No CSS files will be analyzed."); +    verify(analysisWarnings).addUnique(matches("CSS files were not analyzed. Node.js version could not be detected using command: .* -v")); +  } + +  @Test +  public void test_old_node_with_analysisWarnings() { +    TestLinterCommandProvider commandProvider = getCommandProvider(); +    commandProvider.nodeExecutable += " " + TestLinterCommandProvider.resourceScript("/executables/oldNodeVersion.js"); +    CssRuleSensor sensor = createCssRuleSensor(commandProvider, analysisWarnings); +    sensor.execute(context); + +    assertThat(context.allIssues()).hasSize(0); +    assertThat(logTester.logs(LoggerLevel.ERROR)).contains("Only Node.js v6 or later is supported, got 3.2.1. No CSS files will be analyzed."); +    verify(analysisWarnings).addUnique(eq("CSS files were not analyzed. Only Node.js v6 or later is supported, got 3.2.1."));    }    @Test    public void test_error() {      TestLinterCommandProvider commandProvider = new TestLinterCommandProvider().nodeScript("/executables/mockError.js", inputFile.absolutePath()); -    CssRuleSensor sensor = new CssRuleSensor(new TestBundleHandler(), checkFactory, commandProvider); +    CssRuleSensor sensor = createCssRuleSensor(commandProvider);      sensor.execute(context);      assertThat(logTester.logs(LoggerLevel.ERROR)).anyMatch(s -> s.startsWith("Failed to run external linting process")); @@ -142,7 +204,7 @@ public class CssRuleSensorTest {    @Test    public void test_not_execute_rules_if_nothing_enabled() {      TestLinterCommandProvider commandProvider = new TestLinterCommandProvider().nodeScript("/executables/mockError.js", inputFile.absolutePath()); -    CssRuleSensor sensor = new CssRuleSensor(new TestBundleHandler(), new CheckFactory(new TestActiveRules()), commandProvider); +    CssRuleSensor sensor = new CssRuleSensor(new TestBundleHandler(), new CheckFactory(new TestActiveRules()), commandProvider, analysisWarnings);      sensor.execute(context);      assertThat(logTester.logs(LoggerLevel.WARN)).contains("No rules are activated in CSS Quality Profile"); @@ -151,7 +213,7 @@ public class CssRuleSensorTest {    @Test    public void test_stylelint_throws() {      TestLinterCommandProvider commandProvider = new TestLinterCommandProvider().nodeScript("/executables/mockThrow.js", inputFile.absolutePath()); -    CssRuleSensor sensor = new CssRuleSensor(new TestBundleHandler(), checkFactory, commandProvider); +    CssRuleSensor sensor = createCssRuleSensor(commandProvider);      sensor.execute(context);      await().until(() -> logTester.logs(LoggerLevel.ERROR) @@ -161,7 +223,7 @@ public class CssRuleSensorTest {    @Test    public void test_stylelint_exitvalue() {      TestLinterCommandProvider commandProvider = new TestLinterCommandProvider().nodeScript("/executables/mockExit.js", "1"); -    CssRuleSensor sensor = new CssRuleSensor(new TestBundleHandler(), checkFactory, commandProvider); +    CssRuleSensor sensor = createCssRuleSensor(commandProvider);      sensor.execute(context);      await().until(() -> logTester.logs(LoggerLevel.ERROR) @@ -174,7 +236,7 @@ public class CssRuleSensorTest {      context.fileSystem().setWorkDir(tmpDir.getRoot().toPath());      DefaultInputFile inputFile = createInputFile(context, "some css content\n on 2 lines", "dir/file.css");      TestLinterCommandProvider rulesExecution = new TestLinterCommandProvider().nodeScript("/executables/mockSyntaxError.js", inputFile.absolutePath()); -    CssRuleSensor sensor = new CssRuleSensor(new TestBundleHandler(), checkFactory, rulesExecution); +    CssRuleSensor sensor = createCssRuleSensor(rulesExecution);      sensor.execute(context);      assertThat(logTester.logs(LoggerLevel.ERROR)).contains("Failed to parse " + inputFile.uri()); @@ -186,7 +248,7 @@ public class CssRuleSensorTest {      context.fileSystem().setWorkDir(tmpDir.getRoot().toPath());      DefaultInputFile inputFile = createInputFile(context, "some css content\n on 2 lines", "dir/file.css");      TestLinterCommandProvider rulesExecution = new TestLinterCommandProvider().nodeScript("/executables/mockUnknownRule.js", inputFile.absolutePath()); -    CssRuleSensor sensor = new CssRuleSensor(new TestBundleHandler(), checkFactory, rulesExecution); +    CssRuleSensor sensor = createCssRuleSensor(rulesExecution);      sensor.execute(context);      assertThat(logTester.logs(LoggerLevel.ERROR)).contains("Unknown stylelint rule or rule not enabled: 'unknown-rule-key'"); @@ -205,6 +267,14 @@ public class CssRuleSensorTest {      return inputFile;    } +  private CssRuleSensor createCssRuleSensor(TestLinterCommandProvider commandProvider) { +    return new CssRuleSensor(new TestBundleHandler(), checkFactory, commandProvider); +  } + +  private CssRuleSensor createCssRuleSensor(TestLinterCommandProvider commandProvider, @Nullable AnalysisWarningsWrapper analysisWarnings) { +    return new CssRuleSensor(new TestBundleHandler(), checkFactory, commandProvider, analysisWarnings); +  } +    private TestLinterCommandProvider getCommandProvider() {      return new TestLinterCommandProvider().nodeScript("/executables/mockStylelint.js", inputFile.absolutePath());    } | 
