Skip to content
This repository has been archived by the owner on Jul 8, 2019. It is now read-only.

Commit

Permalink
tslint 4.0.0+ support (#71)
Browse files Browse the repository at this point in the history
Adds support for tslint 4.0.0's new (fixed!) output format, and definitions for more rules introduced in the past while. Fixes long tslint output on Linux being truncated (merged issue69 branch).

Fixes #67, #69, #70
  • Loading branch information
Pablissimo authored Dec 6, 2016
1 parent 4c93d89 commit 35918a9
Show file tree
Hide file tree
Showing 12 changed files with 399 additions and 480 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<groupId>com.pablissimo.sonar</groupId>
<artifactId>sonar-typescript-plugin</artifactId>
<packaging>sonar-plugin</packaging>
<version>0.94-SNAPSHOT</version>
<version>0.96-SNAPSHOT</version>

<name>TypeScript</name>
<description>Analyse TypeScript projects</description>
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/pablissimo/sonar/TsLintExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
import java.util.List;

public interface TsLintExecutor {
String execute(String pathToTsLint, String configFile, String rulesDir, List<String> files, Integer timeoutMs);
List<String> execute(String pathToTsLint, String configFile, String rulesDir, List<String> files, Integer timeoutMs);
}
90 changes: 58 additions & 32 deletions src/main/java/com/pablissimo/sonar/TsLintExecutorImpl.java
Original file line number Diff line number Diff line change
@@ -1,29 +1,34 @@
package com.pablissimo.sonar;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.utils.System2;
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 {
public static final int MAX_COMMAND_LENGTH = 4096;
private static final Logger LOG = LoggerFactory.getLogger(TsLintExecutorImpl.class);

private StringBuilder stdOut;
private StringBuilder stdErr;

private boolean mustQuoteSpaceContainingPaths = false;
private TempFolder tempFolder;

public TsLintExecutorImpl(System2 system) {
public TsLintExecutorImpl(System2 system, TempFolder tempFolder) {
this.mustQuoteSpaceContainingPaths = system.isOsWindows();
this.tempFolder = tempFolder;
}

public String preparePath(String path) {
private String preparePath(String path) {
if (path == null) {
return null;
}
Expand All @@ -35,7 +40,7 @@ else if (path.contains(" ") && this.mustQuoteSpaceContainingPaths) {
}
}

private Command getBaseCommand(String pathToTsLint, String configFile, String rulesDir) {
private Command getBaseCommand(String pathToTsLint, String configFile, String rulesDir, String tempPath) {
Command command =
Command
.create("node")
Expand All @@ -48,6 +53,12 @@ private Command getBaseCommand(String pathToTsLint, String configFile, String ru
.addArgument("--rules-dir")
.addArgument(this.preparePath(rulesDir));
}

if (tempPath != null && tempPath.length() > 0) {
command
.addArgument("--out")
.addArgument(this.preparePath(tempPath));
}

command
.addArgument("--config")
Expand All @@ -57,10 +68,15 @@ private Command getBaseCommand(String pathToTsLint, String configFile, String ru
return command;
}

public String execute(String pathToTsLint, String configFile, String rulesDir, List<String> files, Integer timeoutMs) {
public List<String> execute(String pathToTsLint, String configFile, String rulesDir, List<String> files, Integer timeoutMs) {
// 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
int baseCommandLength = getBaseCommand(pathToTsLint, configFile, rulesDir).toCommandLine().length();
File tslintOutputFile = this.tempFolder.newFile();
String tslintOutputFilePath = tslintOutputFile.getAbsolutePath();

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

int baseCommandLength = getBaseCommand(pathToTsLint, configFile, rulesDir, tslintOutputFilePath).toCommandLine().length();
int availableForBatching = MAX_COMMAND_LENGTH - baseCommandLength;

List<List<String>> batches = new ArrayList<List<String>>();
Expand All @@ -84,26 +100,18 @@ public String execute(String pathToTsLint, String configFile, String rulesDir, L
}

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

this.stdOut = new StringBuilder();
this.stdErr = new StringBuilder();

StreamConsumer stdOutConsumer = new StreamConsumer() {
public void consumeLine(String line) {
stdOut.append(line);
}
};

StreamConsumer stdErrConsumer = new StreamConsumer() {
public void consumeLine(String line) {
LOG.error("TsLint Err: " + line);
stdErr.append(line + "\n");
}
};


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(pathToTsLint, configFile, rulesDir);

Command thisCommand = getBaseCommand(pathToTsLint, configFile, rulesDir, tslintOutputFilePath);

for (int fileIndex = 0; fileIndex < thisBatch.size(); fileIndex++) {
thisCommand.addArgument(thisBatch.get(fileIndex));
Expand All @@ -114,13 +122,31 @@ public void consumeLine(String line) {
// Timeout is specified per file, not per batch (which can vary a lot)
// so multiply it up
this.createExecutor().execute(thisCommand, stdOutConsumer, stdErrConsumer, timeoutMs * thisBatch.size());

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

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

reader.close();

toReturn.add(outputBuilder.toString());
}
catch (IOException ex) {
LOG.error("Failed to re-read TsLint output from " + tslintOutputFilePath, ex);
}
}

String rawOutput = stdOut.toString();

// TsLint returns nonsense for its JSON output when faced with multiple files
// so we need to fix it up before we do anything else
return "[" + rawOutput.replaceAll("\\]\\[", "],[") + "]";
return toReturn;
}

protected BufferedReader getBufferedReaderForFile(File file) throws IOException {
return new BufferedReader(
new InputStreamReader(
new FileInputStream(file), "UTF8"));
}

protected CommandExecutor createExecutor() {
Expand Down
5 changes: 4 additions & 1 deletion src/main/java/com/pablissimo/sonar/TsLintParser.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.pablissimo.sonar;

import java.util.List;
import java.util.Map;

import com.pablissimo.sonar.model.TsLintIssue;

public interface TsLintParser {
TsLintIssue[][] parse(String toParse);
Map<String, List<TsLintIssue>> parse(List<String> rawOutputBatches);
}
46 changes: 44 additions & 2 deletions src/main/java/com/pablissimo/sonar/TsLintParserImpl.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,56 @@
package com.pablissimo.sonar;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.pablissimo.sonar.model.TsLintIssue;

public class TsLintParserImpl implements TsLintParser {
public TsLintIssue[][] parse(String toParse) {
private static final Logger LOG = LoggerFactory.getLogger(TsLintParserImpl.class);

public Map<String, List<TsLintIssue>> parse(List<String> toParse) {
GsonBuilder builder = new GsonBuilder();
Gson gson = builder.create();

List<TsLintIssue> allIssues = new ArrayList<TsLintIssue>();

for (String batch : toParse) {
TsLintIssue[] batchIssues = gson.fromJson(getFixedUpOutput(batch), TsLintIssue[].class);
for (TsLintIssue batchIssue : batchIssues) {
allIssues.add(batchIssue);
}
}

return gson.fromJson(toParse, TsLintIssue[][].class);
// Remap by filename
Map<String, List<TsLintIssue>> toReturn = new HashMap<String, List<TsLintIssue>>();
for (TsLintIssue issue : allIssues) {
List<TsLintIssue> issuesByFile = toReturn.get(issue.getName());
if (issuesByFile == null) {
issuesByFile = new ArrayList<TsLintIssue>();
toReturn.put(issue.getName(), issuesByFile);
}

issuesByFile.add(issue);
}

return toReturn;
}

private String getFixedUpOutput(String toParse) {
if (toParse.contains("][")) {
// Pre 4.0.0-versions of TsLint return nonsense for its JSON output
// when faced with multiple files so we need to fix it up before we
// do anything else
return toParse.replaceAll("\\]\\[", ",");
}

return toParse;
}
}
21 changes: 12 additions & 9 deletions src/main/java/com/pablissimo/sonar/TsLintSensor.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.sonar.api.config.Settings;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.TempFolder;

import java.util.*;

Expand All @@ -24,18 +25,20 @@ public class TsLintSensor implements Sensor {

private Settings settings;
private System2 system;

public TsLintSensor(Settings settings, System2 system) {
private TempFolder tempFolder;

public TsLintSensor(Settings settings, System2 system, TempFolder tempFolder) {
this.settings = settings;
this.system = system;
this.tempFolder = tempFolder;
}

protected PathResolver getPathResolver() {
return new PathResolverImpl();
}

protected TsLintExecutor getTsLintExecutor() {
return new TsLintExecutorImpl(this.system);
return new TsLintExecutorImpl(this.system, this.tempFolder);
}

protected TsLintParser getTsLintParser() {
Expand Down Expand Up @@ -96,22 +99,22 @@ else if (pathToTsLintConfig == null) {
fileMap.put(pathAdjusted, file);
}

String jsonResult = executor.execute(pathToTsLint, pathToTsLintConfig, rulesDir, paths, tsLintTimeoutMs);
List<String> jsonResults = executor.execute(pathToTsLint, pathToTsLintConfig, rulesDir, paths, tsLintTimeoutMs);

TsLintIssue[][] issues = parser.parse(jsonResult);
Map<String, List<TsLintIssue>> issues = parser.parse(jsonResults);

if (issues == null) {
LOG.warn("TsLint returned no result at all");
return;
}

// Each issue bucket will contain info about a single file
for (TsLintIssue[] batchIssues : issues) {
if (batchIssues == null || batchIssues.length == 0) {
for (String filePath : issues.keySet()) {
List<TsLintIssue> batchIssues = issues.get(filePath);

if (batchIssues == null || batchIssues.size() == 0) {
continue;
}

String filePath = batchIssues[0].getName();

if (!fileMap.containsKey(filePath)) {
LOG.warn("TsLint reported issues against a file that wasn't sent to it - will be ignored: " + filePath);
Expand Down
Loading

0 comments on commit 35918a9

Please sign in to comment.