Skip to content
This repository was archived by the owner on Aug 2, 2022. It is now read-only.

Commit 2ace27f

Browse files
authored
Feature/Rubocop (#10)
* Added Rubocop rules inside SQ profile file ref #3 * Added rubocop rules definitions repo XML ref #3 * Added project property for Rubocop report path ref #3 * Added Rubocop report data models ref #3 * Added loading cops rules definitions ref #3 * Implemented rubocop report json parser ref #3 * Completed RubocopSensor ref #3 * Register RubocopSensor and report json parser ref #3 * Fixed rule status naming in rule definitions ref #3 Actually all rule statuses should be uppercased to be conformed with enumerable org.sonar.api.rule.RuleStatus * Fixed rubocop report parser - Added required annotations - Fixed parsing `corrected` property of an offense - in some cases it is null - Updated readme with Rubocop mentions ref #3 * Made rubocop support not required ref #3 Before if the code base did not has the rubocop report file the analyzing process had been failed. This was a very unconvinient way to provide an additional ability for the end user. This commit changes this behaviour and of there is no report file the system skips rubocop sensor. * Rebased the rubocop PR * removed and optimized imports
1 parent a36ec38 commit 2ace27f

File tree

25 files changed

+5655
-2
lines changed

25 files changed

+5655
-2
lines changed

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ which will generate a metric report. Please see the [gem's homepage](https://git
5050

5151
**Important:** We recommend using metric_fu by running `metric_fu -r --no-flog --no-flay --no-roodi --no-open` which would analyze and report all of the metrics supported by the plugin. Such as Saikuro/Cane coverage, Cane issues, Hostpots, Code smells and more..
5252

53-
5453
##### Multiple Testing Suites
5554
If you are using multiple testing frameworks or maintaining different testing
5655
logical suits using the
@@ -83,7 +82,12 @@ Use the `sonar.ruby.coverage.testSuites` property to set the correct tests aggre
8382
* null or "all": all suites will be published
8483
* comma delimited test suite names: selected suits will be published
8584

86-
## Future Plans
85+
##### Rubocop static code analyzing
86+
[Rubocop](https://github.com/bbatsov/rubocop) is a Ruby community-driven static code analyzing tool, which is used [ruby-style-guide](https://github.com/bbatsov/ruby-style-guide) for rules definition. So its very valuable to have those rules inside SonarQube.
87+
88+
**Important:** We recommend run rubocop as such `rubocop -f json -o tmp/rubocop/report.json`.
89+
90+
##Future Plans
8791
* Code Duplication
8892
* Structural Analysis
8993
* Code Debt
@@ -102,6 +106,7 @@ This plugin has been tested with the following dependency versions:
102106
* metric_fu gem version 4.12.0 (latest at time of edit)
103107
* simplecov 0.12.0
104108
* simplecov-rcov 0.2.3
109+
* rubocop 0.47.1
105110

106111
## Authors
107112
* [Brian Clifton](https://github.com/bsclifton)

src/main/java/com/godaddy/sonar/ruby/RubyPlugin.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@
44
import com.godaddy.sonar.ruby.core.RubySourceCodeColorizer;
55
import com.godaddy.sonar.ruby.core.profiles.SonarWayProfile;
66
import com.godaddy.sonar.ruby.metricfu.*;
7+
78
import com.godaddy.sonar.ruby.simplecovrcov.DefaultCoverageSettings;
89
import com.godaddy.sonar.ruby.simplecovrcov.CoverageReportFileAnalyzerImpl;
10+
import com.godaddy.sonar.ruby.rubocop.CopsRulesDefinition;
11+
import com.godaddy.sonar.ruby.rubocop.RubocopSensor;
12+
import com.godaddy.sonar.ruby.rubocop.parsing.impl.DefaultReportJsonParser;
913
import com.godaddy.sonar.ruby.simplecovrcov.SimpleCovRcovSensor;
14+
1015
import org.sonar.api.CoreProperties;
1116
import org.sonar.api.Properties;
1217
import org.sonar.api.PropertyType;
@@ -32,12 +37,17 @@ public final class RubyPlugin extends SonarPlugin {
3237
public static final String KEY_REPOSITORY_ROODI = "roodi";
3338
public static final String NAME_REPOSITORY_ROODI = "Roodi";
3439

40+
public static final String KEY_REPOSITORY_RUBOCOP = "rubocop";
41+
public static final String NAME_REPOSITORY_RUBOCOP = "Rubocop";
42+
3543
public static final String SIMPLECOVRCOV_REPORT_PATH_PROPERTY = "sonar.simplecovrcov.reportPath";
3644
public static final String COVERAGE_TEST_SUITES_PROPERTY = "sonar.ruby.coverage.testSuites";
3745

3846
public static final String METRICFU_REPORT_PATH_PROPERTY = "sonar.metricfu.reportPath";
3947
public static final String METRICFU_COMPLEXITY_METRIC_PROPERTY = "sonar.metricfu.complexityMetric";
4048

49+
public static final String RUBOCOP_REPORT_PATH_PROPERTY = "sonar.rubocop.reportPath";
50+
4151
public List<Object> getExtensions() {
4252
List<Object> extensions = new ArrayList<Object>();
4353
extensions.add(Ruby.class);
@@ -47,6 +57,8 @@ public List<Object> getExtensions() {
4757
extensions.add(SimpleCovRcovSensor.class);
4858
extensions.add(CoverageReportFileAnalyzerImpl.class);
4959
extensions.add(MetricfuYamlParser.class);
60+
extensions.add(RubocopSensor.class);
61+
extensions.add(DefaultReportJsonParser.class);
5062
extensions.add(RubySourceCodeColorizer.class);
5163
extensions.add(RubySensor.class);
5264
extensions.add(MetricfuComplexitySensor.class);
@@ -55,6 +67,7 @@ public List<Object> getExtensions() {
5567
extensions.add(CaneRulesDefinition.class);
5668
extensions.add(ReekRulesDefinition.class);
5769
extensions.add(RoodiRulesDefinition.class);
70+
extensions.add(CopsRulesDefinition.class);
5871

5972

6073
// Profiles
@@ -80,6 +93,7 @@ public List<Object> getExtensions() {
8093
.build();
8194
extensions.add(simplecovrcovReportPath);
8295

96+
8397
PropertyDefinition coverageTestSuitesProperty = PropertyDefinition.builder(COVERAGE_TEST_SUITES_PROPERTY)
8498
.category(CoreProperties.CATEGORY_CODE_COVERAGE)
8599
.subCategory("Ruby Coverage")
@@ -94,6 +108,16 @@ public List<Object> getExtensions() {
94108
.build();
95109
extensions.add(coverageTestSuitesProperty);
96110

111+
PropertyDefinition rubocopReportPathProperty = PropertyDefinition.builder(RUBOCOP_REPORT_PATH_PROPERTY)
112+
.category(CoreProperties.CATEGORY_CODE_COVERAGE)
113+
.subCategory("Ruby Coverage")
114+
.name("Rubocop Report path")
115+
.description("Path (absolute or relative) to Rubocop json report file.")
116+
.defaultValue("tmp/rubocop/report.json")
117+
.onQualifiers(Qualifiers.PROJECT)
118+
.build();
119+
extensions.add(rubocopReportPathProperty);
120+
97121
List<String> options = Arrays.asList("Saikuro", "Cane");
98122

99123
PropertyDefinition ComplexityMetric = PropertyDefinition.builder(METRICFU_COMPLEXITY_METRIC_PROPERTY)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.godaddy.sonar.ruby.rubocop;
2+
3+
import com.godaddy.sonar.ruby.RubyPlugin;
4+
import com.godaddy.sonar.ruby.core.Ruby;
5+
import org.sonar.api.server.rule.RulesDefinition;
6+
import org.sonar.api.server.rule.RulesDefinitionXmlLoader;
7+
8+
import java.io.InputStream;
9+
10+
/**
11+
* Created by sergio on 3/13/17.
12+
*/
13+
public class CopsRulesDefinition implements RulesDefinition {
14+
private static final String RULES_DEFINITION_XML_RESOURCE_PATH = "/com/godaddy/sonar/ruby/rubocop/RulesRepository.xml";
15+
16+
private final RulesDefinitionXmlLoader xmlLoader;
17+
18+
public CopsRulesDefinition(RulesDefinitionXmlLoader xmlLoader) {
19+
this.xmlLoader = xmlLoader;
20+
}
21+
22+
@Override
23+
public void define(Context context) {
24+
NewRepository repository = createRepository(context);
25+
loadXmlRulesDefinitions(repository);
26+
repository.done();
27+
}
28+
29+
private NewRepository createRepository(Context context) {
30+
return context
31+
.createRepository(RubyPlugin.KEY_REPOSITORY_RUBOCOP, Ruby.KEY)
32+
.setName(RubyPlugin.NAME_REPOSITORY_RUBOCOP);
33+
}
34+
35+
private void loadXmlRulesDefinitions(NewRepository repository) {
36+
InputStream inputStream = getClass().getResourceAsStream(RULES_DEFINITION_XML_RESOURCE_PATH);
37+
xmlLoader.load(repository, inputStream, "UTF-8");
38+
}
39+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.godaddy.sonar.ruby.rubocop;
2+
3+
import java.io.File;
4+
5+
/**
6+
* Created by sergio on 3/15/17.
7+
*/
8+
public class ReportFile {
9+
private String path;
10+
private File ioFile;
11+
12+
public ReportFile(String baseDir, String reportPath) {
13+
this.path = baseDir + "/" + reportPath;
14+
this.ioFile = new File(path);
15+
}
16+
17+
public String getPath() {
18+
return this.path;
19+
}
20+
21+
public File getIoFile() {
22+
return this.ioFile;
23+
}
24+
25+
public Boolean isFileExists() {
26+
return this.ioFile.exists();
27+
}
28+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package com.godaddy.sonar.ruby.rubocop;
2+
3+
import com.godaddy.sonar.ruby.RubyPlugin;
4+
import com.godaddy.sonar.ruby.core.Ruby;
5+
import com.godaddy.sonar.ruby.rubocop.data.File;
6+
import com.godaddy.sonar.ruby.rubocop.data.Offense;
7+
import com.godaddy.sonar.ruby.rubocop.data.Report;
8+
import com.godaddy.sonar.ruby.rubocop.parsing.ReportJsonParser;
9+
import com.google.common.collect.Lists;
10+
import org.sonar.api.batch.fs.FilePredicate;
11+
import org.sonar.api.batch.fs.FileSystem;
12+
import org.sonar.api.batch.fs.InputFile;
13+
import org.sonar.api.batch.fs.TextRange;
14+
import org.sonar.api.batch.sensor.Sensor;
15+
import org.sonar.api.batch.sensor.SensorContext;
16+
import org.sonar.api.batch.sensor.SensorDescriptor;
17+
import org.sonar.api.batch.sensor.issue.NewIssue;
18+
import org.sonar.api.batch.sensor.issue.NewIssueLocation;
19+
import org.sonar.api.config.Settings;
20+
import org.sonar.api.rule.RuleKey;
21+
import org.sonar.api.utils.log.Logger;
22+
import org.sonar.api.utils.log.Loggers;
23+
24+
import java.io.IOException;
25+
import java.util.List;
26+
27+
/**
28+
* Created by sergio on 3/13/17.
29+
*/
30+
public class RubocopSensor implements Sensor {
31+
private static final Logger LOG = Loggers.get(RubocopSensor.class);
32+
33+
private FileSystem fileSystem;
34+
private ReportJsonParser reportJsonParser;
35+
private Report reportData;
36+
private ReportFile reportFile;
37+
38+
public RubocopSensor(FileSystem fileSystem,
39+
ReportJsonParser reportJsonParser,
40+
Settings settings){
41+
this.fileSystem = fileSystem;
42+
this.reportJsonParser = reportJsonParser;
43+
44+
String reportPath = settings.getString(RubyPlugin.RUBOCOP_REPORT_PATH_PROPERTY);
45+
this.reportFile = new ReportFile(fileSystem.baseDir().toString(), reportPath);
46+
}
47+
48+
@Override
49+
public void describe(SensorDescriptor sensorDescriptor) {
50+
sensorDescriptor.onlyOnLanguage(Ruby.KEY).name("RubocopSensor");
51+
}
52+
53+
@Override
54+
public void execute(SensorContext context) {
55+
if (!this.reportFile.isFileExists()) {
56+
LOG.info("There is no Rubocop report file at path " + this.reportFile.getPath() + ". Skip Rubocop sensor.");
57+
return;
58+
}
59+
this.parseJsonReport();
60+
for (InputFile file : inputRubyFiles()) {
61+
LOG.debug("analyzing issues in the file: " + file.absolutePath());
62+
try {
63+
analyzeFile(file, context);
64+
} catch (IOException e) {
65+
LOG.error("Can not analyze the file " + file.absolutePath() + " for issues");
66+
}
67+
}
68+
}
69+
70+
private List<InputFile> inputRubyFiles() {
71+
FilePredicate filePredicate = fileSystem.predicates().hasLanguage(Ruby.KEY);
72+
return Lists.newArrayList(fileSystem.inputFiles(filePredicate));
73+
}
74+
75+
private void parseJsonReport() {
76+
try {
77+
this.reportData = reportJsonParser.parse(this.reportFile.getIoFile());
78+
} catch (IOException e) {
79+
LOG.error("Unable to load Rubocop report file from " + this.reportFile.getPath() + "!");
80+
e.printStackTrace(System.out);
81+
}
82+
}
83+
84+
private void analyzeFile(InputFile file, SensorContext context) throws IOException {
85+
File fileData = this.reportData.getFileInfoByPath(file.relativePath());
86+
if (fileData == null) {
87+
LOG.error("Unable to find report for file: " + file.relativePath());
88+
return;
89+
}
90+
for(Offense offense : fileData.getOffenses()) {
91+
createIssueForOffence(context, file, offense);
92+
}
93+
}
94+
95+
private void createIssueForOffence(SensorContext context, InputFile file, Offense offense) {
96+
NewIssue issue = context.newIssue();
97+
TextRange textRange = file.selectLine(offense.getLocation().getLine());
98+
NewIssueLocation issueLocation = issue.newLocation().on(file).at(textRange).message(offense.getMessage());
99+
RuleKey ruleKey = RuleKey.of(RubyPlugin.KEY_REPOSITORY_RUBOCOP, offense.getCopName());
100+
issue.forRule(ruleKey).at(issueLocation).save();
101+
}
102+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.godaddy.sonar.ruby.rubocop.data;
2+
3+
import java.util.List;
4+
5+
/**
6+
* Created by sergio on 3/13/17.
7+
*/
8+
public class File {
9+
private String path;
10+
private List<Offense> offenses;
11+
12+
public String getPath() {
13+
return path;
14+
}
15+
16+
public void setPath(String path) {
17+
this.path = path;
18+
}
19+
20+
public List<Offense> getOffenses() {
21+
return offenses;
22+
}
23+
24+
public void setOffenses(List<Offense> offenses) {
25+
this.offenses = offenses;
26+
}
27+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.godaddy.sonar.ruby.rubocop.data;
2+
3+
/**
4+
* Created by sergio on 3/13/17.
5+
*/
6+
public class Location {
7+
private Integer line;
8+
private Integer column;
9+
private Integer length;
10+
11+
public Integer getLine() {
12+
return line;
13+
}
14+
15+
public void setLine(Integer line) {
16+
this.line = line;
17+
}
18+
19+
public Integer getColumn() {
20+
return column;
21+
}
22+
23+
public void setColumn(Integer column) {
24+
this.column = column;
25+
}
26+
27+
public Integer getLength() {
28+
return length;
29+
}
30+
31+
public void setLength(Integer length) {
32+
this.length = length;
33+
}
34+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.godaddy.sonar.ruby.rubocop.data;
2+
3+
/**
4+
* Created by sergio on 3/13/17.
5+
*/
6+
public class Metadata {
7+
private String rubocopVersion;
8+
private String rubyEngine;
9+
private String rubyVersion;
10+
private String rubyPatchlevel;
11+
private String rubyPlatform;
12+
13+
public String getRubocopVersion() {
14+
return rubocopVersion;
15+
}
16+
17+
public void setRubocopVersion(String rubocopVersion) {
18+
this.rubocopVersion = rubocopVersion;
19+
}
20+
21+
public String getRubyEngine() {
22+
return rubyEngine;
23+
}
24+
25+
public void setRubyEngine(String rubyEngine) {
26+
this.rubyEngine = rubyEngine;
27+
}
28+
29+
public String getRubyVersion() {
30+
return rubyVersion;
31+
}
32+
33+
public void setRubyVersion(String rubyVersion) {
34+
this.rubyVersion = rubyVersion;
35+
}
36+
37+
public String getRubyPatchlevel() {
38+
return rubyPatchlevel;
39+
}
40+
41+
public void setRubyPatchlevel(String rubyPatchlevel) {
42+
this.rubyPatchlevel = rubyPatchlevel;
43+
}
44+
45+
public String getRubyPlatform() {
46+
return rubyPlatform;
47+
}
48+
49+
public void setRubyPlatform(String rubyPlatform) {
50+
this.rubyPlatform = rubyPlatform;
51+
}
52+
}

0 commit comments

Comments
 (0)