Skip to content

Commit

Permalink
Merge pull request #139 from TouK/tslint
Browse files Browse the repository at this point in the history
Tslint support
  • Loading branch information
pjagielski committed Apr 5, 2016
2 parents 51116b9 + 38e24f0 commit 429179d
Show file tree
Hide file tree
Showing 24 changed files with 534 additions and 9 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
![sputnik](http://touk.github.io/sputnik/images/sputnik.png)

> Static code review for your Gerrit and Stash patchsets. Runs Checkstyle, PMD, FindBugs, Scalastyle, CodeNarc, JSLint and Sonar for you!
> Static code review for your Gerrit and Stash patchsets. Runs Checkstyle, PMD, FindBugs, Scalastyle, CodeNarc, JSLint, JSHint, TSLint and Sonar for you!
[![Build Status](https://img.shields.io/travis/TouK/sputnik/master.svg?style=flat-square)](https://travis-ci.org/TouK/sputnik)
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/pl.touk/sputnik/badge.svg?style=flat-square)](https://maven-badges.herokuapp.com/maven-central/pl.touk/sputnik)
Expand All @@ -18,7 +18,7 @@ Three parameters are required: your configuration file (details below), Gerrit's
sputnik -conf /home/spoonman/sputnik/conf.properties -changeId I0a2afb7ae4a94ab1ab473ba00e2ec7de381799a0 -revisionId 3f37692af2290e8e3fd16d2f43701c24346197f0
```

Sputnik runs Checkstyle, PMD, FindBugs, CodeNarc, JSHint (or JSLint) and Sonar only on files affected by Gerrit's patchset. It collects all violations and report them back to Gerrit or Stash.
Sputnik runs Checkstyle, PMD, FindBugs, CodeNarc, JSHint (or JSLint), TSLint and Sonar only on files affected by Gerrit's patchset. It collects all violations and report them back to Gerrit or Stash.

Typical configuration file looks like this:

Expand All @@ -44,6 +44,9 @@ codenarc.excludes=**/*.java
jslint.enabled=false
jshint.enabled=true
jshint.configurationFile=jshint.json
tslint.enabled=true
tslint.script=/usr/bin/tslint
tslint.configurationFile=tslint.json
sonar.enabled=true
sonar.configurationFiles=sonar-project.properties, sonar-runner.properties
sonar.verbose=false
Expand Down Expand Up @@ -138,6 +141,7 @@ gradle run -Dexec.args="--conf example.properties --changeId 1234 --revisionId 4
- Karol Lassak
- Henning Hoefer
- Dominik Przybysz
- Damian Szczepanik

## License

Expand Down
9 changes: 9 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ run {
// Need to split the space-delimited value in the exec.args
args arguments.split()
}

// change working directory and run sputnk from workspace
def newWorkingDir = System.getProperty("exec.workingDir")
if (newWorkingDir) {
workingDir = newWorkingDir
}
}

repositories {
Expand Down Expand Up @@ -111,6 +117,9 @@ dependencies {
compile 'com.jcabi:jcabi-github:0.24'
compile 'com.github.spullara.mustache.java:compiler:0.8.17'

// external processes
compile 'org.zeroturnaround:zt-exec:1.8'

// Test dependencies
testCompile 'junit:junit:4.11'
testCompile 'org.mockito:mockito-core:1.9.5'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ public enum GeneralOption implements ConfigurationOption {
JSHINT_ENABLED("jshint.enabled", "JSHint enabled", "false"),
JSHINT_CONFIGURATION_FILE("jshint.configurationFile", "JSHint configuration file", ""),

TSLINT_ENABLED("tslint.enabled", "TSLint enabled", "false"),
TSLINT_CONFIGURATION_FILE("tslint.configurationFile", "TSLint configuration file", "tslint.json"),
TSLINT_SCRIPT("tslint.script", "TSLint script for validating files", "/usr/bin/tslint"),

SONAR_ENABLED("sonar.enabled", "Sonar enabled", "false"),
SONAR_PROPERTIES("sonar.configurationFiles", "Sonar base configuration", "sonar-project.properties"),
SONAR_VERBOSE("sonar.verbose", "Run sonar in verbose mode", "false"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
@Slf4j
@AllArgsConstructor
public class LimitCommentVisitor implements AfterReviewVisitor {
private static final String MESSAGE_FORMAT = "Showing only first %d comments. %d comments are filtered out";
private static final String MESSAGE_FORMAT = "Showing only first %d comments. Rest %d comments are filtered out";
private int maximumCount;

@Override
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/pl/touk/sputnik/exec/ExternalProcess.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package pl.touk.sputnik.exec;

import lombok.extern.slf4j.Slf4j;
import org.zeroturnaround.exec.ProcessExecutor;
import org.zeroturnaround.exec.stream.slf4j.Slf4jStream;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;

@Slf4j
public final class ExternalProcess {

public ProcessExecutor executor() {
return new ProcessExecutor();
}

public String executeCommand(String... args) {
try {
log.debug("Executing command " + Arrays.asList(args));
return executor().command(args)
.timeout(60, TimeUnit.SECONDS)
.redirectError(Slf4jStream.of(getClass()).asInfo())
.readOutput(true).execute()
.outputUTF8();
} catch (Exception e) {
log.warn("Exception while calling command " + Arrays.asList(args) + ": " + e);
throw new ExternalProcessException(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package pl.touk.sputnik.exec;

public class ExternalProcessException extends RuntimeException {

public ExternalProcessException(Throwable cause) {
super(cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package pl.touk.sputnik.processor.tslint;

import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import pl.touk.sputnik.configuration.Configuration;
import pl.touk.sputnik.configuration.GeneralOption;
import pl.touk.sputnik.review.Review;
import pl.touk.sputnik.review.ReviewProcessor;
import pl.touk.sputnik.review.ReviewResult;
import pl.touk.sputnik.review.Violation;
import pl.touk.sputnik.review.filter.TypeScriptFilter;
import pl.touk.sputnik.review.transformer.IOFileTransformer;

import java.io.File;
import java.util.List;

@Slf4j
public class TSLintProcessor implements ReviewProcessor {

private static final String SOURCE_NAME = "TSLint";

private final TSLintScript tsLintScript;
private final TSLintResultParser resultParser;

public TSLintProcessor(Configuration config) {
String tsScript = config.getProperty(GeneralOption.TSLINT_SCRIPT);
String configFile = config.getProperty(GeneralOption.TSLINT_CONFIGURATION_FILE);

tsLintScript = new TSLintScript(tsScript, configFile);
tsLintScript.validateConfiguration();
resultParser = new TSLintResultParser();
}

@Override
public String getName() {
return SOURCE_NAME;
}

@Override
@NotNull
public ReviewResult process(Review review) {
ReviewResult result = new ReviewResult();

List<File> files = review.getFiles(new TypeScriptFilter(), new IOFileTransformer());
for (File file : files) {
for (Violation violation : resultParser.parse(tsLintScript.reviewFile(file.getAbsolutePath()))) {
result.add(violation);
}
}
return result;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package pl.touk.sputnik.processor.tslint;

import pl.touk.sputnik.configuration.Configuration;
import pl.touk.sputnik.configuration.GeneralOption;
import pl.touk.sputnik.processor.ReviewProcessorFactory;

public class TSLintProcessorFactory implements ReviewProcessorFactory<TSLintProcessor> {

@Override
public boolean isEnabled(Configuration configuration) {
return Boolean.valueOf(configuration.getProperty(GeneralOption.TSLINT_ENABLED));
}

@Override
public TSLintProcessor create(Configuration configuration) {
return new TSLintProcessor(configuration);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package pl.touk.sputnik.processor.tslint;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import pl.touk.sputnik.connector.gerrit.GerritException;
import pl.touk.sputnik.processor.tslint.json.ListViolationsResponse;
import pl.touk.sputnik.processor.tslint.json.TSLintFileInfo;
import pl.touk.sputnik.review.Severity;
import pl.touk.sputnik.review.Violation;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@Slf4j
public class TSLintResultParser {

private final ObjectMapper objectMapper = new ObjectMapper();

public List<Violation> parse(String jsonViolations) {
if (StringUtils.isEmpty(jsonViolations)) {
return Collections.emptyList();
}
try {
List<Violation> result = new ArrayList<>();
ListViolationsResponse violations = objectMapper
.readValue(jsonViolations, ListViolationsResponse.class);
log.debug(String.format("Converted from json format to %d violations.", violations.size()));
for (TSLintFileInfo fileInfo : violations) {
Violation violation = new Violation(fileInfo.getName(), fileInfo.getStartPosition().getLine(),
fileInfo.getFailure(), Severity.ERROR);
result.add(violation);
}
return result;
} catch (IOException e) {
throw new GerritException("Error when converting from json format", e);
}
}
}
63 changes: 63 additions & 0 deletions src/main/java/pl/touk/sputnik/processor/tslint/TSLintScript.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package pl.touk.sputnik.processor.tslint;

import lombok.extern.slf4j.Slf4j;
import pl.touk.sputnik.exec.ExternalProcess;
import pl.touk.sputnik.review.ReviewException;

import java.io.File;

/**
* Represents instance of TSLint executable file, which is used for validating files.
*/
@Slf4j
public class TSLintScript {

/** Name of the NodeJs process. */
private static final String NODE_JS = "node";
/** Argument for validating the file. */
private static final String TS_LINT_CONFIG_PARAM = "--config";
/** Determines the output format. */
private static final String TS_LINT_OUTPUT_KEY = "--format";
private static final String TS_LINT_OUTPUT_VALUE = "json";

/** TSLint script that validates files. */
private final String tsScript;

/** File with rules. */
private final String configFile;

public TSLintScript(String tsScript, String configFile) {
this.tsScript = tsScript;
this.configFile = configFile;
}

/**
* Since this class needs to have setup correctly external configuration, we want to validate this configuration
* before validation starts.
*
* @throws ReviewException
* when configuration is not valid or completed
*/
public void validateConfiguration() throws ReviewException {
// check if config file exist
if (!new File(configFile).exists()) {
throw new ReviewException("Could not find tslint configuration file: " + configFile);
}
}

/**
* Executes TSLint to look for violations.
*
* @param filePath
* file that will be examined
* @return violations in JSON format
*/
public String reviewFile(String filePath) {
log.info("Reviewing file: " + filePath);
// use this format to make sure that ' ' are parsed properly
String[] args = new String[] {NODE_JS, tsScript, TS_LINT_OUTPUT_KEY, TS_LINT_OUTPUT_VALUE,
TS_LINT_CONFIG_PARAM, configFile, filePath };
return new ExternalProcess().executeCommand(args);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package pl.touk.sputnik.processor.tslint.json;

import lombok.Data;
import lombok.NoArgsConstructor;

/**
* TSLint output with violations entry.
* Used with JSON unmarshaller only.
*
"startPosition":
{
"position":1144,
"line":20,
"character":0
},
"endPosition":
{
"position":1275,
"line":20,
"character":131
},
*/
@Data
@NoArgsConstructor
public final class ChangePosition {
private int position;
private int line;
private int character;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package pl.touk.sputnik.processor.tslint.json;

import java.util.ArrayList;

/**
* TSLint response with violations.
* Used with JSON unmarshaller only.
*/
@SuppressWarnings("serial")
public final class ListViolationsResponse extends ArrayList<TSLintFileInfo> {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package pl.touk.sputnik.processor.tslint.json;

import lombok.Data;
import lombok.NoArgsConstructor;

/**
* TSLint output with violations entry.
* Used with JSON unmarshaller only.
[
{
"name":"service.ts",
"failure":"exceeds maximum line length of 120",
"startPosition":
{
"position":897,
"line":18,
"character":0
},
"endPosition":
{
"position":1028,
"line":18,
"character":131
},
"ruleName":"max-line-length"
},
{
"name":"administration.ts",
"failure":"exceeds maximum line length of 120",
"startPosition":
{
"position":1144,
"line":20,
"character":0
},
"endPosition":
{
"position":1275,
"line":20,
"character":131
},
"ruleName":"max-line-length"
}
]
*/
@Data
@NoArgsConstructor
public final class TSLintFileInfo {
private String name;
private String failure;

private ChangePosition startPosition;

private ChangePosition endPosition;

private String ruleName;
}
Loading

0 comments on commit 429179d

Please sign in to comment.