Skip to content

Commit 4fe1f30

Browse files
hjhamalafsantiag
authored andcommitted
4. Kibit support (#17)
* Remove testing extensions by number Extensios adding is easier if there is not a test which requires certain number of extensions to be present. Easier is to add tests one by one * Load property definition in first extension Current test assumes propertydefinition to be in certain position. If position is changed to first this test cannot break when adding new extensions * Move rules checking to sensors test A sensor test should not assume certain numbers of rules to be present to avoid collisions with other sensors. Instead test should assert that rules that are used by sensor are in the rules not that rules are expected to contain only sensors rules. * Add instructions to run SonarQube locally in docker The script is added which builds the plugin and starts SonarQube with plugin installed in local environment. This is useful fo locally testing the components * Change readme to have sensors in their own chapters * Add support for Kibit Kibit is run via Leiningen. Suggestions are mapped against correct lines. Kibit suggestion is by default minor code smell * Add Kibit to readme * Add Kibit support
1 parent 6bf2c8e commit 4fe1f30

File tree

19 files changed

+402
-89
lines changed

19 files changed

+402
-89
lines changed

Dockerfile

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
FROM sonarqube:7.1
22

3-
COPY target/sonar-clojure-plugin-*-SNAPSHOT.jar $SONARQUBE_HOME/extensions/plugins/
4-
3+
COPY target/sonar-clojure-plugin-*-SNAPSHOT.jar $SONARQUBE_HOME/extensions/plugins/

README.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,17 @@ that uses [Eastwood](https://github.com/jonase/eastwood) lint tool to analyze Cl
1717
[Eastwood](https://github.com/jonase/eastwood) is a lintter for Clojure (no CLJS support) which detects for example misplaced docstrings
1818
, def in defs and tests which always returns true.
1919

20+
### Kibit
21+
[Kibit](https://github.com/jonase/eastwood) is a static code analyzer which detects code that could be rewritten with a more idiomatic
22+
function or macro. For example:
23+
```clojure
24+
(> x 0)
25+
; more idiomatically
26+
(pos? x)
27+
```
28+
29+
Kibit is most useful for beginning Clojure programmers. Kibit supports also Clojurescript.
30+
2031
### ancient-clj
2132

2233
[ancient-clj](https://github.com/xsc/lein-ancient) is a Clojure library with Leiningen plugin which checks your project for outdated dependencies and plugins and suggest updates. The Sonarqube plugin
@@ -45,6 +56,7 @@ Set the ```sonar.clojure.lein-nvd.json-output-location``` property in sonar-proj
4556
this moment is not under development anymore and doesn't support SonarQube 6.7. Since the changes to port
4657
the old plugin were very extensive, I decided to start from scratch and use the old plugin as inspiration.
4758

59+
4860
## Installation
4961

5062
In order to install SonarClojure:
@@ -77,18 +89,18 @@ In order to install SonarClojure:
7789
Sensors can be disabled by setting ```sonar.clojure.sensorname.disabled=true``` or
7890
by using command line switch ```-Dsonar.clojure.sensorname.disabled``` when running ```sonar-scanner```.
7991

80-
Sensor names are ```eastwood```, ```ancient-clj```, ``lein-nvd``` and ```cloverage```.
92+
Sensor names are ```eastwood```, ```kibit```, ```ancient-clj```, ``lein-nvd``` and ```cloverage```.
8193

82-
3. Run [sonnar-scanner](https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner) on your project.
94+
3. Run [sonar-scanner](https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner) on your project.
8395

8496
## Building from source
85-
97+
8698
```sh
8799
./mvnw clean package
88100
```
89101

90102
Maven will generate an SNAPSHOT under the folder ***target***.
91-
103+
``
92104
## Testing the plugin with locally running dockerized Sonarqube
93105

94106
```sh

src/main/java/org/sonar/plugins/clojure/ClojurePlugin.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import org.sonar.plugins.clojure.sensors.CommandRunner;
99
import org.sonar.plugins.clojure.sensors.ancient.AncientSensor;
1010
import org.sonar.plugins.clojure.sensors.eastwood.EastwoodSensor;
11+
import org.sonar.plugins.clojure.sensors.kibit.KibitSensor;
1112
import org.sonar.plugins.clojure.sensors.leinnvd.LeinNvdSensor;
1213
import org.sonar.plugins.clojure.settings.ClojureProperties;
1314

@@ -20,6 +21,7 @@ public void define(Context context) {
2021
context.addExtension(ClojureLintRulesDefinition.class);
2122
context.addExtension(CommandRunner.class);
2223
context.addExtension(EastwoodSensor.class);
24+
context.addExtension(KibitSensor.class);
2325
context.addExtension(AncientSensor.class);
2426
context.addExtension(CloverageSensor.class);
2527
context.addExtension(LeinNvdSensor.class);

src/main/java/org/sonar/plugins/clojure/sensors/AbstractSensor.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@
33
import org.sonar.api.batch.fs.FileSystem;
44
import org.sonar.api.batch.fs.InputFile;
55
import org.sonar.api.batch.sensor.SensorContext;
6+
import org.sonar.api.batch.sensor.issue.NewIssue;
7+
import org.sonar.api.batch.sensor.issue.NewIssueLocation;
8+
import org.sonar.api.rule.RuleKey;
69
import org.sonar.api.utils.log.Logger;
710
import org.sonar.api.utils.log.Loggers;
11+
import org.sonar.plugins.clojure.rules.ClojureLintRulesDefinition;
812

913
import java.io.IOException;
1014
import java.nio.file.Files;
@@ -71,6 +75,36 @@ protected Optional<InputFile> getFile(String filePath, FileSystem fileSystem) {
7175
fileSystem.predicates().hasType(InputFile.Type.MAIN))));
7276
}
7377

78+
private InputFile getFile(Issue issue, FileSystem fileSystem) {
79+
return fileSystem.inputFile(
80+
fileSystem.predicates().and(
81+
fileSystem.predicates().hasRelativePath(issue.getFilePath()),
82+
fileSystem.predicates().hasType(InputFile.Type.MAIN)));
83+
}
84+
85+
protected void saveIssue(Issue issue, SensorContext context) {
86+
InputFile file = getFile(issue, context.fileSystem());
87+
88+
if (file == null) {
89+
LOG.warn("Not able to find a file with path '{}'", issue.getFilePath());
90+
return;
91+
}
92+
93+
RuleKey ruleKey = RuleKey.of(ClojureLintRulesDefinition.REPOSITORY_KEY, issue.getExternalRuleId().trim());
94+
95+
NewIssue newIssue = context.newIssue().forRule(ruleKey);
96+
97+
NewIssueLocation primaryLocation = newIssue
98+
.newLocation()
99+
.on(file)
100+
.message(issue.getDescription().trim());
101+
102+
primaryLocation.at(file.selectLine(issue.getLine()));
103+
104+
newIssue.at(primaryLocation);
105+
newIssue.save();
106+
}
107+
74108
/**
75109
* Gets the file directly from filesystem. This is useful when the file is needed to be read which is not wanted to
76110
* be part of SonarQube scanning

src/main/java/org/sonar/plugins/clojure/sensors/eastwood/EastwoodIssue.java renamed to src/main/java/org/sonar/plugins/clojure/sensors/Issue.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
package org.sonar.plugins.clojure.sensors.eastwood;
1+
package org.sonar.plugins.clojure.sensors;
22

3-
public class EastwoodIssue {
3+
public class Issue {
44

55
private String externalRuleId;
66
private String issueMessage;
77
private String filePath;
88
private int line;
99

10-
public EastwoodIssue(String externalRuleId, String description, String filePath, int line) {
10+
public Issue(String externalRuleId, String description, String filePath, int line) {
1111
this.externalRuleId = externalRuleId;
1212
this.issueMessage = description;
1313
this.filePath = filePath;

src/main/java/org/sonar/plugins/clojure/sensors/eastwood/EastwoodIssueParser.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.sonar.plugins.clojure.sensors.eastwood;
22

33
import org.sonar.plugins.clojure.sensors.CommandStreamConsumer;
4+
import org.sonar.plugins.clojure.sensors.Issue;
45

56
import java.util.ArrayList;
67
import java.util.List;
@@ -23,8 +24,8 @@ public static String parseRuntimeInfo(CommandStreamConsumer output) {
2324
return null;
2425
}
2526

26-
public static List<EastwoodIssue> parse(CommandStreamConsumer output) {
27-
List<EastwoodIssue> eastwoodIssues = new ArrayList<>();
27+
public static List<Issue> parse(CommandStreamConsumer output) {
28+
List<Issue> issues = new ArrayList<>();
2829

2930
if (output != null) {
3031
for (String line : output.getData()) {
@@ -36,11 +37,11 @@ public static List<EastwoodIssue> parse(CommandStreamConsumer output) {
3637
String filePath = matcher.group(1);
3738
int lineNumber = Integer.parseInt(matcher.group(2));
3839

39-
eastwoodIssues.add(new EastwoodIssue(externalRuleId, description, filePath, lineNumber));
40+
issues.add(new Issue(externalRuleId, description, filePath, lineNumber));
4041
}
4142
}
4243
}
4344

44-
return eastwoodIssues;
45+
return issues;
4546
}
4647
}

src/main/java/org/sonar/plugins/clojure/sensors/eastwood/EastwoodSensor.java

Lines changed: 5 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.sonar.plugins.clojure.sensors.AbstractSensor;
1717
import org.sonar.plugins.clojure.sensors.CommandStreamConsumer;
1818
import org.sonar.plugins.clojure.sensors.CommandRunner;
19+
import org.sonar.plugins.clojure.sensors.Issue;
1920
import org.sonar.plugins.clojure.settings.ClojureProperties;
2021

2122
import java.util.List;
@@ -30,36 +31,7 @@ public EastwoodSensor(CommandRunner commandRunner) {
3031
super(commandRunner);
3132
}
3233

33-
private void saveIssue(EastwoodIssue eastwoodIssue, SensorContext context) {
34-
InputFile file = getFile(eastwoodIssue, context.fileSystem());
3534

36-
if (file == null) {
37-
LOG.warn("Not able to find a file with path '{}'", eastwoodIssue.getFilePath());
38-
return;
39-
}
40-
41-
RuleKey ruleKey = RuleKey.of(ClojureLintRulesDefinition.REPOSITORY_KEY, eastwoodIssue.getExternalRuleId().trim());
42-
43-
NewIssue newIssue = context.newIssue().forRule(ruleKey);
44-
45-
NewIssueLocation primaryLocation = newIssue
46-
.newLocation()
47-
.on(file)
48-
.message(eastwoodIssue.getDescription().trim());
49-
50-
primaryLocation.at(file.selectLine(eastwoodIssue.getLine()));
51-
52-
newIssue.at(primaryLocation);
53-
54-
newIssue.save();
55-
}
56-
57-
private InputFile getFile(EastwoodIssue eastwoodIssue, FileSystem fileSystem) {
58-
return fileSystem.inputFile(
59-
fileSystem.predicates().and(
60-
fileSystem.predicates().hasRelativePath(eastwoodIssue.getFilePath()),
61-
fileSystem.predicates().hasType(InputFile.Type.MAIN)));
62-
}
6335

6436

6537
@Override
@@ -84,10 +56,10 @@ public void execute(SensorContext context) {
8456
LOG.warn("Eastwood resulted in empty output");
8557
}
8658

87-
List<EastwoodIssue> eastwoodIssues = EastwoodIssueParser.parse(stdOut);
88-
LOG.info("Saving eastwoodIssues");
89-
for (EastwoodIssue eastwoodIssue : eastwoodIssues) {
90-
saveIssue(eastwoodIssue, context);
59+
List<Issue> issues = EastwoodIssueParser.parse(stdOut);
60+
LOG.info("Saving issues");
61+
for (Issue issue : issues) {
62+
saveIssue(issue, context);
9163
}
9264
}
9365
} else {
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package org.sonar.plugins.clojure.sensors.kibit;
2+
3+
import org.sonar.api.utils.log.Logger;
4+
import org.sonar.api.utils.log.Loggers;
5+
import org.sonar.plugins.clojure.sensors.CommandStreamConsumer;
6+
import org.sonar.plugins.clojure.sensors.Issue;
7+
8+
import java.util.ArrayDeque;
9+
import java.util.ArrayList;
10+
import java.util.List;
11+
import java.util.regex.Matcher;
12+
import java.util.regex.Pattern;
13+
import java.util.stream.Collectors;
14+
15+
public class KibitIssueParser {
16+
private static final Pattern KIBIT_START = Pattern.compile("----");
17+
private static final Pattern KIBIT_ENTRY = Pattern.compile("At\\s([^:]+):([^`]+):");
18+
private KibitIssueParser() {
19+
}
20+
21+
private static boolean isEntryStart(String row) {
22+
return KIBIT_START.matcher(row).find();
23+
}
24+
25+
public static List<Issue> parse(CommandStreamConsumer output) {
26+
List<Issue> issues = new ArrayList<>();
27+
if (output != null) {
28+
List<String> kibitReport = output.getData();
29+
ArrayDeque<String> filteredReport = kibitReport.stream()
30+
.filter(line -> !line.isEmpty())
31+
.filter(line -> !isEntryStart(line))
32+
.collect(Collectors.toCollection(ArrayDeque::new));
33+
34+
while (!filteredReport.isEmpty()) {
35+
String path = filteredReport.pop();
36+
Matcher matcher = KIBIT_ENTRY.matcher(path);
37+
if (matcher.find()) {
38+
String filename = matcher.group(1);
39+
// Kibit may return line number sometimes as string "null"
40+
int lineNumber = !matcher.group(2).equals("null") ? Integer.parseInt(matcher.group(2)) : 1;
41+
StringBuilder description = new StringBuilder();
42+
while (!filteredReport.isEmpty() && !KIBIT_ENTRY.matcher(filteredReport.peek()).find()) {
43+
description.append(filteredReport.pop()).append("\n");
44+
}
45+
issues.add(new Issue("kibit", description.toString(), filename, lineNumber));
46+
}
47+
}
48+
}
49+
return issues;
50+
}
51+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package org.sonar.plugins.clojure.sensors.kibit;
2+
3+
4+
import org.sonar.api.batch.fs.FileSystem;
5+
import org.sonar.api.batch.fs.InputFile;
6+
7+
import org.sonar.api.batch.sensor.Sensor;
8+
import org.sonar.api.batch.sensor.SensorContext;
9+
import org.sonar.api.batch.sensor.SensorDescriptor;
10+
11+
import org.sonar.api.utils.log.Logger;
12+
import org.sonar.api.utils.log.Loggers;
13+
import org.sonar.plugins.clojure.language.ClojureLanguage;
14+
15+
import org.sonar.plugins.clojure.sensors.AbstractSensor;
16+
import org.sonar.plugins.clojure.sensors.CommandRunner;
17+
import org.sonar.plugins.clojure.sensors.CommandStreamConsumer;
18+
import org.sonar.plugins.clojure.sensors.Issue;
19+
import org.sonar.plugins.clojure.settings.ClojureProperties;
20+
21+
import java.util.List;
22+
23+
public class KibitSensor extends AbstractSensor implements Sensor {
24+
25+
private static final Logger LOG = Loggers.get(KibitSensor.class);
26+
27+
private static final String KIBIT_COMMAND = "kibit";
28+
29+
public KibitSensor(CommandRunner commandRunner) { super(commandRunner); }
30+
31+
@Override
32+
public void describe(SensorDescriptor descriptor) {
33+
descriptor.name("SonarClojureKibit")
34+
.onlyOnLanguage(ClojureLanguage.KEY)
35+
.global();
36+
}
37+
38+
@Override
39+
public void execute(SensorContext context) {
40+
LOG.info("Running Kibit");
41+
CommandStreamConsumer stdOut = this.commandRunner.run(LEIN_COMMAND, KIBIT_COMMAND);
42+
if (!checkIfPluginIsDisabled(context, ClojureProperties.KIBIT_DISABLED)) {
43+
if (isLeinInstalled(stdOut.getData()) && isPluginInstalled(stdOut.getData(), KIBIT_COMMAND)){
44+
45+
List<Issue> issues = KibitIssueParser.parse(stdOut);
46+
LOG.info("Saving issues");
47+
for (Issue issue : issues) {
48+
saveIssue(issue, context);
49+
}
50+
} else {
51+
LOG.warn("Parsing skipped because Leiningen or Kibit are not installed");
52+
}
53+
} else {
54+
LOG.info ("Kibit sensor is disabled");
55+
}
56+
}
57+
58+
}

src/main/java/org/sonar/plugins/clojure/settings/ClojureProperties.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public class ClojureProperties {
1616
public static final String LEIN_NVD_JSON_OUTPUT_LOCATION = "sonar.clojure.lein-nvd.json-output-location";
1717
public static final String MAIN_CATEGORY = "ClojureLanguage";
1818
public static final String CLOVERAGE_DISABLED = "sonar.clojure.cloverage.disabled";
19+
public static final String KIBIT_DISABLED = "sonar.clojure.kibit.disabled";
1920
public static final String CLOVERAGE_JSON_OUTPUT_LOCATION = "sonar.clojure.cloverage.json-output-location";
2021

2122
private ClojureProperties() {}
@@ -27,7 +28,8 @@ public static List<PropertyDefinition> getProperties() {
2728
getCloverageDisabledProperty(),
2829
getCloverageJsonOutputLocation(),
2930
getLeinNvdDisabledProperty(),
30-
getLeinNVdXMLOutputLocation());
31+
getLeinNVdXMLOutputLocation(),
32+
getKibitDisabledProperty());
3133
}
3234

3335
public static PropertyDefinition getFileSuffixProperty() {
@@ -99,4 +101,14 @@ public static PropertyDefinition getLeinNVdXMLOutputLocation() {
99101
.description("Set this to path where Lein NVD generates the result xml file.")
100102
.build();
101103
}
104+
105+
public static PropertyDefinition getKibitDisabledProperty() {
106+
return PropertyDefinition.builder(KIBIT_DISABLED)
107+
.category("ClojureLanguage")
108+
.subCategory("Sensors")
109+
.defaultValue("false")
110+
.name("Kibit sensor disabling")
111+
.description("Set true to disable the sensor.")
112+
.build();
113+
}
102114
}

0 commit comments

Comments
 (0)