Skip to content

Commit

Permalink
Fix Issue Pablissimo#75 - support --project and --type-check (Pabliss…
Browse files Browse the repository at this point in the history
…imo#88)

Fix issue Pablissimo#75: adds support for two new options:

- ```sonar.ts.tslintprojectpath``` - set to 'tsconfig.json' or similar, the path to your TypeScript configuration file describing what files to compile and lint
- ```sonar.ts.tslinttypecheck``` - true/false, defaults to false - if true, requests ```tslint``` perform a type-check too, which allows certain rules requiring type information to work
  • Loading branch information
Pablissimo committed Jan 18, 2017
1 parent 224a249 commit a2b382d
Show file tree
Hide file tree
Showing 6 changed files with 328 additions and 76 deletions.
43 changes: 43 additions & 0 deletions src/main/java/com/pablissimo/sonar/TsLintExecutorConfig.java
@@ -1,10 +1,37 @@
package com.pablissimo.sonar;

import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.config.Settings;

public class TsLintExecutorConfig {
public static final String CONFIG_FILENAME = "tslint.json";
public static final String TSLINT_FALLBACK_PATH = "node_modules/tslint/bin/tslint";

private String pathToTsLint;
private String configFile;
private String rulesDir;
private String pathToTsConfig;
private boolean shouldPerformTypeCheck;

private Integer timeoutMs;

public static TsLintExecutorConfig fromSettings(Settings settings, SensorContext ctx, PathResolver resolver) {
TsLintExecutorConfig toReturn = new TsLintExecutorConfig();

toReturn.setPathToTsLint(resolver.getPath(ctx, TypeScriptPlugin.SETTING_TS_LINT_PATH, TSLINT_FALLBACK_PATH));
toReturn.setConfigFile(resolver.getPath(ctx, TypeScriptPlugin.SETTING_TS_LINT_CONFIG_PATH, CONFIG_FILENAME));
toReturn.setRulesDir(resolver.getPath(ctx, TypeScriptPlugin.SETTING_TS_LINT_RULES_DIR, null));
toReturn.setPathToTsConfig(resolver.getPath(ctx, TypeScriptPlugin.SETTING_TS_LINT_PROJECT_PATH, null));

toReturn.setTimeoutMs(Math.max(5000, settings.getInt(TypeScriptPlugin.SETTING_TS_LINT_TIMEOUT)));
toReturn.setShouldPerformTypeCheck(settings.getBoolean(TypeScriptPlugin.SETTING_TS_LINT_TYPECHECK));

return toReturn;
}

public Boolean useTsConfigInsteadOfFileList() {
return this.pathToTsConfig != null && !this.pathToTsConfig.isEmpty();
}

public String getPathToTsLint() {
return pathToTsLint;
Expand Down Expand Up @@ -37,4 +64,20 @@ public Integer getTimeoutMs() {
public void setTimeoutMs(Integer timeoutMs) {
this.timeoutMs = timeoutMs;
}

public String getPathToTsConfig() {
return pathToTsConfig;
}

public void setPathToTsConfig(String pathToTsConfig) {
this.pathToTsConfig = pathToTsConfig;
}

public boolean shouldPerformTypeCheck() {
return this.shouldPerformTypeCheck;
}

public void setShouldPerformTypeCheck(boolean performTypeCheck) {
this.shouldPerformTypeCheck = performTypeCheck;
}
}
150 changes: 92 additions & 58 deletions src/main/java/com/pablissimo/sonar/TsLintExecutorImpl.java
Expand Up @@ -14,6 +14,7 @@
import org.sonar.api.utils.TempFolder;
import org.sonar.api.utils.command.Command;
import org.sonar.api.utils.command.CommandExecutor;
import org.sonar.api.utils.command.StreamConsumer;
import org.sonar.api.utils.command.StringStreamConsumer;

public class TsLintExecutorImpl implements TsLintExecutor {
Expand Down Expand Up @@ -63,8 +64,20 @@ private Command getBaseCommand(TsLintExecutorConfig config, String tempPath) {

command
.addArgument("--config")
.addArgument(this.preparePath(config.getConfigFile()))
.setNewShell(false);
.addArgument(this.preparePath(config.getConfigFile()));

if (config.useTsConfigInsteadOfFileList()) {
command
.addArgument("--project")
.addArgument(this.preparePath(config.getPathToTsConfig()));
}

if (config.shouldPerformTypeCheck()) {
command
.addArgument("--type-check");
}

command.setNewShell(false);

return command;
}
Expand All @@ -73,85 +86,106 @@ public List<String> execute(TsLintExecutorConfig config, List<String> files) {
if (config == null) {
throw new IllegalArgumentException("config");
}

if (files == null) {
else if (files == null) {
throw new IllegalArgumentException("files");
}

// New up a command that's everything we need except the files to process
// We'll use this as our reference for chunking up files
// We'll use this as our reference for chunking up files, if we need to
File tslintOutputFile = this.tempFolder.newFile();
String tslintOutputFilePath = tslintOutputFile.getAbsolutePath();
Command baseCommand = getBaseCommand(config, tslintOutputFilePath);

LOG.debug("Using a temporary path for TsLint output: " + tslintOutputFilePath);

int baseCommandLength = getBaseCommand(config, tslintOutputFilePath).toCommandLine().length();
int availableForBatching = MAX_COMMAND_LENGTH - baseCommandLength;

List<List<String>> batches = new ArrayList<List<String>>();
List<String> currentBatch = new ArrayList<String>();
batches.add(currentBatch);

int currentBatchLength = 0;
for (int i = 0; i < files.size(); i++) {
String nextPath = this.preparePath(files.get(i).trim());

// +1 for the space we'll be adding between filenames
if (currentBatchLength + nextPath.length() + 1 > availableForBatching) {
// Too long to add to this batch, create new
currentBatch = new ArrayList<String>();
currentBatchLength = 0;
batches.add(currentBatch);
}

currentBatch.add(nextPath);
currentBatchLength += nextPath.length() + 1;
}

LOG.debug("Split " + files.size() + " files into " + batches.size() + " batches for processing");

StringStreamConsumer stdOutConsumer = new StringStreamConsumer();
StringStreamConsumer stdErrConsumer = new StringStreamConsumer();

List<String> toReturn = new ArrayList<String>();

for (int i = 0; i < batches.size(); i++) {
StringBuilder outputBuilder = new StringBuilder();

List<String> thisBatch = batches.get(i);

Command thisCommand = getBaseCommand(config, tslintOutputFilePath);

for (int fileIndex = 0; fileIndex < thisBatch.size(); fileIndex++) {
thisCommand.addArgument(thisBatch.get(fileIndex));
}

LOG.debug("Executing TsLint with command: " + thisCommand.toCommandLine());

// Timeout is specified per file, not per batch (which can vary a lot)
// so multiply it up
this.createExecutor().execute(thisCommand, stdOutConsumer, stdErrConsumer, config.getTimeoutMs() * thisBatch.size());
if (config.useTsConfigInsteadOfFileList()) {
LOG.debug("Running against a single project JSON file");

try {
BufferedReader reader = this.getBufferedReaderForFile(tslintOutputFile);

String str;
while ((str = reader.readLine()) != null) {
outputBuilder.append(str);
// If we're being asked to use a tsconfig.json file, it'll contain
// the file list to lint - so don't batch, and just run with it
toReturn.add(this.getCommandOutput(baseCommand, stdOutConsumer, stdErrConsumer, tslintOutputFile, config.getTimeoutMs()));
}
else {
int baseCommandLength = baseCommand.toCommandLine().length();
int availableForBatching = MAX_COMMAND_LENGTH - baseCommandLength;

List<List<String>> batches = new ArrayList<List<String>>();
List<String> currentBatch = new ArrayList<String>();
batches.add(currentBatch);

int currentBatchLength = 0;
for (int i = 0; i < files.size(); i++) {
String nextPath = this.preparePath(files.get(i).trim());

// +1 for the space we'll be adding between filenames
if (currentBatchLength + nextPath.length() + 1 > availableForBatching) {
// Too long to add to this batch, create new
currentBatch = new ArrayList<String>();
currentBatchLength = 0;
batches.add(currentBatch);
}

reader.close();

toReturn.add(outputBuilder.toString());

currentBatch.add(nextPath);
currentBatchLength += nextPath.length() + 1;
}
catch (IOException ex) {
LOG.error("Failed to re-read TsLint output from " + tslintOutputFilePath, ex);

LOG.debug("Split " + files.size() + " files into " + batches.size() + " batches for processing");

for (int i = 0; i < batches.size(); i++) {
StringBuilder outputBuilder = new StringBuilder();

List<String> thisBatch = batches.get(i);

Command thisCommand = getBaseCommand(config, tslintOutputFilePath);

for (int fileIndex = 0; fileIndex < thisBatch.size(); fileIndex++) {
thisCommand.addArgument(thisBatch.get(fileIndex));
}

LOG.debug("Executing TsLint with command: " + thisCommand.toCommandLine());

// Timeout is specified per file, not per batch (which can vary a lot)
// so multiply it up
toReturn.add(this.getCommandOutput(thisCommand, stdOutConsumer, stdErrConsumer, tslintOutputFile, config.getTimeoutMs() * thisBatch.size()));
}
}

return toReturn;
}

private String getCommandOutput(Command thisCommand, StreamConsumer stdOutConsumer, StreamConsumer stdErrConsumer, File tslintOutputFile, Integer timeoutMs) {
LOG.debug("Executing TsLint with command: " + thisCommand.toCommandLine());

// Timeout is specified per file, not per batch (which can vary a lot)
// so multiply it up
this.createExecutor().execute(thisCommand, stdOutConsumer, stdErrConsumer, timeoutMs);

StringBuilder outputBuilder = new StringBuilder();

try {
BufferedReader reader = this.getBufferedReaderForFile(tslintOutputFile);

String str;
while ((str = reader.readLine()) != null) {
outputBuilder.append(str);
}

reader.close();

return outputBuilder.toString();
}
catch (IOException ex) {
LOG.error("Failed to re-read TsLint output", ex);
}

return "";
}

protected BufferedReader getBufferedReaderForFile(File file) throws IOException {
return new BufferedReader(
new InputStreamReader(
Expand Down
21 changes: 4 additions & 17 deletions src/main/java/com/pablissimo/sonar/TsLintSensor.java
Expand Up @@ -16,9 +16,6 @@
import java.util.*;

public class TsLintSensor implements Sensor {
public static final String CONFIG_FILENAME = "tslint.json";
public static final String TSLINT_FALLBACK_PATH = "node_modules/tslint/bin/tslint";

private static final Logger LOG = LoggerFactory.getLogger(TsLintExecutorImpl.class);

private Settings settings;
Expand Down Expand Up @@ -48,18 +45,14 @@ public void execute(SensorContext ctx) {
return;
}

String pathToTsLint = this.resolver.getPath(ctx, TypeScriptPlugin.SETTING_TS_LINT_PATH, TSLINT_FALLBACK_PATH);
String pathToTsLintConfig = this.resolver.getPath(ctx, TypeScriptPlugin.SETTING_TS_LINT_CONFIG_PATH, CONFIG_FILENAME);
String rulesDir = this.resolver.getPath(ctx, TypeScriptPlugin.SETTING_TS_LINT_RULES_DIR, null);
TsLintExecutorConfig config = TsLintExecutorConfig.fromSettings(this.settings, ctx, this.resolver);

Integer tsLintTimeoutMs = Math.max(5000, settings.getInt(TypeScriptPlugin.SETTING_TS_LINT_TIMEOUT));

if (pathToTsLint == null) {
if (config.getPathToTsLint() == null) {
LOG.warn("Path to tslint not defined or not found. Skipping tslint analysis.");
return;
}
else if (pathToTsLintConfig == null) {
LOG.warn("Path to tslint.json configuration file not defined or not found. Skipping tslint analysis.");
else if (config.getConfigFile() == null && config.getPathToTsConfig() == null) {
LOG.warn("Path to tslint.json and tsconfig.json configuration files either not defined or not found - at least one is required. Skipping tslint analysis.");
return;
}

Expand All @@ -83,12 +76,6 @@ else if (pathToTsLintConfig == null) {
paths.add(pathAdjusted);
fileMap.put(pathAdjusted, file);
}

TsLintExecutorConfig config = new TsLintExecutorConfig();
config.setPathToTsLint(pathToTsLint);
config.setConfigFile(pathToTsLintConfig);
config.setRulesDir(rulesDir);
config.setTimeoutMs(tsLintTimeoutMs);

List<String> jsonResults = this.executor.execute(config, paths);

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/pablissimo/sonar/TypeScriptPlugin.java
Expand Up @@ -123,7 +123,7 @@ public class TypeScriptPlugin implements Plugin {
public static final String SETTING_LCOV_REPORT_PATH = "sonar.ts.lcov.reportpath";
public static final String SETTING_TS_RULE_CONFIGS = "sonar.ts.ruleconfigs";
public static final String SETTING_TS_LINT_TYPECHECK = "sonar.ts.tslinttypecheck";
public static final String SETTING_TS_LINT_TSCONFIG_PATH = "sonar.ts.tslinttsconfigpath";
public static final String SETTING_TS_LINT_PROJECT_PATH = "sonar.ts.tslintprojectpath";

@Override
public void define(Context ctx) {
Expand Down

0 comments on commit a2b382d

Please sign in to comment.