-
Notifications
You must be signed in to change notification settings - Fork 120
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add basic shellcheck support * Add shellcheck exclude rules * Update junit annotations * Shellcheck review
- Loading branch information
Showing
19 changed files
with
477 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
11 changes: 11 additions & 0 deletions
11
src/main/java/pl/touk/sputnik/processor/shellcheck/ShellcheckException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package pl.touk.sputnik.processor.shellcheck; | ||
|
||
class ShellcheckException extends RuntimeException { | ||
ShellcheckException(String message) { | ||
super(message); | ||
} | ||
|
||
ShellcheckException(String message, Throwable t) { | ||
super(message, t); | ||
} | ||
} |
47 changes: 47 additions & 0 deletions
47
src/main/java/pl/touk/sputnik/processor/shellcheck/ShellcheckExecutor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package pl.touk.sputnik.processor.shellcheck; | ||
|
||
import com.google.common.collect.ImmutableList; | ||
import com.google.common.collect.Iterables; | ||
import com.google.common.collect.Lists; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.apache.commons.lang3.StringUtils; | ||
import org.jetbrains.annotations.NotNull; | ||
import pl.touk.sputnik.exec.ExternalProcess; | ||
|
||
import java.util.List; | ||
|
||
@Slf4j | ||
class ShellcheckExecutor { | ||
private static final String SHELLCHECK_EXECUTABLE = "shellcheck"; | ||
private static final String SHELLCHECK_OUTPUT_FORMAT = "-fjson"; | ||
private static final String SHELLCHECK_EXCLUDE_RULES = "-e"; | ||
|
||
private final List<String> excludeRules; | ||
|
||
ShellcheckExecutor(List<String> excludeRules) { | ||
this.excludeRules = excludeRules; | ||
} | ||
|
||
String runOnFile(String filePath) { | ||
log.info("Review on file: {}", filePath); | ||
return new ExternalProcess().executeCommand(buildParams(filePath)); | ||
} | ||
|
||
@NotNull | ||
private String[] buildParams(String filePath) { | ||
List<String> shellcheckArgs = ImmutableList.of( | ||
SHELLCHECK_EXECUTABLE, | ||
SHELLCHECK_OUTPUT_FORMAT); | ||
List<String> filePathArg = ImmutableList.of(filePath); | ||
List<String> excludeRulesArg = getExcludeRulesArgs(); | ||
List<String> allArgs = Lists.newArrayList(Iterables.concat(shellcheckArgs, filePathArg, excludeRulesArg)); | ||
return allArgs.toArray(new String[allArgs.size()]); | ||
} | ||
|
||
private List<String> getExcludeRulesArgs() { | ||
if (excludeRules.isEmpty()) { | ||
return ImmutableList.of(); | ||
} | ||
return ImmutableList.of(SHELLCHECK_EXCLUDE_RULES + StringUtils.join(excludeRules, ",")); | ||
} | ||
} |
57 changes: 57 additions & 0 deletions
57
src/main/java/pl/touk/sputnik/processor/shellcheck/ShellcheckProcessor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package pl.touk.sputnik.processor.shellcheck; | ||
|
||
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.processor.tools.externalprocess.ExternalProcessResultParser; | ||
import pl.touk.sputnik.processor.tools.externalprocess.ProcessorRunningExternalProcess; | ||
import pl.touk.sputnik.review.filter.FileFilter; | ||
import pl.touk.sputnik.review.filter.ShellFilter; | ||
|
||
import java.io.File; | ||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
|
||
@Slf4j | ||
class ShellcheckProcessor extends ProcessorRunningExternalProcess { | ||
|
||
private ShellcheckExecutor shellcheckExecutor; | ||
private ShellcheckResultParser shellcheckResultParser; | ||
|
||
ShellcheckProcessor(Configuration configuration) { | ||
shellcheckExecutor = new ShellcheckExecutor(getExcludeRules(configuration)); | ||
shellcheckResultParser = new ShellcheckResultParser(); | ||
} | ||
|
||
@NotNull | ||
@Override | ||
public String getName() { | ||
return "Shellcheck"; | ||
} | ||
|
||
@Override | ||
public FileFilter getReviewFileFilter() { | ||
return new ShellFilter(); | ||
} | ||
|
||
@Override | ||
public ExternalProcessResultParser getParser() { | ||
return shellcheckResultParser; | ||
} | ||
|
||
@Override | ||
public String processFileAndDumpOutput(File fileToReview) { | ||
return shellcheckExecutor.runOnFile(fileToReview.getAbsolutePath()); | ||
} | ||
|
||
@NotNull | ||
private List<String> getExcludeRules(Configuration configuration) { | ||
String excludeRules = configuration.getProperty(GeneralOption.SHELLCHECK_EXCLUDE); | ||
if (excludeRules == null) { | ||
return new ArrayList<>(); | ||
} | ||
return Arrays.asList(excludeRules.split(",")); | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
src/main/java/pl/touk/sputnik/processor/shellcheck/ShellcheckProcessorFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package pl.touk.sputnik.processor.shellcheck; | ||
|
||
import pl.touk.sputnik.configuration.Configuration; | ||
import pl.touk.sputnik.configuration.GeneralOption; | ||
import pl.touk.sputnik.processor.ReviewProcessorFactory; | ||
|
||
public class ShellcheckProcessorFactory implements ReviewProcessorFactory<ShellcheckProcessor> { | ||
@Override | ||
public boolean isEnabled(Configuration configuration) { | ||
return Boolean.valueOf(configuration.getProperty(GeneralOption.SHELLCHECK_ENABLED)); | ||
} | ||
|
||
@Override | ||
public ShellcheckProcessor create(Configuration configuration) { | ||
return new ShellcheckProcessor(configuration); | ||
} | ||
} |
66 changes: 66 additions & 0 deletions
66
src/main/java/pl/touk/sputnik/processor/shellcheck/ShellcheckResultParser.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package pl.touk.sputnik.processor.shellcheck; | ||
|
||
import com.fasterxml.jackson.core.type.TypeReference; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import org.apache.commons.lang3.StringUtils; | ||
import pl.touk.sputnik.processor.shellcheck.json.ShellcheckMessage; | ||
import pl.touk.sputnik.processor.tools.externalprocess.ExternalProcessResultParser; | ||
import pl.touk.sputnik.review.Severity; | ||
import pl.touk.sputnik.review.Violation; | ||
|
||
import java.io.IOException; | ||
import java.util.Collections; | ||
import java.util.List; | ||
|
||
import static java.util.stream.Collectors.toList; | ||
|
||
class ShellcheckResultParser implements ExternalProcessResultParser { | ||
|
||
private final ObjectMapper objectMapper = new ObjectMapper(); | ||
|
||
private static final String SHELLCHECK_ERROR = "error"; | ||
private static final String SHELLCHECK_WARNING = "warning"; | ||
private static final String SHELLCHECK_INFO = "info"; | ||
|
||
@Override | ||
public List<Violation> parse(String shellcheckOutput) { | ||
if (StringUtils.isEmpty(shellcheckOutput)) { | ||
return Collections.emptyList(); | ||
} | ||
try { | ||
List<ShellcheckMessage> violationMessages = objectMapper.readValue(shellcheckOutput, | ||
new TypeReference<List<ShellcheckMessage>>() { | ||
}); | ||
|
||
return violationMessages | ||
.stream() | ||
.map(violationMessage -> mapToViolation(violationMessage)) | ||
.collect(toList()); | ||
} catch (IOException e) { | ||
throw new ShellcheckException("Error when converting from json format", e); | ||
} | ||
} | ||
|
||
private Violation mapToViolation(ShellcheckMessage violationMessage) { | ||
return new Violation(violationMessage.getFile(), | ||
violationMessage.getLine(), | ||
formatViolationMessage(violationMessage), | ||
shellcheckMessageTypeToSeverity(violationMessage.getLevel())); | ||
} | ||
|
||
private String formatViolationMessage(ShellcheckMessage violationMessage) { | ||
return "[Code:" + violationMessage.getCode() + "] Message: " + violationMessage.getMessage(); | ||
} | ||
|
||
private Severity shellcheckMessageTypeToSeverity(String messageType) { | ||
if (SHELLCHECK_ERROR.equals(messageType)) { | ||
return Severity.ERROR; | ||
} else if (SHELLCHECK_WARNING.equals(messageType)) { | ||
return Severity.WARNING; | ||
} else if (SHELLCHECK_INFO.equals(messageType)) { | ||
return Severity.INFO; | ||
} else { | ||
throw new ShellcheckException("Unknown message type returned by shellcheck (type = " + messageType + ")"); | ||
} | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
src/main/java/pl/touk/sputnik/processor/shellcheck/json/ShellcheckMessage.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package pl.touk.sputnik.processor.shellcheck.json; | ||
|
||
import lombok.Data; | ||
import lombok.NoArgsConstructor; | ||
|
||
@Data | ||
@NoArgsConstructor | ||
public class ShellcheckMessage { | ||
private String file; | ||
private int line; | ||
private Integer endLine; | ||
private int column; | ||
private Integer endColumn; | ||
private String level; | ||
private String code; | ||
private String message; | ||
private String fix; | ||
} |
10 changes: 10 additions & 0 deletions
10
src/main/java/pl/touk/sputnik/review/filter/ShellFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package pl.touk.sputnik.review.filter; | ||
|
||
import java.util.Collections; | ||
|
||
public class ShellFilter extends FileExtensionFilter { | ||
|
||
public ShellFilter() { | ||
super(Collections.singletonList("sh")); | ||
} | ||
} |
117 changes: 117 additions & 0 deletions
117
src/test/java/pl/touk/sputnik/processor/shellcheck/ShellcheckProcessorTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
package pl.touk.sputnik.processor.shellcheck; | ||
|
||
import org.junit.jupiter.api.Assumptions; | ||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.platform.commons.util.StringUtils; | ||
import pl.touk.sputnik.TestEnvironment; | ||
import pl.touk.sputnik.configuration.ConfigurationBuilder; | ||
import pl.touk.sputnik.exec.ExternalProcess; | ||
import pl.touk.sputnik.review.Review; | ||
import pl.touk.sputnik.review.ReviewFile; | ||
import pl.touk.sputnik.review.ReviewFormatterFactory; | ||
import pl.touk.sputnik.review.ReviewResult; | ||
import pl.touk.sputnik.review.Severity; | ||
import pl.touk.sputnik.review.Violation; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
public class ShellcheckProcessorTest extends TestEnvironment { | ||
|
||
private static final String CONFIGURATION_WITH_SHELLCHECK_ENABLED = "shellcheck/sputnik/noConfigurationFile.properties"; | ||
private static final String REVIEW_FILE_WITH_ONE_VIOLATION = "src/test/resources/shellcheck/testFiles/oneViolation.sh"; | ||
private static final String REVIEW_FILE_WITH_EXCLUDED_VIOLATION = "src/test/resources/shellcheck/testFiles/excludedViolation.sh"; | ||
private static final String REVIEW_FILE_WITH_MULTIPLE_VIOLATIONS = "src/test/resources/shellcheck/testFiles/multipleViolations.sh"; | ||
private ShellcheckProcessor sut; | ||
|
||
@BeforeEach | ||
public void setUp() { | ||
config = ConfigurationBuilder.initFromResource(CONFIGURATION_WITH_SHELLCHECK_ENABLED); | ||
sut = new ShellcheckProcessor(config); | ||
} | ||
|
||
@Test | ||
public void shouldReturnNoViolationsWhenThereIsNoFileToReview() { | ||
ReviewResult reviewResult = sut.process(nonExistentReview()); | ||
|
||
assertThat(reviewResult).isNotNull(); | ||
assertThat(reviewResult.getViolations()).isEmpty(); | ||
} | ||
|
||
@Test | ||
public void shouldReturnOneViolationsForFile() { | ||
Assumptions.assumeTrue(isShellcheckInstalled()); | ||
Review review = getReview(REVIEW_FILE_WITH_ONE_VIOLATION); | ||
|
||
ReviewResult result = sut.process(review); | ||
|
||
assertThat(result).isNotNull(); | ||
assertThat(result.getViolations()) | ||
.hasSize(1); | ||
Violation violation = result.getViolations().get(0); | ||
assertThat(violation.getFilenameOrJavaClassName()).contains(REVIEW_FILE_WITH_ONE_VIOLATION); | ||
assertThat(violation.getLine()).isEqualTo(3); | ||
assertThat(violation.getMessage()).isEqualTo("[Code:2154] Message: variable is referenced but not assigned."); | ||
assertThat(violation.getSeverity()).isEqualTo(Severity.WARNING); | ||
} | ||
|
||
@Test | ||
public void shouldReturnMultipleViolationsForFile() { | ||
Assumptions.assumeTrue(isShellcheckInstalled()); | ||
Review review = getReview(REVIEW_FILE_WITH_MULTIPLE_VIOLATIONS); | ||
|
||
ReviewResult result = sut.process(review); | ||
|
||
assertThat(result).isNotNull(); | ||
assertThat(result.getViolations()) | ||
.hasSize(3); | ||
Violation violation1 = result.getViolations().get(0); | ||
assertThat(violation1.getFilenameOrJavaClassName()).contains(REVIEW_FILE_WITH_MULTIPLE_VIOLATIONS); | ||
assertThat(violation1.getLine()).isEqualTo(3); | ||
assertThat(violation1.getMessage()).isEqualTo("[Code:2154] Message: variable is referenced but not assigned."); | ||
assertThat(violation1.getSeverity()).isEqualTo(Severity.WARNING); | ||
|
||
Violation violation2 = result.getViolations().get(1); | ||
assertThat(violation2.getFilenameOrJavaClassName()).contains(REVIEW_FILE_WITH_MULTIPLE_VIOLATIONS); | ||
assertThat(violation2.getLine()).isEqualTo(5); | ||
assertThat(violation2.getMessage()).isEqualTo("[Code:1037] Message: Braces are required for positionals over 9, e.g. ${10}."); | ||
assertThat(violation2.getSeverity()).isEqualTo(Severity.ERROR); | ||
|
||
Violation violation3 = result.getViolations().get(2); | ||
assertThat(violation3.getFilenameOrJavaClassName()).contains(REVIEW_FILE_WITH_MULTIPLE_VIOLATIONS); | ||
assertThat(violation3.getLine()).isEqualTo(7); | ||
assertThat(violation3.getMessage()).isEqualTo("[Code:2078] Message: This expression is constant. Did you forget a $ somewhere?"); | ||
assertThat(violation3.getSeverity()).isEqualTo(Severity.ERROR); | ||
} | ||
|
||
@Test | ||
public void shouldReturnNoViolationWhenRuleExcludedInConfig() { | ||
Assumptions.assumeTrue(isShellcheckInstalled()); | ||
Review review = getReview(REVIEW_FILE_WITH_EXCLUDED_VIOLATION); | ||
|
||
ReviewResult result = sut.process(review); | ||
|
||
assertThat(result).isNotNull(); | ||
assertThat(result.getViolations()).isEmpty(); | ||
} | ||
|
||
private boolean isShellcheckInstalled() { | ||
try { | ||
String result = new ExternalProcess().executeCommand("shellcheck"); | ||
return StringUtils.isBlank(result); | ||
} catch (Exception e) { | ||
return false; | ||
} | ||
} | ||
|
||
private Review getReview(String... filePaths) { | ||
List<ReviewFile> files = new ArrayList<>(); | ||
for (String filePath : filePaths) { | ||
files.add(new ReviewFile(filePath)); | ||
} | ||
return new Review(files, ReviewFormatterFactory.get(config)); | ||
} | ||
} |
Oops, something went wrong.