aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-css-plugin/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'sonar-css-plugin/src/main')
-rw-r--r--sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssPlugin.java10
-rw-r--r--sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssRuleSensor.java99
-rw-r--r--sonar-css-plugin/src/main/java/org/sonar/css/plugin/ExternalProcessStreamConsumer.java68
-rw-r--r--sonar-css-plugin/src/main/java/org/sonar/css/plugin/LinterCommandProvider.java6
-rw-r--r--sonar-css-plugin/src/main/java/org/sonar/css/plugin/StylelintCommandProvider.java52
5 files changed, 50 insertions, 185 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 6f78be1..9e78205 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
@@ -37,7 +37,7 @@ public class CssPlugin implements Plugin {
public static final String STYLELINT_REPORT_PATHS = "sonar.css.stylelint.reportPaths";
public static final String STYLELINT_REPORT_PATHS_DEFAULT_VALUE = "";
- public static final String NODE_EXECUTABLE = "sonar.css.node";
+ public static final String FORMER_NODE_EXECUTABLE = "sonar.css.node";
private static final String CSS_CATEGORY = "CSS";
private static final String LINTER_SUBCATEGORY = "Popular Rule Engines";
@@ -66,14 +66,6 @@ public class CssPlugin implements Plugin {
.category(CSS_CATEGORY)
.onQualifiers(Qualifiers.PROJECT)
.multiValues(true)
- .build(),
-
- PropertyDefinition.builder(NODE_EXECUTABLE)
- .name("Node.js executable")
- .description("Path to the Node.js executable that will be used to run the analysis of CSS files. When not set, expects 'node' to be in the path.")
- .subCategory(GENERAL_SUBCATEGORY)
- .category(CSS_CATEGORY)
- .hidden()
.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 2b268cd..82e6306 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
@@ -31,7 +31,6 @@ 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;
import org.sonar.api.batch.rule.CheckFactory;
@@ -47,33 +46,36 @@ 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;
+import org.sonarsource.nodejs.NodeCommand;
+import org.sonarsource.nodejs.NodeCommandException;
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,
- @Nullable AnalysisWarningsWrapper analysisWarnings) {
+ public CssRuleSensor(
+ BundleHandler bundleHandler,
+ CheckFactory checkFactory,
+ 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) {
+ public CssRuleSensor(
+ BundleHandler bundleHandler,
+ CheckFactory checkFactory,
+ LinterCommandProvider linterCommandProvider
+ ) {
this(bundleHandler, checkFactory, linterCommandProvider, null);
}
@@ -86,39 +88,38 @@ public class CssRuleSensor implements Sensor {
@Override
public void execute(SensorContext context) {
+ // fixme add log and UI warn when old property is provided
+
if (cssRules.isEmpty()) {
LOG.warn("No rules are activated in CSS Quality Profile");
return;
}
- if (!checkCompatibleNodeVersion(context)) {
- return;
- }
-
File deployDestination = context.fileSystem().workDir();
- bundleHandler.deployBundle(deployDestination);
- String[] commandParts = linterCommandProvider.commandParts(deployDestination, context);
try {
- ProcessBuilder processBuilder = new ProcessBuilder(commandParts);
+ bundleHandler.deployBundle(deployDestination);
createLinterConfig(deployDestination);
- Process process = processBuilder.start();
StringBuilder output = new StringBuilder();
- externalProcessStreamConsumer.consumeStream(process.getInputStream(), output::append);
- externalProcessStreamConsumer.consumeStream(process.getErrorStream(), LOG::error);
- if (isSuccessful(process)) {
+
+ NodeCommand nodeCommand = linterCommandProvider.nodeCommand(deployDestination, context, output::append, LOG::error);
+ LOG.debug("Starting process: " + nodeCommand.toString());
+ nodeCommand.start();
+
+ if (isSuccessful(nodeCommand.waitFor())) {
saveIssues(context, output.toString());
}
+ } catch (NodeCommandException e) {
+ LOG.error(e.getMessage() + " No CSS files will be analyzed.", e);
+ if (analysisWarnings != null) {
+ analysisWarnings.addUnique("CSS files were not analyzed. " + e.getMessage());
+ }
} catch (Exception e) {
- LOG.error("Failed to run external linting process " + String.join(" ", commandParts), e);
- } finally {
- externalProcessStreamConsumer.shutdownNow();
+ LOG.error("Failed to run external linting process", e);
}
}
- private boolean isSuccessful(Process process) throws InterruptedException {
- int exitValue = process.waitFor();
- externalProcessStreamConsumer.await();
+ private boolean isSuccessful(int exitValue) {
// exit codes 0 and 2 are expected. 0 - means no issues were found, 2 - means that at least one "error-level" rule found issue
// see https://github.com/stylelint/stylelint/blob/master/docs/user-guide/cli.md#exit-codes
boolean isSuccessful = exitValue == 0 || exitValue == 2;
@@ -128,48 +129,6 @@ public class CssRuleSensor implements Sensor {
return isSuccessful;
}
- private boolean checkCompatibleNodeVersion(SensorContext context) {
- String nodeExecutable = linterCommandProvider.nodeExecutable(context.config());
- LOG.debug("Checking node version");
- String messageSuffix = "No CSS files will be analyzed.";
-
- String version;
- try {
- Process process = Runtime.getRuntime().exec(nodeExecutable + " -v");
- 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;
- }
-
- Pattern versionPattern = Pattern.compile("v?(\\d+)\\.\\d+\\.\\d+");
- Matcher versionMatcher = versionPattern.matcher(version);
- 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.", 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'.", version);
- LOG.error(message + ' ' + messageSuffix);
- if (analysisWarnings != null) {
- analysisWarnings.addUnique(WARNING_PREFIX + message);
- }
- return false;
- }
-
- LOG.debug(String.format("Using Node.js %s", version));
- return true;
- }
-
private void createLinterConfig(File deployDestination) throws IOException {
String configPath = linterCommandProvider.configPath(deployDestination);
StylelintConfig config = cssRules.getConfig();
diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/ExternalProcessStreamConsumer.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/ExternalProcessStreamConsumer.java
deleted file mode 100644
index ef843c4..0000000
--- a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/ExternalProcessStreamConsumer.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * SonarCSS
- * Copyright (C) 2018-2019 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.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.nio.charset.StandardCharsets;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Consumer;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
-
-class ExternalProcessStreamConsumer {
-
- private static final Logger LOG = Loggers.get(ExternalProcessStreamConsumer.class);
- private ExecutorService executorService;
-
- ExternalProcessStreamConsumer() {
- executorService = Executors.newCachedThreadPool(r -> {
- Thread thread = new Thread(r);
- thread.setName("nodejs-stream-consumer");
- thread.setDaemon(true);
- return thread;
- });
- }
-
- void consumeStream(InputStream inputStream, Consumer<String> consumer) {
- executorService.submit(() -> {
- try (BufferedReader errorReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
- errorReader.lines().forEach(consumer);
- } catch (IOException e) {
- LOG.error("Error while reading stream", e);
- }
- });
- }
-
- void await() throws InterruptedException {
- executorService.shutdown();
- if (!executorService.awaitTermination(5, TimeUnit.MINUTES)) {
- LOG.error("External process stream consumer timed out");
- }
- }
-
- void shutdownNow() {
- executorService.shutdownNow();
- }
-}
diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/LinterCommandProvider.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/LinterCommandProvider.java
index 133e9a4..74a343c 100644
--- a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/LinterCommandProvider.java
+++ b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/LinterCommandProvider.java
@@ -20,14 +20,14 @@
package org.sonar.css.plugin;
import java.io.File;
+import java.util.function.Consumer;
import org.sonar.api.batch.sensor.SensorContext;
-import org.sonar.api.config.Configuration;
+import org.sonarsource.nodejs.NodeCommand;
public interface LinterCommandProvider {
- String[] commandParts(File deployDestination, SensorContext context);
+ NodeCommand nodeCommand(File deployDestination, SensorContext context, Consumer<String> output, Consumer<String> error);
String configPath(File deployDestination);
- String nodeExecutable(Configuration configuration);
}
diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/StylelintCommandProvider.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/StylelintCommandProvider.java
index 0265c20..90d3ae8 100644
--- a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/StylelintCommandProvider.java
+++ b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/StylelintCommandProvider.java
@@ -21,38 +21,43 @@ package org.sonar.css.plugin;
import java.io.File;
import java.nio.file.Paths;
-import java.util.Optional;
+import java.util.function.Consumer;
import org.sonar.api.batch.ScannerSide;
import org.sonar.api.batch.sensor.SensorContext;
-import org.sonar.api.config.Configuration;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
+import org.sonarsource.nodejs.NodeCommand;
+import org.sonarsource.nodejs.NodeCommandException;
@ScannerSide
public class StylelintCommandProvider implements LinterCommandProvider {
- private static final Logger LOG = Loggers.get(StylelintCommandProvider.class);
-
private static final String CONFIG_PATH = "css-bundle/stylelintconfig.json";
- private static final String NODE_EXECUTABLE_DEFAULT = "node";
-
- private String nodeExecutable = null;
@Override
- public String[] commandParts(File deployDestination, SensorContext context) {
+ public NodeCommand nodeCommand(File deployDestination, SensorContext context, Consumer<String> output, Consumer<String> error) {
String projectBaseDir = context.fileSystem().baseDir().getAbsolutePath();
String[] suffixes = context.config().getStringArray(CssPlugin.FILE_SUFFIXES_KEY);
String filesGlob = "**" + File.separator + "*{" + String.join(",", suffixes) + "}";
String filesToAnalyze = Paths.get(projectBaseDir, "TOREPLACE").toString();
filesToAnalyze = filesToAnalyze.replace("TOREPLACE", filesGlob);
- return new String[]{
- nodeExecutable(context.config()),
+ String[] args = {
new File(deployDestination, "css-bundle/node_modules/stylelint/bin/stylelint").getAbsolutePath(),
filesToAnalyze,
"--config", new File(deployDestination, CONFIG_PATH).getAbsolutePath(),
"-f", "json"
};
+
+ try {
+ return NodeCommand.builder()
+ .outputConsumer(output)
+ .errorConsumer(error)
+ .minNodeVersion(6)
+ .configuration(context.config())
+ .nodeJsArgs(args)
+ .build();
+ } catch (IllegalArgumentException e) {
+ throw new NodeCommandException(e.getMessage(), e);
+ }
}
@Override
@@ -60,27 +65,4 @@ public class StylelintCommandProvider implements LinterCommandProvider {
return new File(deployDestination, CONFIG_PATH).getAbsolutePath();
}
- @Override
- public String nodeExecutable(Configuration configuration) {
- if (nodeExecutable == null) {
- nodeExecutable = retrieveNodeExecutableFromConfig(configuration);
- }
-
- return nodeExecutable;
- }
-
- private static String retrieveNodeExecutableFromConfig(Configuration configuration) {
- Optional<String> nodeExecutableOptional = configuration.get(CssPlugin.NODE_EXECUTABLE);
- if (nodeExecutableOptional.isPresent()) {
- String nodeExecutable = nodeExecutableOptional.get();
- File file = new File(nodeExecutable);
- if (file.exists()) {
- return nodeExecutable;
- }
-
- LOG.warn("Provided node executable file does not exist: " + file + ". Fallback to using 'node' from path.");
- }
-
- return NODE_EXECUTABLE_DEFAULT;
- }
}