Browse Source

SONAR-6052 Extract primaryLocation as a special attribute

Also rework new issue API on batch side.
tags/5.2-RC1
Julien HENRY 8 years ago
parent
commit
93420cb740
65 changed files with 1536 additions and 955 deletions
  1. 2
    2
      plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/checks/TemplateRuleCheck.java
  2. 2
    2
      plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/CreateIssueByInternalKeySensor.java
  3. 10
    5
      plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/MultilineIssuesSensor.java
  4. 2
    2
      plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssueOnDirPerFileSensor.java
  5. 2
    2
      plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssuePerLineSensor.java
  6. 2
    2
      plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/RandomAccessSensor.java
  7. 719
    529
      sonar-batch-protocol/src/main/gen-java/org/sonar/batch/protocol/output/BatchReport.java
  8. 4
    3
      sonar-batch-protocol/src/main/protobuf/batch_report.proto
  9. 7
    14
      sonar-batch/src/main/java/org/sonar/batch/index/BatchComponent.java
  10. 8
    7
      sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java
  11. 1
    1
      sonar-batch/src/main/java/org/sonar/batch/issue/DefaultIssuable.java
  12. 20
    25
      sonar-batch/src/main/java/org/sonar/batch/issue/DeprecatedIssueBuilderWrapper.java
  13. 1
    1
      sonar-batch/src/main/java/org/sonar/batch/issue/DeprecatedIssueWrapper.java
  14. 163
    8
      sonar-batch/src/main/java/org/sonar/batch/issue/IssueFilters.java
  15. 84
    67
      sonar-batch/src/main/java/org/sonar/batch/issue/ModuleIssues.java
  16. 2
    5
      sonar-batch/src/main/java/org/sonar/batch/issue/tracking/LocalIssueTracking.java
  17. 1
    1
      sonar-batch/src/main/java/org/sonar/batch/issue/tracking/ServerIssueRepository.java
  18. 1
    1
      sonar-batch/src/main/java/org/sonar/batch/mediumtest/TaskResult.java
  19. 5
    6
      sonar-batch/src/main/java/org/sonar/batch/postjob/DefaultPostJobContext.java
  20. 1
    1
      sonar-batch/src/main/java/org/sonar/batch/report/ComponentsPublisher.java
  21. 43
    37
      sonar-batch/src/main/java/org/sonar/batch/report/CoveragePublisher.java
  22. 1
    1
      sonar-batch/src/main/java/org/sonar/batch/report/SourcePublisher.java
  23. 1
    1
      sonar-batch/src/main/java/org/sonar/batch/report/TestExecutionAndCoveragePublisher.java
  24. 2
    2
      sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/ComponentIndexer.java
  25. 4
    18
      sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputPathCache.java
  26. 5
    6
      sonar-batch/src/main/java/org/sonar/batch/scan/report/SourceProvider.java
  27. 1
    1
      sonar-batch/src/main/java/org/sonar/batch/source/HighlightableBuilder.java
  28. 1
    1
      sonar-batch/src/main/java/org/sonar/batch/source/SymbolizableBuilder.java
  29. 1
    1
      sonar-batch/src/main/java/org/sonar/batch/test/TestPlanBuilder.java
  30. 1
    1
      sonar-batch/src/main/java/org/sonar/batch/test/TestableBuilder.java
  31. 10
    9
      sonar-batch/src/test/java/org/sonar/batch/issue/IssueFiltersTest.java
  32. 15
    18
      sonar-batch/src/test/java/org/sonar/batch/issue/ModuleIssuesTest.java
  33. 60
    9
      sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/MultilineIssuesMediumTest.java
  34. 2
    2
      sonar-batch/src/test/java/org/sonar/batch/mediumtest/preview/PreviewAndReportsMediumTest.java
  35. 5
    6
      sonar-batch/src/test/java/org/sonar/batch/postjob/DefaultPostJobContextTest.java
  36. 12
    10
      sonar-batch/src/test/java/org/sonar/batch/report/ComponentsPublisherTest.java
  37. 3
    2
      sonar-batch/src/test/java/org/sonar/batch/report/CoveragePublisherTest.java
  38. 8
    8
      sonar-batch/src/test/java/org/sonar/batch/report/DuplicationsPublisherTest.java
  39. 3
    2
      sonar-batch/src/test/java/org/sonar/batch/report/SourcePublisherTest.java
  40. 1
    1
      sonar-batch/src/test/java/org/sonar/batch/sensor/DefaultSensorStorageTest.java
  41. 4
    2
      sonar-batch/src/test/java/org/sonar/batch/source/HighlightableBuilderTest.java
  42. 4
    2
      sonar-batch/src/test/java/org/sonar/batch/source/SymbolizableBuilderTest.java
  43. 0
    2
      sonar-batch/src/test/resources/mediumtest/xoo/sample-multiline/xources/hello/HelloJava.xoo.measures
  44. 9
    0
      sonar-batch/src/test/resources/mediumtest/xoo/sample-multiline/xources/hello/Multiline.xoo
  45. 9
    0
      sonar-batch/src/test/resources/mediumtest/xoo/sample-multiline/xources/hello/Multiple.xoo
  46. 0
    0
      sonar-batch/src/test/resources/mediumtest/xoo/sample-multiline/xources/hello/Single.xoo
  47. 5
    0
      sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueBuilder.java
  48. 36
    0
      sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/InputComponent.java
  49. 29
    0
      sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/InputModule.java
  50. 1
    2
      sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/InputPath.java
  51. 53
    0
      sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputComponent.java
  52. 9
    4
      sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputDir.java
  53. 7
    1
      sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputFile.java
  54. 45
    0
      sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputModule.java
  55. 4
    5
      sonar-plugin-api/src/main/java/org/sonar/api/batch/postjob/issue/Issue.java
  56. 14
    1
      sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/Issue.java
  57. 3
    4
      sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/IssueLocation.java
  58. 13
    1
      sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/NewIssue.java
  59. 4
    14
      sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/NewIssueLocation.java
  60. 28
    34
      sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssue.java
  61. 11
    38
      sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueLocation.java
  62. 8
    2
      sonar-plugin-api/src/main/java/org/sonar/api/issue/Issuable.java
  63. 2
    2
      sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/internal/SensorContextTesterTest.java
  64. 6
    6
      sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueLocationTest.java
  65. 16
    15
      sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueTest.java

+ 2
- 2
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/checks/TemplateRuleCheck.java View File

@@ -40,8 +40,8 @@ public class TemplateRuleCheck implements Check {
NewIssue newIssue = sensorContext.newIssue();
newIssue
.forRule(ruleKey)
.addLocation(newIssue.newLocation()
.onFile(file)
.at(newIssue.newLocation()
.on(file)
.at(file.selectLine(line)))
.save();
}

+ 2
- 2
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/CreateIssueByInternalKeySensor.java View File

@@ -58,8 +58,8 @@ public class CreateIssueByInternalKeySensor implements Sensor {
NewIssue newIssue = context.newIssue();
newIssue
.forRule(rule.ruleKey())
.addLocation(newIssue.newLocation()
.onFile(file)
.at(newIssue.newLocation()
.on(file)
.message("This issue is generated on each file"))
.save();
}

+ 10
- 5
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/MultilineIssuesSensor.java View File

@@ -34,6 +34,7 @@ import org.sonar.api.batch.sensor.Sensor;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.SensorDescriptor;
import org.sonar.api.batch.sensor.issue.NewIssue;
import org.sonar.api.batch.sensor.issue.NewIssueLocation;
import org.sonar.api.rule.RuleKey;
import org.sonar.xoo.Xoo;

@@ -76,7 +77,7 @@ public class MultilineIssuesSensor implements Sensor {
while (m.find()) {
Integer issueId = Integer.parseInt(m.group(1));
Integer issueLocationId = Integer.parseInt(m.group(2));
TextPointer newPointer = file.newPointer(currentLine, m.start());
TextPointer newPointer = file.newPointer(currentLine, m.end());
if (!startPositions.containsKey(issueId)) {
startPositions.put(issueId, new HashMap<Integer, TextPointer>());
}
@@ -100,10 +101,14 @@ public class MultilineIssuesSensor implements Sensor {
for (Map.Entry<Integer, Map<Integer, TextPointer>> entry : startPositions.entrySet()) {
NewIssue newIssue = context.newIssue().forRule(ruleKey);
for (Map.Entry<Integer, TextPointer> location : entry.getValue().entrySet()) {
newIssue.addLocation(newIssue.newLocation()
.onFile(file)
.at(file.newRange(location.getValue(), endPositions.get(entry.getKey()).get(location.getKey())))
.message("Multiline issue"));
NewIssueLocation newLocation = newIssue.newLocation()
.on(file)
.at(file.newRange(location.getValue(), endPositions.get(entry.getKey()).get(location.getKey())));
if (location.getKey() == 1) {
newIssue.at(newLocation.message("Primary location"));
} else {
newIssue.addLocation(newLocation.message("Location #" + location.getKey()));
}
}
newIssue.save();
}

+ 2
- 2
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssueOnDirPerFileSensor.java View File

@@ -54,8 +54,8 @@ public class OneIssueOnDirPerFileSensor implements Sensor {
NewIssue newIssue = context.newIssue();
newIssue
.forRule(ruleKey)
.addLocation(newIssue.newLocation()
.onDir(inputDir)
.at(newIssue.newLocation()
.on(inputDir)
.message("This issue is generated for file " + file.relativePath()))
.save();
}

+ 2
- 2
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssuePerLineSensor.java View File

@@ -61,8 +61,8 @@ public class OneIssuePerLineSensor implements Sensor {
NewIssue newIssue = context.newIssue();
newIssue
.forRule(ruleKey)
.addLocation(newIssue.newLocation()
.onFile(file)
.at(newIssue.newLocation()
.on(file)
.at(file.selectLine(line))
.message("This issue is generated on each line"))
.effortToFix(context.settings().getDouble(EFFORT_TO_FIX_PROPERTY))

+ 2
- 2
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/RandomAccessSensor.java View File

@@ -66,8 +66,8 @@ public class RandomAccessSensor implements Sensor {
NewIssue newIssue = context.newIssue();
newIssue
.forRule(ruleKey)
.addLocation(newIssue.newLocation()
.onFile(file)
.at(newIssue.newLocation()
.on(file)
.at(file.selectLine(1))
.message("This issue is generated on each file"))
.save();

+ 719
- 529
sonar-batch-protocol/src/main/gen-java/org/sonar/batch/protocol/output/BatchReport.java
File diff suppressed because it is too large
View File


+ 4
- 3
sonar-batch-protocol/src/main/protobuf/batch_report.proto View File

@@ -103,8 +103,9 @@ message Issue {
optional Severity severity = 5;
optional double effort_to_fix = 6;
optional string attributes = 7;
repeated IssueLocation locations = 8;
repeated ExecutionFlow execution_flows = 9;
optional IssueLocation primary_location = 9;
repeated IssueLocation additional_location = 10;
repeated ExecutionFlow execution_flow = 11;
}

message IssueLocation {
@@ -115,7 +116,7 @@ message IssueLocation {
}

message ExecutionFlow {
repeated IssueLocation locations = 1;
repeated IssueLocation location = 1;
}

message Changesets {

+ 7
- 14
sonar-batch/src/main/java/org/sonar/batch/index/BatchComponent.java View File

@@ -23,9 +23,7 @@ import java.util.ArrayList;
import java.util.Collection;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.batch.fs.InputPath;
import org.sonar.api.resources.Qualifiers;
import org.sonar.api.batch.fs.InputComponent;
import org.sonar.api.resources.Resource;
import org.sonar.api.resources.ResourceUtils;

@@ -35,7 +33,7 @@ public class BatchComponent {
private final Resource r;
private final BatchComponent parent;
private final Collection<BatchComponent> children = new ArrayList<>();
private InputPath inputPath;
private InputComponent inputComponent;

public BatchComponent(int batchId, Resource r, @Nullable BatchComponent parent) {
this.batchId = batchId;
@@ -68,21 +66,16 @@ public class BatchComponent {
}

public boolean isFile() {
return Qualifiers.isFile(r) || StringUtils.equals(Qualifiers.UNIT_TEST_FILE, r.getQualifier());
return this.inputComponent.isFile();
}

public boolean isDir() {
return Qualifiers.isDirectory(r);
}

public BatchComponent setInputPath(InputPath inputPath) {
this.inputPath = inputPath;
public BatchComponent setInputComponent(InputComponent inputComponent) {
this.inputComponent = inputComponent;
return this;
}

@CheckForNull
public InputPath inputPath() {
return inputPath;
public InputComponent inputComponent() {
return inputComponent;
}

public boolean isProjectOrModule() {

+ 8
- 7
sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java View File

@@ -37,6 +37,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.SonarIndex;
import org.sonar.api.batch.bootstrap.ProjectDefinition;
import org.sonar.api.batch.fs.internal.DefaultInputModule;
import org.sonar.api.batch.measure.MetricFinder;
import org.sonar.api.design.Dependency;
import org.sonar.api.measures.Measure;
@@ -82,7 +83,8 @@ public class DefaultIndex extends SonarIndex {
void doStart(Project rootProject) {
Bucket bucket = new Bucket(rootProject);
addBucket(rootProject, bucket);
componentCache.add(rootProject, null);
BatchComponent component = componentCache.add(rootProject, null);
component.setInputComponent(new DefaultInputModule(rootProject.getEffectiveKey()));
currentProject = rootProject;

for (Project module : rootProject.getModules()) {
@@ -269,11 +271,7 @@ public class DefaultIndex extends SonarIndex {
return null;
}

Resource parent = null;
if (!ResourceUtils.isLibrary(resource)) {
// a library has no parent
parent = (Resource) ObjectUtils.defaultIfNull(parentReference, currentProject);
}
Resource parent = (Resource) ObjectUtils.defaultIfNull(parentReference, currentProject);

Bucket parentBucket = getBucket(parent);
if (parentBucket == null && parent != null) {
@@ -290,7 +288,10 @@ public class DefaultIndex extends SonarIndex {
addBucket(resource, bucket);

Resource parentResource = parentBucket != null ? parentBucket.getResource() : null;
componentCache.add(resource, parentResource);
BatchComponent component = componentCache.add(resource, parentResource);
if (ResourceUtils.isProject(resource)) {
component.setInputComponent(new DefaultInputModule(resource.getEffectiveKey()));
}

return bucket;
}

+ 1
- 1
sonar-batch/src/main/java/org/sonar/batch/issue/DefaultIssuable.java View File

@@ -43,7 +43,7 @@ public class DefaultIssuable implements Issuable {
@Override
public IssueBuilder newIssueBuilder() {
DefaultIssue newIssue = (DefaultIssue) sensorContext.newIssue();
return new DeprecatedIssueBuilderWrapper(component, newIssue);
return new DeprecatedIssueBuilderWrapper(component.inputComponent(), newIssue);
}

@Override

+ 20
- 25
sonar-batch/src/main/java/org/sonar/batch/issue/DeprecatedIssueBuilderWrapper.java View File

@@ -19,10 +19,9 @@
*/
package org.sonar.batch.issue;

import java.util.ArrayList;
import java.util.List;
import com.google.common.base.Preconditions;
import javax.annotation.Nullable;
import org.sonar.api.batch.fs.InputDir;
import org.sonar.api.batch.fs.InputComponent;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.TextRange;
import org.sonar.api.batch.rule.Severity;
@@ -33,17 +32,15 @@ import org.sonar.api.issue.Issuable;
import org.sonar.api.issue.Issuable.IssueBuilder;
import org.sonar.api.issue.Issue;
import org.sonar.api.rule.RuleKey;
import org.sonar.batch.index.BatchComponent;

public class DeprecatedIssueBuilderWrapper implements Issuable.IssueBuilder {

private final DefaultIssue newIssue;
private final BatchComponent primaryComponent;
private final InputComponent primaryComponent;
private TextRange primaryRange = null;
private String primaryMessage = null;
private List<NewIssueLocation> locations = new ArrayList<>();

public DeprecatedIssueBuilderWrapper(BatchComponent primaryComponent, DefaultIssue newIssue) {
public DeprecatedIssueBuilderWrapper(InputComponent primaryComponent, DefaultIssue newIssue) {
this.primaryComponent = primaryComponent;
this.newIssue = newIssue;
}
@@ -56,9 +53,10 @@ public class DeprecatedIssueBuilderWrapper implements Issuable.IssueBuilder {

@Override
public IssueBuilder line(@Nullable Integer line) {
Preconditions.checkState(newIssue.primaryLocation() == null, "Do not use line() and at() for the same issue");
if (primaryComponent.isFile()) {
if (line != null) {
this.primaryRange = ((InputFile) primaryComponent.inputPath()).selectLine(line.intValue());
this.primaryRange = ((InputFile) primaryComponent).selectLine(line.intValue());
}
return this;
} else {
@@ -68,6 +66,7 @@ public class DeprecatedIssueBuilderWrapper implements Issuable.IssueBuilder {

@Override
public IssueBuilder message(String message) {
Preconditions.checkState(newIssue.primaryLocation() == null, "Do not use message() and at() for the same issue");
this.primaryMessage = message;
return this;
}
@@ -77,9 +76,16 @@ public class DeprecatedIssueBuilderWrapper implements Issuable.IssueBuilder {
return new DefaultIssueLocation();
}

@Override
public IssueBuilder at(NewIssueLocation primaryLocation) {
Preconditions.checkState(primaryMessage == null && primaryRange == null, "Do not use message() or line() and at() for the same issue");
newIssue.at(primaryLocation);
return this;
}

@Override
public IssueBuilder addLocation(NewIssueLocation location) {
locations.add(location);
newIssue.addLocation(location);
return this;
}

@@ -113,27 +119,16 @@ public class DeprecatedIssueBuilderWrapper implements Issuable.IssueBuilder {

@Override
public Issue build() {
if (primaryMessage != null || primaryRange != null || locations.isEmpty()) {
NewIssueLocation newLocation = newIssue.newLocation();
if (newIssue.primaryLocation() == null) {
NewIssueLocation newLocation = newIssue.newLocation().on(primaryComponent);
if (primaryMessage != null) {
newLocation.message(primaryMessage);
}
if (primaryComponent.isProjectOrModule()) {
newLocation.onProject();
} else if (primaryComponent.isFile()) {
newLocation.onFile((InputFile) primaryComponent.inputPath());
if (primaryRange != null) {
newLocation.at(primaryRange);
}
} else if (primaryComponent.isDir()) {
newLocation.onDir((InputDir) primaryComponent.inputPath());
if (primaryComponent.isFile() && primaryRange != null) {
newLocation.at(primaryRange);
}
newIssue.addLocation(newLocation);
}
for (NewIssueLocation issueLocation : locations) {
newIssue.addLocation(issueLocation);
newIssue.at(newLocation);
}

return new DeprecatedIssueWrapper(newIssue);
}


+ 1
- 1
sonar-batch/src/main/java/org/sonar/batch/issue/DeprecatedIssueWrapper.java View File

@@ -45,7 +45,7 @@ public class DeprecatedIssueWrapper implements Issue {

@Override
public String key() {
return newIssue.key();
return null;
}

@Override

+ 163
- 8
sonar-batch/src/main/java/org/sonar/batch/issue/IssueFilters.java View File

@@ -19,34 +19,48 @@
*/
package org.sonar.batch.issue;

import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.sonar.api.batch.BatchSide;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.IssueComment;
import org.sonar.api.issue.batch.IssueFilter;
import org.sonar.api.resources.Project;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.utils.Duration;
import org.sonar.api.utils.KeyValueFormat;
import org.sonar.batch.protocol.output.BatchReport;

@BatchSide
public class IssueFilters {

private final org.sonar.api.issue.IssueFilter[] exclusionFilters;
private final IssueFilter[] filters;
private final Project project;

public IssueFilters(org.sonar.api.issue.IssueFilter[] exclusionFilters, IssueFilter[] filters) {
public IssueFilters(Project project, org.sonar.api.issue.IssueFilter[] exclusionFilters, IssueFilter[] filters) {
this.project = project;
this.exclusionFilters = exclusionFilters;
this.filters = filters;
}

public IssueFilters(org.sonar.api.issue.IssueFilter[] exclusionFilters) {
this(exclusionFilters, new IssueFilter[0]);
public IssueFilters(Project project, org.sonar.api.issue.IssueFilter[] exclusionFilters) {
this(project, exclusionFilters, new IssueFilter[0]);
}

public IssueFilters(IssueFilter[] filters) {
this(new org.sonar.api.issue.IssueFilter[0], filters);
public IssueFilters(Project project, IssueFilter[] filters) {
this(project, new org.sonar.api.issue.IssueFilter[0], filters);
}

public IssueFilters() {
this(new org.sonar.api.issue.IssueFilter[0], new IssueFilter[0]);
public IssueFilters(Project project) {
this(project, new org.sonar.api.issue.IssueFilter[0], new IssueFilter[0]);
}

public boolean accept(Issue issue) {
public boolean accept(String componentKey, BatchReport.Issue rawIssue) {
Issue issue = toIssueForIssueFilter(componentKey, rawIssue);
if (new DefaultIssueFilterChain(filters).accept(issue)) {
// Apply deprecated rules only if filter chain accepts the current issue
for (org.sonar.api.issue.IssueFilter filter : exclusionFilters) {
@@ -59,4 +73,145 @@ public class IssueFilters {
return false;
}
}

private Issue toIssueForIssueFilter(final String componentKey, final BatchReport.Issue rawIssue) {
return new Issue() {

@Override
public String key() {
throw unsupported();
}

@Override
public String componentKey() {
return componentKey;
}

@Override
public RuleKey ruleKey() {
return RuleKey.of(rawIssue.getRuleRepository(), rawIssue.getRuleKey());
}

@Override
public String language() {
throw unsupported();
}

@Override
public String severity() {
return rawIssue.getSeverity().name();
}

@Override
public String message() {
return rawIssue.getMsg();
}

@Override
public Integer line() {
return rawIssue.hasLine() ? rawIssue.getLine() : null;
}

@Override
public Double effortToFix() {
return rawIssue.hasEffortToFix() ? rawIssue.getEffortToFix() : null;
}

@Override
public String status() {
return Issue.STATUS_OPEN;
}

@Override
public String resolution() {
return null;
}

@Override
public String reporter() {
throw unsupported();
}

@Override
public String assignee() {
return null;
}

@Override
public Date creationDate() {
return project.getAnalysisDate();
}

@Override
public Date updateDate() {
return null;
}

@Override
public Date closeDate() {
return null;
}

@Override
public String attribute(String key) {
return attributes().get(key);
}

@Override
public Map<String, String> attributes() {
return rawIssue.hasAttributes() ? KeyValueFormat.parse(rawIssue.getAttributes()) : Collections.<String, String>emptyMap();
}

@Override
public String authorLogin() {
throw unsupported();
}

@Override
public String actionPlanKey() {
throw unsupported();
}

@Override
public List<IssueComment> comments() {
throw unsupported();
}

@Override
public boolean isNew() {
throw unsupported();
}

@Override
public Duration debt() {
throw unsupported();
}

@Override
public String projectKey() {
return project.getEffectiveKey();
}

@Override
public String projectUuid() {
throw unsupported();
}

@Override
public String componentUuid() {
throw unsupported();
}

@Override
public Collection<String> tags() {
throw unsupported();
}

private UnsupportedOperationException unsupported() {
return new UnsupportedOperationException("Not available for issues filters");
}

};

}
}

+ 84
- 67
sonar-batch/src/main/java/org/sonar/batch/issue/ModuleIssues.java View File

@@ -20,25 +20,24 @@
package org.sonar.batch.issue;

import com.google.common.base.Strings;
import javax.annotation.Nullable;
import org.sonar.api.batch.fs.InputPath;
import org.sonar.api.batch.fs.TextRange;
import org.sonar.api.batch.fs.internal.DefaultInputComponent;
import org.sonar.api.batch.rule.ActiveRule;
import org.sonar.api.batch.rule.ActiveRules;
import org.sonar.api.batch.rule.Rule;
import org.sonar.api.batch.rule.Rules;
import org.sonar.api.batch.rule.Severity;
import org.sonar.api.batch.sensor.issue.Issue;
import org.sonar.api.resources.Project;
import org.sonar.api.batch.sensor.issue.Issue.ExecutionFlow;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.utils.KeyValueFormat;
import org.sonar.api.utils.MessageException;
import org.sonar.batch.index.BatchComponent;
import org.sonar.batch.index.BatchComponentCache;
import org.sonar.batch.protocol.Constants;
import org.sonar.batch.protocol.Constants.Severity;
import org.sonar.batch.protocol.output.BatchReport;
import org.sonar.batch.protocol.output.BatchReport.IssueLocation;
import org.sonar.batch.protocol.output.BatchReport.IssueLocation.Builder;
import org.sonar.batch.report.ReportPublisher;
import org.sonar.core.issue.DefaultIssue;

/**
* Initialize the issues raised during scan.
@@ -47,108 +46,126 @@ public class ModuleIssues {

private final ActiveRules activeRules;
private final Rules rules;
private final Project project;
private final IssueFilters filters;
private final ReportPublisher reportPublisher;
private final BatchComponentCache componentCache;
private final BatchReport.Issue.Builder builder = BatchReport.Issue.newBuilder();
private final Builder locationBuilder = IssueLocation.newBuilder();
private final org.sonar.batch.protocol.output.BatchReport.TextRange.Builder textRangeBuilder = org.sonar.batch.protocol.output.BatchReport.TextRange.newBuilder();
private final BatchReport.ExecutionFlow.Builder flowBuilder = BatchReport.ExecutionFlow.newBuilder();

public ModuleIssues(ActiveRules activeRules, Rules rules, Project project, IssueFilters filters, ReportPublisher reportPublisher, BatchComponentCache componentCache) {
public ModuleIssues(ActiveRules activeRules, Rules rules, IssueFilters filters, ReportPublisher reportPublisher, BatchComponentCache componentCache) {
this.activeRules = activeRules;
this.rules = rules;
this.project = project;
this.filters = filters;
this.reportPublisher = reportPublisher;
this.componentCache = componentCache;
}

public boolean initAndAddIssue(Issue issue) {
BatchComponent component;
InputPath inputPath = issue.locations().get(0).inputPath();
if (inputPath != null) {
component = componentCache.get(inputPath);
} else {
component = componentCache.get(project);
}
DefaultIssue defaultIssue = toDefaultIssue(project.getKey(), component.key(), issue);
RuleKey ruleKey = defaultIssue.ruleKey();
Rule rule = rules.find(ruleKey);
validateRule(defaultIssue, rule);
ActiveRule activeRule = activeRules.find(ruleKey);
String key = ((DefaultInputComponent) issue.primaryLocation().inputComponent()).key();
BatchComponent component = componentCache.get(key);

Rule rule = validateRule(issue);
ActiveRule activeRule = activeRules.find(issue.ruleKey());
if (activeRule == null) {
// rule does not exist or is not enabled -> ignore the issue
return false;
}
updateIssue(defaultIssue, rule, activeRule);
if (filters.accept(defaultIssue)) {
write(component, defaultIssue);
return true;
}
return false;
}

public void write(BatchComponent component, DefaultIssue issue) {
reportPublisher.getWriter().appendComponentIssue(component.batchId(), toReportIssue(builder, issue));
}
String primaryMessage = Strings.isNullOrEmpty(issue.primaryLocation().message()) ? rule.name() : issue.primaryLocation().message();
org.sonar.api.batch.rule.Severity overriddenSeverity = issue.overriddenSeverity();
Severity severity = overriddenSeverity != null ? Severity.valueOf(overriddenSeverity.name()) : Severity.valueOf(activeRule.severity());

private static BatchReport.Issue toReportIssue(BatchReport.Issue.Builder builder, DefaultIssue issue) {
builder.clear();
locationBuilder.clear();
// non-null fields
builder.setSeverity(Constants.Severity.valueOf(issue.severity()));
builder.setSeverity(severity);
builder.setRuleRepository(issue.ruleKey().repository());
builder.setRuleKey(issue.ruleKey().rule());
builder.setAttributes(KeyValueFormat.format(issue.attributes()));
builder.setMsg(primaryMessage);
locationBuilder.setMsg(primaryMessage);

// nullable fields
Integer line = issue.line();
if (line != null) {
builder.setLine(line);
}
String message = issue.message();
if (message != null) {
builder.setMsg(message);
locationBuilder.setComponentRef(component.batchId());
TextRange primaryTextRange = issue.primaryLocation().textRange();
applyTextRange(primaryTextRange);
if (primaryTextRange != null) {
builder.setLine(primaryTextRange.start().line());
}
builder.setPrimaryLocation(locationBuilder.build());
Double effortToFix = issue.effortToFix();
if (effortToFix != null) {
builder.setEffortToFix(effortToFix);
}
return builder.build();
applyAdditionalLocations(issue);
applyExecutionFlows(issue);
BatchReport.Issue rawIssue = builder.build();

if (filters.accept(key, rawIssue)) {
write(component, rawIssue);
return true;
}
return false;
}

public static DefaultIssue toDefaultIssue(String projectKey, String componentKey, Issue issue) {
Severity overriddenSeverity = issue.overriddenSeverity();
TextRange textRange = issue.locations().get(0).textRange();
return new org.sonar.core.issue.DefaultIssueBuilder()
.componentKey(componentKey)
.projectKey(projectKey)
.ruleKey(RuleKey.of(issue.ruleKey().repository(), issue.ruleKey().rule()))
.effortToFix(issue.effortToFix())
.line(textRange != null ? textRange.start().line() : null)
.message(issue.locations().get(0).message())
.severity(overriddenSeverity != null ? overriddenSeverity.name() : null)
.build();
private void applyAdditionalLocations(Issue issue) {
for (org.sonar.api.batch.sensor.issue.IssueLocation additionalLocation : issue.locations()) {
locationBuilder.clear();
String locationComponentKey = ((DefaultInputComponent) additionalLocation.inputComponent()).key();
locationBuilder.setComponentRef(componentCache.get(locationComponentKey).batchId());
String message = additionalLocation.message();
if (message != null) {
locationBuilder.setMsg(message);
}
applyTextRange(additionalLocation.textRange());
builder.addAdditionalLocation(locationBuilder.build());
}
}

private void applyExecutionFlows(Issue issue) {
for (ExecutionFlow executionFlow : issue.executionFlows()) {
flowBuilder.clear();
for (org.sonar.api.batch.sensor.issue.IssueLocation location : executionFlow.locations()) {
locationBuilder.clear();
String locationComponentKey = ((DefaultInputComponent) location.inputComponent()).key();
locationBuilder.setComponentRef(componentCache.get(locationComponentKey).batchId());
String message = location.message();
if (message != null) {
locationBuilder.setMsg(message);
}
applyTextRange(location.textRange());
flowBuilder.addLocation(locationBuilder.build());
}
builder.addExecutionFlow(flowBuilder.build());
}
}

private void applyTextRange(TextRange primaryTextRange) {
if (primaryTextRange != null) {
textRangeBuilder.clear();
textRangeBuilder.setStartLine(primaryTextRange.start().line());
textRangeBuilder.setStartOffset(primaryTextRange.start().lineOffset());
textRangeBuilder.setEndLine(primaryTextRange.end().line());
textRangeBuilder.setEndOffset(primaryTextRange.end().lineOffset());
locationBuilder.setTextRange(textRangeBuilder.build());
}
}

private static void validateRule(DefaultIssue issue, @Nullable Rule rule) {
private Rule validateRule(Issue issue) {
RuleKey ruleKey = issue.ruleKey();
Rule rule = rules.find(ruleKey);
if (rule == null) {
throw MessageException.of(String.format("The rule '%s' does not exist.", ruleKey));
}
if (Strings.isNullOrEmpty(rule.name()) && Strings.isNullOrEmpty(issue.message())) {
if (Strings.isNullOrEmpty(rule.name()) && Strings.isNullOrEmpty(issue.primaryLocation().message())) {
throw MessageException.of(String.format("The rule '%s' has no name and the related issue has no message.", ruleKey));
}
return rule;
}

private void updateIssue(DefaultIssue issue, @Nullable Rule rule, ActiveRule activeRule) {
if (Strings.isNullOrEmpty(issue.message())) {
issue.setMessage(rule.name());
}
if (project != null) {
issue.setCreationDate(project.getAnalysisDate());
issue.setUpdateDate(project.getAnalysisDate());
}
if (issue.severity() == null) {
issue.setSeverity(activeRule.severity());
}
public void write(BatchComponent component, BatchReport.Issue rawIssue) {
reportPublisher.getWriter().appendComponentIssue(component.batchId(), rawIssue);
}

}

+ 2
- 5
sonar-batch/src/main/java/org/sonar/batch/issue/tracking/LocalIssueTracking.java View File

@@ -48,7 +48,6 @@ import org.sonar.batch.protocol.input.ProjectRepositories;
import org.sonar.batch.protocol.output.BatchReport;
import org.sonar.batch.protocol.output.BatchReportReader;
import org.sonar.batch.report.ReportPublisher;
import org.sonar.batch.scan.filesystem.InputPathCache;
import org.sonar.core.component.ComponentKeys;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.IssueChangeContext;
@@ -68,7 +67,6 @@ public class LocalIssueTracking {
private final IssueUpdater updater;
private final IssueChangeContext changeContext;
private final ActiveRules activeRules;
private final InputPathCache inputPathCache;
private final BatchComponentCache componentCache;
private final ServerIssueRepository serverIssueRepository;
private final ProjectRepositories projectRepositories;
@@ -78,7 +76,7 @@ public class LocalIssueTracking {

public LocalIssueTracking(BatchComponentCache resourceCache, IssueCache issueCache, IssueTracking tracking,
ServerLineHashesLoader lastLineHashes, IssueWorkflow workflow, IssueUpdater updater,
ActiveRules activeRules, InputPathCache inputPathCache, ServerIssueRepository serverIssueRepository,
ActiveRules activeRules, ServerIssueRepository serverIssueRepository,
ProjectRepositories projectRepositories, AnalysisMode analysisMode, ReportPublisher reportPublisher) {
this.componentCache = resourceCache;
this.issueCache = issueCache;
@@ -86,7 +84,6 @@ public class LocalIssueTracking {
this.lastLineHashes = lastLineHashes;
this.workflow = workflow;
this.updater = updater;
this.inputPathCache = inputPathCache;
this.serverIssueRepository = serverIssueRepository;
this.projectRepositories = projectRepositories;
this.analysisMode = analysisMode;
@@ -183,7 +180,7 @@ public class LocalIssueTracking {
private SourceHashHolder loadSourceHashes(BatchComponent component) {
SourceHashHolder sourceHashHolder = null;
if (component.isFile()) {
DefaultInputFile file = (DefaultInputFile) inputPathCache.getInputPath(component);
DefaultInputFile file = (DefaultInputFile) component.inputComponent();
if (file == null) {
throw new IllegalStateException("Resource " + component.resource() + " was not found in InputPath cache");
}

+ 1
- 1
sonar-batch/src/main/java/org/sonar/batch/issue/tracking/ServerIssueRepository.java View File

@@ -99,7 +99,7 @@ public class ServerIssueRepository {
if (!component.isFile()) {
throw new UnsupportedOperationException("Incremental mode should only get issues on files");
}
InputFile inputFile = (InputFile) inputPathCache.getInputPath(component);
InputFile inputFile = (InputFile) component.inputComponent();
if (inputFile.status() == Status.ADDED) {
return Collections.emptyList();
}

+ 1
- 1
sonar-batch/src/main/java/org/sonar/batch/mediumtest/TaskResult.java View File

@@ -140,7 +140,7 @@ public class TaskResult implements org.sonar.batch.mediumtest.ScanTaskObserver {
return result;
}

private String key(InputPath inputPath) {
private static String key(InputPath inputPath) {
return inputPath instanceof InputFile ? ((DefaultInputFile) inputPath).key() : ((DefaultInputDir) inputPath).key();
}


+ 5
- 6
sonar-batch/src/main/java/org/sonar/batch/postjob/DefaultPostJobContext.java View File

@@ -22,19 +22,18 @@ package org.sonar.batch.postjob;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import javax.annotation.Nullable;
import org.sonar.api.batch.AnalysisMode;
import org.sonar.api.batch.fs.InputPath;
import org.sonar.api.batch.fs.InputComponent;
import org.sonar.api.batch.postjob.PostJobContext;
import org.sonar.api.batch.postjob.issue.Issue;
import org.sonar.api.batch.rule.Severity;
import org.sonar.api.config.Settings;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.api.rule.RuleKey;
import org.sonar.batch.index.BatchComponent;
import org.sonar.batch.index.BatchComponentCache;
import org.sonar.batch.issue.IssueCache;

import javax.annotation.Nullable;
import org.sonar.core.issue.DefaultIssue;

public class DefaultPostJobContext implements PostJobContext {

@@ -104,9 +103,9 @@ public class DefaultPostJobContext implements PostJobContext {
}

@Override
public InputPath inputPath() {
public InputComponent inputComponent() {
BatchComponent component = resourceCache.get(wrapped.componentKey());
return component != null ? component.inputPath() : null;
return component != null ? component.inputComponent() : null;
}

@Override

+ 1
- 1
sonar-batch/src/main/java/org/sonar/batch/report/ComponentsPublisher.java View File

@@ -74,7 +74,7 @@ public class ComponentsPublisher implements ReportPublisherStep {

if (batchComponent.isFile()) {
builder.setIsTest(ResourceUtils.isUnitTestFile(r));
builder.setLines(((InputFile) batchComponent.inputPath()).lines());
builder.setLines(((InputFile) batchComponent.inputComponent()).lines());
}
String name = getName(r);
if (name != null) {

+ 43
- 37
sonar-batch/src/main/java/org/sonar/batch/report/CoveragePublisher.java View File

@@ -54,42 +54,48 @@ public class CoveragePublisher implements ReportPublisherStep {
}
Map<Integer, Coverage.Builder> coveragePerLine = new LinkedHashMap<>();

applyLineMeasure(resource.key(), ((InputFile) resource.inputPath()).lines(), CoreMetrics.COVERAGE_LINE_HITS_DATA_KEY, coveragePerLine, new MeasureOperation() {
@Override
public void apply(String value, Coverage.Builder builder) {
builder.setUtHits(Integer.parseInt(value) > 0);
}
});
applyLineMeasure(resource.key(), ((InputFile) resource.inputPath()).lines(), CoreMetrics.CONDITIONS_BY_LINE_KEY, coveragePerLine, new MeasureOperation() {
@Override
public void apply(String value, Coverage.Builder builder) {
builder.setConditions(Integer.parseInt(value));
}
});
applyLineMeasure(resource.key(), ((InputFile) resource.inputPath()).lines(), CoreMetrics.COVERED_CONDITIONS_BY_LINE_KEY, coveragePerLine, new MeasureOperation() {
@Override
public void apply(String value, Coverage.Builder builder) {
builder.setUtCoveredConditions(Integer.parseInt(value));
}
});
applyLineMeasure(resource.key(), ((InputFile) resource.inputPath()).lines(), CoreMetrics.IT_COVERAGE_LINE_HITS_DATA_KEY, coveragePerLine, new MeasureOperation() {
@Override
public void apply(String value, Coverage.Builder builder) {
builder.setItHits(Integer.parseInt(value) > 0);
}
});
applyLineMeasure(resource.key(), ((InputFile) resource.inputPath()).lines(), CoreMetrics.IT_COVERED_CONDITIONS_BY_LINE_KEY, coveragePerLine, new MeasureOperation() {
@Override
public void apply(String value, Coverage.Builder builder) {
builder.setItCoveredConditions(Integer.parseInt(value));
}
});
applyLineMeasure(resource.key(), ((InputFile) resource.inputPath()).lines(), CoreMetrics.OVERALL_COVERED_CONDITIONS_BY_LINE_KEY, coveragePerLine, new MeasureOperation() {
@Override
public void apply(String value, Coverage.Builder builder) {
builder.setOverallCoveredConditions(Integer.parseInt(value));
}
});
applyLineMeasure(resource.key(), ((InputFile) resource.inputComponent()).lines(), CoreMetrics.COVERAGE_LINE_HITS_DATA_KEY, coveragePerLine,
new MeasureOperation() {
@Override
public void apply(String value, Coverage.Builder builder) {
builder.setUtHits(Integer.parseInt(value) > 0);
}
});
applyLineMeasure(resource.key(), ((InputFile) resource.inputComponent()).lines(), CoreMetrics.CONDITIONS_BY_LINE_KEY, coveragePerLine,
new MeasureOperation() {
@Override
public void apply(String value, Coverage.Builder builder) {
builder.setConditions(Integer.parseInt(value));
}
});
applyLineMeasure(resource.key(), ((InputFile) resource.inputComponent()).lines(), CoreMetrics.COVERED_CONDITIONS_BY_LINE_KEY, coveragePerLine,
new MeasureOperation() {
@Override
public void apply(String value, Coverage.Builder builder) {
builder.setUtCoveredConditions(Integer.parseInt(value));
}
});
applyLineMeasure(resource.key(), ((InputFile) resource.inputComponent()).lines(), CoreMetrics.IT_COVERAGE_LINE_HITS_DATA_KEY, coveragePerLine,
new MeasureOperation() {
@Override
public void apply(String value, Coverage.Builder builder) {
builder.setItHits(Integer.parseInt(value) > 0);
}
});
applyLineMeasure(resource.key(), ((InputFile) resource.inputComponent()).lines(), CoreMetrics.IT_COVERED_CONDITIONS_BY_LINE_KEY, coveragePerLine,
new MeasureOperation() {
@Override
public void apply(String value, Coverage.Builder builder) {
builder.setItCoveredConditions(Integer.parseInt(value));
}
});
applyLineMeasure(resource.key(), ((InputFile) resource.inputComponent()).lines(), CoreMetrics.OVERALL_COVERED_CONDITIONS_BY_LINE_KEY, coveragePerLine,
new MeasureOperation() {
@Override
public void apply(String value, Coverage.Builder builder) {
builder.setOverallCoveredConditions(Integer.parseInt(value));
}
});

writer.writeComponentCoverage(resource.batchId(), Iterables.transform(coveragePerLine.values(), BuildCoverage.INSTANCE));
}
@@ -122,7 +128,7 @@ public class CoveragePublisher implements ReportPublisherStep {
void apply(String value, Coverage.Builder builder);
}

private enum BuildCoverage implements Function<Coverage.Builder, Coverage>{
private enum BuildCoverage implements Function<Coverage.Builder, Coverage> {
INSTANCE;

@Override

+ 1
- 1
sonar-batch/src/main/java/org/sonar/batch/report/SourcePublisher.java View File

@@ -50,7 +50,7 @@ public class SourcePublisher implements ReportPublisherStep {
continue;
}

DefaultInputFile inputFile = (DefaultInputFile) resource.inputPath();
DefaultInputFile inputFile = (DefaultInputFile) resource.inputComponent();
File iofile = writer.getSourceFile(resource.batchId());
int line = 0;
try (FileOutputStream output = new FileOutputStream(iofile); BOMInputStream bomIn = new BOMInputStream(new FileInputStream(inputFile.file()),

+ 1
- 1
sonar-batch/src/main/java/org/sonar/batch/report/TestExecutionAndCoveragePublisher.java View File

@@ -119,7 +119,7 @@ public class TestExecutionAndCoveragePublisher implements ReportPublisherStep {
continue;
}

DefaultInputFile inputFile = (DefaultInputFile) component.inputPath();
DefaultInputFile inputFile = (DefaultInputFile) component.inputComponent();
if (inputFile.type() != Type.TEST) {
continue;
}

+ 2
- 2
sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/ComponentIndexer.java View File

@@ -59,10 +59,10 @@ public class ComponentIndexer {
Resource sonarFile = File.create(inputFile.relativePath(), languages.get(languageKey), unitTest);
sonarIndex.index(sonarFile);
BatchComponent file = componentCache.get(sonarFile);
file.setInputPath(inputFile);
file.setInputComponent(inputFile);
Resource sonarDir = file.parent().resource();
InputDir inputDir = fs.inputDir(inputFile.file().getParentFile());
componentCache.get(sonarDir).setInputPath(inputDir);
componentCache.get(sonarDir).setInputComponent(inputDir);
}
}
}

+ 4
- 18
sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputPathCache.java View File

@@ -21,20 +21,16 @@ package org.sonar.batch.scan.filesystem;

import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import org.sonar.api.batch.BatchSide;
import org.sonar.api.batch.fs.InputDir;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.InputPath;
import org.sonar.batch.index.BatchComponent;

import javax.annotation.CheckForNull;

import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import javax.annotation.CheckForNull;
import org.sonar.api.batch.BatchSide;
import org.sonar.api.batch.fs.InputDir;
import org.sonar.api.batch.fs.InputFile;

/**
* Cache of all files and dirs. This cache is shared amongst all project modules. Inclusion and
@@ -130,14 +126,4 @@ public class InputPathCache {
return null;
}

@CheckForNull
public InputPath getInputPath(BatchComponent component) {
if (component.isFile()) {
return getFile(component.parent().parent().resource().getEffectiveKey(), component.resource().getPath());
} else if (component.isDir()) {
return getDir(component.parent().parent().resource().getEffectiveKey(), component.resource().getPath());
}
return null;
}

}

+ 5
- 6
sonar-batch/src/main/java/org/sonar/batch/scan/report/SourceProvider.java View File

@@ -19,6 +19,10 @@
*/
package org.sonar.batch.scan.report;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.slf4j.Logger;
@@ -29,11 +33,6 @@ import org.sonar.api.batch.fs.InputFile;
import org.sonar.batch.index.BatchComponent;
import org.sonar.batch.scan.filesystem.InputPathCache;

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

@BatchSide
public class SourceProvider {

@@ -52,7 +51,7 @@ public class SourceProvider {
return Collections.emptyList();
}
try {
InputFile inputFile = (InputFile) inputPathCache.getInputPath(component);
InputFile inputFile = (InputFile) component.inputComponent();
List<String> lines = FileUtils.readLines(inputFile.file(), fs.encoding());
List<String> escapedLines = new ArrayList<>(lines.size());
for (String line : lines) {

+ 1
- 1
sonar-batch/src/main/java/org/sonar/batch/source/HighlightableBuilder.java View File

@@ -40,7 +40,7 @@ public class HighlightableBuilder extends PerspectiveBuilder<Highlightable> {
@Override
public Highlightable loadPerspective(Class<Highlightable> perspectiveClass, BatchComponent component) {
if (component.isFile()) {
InputFile path = (InputFile) component.inputPath();
InputFile path = (InputFile) component.inputComponent();
return new DefaultHighlightable((DefaultInputFile) path, sensorStorage);
}
return null;

+ 1
- 1
sonar-batch/src/main/java/org/sonar/batch/source/SymbolizableBuilder.java View File

@@ -41,7 +41,7 @@ public class SymbolizableBuilder extends PerspectiveBuilder<Symbolizable> {
@Override
public Symbolizable loadPerspective(Class<Symbolizable> perspectiveClass, BatchComponent component) {
if (component.isFile()) {
InputFile path = (InputFile) component.inputPath();
InputFile path = (InputFile) component.inputComponent();
return new DefaultSymbolizable((DefaultInputFile) path, sensorStorage);
}
return null;

+ 1
- 1
sonar-batch/src/main/java/org/sonar/batch/test/TestPlanBuilder.java View File

@@ -40,7 +40,7 @@ public class TestPlanBuilder extends PerspectiveBuilder<MutableTestPlan> {
@Override
public MutableTestPlan loadPerspective(Class<MutableTestPlan> perspectiveClass, BatchComponent component) {
if (component.isFile()) {
InputFile inputFile = (InputFile) component.inputPath();
InputFile inputFile = (InputFile) component.inputComponent();
if (inputFile.type() == Type.TEST) {
if (!testPlanByFile.containsKey(inputFile)) {
testPlanByFile.put(inputFile, new DefaultTestPlan());

+ 1
- 1
sonar-batch/src/main/java/org/sonar/batch/test/TestableBuilder.java View File

@@ -37,7 +37,7 @@ public class TestableBuilder extends PerspectiveBuilder<MutableTestable> {
@Override
public MutableTestable loadPerspective(Class<MutableTestable> perspectiveClass, BatchComponent component) {
if (component.isFile()) {
InputFile inputFile = (InputFile) component.inputPath();
InputFile inputFile = (InputFile) component.inputComponent();
if (inputFile.type() == Type.MAIN) {
return new DefaultTestable((DefaultInputFile) inputFile);
}

+ 10
- 9
sonar-batch/src/test/java/org/sonar/batch/issue/IssueFiltersTest.java View File

@@ -21,7 +21,8 @@ package org.sonar.batch.issue;

import org.junit.Test;
import org.sonar.api.issue.Issue;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.api.resources.Project;
import org.sonar.batch.protocol.output.BatchReport;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
@@ -38,19 +39,19 @@ public class IssueFiltersTest {
org.sonar.api.issue.IssueFilter ko = mock(org.sonar.api.issue.IssueFilter.class);
when(ko.accept(any(Issue.class))).thenReturn(false);

IssueFilters filters = new IssueFilters(new org.sonar.api.issue.IssueFilter[] {ok, ko});
assertThat(filters.accept(new DefaultIssue())).isFalse();
IssueFilters filters = new IssueFilters(new Project("foo"), new org.sonar.api.issue.IssueFilter[] {ok, ko});
assertThat(filters.accept("foo:src/Foo.java", BatchReport.Issue.newBuilder().build())).isFalse();

filters = new IssueFilters(new org.sonar.api.issue.IssueFilter[] {ok});
assertThat(filters.accept(new DefaultIssue())).isTrue();
filters = new IssueFilters(new Project("foo"), new org.sonar.api.issue.IssueFilter[] {ok});
assertThat(filters.accept("foo:src/Foo.java", BatchReport.Issue.newBuilder().build())).isTrue();

filters = new IssueFilters(new org.sonar.api.issue.IssueFilter[] {ko});
assertThat(filters.accept(new DefaultIssue())).isFalse();
filters = new IssueFilters(new Project("foo"), new org.sonar.api.issue.IssueFilter[] {ko});
assertThat(filters.accept("foo:src/Foo.java", BatchReport.Issue.newBuilder().build())).isFalse();
}

@Test
public void should_always_accept_if_no_filters() {
IssueFilters filters = new IssueFilters();
assertThat(filters.accept(new DefaultIssue())).isTrue();
IssueFilters filters = new IssueFilters(new Project("foo"));
assertThat(filters.accept("foo:src/Foo.java", BatchReport.Issue.newBuilder().build())).isTrue();
}
}

+ 15
- 18
sonar-batch/src/test/java/org/sonar/batch/issue/ModuleIssuesTest.java View File

@@ -20,7 +20,6 @@
package org.sonar.batch.issue;

import java.io.StringReader;
import java.util.Date;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -35,7 +34,6 @@ import org.sonar.api.batch.rule.internal.RulesBuilder;
import org.sonar.api.batch.sensor.issue.internal.DefaultIssue;
import org.sonar.api.batch.sensor.issue.internal.DefaultIssueLocation;
import org.sonar.api.resources.File;
import org.sonar.api.resources.Project;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.Severity;
import org.sonar.api.utils.MessageException;
@@ -46,6 +44,7 @@ import org.sonar.batch.report.ReportPublisher;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
@@ -59,8 +58,6 @@ public class ModuleIssuesTest {
static final RuleKey SQUID_RULE_KEY = RuleKey.of("squid", "AvoidCycle");
static final String SQUID_RULE_NAME = "Avoid Cycle";

Project project = new Project("foo").setAnalysisDate(new Date());

@Mock
IssueFilters filters;

@@ -75,14 +72,14 @@ public class ModuleIssuesTest {

@Before
public void prepare() {
componentCache.add(File.create("src/Foo.php").setEffectiveKey("foo:src/Foo.php"), null).setInputPath(file);
componentCache.add(File.create("src/Foo.php").setEffectiveKey("foo:src/Foo.php"), null).setInputComponent(file);
}

@Test
public void fail_on_unknown_rule() {
initModuleIssues();
DefaultIssue issue = new DefaultIssue()
.addLocation(new DefaultIssueLocation().onFile(file).at(file.selectLine(3)).message("Foo"))
.at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message("Foo"))
.forRule(SQUID_RULE_KEY);
try {
moduleIssues.initAndAddIssue(issue);
@@ -99,7 +96,7 @@ public class ModuleIssuesTest {
ruleBuilder.add(SQUID_RULE_KEY).setInternalKey(SQUID_RULE_KEY.rule());
initModuleIssues();
DefaultIssue issue = new DefaultIssue()
.addLocation(new DefaultIssueLocation().onFile(file).at(file.selectLine(3)).message(""))
.at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message(""))
.forRule(SQUID_RULE_KEY);
try {
moduleIssues.initAndAddIssue(issue);
@@ -116,7 +113,7 @@ public class ModuleIssuesTest {
ruleBuilder.add(SQUID_RULE_KEY).setName(SQUID_RULE_NAME);
initModuleIssues();
DefaultIssue issue = new DefaultIssue()
.addLocation(new DefaultIssueLocation().onFile(file).at(file.selectLine(3)).message("Foo"))
.at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message("Foo"))
.forRule(SQUID_RULE_KEY);
boolean added = moduleIssues.initAndAddIssue(issue);

@@ -131,7 +128,7 @@ public class ModuleIssuesTest {
initModuleIssues();

DefaultIssue issue = new DefaultIssue()
.addLocation(new DefaultIssueLocation().onFile(file).at(file.selectLine(3)).message("Foo"))
.at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message("Foo"))
.forRule(SQUID_RULE_KEY);
boolean added = moduleIssues.initAndAddIssue(issue);

@@ -146,11 +143,11 @@ public class ModuleIssuesTest {
initModuleIssues();

DefaultIssue issue = new DefaultIssue()
.addLocation(new DefaultIssueLocation().onFile(file).at(file.selectLine(3)).message("Foo"))
.at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message("Foo"))
.forRule(SQUID_RULE_KEY)
.overrideSeverity(org.sonar.api.batch.rule.Severity.CRITICAL);

when(filters.accept(any(org.sonar.core.issue.DefaultIssue.class))).thenReturn(true);
when(filters.accept(anyString(), any(BatchReport.Issue.class))).thenReturn(true);

boolean added = moduleIssues.initAndAddIssue(issue);

@@ -167,9 +164,9 @@ public class ModuleIssuesTest {
initModuleIssues();

DefaultIssue issue = new DefaultIssue()
.addLocation(new DefaultIssueLocation().onFile(file).at(file.selectLine(3)).message("Foo"))
.at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message("Foo"))
.forRule(SQUID_RULE_KEY);
when(filters.accept(any(org.sonar.core.issue.DefaultIssue.class))).thenReturn(true);
when(filters.accept(anyString(), any(BatchReport.Issue.class))).thenReturn(true);
moduleIssues.initAndAddIssue(issue);

ArgumentCaptor<BatchReport.Issue> argument = ArgumentCaptor.forClass(BatchReport.Issue.class);
@@ -184,9 +181,9 @@ public class ModuleIssuesTest {
initModuleIssues();

DefaultIssue issue = new DefaultIssue()
.addLocation(new DefaultIssueLocation().onFile(file).at(file.selectLine(3)).message(""))
.at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message(""))
.forRule(SQUID_RULE_KEY);
when(filters.accept(any(org.sonar.core.issue.DefaultIssue.class))).thenReturn(true);
when(filters.accept(anyString(), any(BatchReport.Issue.class))).thenReturn(true);

boolean added = moduleIssues.initAndAddIssue(issue);

@@ -203,10 +200,10 @@ public class ModuleIssuesTest {
initModuleIssues();

DefaultIssue issue = new DefaultIssue()
.addLocation(new DefaultIssueLocation().onFile(file).at(file.selectLine(3)).message(""))
.at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message(""))
.forRule(SQUID_RULE_KEY);

when(filters.accept(any(org.sonar.core.issue.DefaultIssue.class))).thenReturn(false);
when(filters.accept(anyString(), any(BatchReport.Issue.class))).thenReturn(false);

boolean added = moduleIssues.initAndAddIssue(issue);

@@ -218,7 +215,7 @@ public class ModuleIssuesTest {
* Every rules and active rules has to be added in builders before creating ModuleIssues
*/
private void initModuleIssues() {
moduleIssues = new ModuleIssues(activeRulesBuilder.build(), ruleBuilder.build(), project, filters, reportPublisher, componentCache);
moduleIssues = new ModuleIssues(activeRulesBuilder.build(), ruleBuilder.build(), filters, reportPublisher, componentCache);
}

}

+ 60
- 9
sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/MultilineIssuesMediumTest.java View File

@@ -20,6 +20,7 @@
package org.sonar.batch.mediumtest.issues;

import java.io.File;
import java.util.List;
import org.apache.commons.io.FileUtils;
import org.junit.After;
import org.junit.Before;
@@ -29,6 +30,8 @@ import org.sonar.batch.mediumtest.BatchMediumTester;
import org.sonar.batch.mediumtest.TaskResult;
import org.sonar.batch.protocol.input.ActiveRule;
import org.sonar.batch.protocol.input.Rule;
import org.sonar.batch.protocol.output.BatchReport.Issue;
import org.sonar.batch.protocol.output.BatchReport.IssueLocation;
import org.sonar.xoo.XooPlugin;
import org.sonar.xoo.rule.XooRulesDefinition;

@@ -47,9 +50,19 @@ public class MultilineIssuesMediumTest {
.activateRule(new ActiveRule("xoo", "MultilineIssue", null, "Multinile Issue", "MAJOR", null, "xoo"))
.build();

private TaskResult result;

@Before
public void prepare() {
public void prepare() throws Exception {
tester.start();

File projectDir = new File(MultilineIssuesMediumTest.class.getResource("/mediumtest/xoo/sample-multiline").toURI());
File tmpDir = temp.newFolder();
FileUtils.copyDirectory(projectDir, tmpDir);

result = tester
.newScanTask(new File(tmpDir, "sonar-project.properties"))
.start();
}

@After
@@ -59,16 +72,54 @@ public class MultilineIssuesMediumTest {

@Test
public void testIssueRange() throws Exception {
File projectDir = new File(MultilineIssuesMediumTest.class.getResource("/mediumtest/xoo/sample-multiline").toURI());
File tmpDir = temp.newFolder();
FileUtils.copyDirectory(projectDir, tmpDir);
List<Issue> issues = result.issuesFor(result.inputFile("xources/hello/Single.xoo"));
assertThat(issues).hasSize(1);
Issue issue = issues.get(0);
assertThat(issue.getLine()).isEqualTo(6);
assertThat(issue.getMsg()).isEqualTo("Primary location");
IssueLocation primaryLocation = issue.getPrimaryLocation();
assertThat(primaryLocation.getMsg()).isEqualTo("Primary location");
assertThat(primaryLocation.getTextRange().getStartLine()).isEqualTo(6);
assertThat(primaryLocation.getTextRange().getStartOffset()).isEqualTo(25);
assertThat(primaryLocation.getTextRange().getEndLine()).isEqualTo(6);
assertThat(primaryLocation.getTextRange().getEndOffset()).isEqualTo(52);
}

TaskResult result = tester
.newScanTask(new File(tmpDir, "sonar-project.properties"))
.start();
@Test
public void testMultilineIssueRange() throws Exception {
List<Issue> issues = result.issuesFor(result.inputFile("xources/hello/Multiline.xoo"));
assertThat(issues).hasSize(1);
Issue issue = issues.get(0);
assertThat(issue.getLine()).isEqualTo(6);
assertThat(issue.getMsg()).isEqualTo("Primary location");
IssueLocation primaryLocation = issue.getPrimaryLocation();
assertThat(primaryLocation.getMsg()).isEqualTo("Primary location");
assertThat(primaryLocation.getTextRange().getStartLine()).isEqualTo(6);
assertThat(primaryLocation.getTextRange().getStartOffset()).isEqualTo(25);
assertThat(primaryLocation.getTextRange().getEndLine()).isEqualTo(7);
assertThat(primaryLocation.getTextRange().getEndOffset()).isEqualTo(23);
}

assertThat(result.issuesFor(result.inputFile("xources/hello/HelloJava.xoo"))).hasSize(1);
@Test
public void testMultipleIssueLocation() throws Exception {
List<Issue> issues = result.issuesFor(result.inputFile("xources/hello/Multiple.xoo"));
assertThat(issues).hasSize(1);
Issue issue = issues.get(0);
assertThat(issue.getLine()).isEqualTo(6);
assertThat(issue.getMsg()).isEqualTo("Primary location");
IssueLocation primaryLocation = issue.getPrimaryLocation();
assertThat(primaryLocation.getMsg()).isEqualTo("Primary location");
assertThat(primaryLocation.getTextRange().getStartLine()).isEqualTo(6);
assertThat(primaryLocation.getTextRange().getStartOffset()).isEqualTo(25);
assertThat(primaryLocation.getTextRange().getEndLine()).isEqualTo(6);
assertThat(primaryLocation.getTextRange().getEndOffset()).isEqualTo(52);

assertThat(issue.getAdditionalLocationList()).hasSize(1);
IssueLocation additionalLocation = issue.getAdditionalLocation(0);
assertThat(additionalLocation.getMsg()).isEqualTo("Location #2");
assertThat(additionalLocation.getTextRange().getStartLine()).isEqualTo(7);
assertThat(additionalLocation.getTextRange().getStartOffset()).isEqualTo(25);
assertThat(additionalLocation.getTextRange().getEndLine()).isEqualTo(7);
assertThat(additionalLocation.getTextRange().getEndOffset()).isEqualTo(52);
}

}

+ 2
- 2
sonar-batch/src/test/java/org/sonar/batch/mediumtest/preview/PreviewAndReportsMediumTest.java View File

@@ -247,8 +247,8 @@ public class PreviewAndReportsMediumTest {
.setIssueListener(issueListener)
.start();

assertThat(result.trackedIssues()).hasSize(14);
assertThat(issueListener.issueList).hasSize(14);
assertThat(result.trackedIssues()).hasSize(17);
assertThat(issueListener.issueList).hasSize(17);
assertThat(result.trackedIssues()).containsExactlyElementsOf(issueListener.issueList);
}


+ 5
- 6
sonar-batch/src/test/java/org/sonar/batch/postjob/DefaultPostJobContextTest.java View File

@@ -19,6 +19,7 @@
*/
package org.sonar.batch.postjob;

import java.util.Arrays;
import org.junit.Before;
import org.junit.Test;
import org.sonar.api.batch.AnalysisMode;
@@ -26,12 +27,10 @@ import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.postjob.issue.Issue;
import org.sonar.api.batch.rule.Severity;
import org.sonar.api.config.Settings;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.api.resources.File;
import org.sonar.batch.index.BatchComponentCache;
import org.sonar.batch.issue.IssueCache;

import java.util.Arrays;
import org.sonar.core.issue.DefaultIssue;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@@ -77,11 +76,11 @@ public class DefaultPostJobContextTest {
assertThat(issue.line()).isEqualTo(1);
assertThat(issue.message()).isEqualTo("msg");
assertThat(issue.severity()).isEqualTo(Severity.BLOCKER);
assertThat(issue.inputPath()).isNull();
assertThat(issue.inputComponent()).isNull();

InputFile inputPath = mock(InputFile.class);
resourceCache.add(File.create("src/Foo.php").setEffectiveKey("foo:src/Foo.php"), null).setInputPath(inputPath);
assertThat(issue.inputPath()).isEqualTo(inputPath);
resourceCache.add(File.create("src/Foo.php").setEffectiveKey("foo:src/Foo.php"), null).setInputComponent(inputPath);
assertThat(issue.inputComponent()).isEqualTo(inputPath);

}
}

+ 12
- 10
sonar-batch/src/test/java/org/sonar/batch/report/ComponentsPublisherTest.java View File

@@ -25,7 +25,9 @@ import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.sonar.api.CoreProperties;
import org.sonar.api.batch.bootstrap.ProjectDefinition;
import org.sonar.api.batch.fs.internal.DefaultInputDir;
import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.batch.fs.internal.DefaultInputModule;
import org.sonar.api.resources.Directory;
import org.sonar.api.resources.Java;
import org.sonar.api.resources.Project;
@@ -55,33 +57,33 @@ public class ComponentsPublisherTest {
Project root = new Project("foo").setName("Root project").setDescription("Root description")
.setAnalysisDate(DateUtils.parseDate(("2012-12-12")));
root.setId(1).setUuid("PROJECT_UUID");
resourceCache.add(root, null);
resourceCache.add(root, null).setInputComponent(new DefaultInputModule("foo"));

Project module1 = new Project("module1").setName("Module1").setDescription("Module description");
module1.setParent(root);
module1.setId(2).setUuid("MODULE_UUID");
resourceCache.add(module1, root);
resourceCache.add(module1, root).setInputComponent(new DefaultInputModule("module1"));
rootDef.addSubProject(ProjectDefinition.create().setKey("module1"));

Directory dir = Directory.create("src");
dir.setEffectiveKey("module1:src");
dir.setId(3).setUuid("DIR_UUID");
resourceCache.add(dir, module1);
resourceCache.add(dir, module1).setInputComponent(new DefaultInputDir("foo", "src"));

org.sonar.api.resources.File file = org.sonar.api.resources.File.create("src/Foo.java", Java.INSTANCE, false);
file.setEffectiveKey("module1:src/Foo.java");
file.setId(4).setUuid("FILE_UUID");
resourceCache.add(file, dir).setInputPath(new DefaultInputFile("module1", "src/Foo.java").setLines(2));
resourceCache.add(file, dir).setInputComponent(new DefaultInputFile("module1", "src/Foo.java").setLines(2));

org.sonar.api.resources.File fileWithoutLang = org.sonar.api.resources.File.create("src/make", null, false);
fileWithoutLang.setEffectiveKey("module1:src/make");
fileWithoutLang.setId(5).setUuid("FILE_WITHOUT_LANG_UUID");
resourceCache.add(fileWithoutLang, dir).setInputPath(new DefaultInputFile("module1", "src/make").setLines(10));
resourceCache.add(fileWithoutLang, dir).setInputComponent(new DefaultInputFile("module1", "src/make").setLines(10));

org.sonar.api.resources.File testFile = org.sonar.api.resources.File.create("test/FooTest.java", Java.INSTANCE, true);
testFile.setEffectiveKey("module1:test/FooTest.java");
testFile.setId(6).setUuid("TEST_FILE_UUID");
resourceCache.add(testFile, dir).setInputPath(new DefaultInputFile("module1", "test/FooTest.java").setLines(4));
resourceCache.add(testFile, dir).setInputComponent(new DefaultInputFile("module1", "test/FooTest.java").setLines(4));

ImmutableProjectReactor reactor = new ImmutableProjectReactor(rootDef);

@@ -122,14 +124,14 @@ public class ComponentsPublisherTest {
Project root = new Project("foo:my_branch").setName("Root project")
.setAnalysisDate(DateUtils.parseDate(("2012-12-12")));
root.setId(1).setUuid("PROJECT_UUID");
resourceCache.add(root, null);
resourceCache.add(root, null).setInputComponent(new DefaultInputModule("foo"));
rootDef.properties().put(CoreProperties.LINKS_HOME_PAGE, "http://home");
rootDef.properties().put(CoreProperties.PROJECT_BRANCH_PROPERTY, "my_branch");

Project module1 = new Project("module1:my_branch").setName("Module1");
module1.setParent(root);
module1.setId(2).setUuid("MODULE_UUID");
resourceCache.add(module1, root);
resourceCache.add(module1, root).setInputComponent(new DefaultInputModule("module1"));
ProjectDefinition moduleDef = ProjectDefinition.create().setKey("module1");
moduleDef.properties().put(CoreProperties.LINKS_CI, "http://ci");
rootDef.addSubProject(moduleDef);
@@ -137,12 +139,12 @@ public class ComponentsPublisherTest {
Directory dir = Directory.create("src");
dir.setEffectiveKey("module1:my_branch:my_branch:src");
dir.setId(3).setUuid("DIR_UUID");
resourceCache.add(dir, module1);
resourceCache.add(dir, module1).setInputComponent(new DefaultInputDir("foo", "src"));

org.sonar.api.resources.File file = org.sonar.api.resources.File.create("src/Foo.java", Java.INSTANCE, false);
file.setEffectiveKey("module1:my_branch:my_branch:src/Foo.java");
file.setId(4).setUuid("FILE_UUID");
resourceCache.add(file, dir).setInputPath(new DefaultInputFile("module1", "src/Foo.java").setLines(2));
resourceCache.add(file, dir).setInputComponent(new DefaultInputFile("module1", "src/Foo.java").setLines(2));

ImmutableProjectReactor reactor = new ImmutableProjectReactor(rootDef);


+ 3
- 2
sonar-batch/src/test/java/org/sonar/batch/report/CoveragePublisherTest.java View File

@@ -28,6 +28,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.batch.fs.internal.DefaultInputModule;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.measures.Measure;
import org.sonar.api.resources.Project;
@@ -58,8 +59,8 @@ public class CoveragePublisherTest {
Project p = new Project("foo").setAnalysisDate(new Date(1234567L));
BatchComponentCache resourceCache = new BatchComponentCache();
sampleFile = org.sonar.api.resources.File.create("src/Foo.php").setEffectiveKey("foo:src/Foo.php");
resourceCache.add(p, null);
resourceCache.add(sampleFile, null).setInputPath(new DefaultInputFile("foo", "src/Foo.php").setLines(5));
resourceCache.add(p, null).setInputComponent(new DefaultInputModule("foo"));
resourceCache.add(sampleFile, null).setInputComponent(new DefaultInputFile("foo", "src/Foo.php").setLines(5));
measureCache = mock(MeasureCache.class);
when(measureCache.byMetric(anyString(), anyString())).thenReturn(Collections.<Measure>emptyList());
publisher = new CoveragePublisher(resourceCache, measureCache);

+ 8
- 8
sonar-batch/src/test/java/org/sonar/batch/report/DuplicationsPublisherTest.java View File

@@ -19,10 +19,15 @@
*/
package org.sonar.batch.report;

import java.io.File;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.batch.fs.internal.DefaultInputModule;
import org.sonar.api.batch.sensor.duplication.Duplication;
import org.sonar.api.batch.sensor.duplication.internal.DefaultDuplication;
import org.sonar.api.resources.Project;
@@ -31,11 +36,6 @@ import org.sonar.batch.index.BatchComponentCache;
import org.sonar.batch.protocol.output.BatchReport;
import org.sonar.batch.protocol.output.BatchReportReader;
import org.sonar.batch.protocol.output.BatchReportWriter;

import java.io.File;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.sonar.core.util.CloseableIterator;

import static org.assertj.core.api.Assertions.assertThat;
@@ -55,11 +55,11 @@ public class DuplicationsPublisherTest {
public void prepare() {
BatchComponentCache resourceCache = new BatchComponentCache();
Project p = new Project("foo");
resourceCache.add(p, null);
resourceCache.add(p, null).setInputComponent(new DefaultInputModule("foo"));
org.sonar.api.resources.Resource sampleFile = org.sonar.api.resources.File.create("src/Foo.php").setEffectiveKey("foo:src/Foo.php");
resourceCache.add(sampleFile, null);
resourceCache.add(sampleFile, null).setInputComponent(new DefaultInputFile("foo", "src/Foo.php").setLines(5));
org.sonar.api.resources.Resource sampleFile2 = org.sonar.api.resources.File.create("src/Foo2.php").setEffectiveKey("foo:src/Foo2.php");
resourceCache.add(sampleFile2, null);
resourceCache.add(sampleFile2, null).setInputComponent(new DefaultInputFile("foo", "src/Foo2.php").setLines(5));
duplicationCache = mock(DuplicationCache.class);
when(duplicationCache.byComponent(anyString())).thenReturn(Collections.<DefaultDuplication>emptyList());
publisher = new DuplicationsPublisher(resourceCache, duplicationCache);

+ 3
- 2
sonar-batch/src/test/java/org/sonar/batch/report/SourcePublisherTest.java View File

@@ -29,6 +29,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.batch.fs.internal.DefaultInputModule;
import org.sonar.api.resources.Project;
import org.sonar.api.resources.Qualifiers;
import org.sonar.batch.index.BatchComponentCache;
@@ -55,10 +56,10 @@ public class SourcePublisherTest {
BatchComponentCache resourceCache = new BatchComponentCache();
sampleFile = org.sonar.api.resources.File.create("src/Foo.php");
sampleFile.setEffectiveKey("foo:src/Foo.php");
resourceCache.add(p, null);
resourceCache.add(p, null).setInputComponent(new DefaultInputModule("foo"));
File baseDir = temp.newFolder();
sourceFile = new File(baseDir, "src/Foo.php");
resourceCache.add(sampleFile, null).setInputPath(
resourceCache.add(sampleFile, null).setInputComponent(
new DefaultInputFile("foo", "src/Foo.php").setLines(5).setModuleBaseDir(baseDir.toPath()).setCharset(StandardCharsets.ISO_8859_1));
publisher = new SourcePublisher(resourceCache);
File outputDir = temp.newFolder();

+ 1
- 1
sonar-batch/src/test/java/org/sonar/batch/sensor/DefaultSensorStorageTest.java View File

@@ -106,7 +106,7 @@ public class DefaultSensorStorageTest {

ArgumentCaptor<org.sonar.api.measures.Measure> argumentCaptor = ArgumentCaptor.forClass(org.sonar.api.measures.Measure.class);
Resource sonarFile = File.create("src/Foo.php").setEffectiveKey("foo:src/Foo.php");
resourceCache.add(sonarFile, null).setInputPath(file);
resourceCache.add(sonarFile, null).setInputComponent(file);
when(measureCache.put(eq(sonarFile), argumentCaptor.capture())).thenReturn(null);
sensorStorage.store(new DefaultMeasure()
.onFile(file)

+ 4
- 2
sonar-batch/src/test/java/org/sonar/batch/source/HighlightableBuilderTest.java View File

@@ -20,6 +20,8 @@
package org.sonar.batch.source;

import org.junit.Test;
import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.batch.fs.internal.DefaultInputModule;
import org.sonar.api.batch.sensor.internal.SensorStorage;
import org.sonar.api.resources.File;
import org.sonar.api.resources.Project;
@@ -35,7 +37,7 @@ public class HighlightableBuilderTest {
@Test
public void should_load_default_perspective() {
Resource file = File.create("foo.c").setEffectiveKey("myproject:path/to/foo.c");
BatchComponent component = new BatchComponent(1, file, null);
BatchComponent component = new BatchComponent(1, file, null).setInputComponent(new DefaultInputFile("foo", "foo.c"));

HighlightableBuilder builder = new HighlightableBuilder(mock(SensorStorage.class));
Highlightable perspective = builder.loadPerspective(Highlightable.class, component);
@@ -45,7 +47,7 @@ public class HighlightableBuilderTest {

@Test
public void project_should_not_be_highlightable() {
BatchComponent component = new BatchComponent(1, new Project("struts").setEffectiveKey("org.struts"), null);
BatchComponent component = new BatchComponent(1, new Project("struts").setEffectiveKey("org.struts"), null).setInputComponent(new DefaultInputModule("struts"));

HighlightableBuilder builder = new HighlightableBuilder(mock(SensorStorage.class));
Highlightable perspective = builder.loadPerspective(Highlightable.class, component);

+ 4
- 2
sonar-batch/src/test/java/org/sonar/batch/source/SymbolizableBuilderTest.java View File

@@ -21,6 +21,8 @@
package org.sonar.batch.source;

import org.junit.Test;
import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.batch.fs.internal.DefaultInputModule;
import org.sonar.api.component.Perspective;
import org.sonar.api.resources.File;
import org.sonar.api.resources.Project;
@@ -37,7 +39,7 @@ public class SymbolizableBuilderTest {
@Test
public void should_load_perspective() {
Resource file = File.create("foo.c").setEffectiveKey("myproject:path/to/foo.c");
BatchComponent component = new BatchComponent(1, file, null);
BatchComponent component = new BatchComponent(1, file, null).setInputComponent(new DefaultInputFile("foo", "foo.c"));

SymbolizableBuilder perspectiveBuilder = new SymbolizableBuilder(mock(DefaultSensorStorage.class));
Perspective perspective = perspectiveBuilder.loadPerspective(Symbolizable.class, component);
@@ -47,7 +49,7 @@ public class SymbolizableBuilderTest {

@Test
public void project_should_not_be_highlightable() {
BatchComponent component = new BatchComponent(1, new Project("struts").setEffectiveKey("org.struts"), null);
BatchComponent component = new BatchComponent(1, new Project("struts").setEffectiveKey("org.struts"), null).setInputComponent(new DefaultInputModule("struts"));

SymbolizableBuilder builder = new SymbolizableBuilder(mock(DefaultSensorStorage.class));
Perspective perspective = builder.loadPerspective(Symbolizable.class, component);

+ 0
- 2
sonar-batch/src/test/resources/mediumtest/xoo/sample-multiline/xources/hello/HelloJava.xoo.measures View File

@@ -1,2 +0,0 @@
ncloc:3
complexity:1

+ 9
- 0
sonar-batch/src/test/resources/mediumtest/xoo/sample-multiline/xources/hello/Multiline.xoo View File

@@ -0,0 +1,9 @@
package hello;

public class HelloJava {

public static void main(String[] args) {
{xoo-start-issue:1:1}System.out
.println("Hello"){xoo-end-issue:1:1};
}
}

+ 9
- 0
sonar-batch/src/test/resources/mediumtest/xoo/sample-multiline/xources/hello/Multiple.xoo View File

@@ -0,0 +1,9 @@
package hello;

public class HelloJava {

public static void main(String[] args) {
{xoo-start-issue:1:1}System.out.println("Hello"){xoo-end-issue:1:1};
{xoo-start-issue:1:2}System.out.println("World"){xoo-end-issue:1:2};
}
}

sonar-batch/src/test/resources/mediumtest/xoo/sample-multiline/xources/hello/HelloJava.xoo → sonar-batch/src/test/resources/mediumtest/xoo/sample-multiline/xources/hello/Single.xoo View File


+ 5
- 0
sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueBuilder.java View File

@@ -85,6 +85,11 @@ public class DefaultIssueBuilder implements Issuable.IssueBuilder {
throw unsupported();
}

@Override
public IssueBuilder at(NewIssueLocation location) {
throw unsupported();
}

@Override
public IssueBuilder addLocation(NewIssueLocation location) {
throw unsupported();

+ 36
- 0
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/InputComponent.java View File

@@ -0,0 +1,36 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* SonarQube is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.api.batch.fs;

/**
* Common interface for all input components.
*
* @since 5.2
* @see InputFile
* @see InputDir
*/
public interface InputComponent {

/**
* Is the component an {@link InputFile}
*/
boolean isFile();

}

+ 29
- 0
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/InputModule.java View File

@@ -0,0 +1,29 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* SonarQube is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.api.batch.fs;

/**
* Used to create issues and measures on modules.
*
* @since 5.2
*/
public interface InputModule extends InputComponent {

}

+ 1
- 2
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/InputPath.java View File

@@ -20,7 +20,6 @@
package org.sonar.api.batch.fs;

import java.io.File;
import java.io.Serializable;
import java.nio.file.Path;

/**
@@ -30,7 +29,7 @@ import java.nio.file.Path;
* @see InputFile
* @see InputDir
*/
public interface InputPath extends Serializable {
public interface InputPath extends InputComponent {

/**
* @see InputFile#relativePath()

+ 53
- 0
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputComponent.java View File

@@ -0,0 +1,53 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* SonarQube is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.api.batch.fs.internal;

import org.sonar.api.batch.fs.InputComponent;

/**
* @since 5.2
*/
public abstract class DefaultInputComponent implements InputComponent {

public abstract String key();

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || this.getClass() != o.getClass()) {
return false;
}

DefaultInputComponent that = (DefaultInputComponent) o;
return key().equals(that.key());
}

@Override
public int hashCode() {
return key().hashCode();
}

@Override
public String toString() {
return "[key=" + key() + "]";
}
}

+ 9
- 4
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputDir.java View File

@@ -19,16 +19,15 @@
*/
package org.sonar.api.batch.fs.internal;

import org.sonar.api.batch.fs.InputDir;
import org.sonar.api.utils.PathUtils;

import java.io.File;
import java.nio.file.Path;
import org.sonar.api.batch.fs.InputDir;
import org.sonar.api.utils.PathUtils;

/**
* @since 4.5
*/
public class DefaultInputDir implements InputDir {
public class DefaultInputDir extends DefaultInputComponent implements InputDir {

private final String relativePath;
private final String moduleKey;
@@ -66,6 +65,7 @@ public class DefaultInputDir implements InputDir {
return moduleKey;
}

@Override
public String key() {
return new StringBuilder().append(moduleKey).append(":").append(relativePath).toString();
}
@@ -78,6 +78,11 @@ public class DefaultInputDir implements InputDir {
return this;
}

@Override
public boolean isFile() {
return false;
}

@Override
public boolean equals(Object o) {
if (this == o) {

+ 7
- 1
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputFile.java View File

@@ -39,7 +39,7 @@ import org.sonar.api.utils.PathUtils;
/**
* @since 4.2
*/
public class DefaultInputFile implements InputFile, org.sonar.api.resources.InputFile {
public class DefaultInputFile extends DefaultInputComponent implements InputFile, org.sonar.api.resources.InputFile {

private final String relativePath;
private final String moduleKey;
@@ -114,6 +114,7 @@ public class DefaultInputFile implements InputFile, org.sonar.api.resources.Inpu
/**
* Component key.
*/
@Override
public String key() {
return new StringBuilder().append(moduleKey).append(":").append(relativePath).toString();
}
@@ -327,4 +328,9 @@ public class DefaultInputFile implements InputFile, org.sonar.api.resources.Inpu
return new BufferedInputStream(new FileInputStream(file()));
}

@Override
public boolean isFile() {
return true;
}

}

+ 45
- 0
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputModule.java View File

@@ -0,0 +1,45 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* SonarQube is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.api.batch.fs.internal;

import org.sonar.api.batch.fs.InputModule;

/**
* @since 5.2
*/
public class DefaultInputModule extends DefaultInputComponent implements InputModule {

private final String moduleKey;

public DefaultInputModule(String moduleKey) {
this.moduleKey = moduleKey;
}

@Override
public String key() {
return moduleKey;
}

@Override
public boolean isFile() {
return false;
}

}

+ 4
- 5
sonar-plugin-api/src/main/java/org/sonar/api/batch/postjob/issue/Issue.java View File

@@ -20,12 +20,11 @@
package org.sonar.api.batch.postjob.issue;

import com.google.common.annotations.Beta;
import org.sonar.api.batch.fs.InputPath;
import javax.annotation.CheckForNull;
import org.sonar.api.batch.fs.InputComponent;
import org.sonar.api.batch.rule.Severity;
import org.sonar.api.rule.RuleKey;

import javax.annotation.CheckForNull;

/**
* Represents an issue state at the end of the batch analysis. Only available after local issue tracking in preview mode.
*
@@ -50,10 +49,10 @@ public interface Issue {
String componentKey();

/**
* The {@link InputPath} this issue belongs to. Returns null if issue is global to the project or if component was deleted (for resolved issues).
* The {@link InputComponent} this issue belongs to. Returns null if component was deleted (for resolved issues).
*/
@CheckForNull
InputPath inputPath();
InputComponent inputComponent();

/**
* Line of the issue. Null for global issues and issues on directories. Can also be null

+ 14
- 1
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/Issue.java View File

@@ -21,6 +21,7 @@ package org.sonar.api.batch.sensor.issue;

import com.google.common.annotations.Beta;
import java.util.List;
import java.util.Map;
import javax.annotation.CheckForNull;
import org.sonar.api.batch.rule.Severity;
import org.sonar.api.batch.sensor.Sensor;
@@ -59,7 +60,13 @@ public interface Issue {
Severity overriddenSeverity();

/**
* List of locations for this issue. Returns at least one location.
* Primary locations for this issue.
* @since 5.2
*/
IssueLocation primaryLocation();

/**
* List of additional locations for this issue.
* @since 5.2
*/
List<IssueLocation> locations();
@@ -70,4 +77,10 @@ public interface Issue {
*/
List<ExecutionFlow> executionFlows();

/**
* Key/value pair of attributes that are attached to the issue.
* @since 5.2
*/
Map<String, String> attributes();

}

+ 3
- 4
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/IssueLocation.java View File

@@ -21,7 +21,7 @@ package org.sonar.api.batch.sensor.issue;

import com.google.common.annotations.Beta;
import javax.annotation.CheckForNull;
import org.sonar.api.batch.fs.InputPath;
import org.sonar.api.batch.fs.InputComponent;
import org.sonar.api.batch.fs.TextRange;

/**
@@ -33,10 +33,9 @@ import org.sonar.api.batch.fs.TextRange;
public interface IssueLocation {

/**
* The {@link InputPath} this location belongs to. Returns null if location is global to the project.
* The {@link InputComponent} this location belongs to.
*/
@CheckForNull
InputPath inputPath();
InputComponent inputComponent();

/**
* Range of the issue. Null for global issues and issues on directories. Can also be null

+ 13
- 1
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/NewIssue.java View File

@@ -51,7 +51,13 @@ public interface NewIssue {

/**
* @since 5.2
* Register a new location for this issue. First registered location is considered as primary location.
* Primary for this issue.
*/
NewIssue at(NewIssueLocation primaryLocation);

/**
* @since 5.2
* Register an additional location for this issue.
*/
NewIssue addLocation(NewIssueLocation location);

@@ -67,6 +73,12 @@ public interface NewIssue {
*/
NewIssueLocation newLocation();

/**
* @since 5.2
* Attach a new attribute to the issue. Not used by SQ but can be reused later for integration needs (for example it is returned by WS).
*/
NewIssue addAttribute(String key, String value);

/**
* Save the issue. If rule key is unknown or rule not enabled in the current quality profile then a warning is logged but no exception
* is thrown.

+ 4
- 14
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/NewIssueLocation.java View File

@@ -20,7 +20,7 @@
package org.sonar.api.batch.sensor.issue;

import com.google.common.annotations.Beta;
import org.sonar.api.batch.fs.InputDir;
import org.sonar.api.batch.fs.InputComponent;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.TextRange;

@@ -38,22 +38,12 @@ public interface NewIssueLocation {
int MESSAGE_MAX_SIZE = 4000;

/**
* The {@link InputFile} the issue location belongs to. For global issues call {@link #onProject()}.
* The {@link InputComponent} the issue location belongs to. Mandatory.
*/
NewIssueLocation onFile(InputFile file);
NewIssueLocation on(InputComponent component);

/**
* The {@link InputDir} the issue location belongs to. For global issues call {@link #onProject()}.
*/
NewIssueLocation onDir(InputDir inputDir);

/**
* Tell that the issue location is global to the project.
*/
NewIssueLocation onProject();

/**
* Position in the file. Only valid when {@link #onFile(InputFile)} has been called.
* Position in the file. Only applicable when {@link #on(InputComponent)} has been called with an InputFile.
* See {@link InputFile#newRange(org.sonar.api.batch.fs.TextPointer, org.sonar.api.batch.fs.TextPointer)}
*/
NewIssueLocation at(TextRange location);

+ 28
- 34
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssue.java View File

@@ -21,11 +21,13 @@ package org.sonar.api.batch.sensor.issue.internal;

import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import org.sonar.api.batch.rule.Severity;
import org.sonar.api.batch.sensor.internal.DefaultStorable;
@@ -35,25 +37,23 @@ import org.sonar.api.batch.sensor.issue.IssueLocation;
import org.sonar.api.batch.sensor.issue.NewIssue;
import org.sonar.api.batch.sensor.issue.NewIssueLocation;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.utils.internal.Uuids;

public class DefaultIssue extends DefaultStorable implements Issue, NewIssue {

private String key;
private RuleKey ruleKey;
private Double effortToFix;
private Severity overriddenSeverity;
private IssueLocation primaryLocation;
private List<IssueLocation> locations = new ArrayList<>();
private List<List<IssueLocation>> executionFlows = new ArrayList<>();
private final Map<String, String> attributes = new LinkedHashMap<>();

public DefaultIssue() {
super(null);
this.key = Uuids.create();
}

public DefaultIssue(SensorStorage storage) {
super(storage);
this.key = Uuids.create();
}

@Override
@@ -79,6 +79,14 @@ public class DefaultIssue extends DefaultStorable implements Issue, NewIssue {
return new DefaultIssueLocation();
}

@Override
public DefaultIssue at(NewIssueLocation primaryLocation) {
Preconditions.checkArgument(primaryLocation != null, "Cannot use a location that is null");
Preconditions.checkState(this.primaryLocation == null, "at() already called");
this.primaryLocation = (DefaultIssueLocation) primaryLocation;
return this;
}

@Override
public DefaultIssue addLocation(NewIssueLocation location) {
locations.add((DefaultIssueLocation) location);
@@ -95,6 +103,17 @@ public class DefaultIssue extends DefaultStorable implements Issue, NewIssue {
return null;
}

@Override
public DefaultIssue addAttribute(String key, String value) {
attributes.put(key, value);
return this;
}

@Override
public Map<String, String> attributes() {
return ImmutableMap.copyOf(attributes);
}

@Override
public RuleKey ruleKey() {
return this.ruleKey;
@@ -110,8 +129,9 @@ public class DefaultIssue extends DefaultStorable implements Issue, NewIssue {
return this.effortToFix;
}

public String key() {
return this.key;
@Override
public IssueLocation primaryLocation() {
return primaryLocation;
}

@Override
@@ -137,34 +157,8 @@ public class DefaultIssue extends DefaultStorable implements Issue, NewIssue {
@Override
public void doSave() {
Preconditions.checkNotNull(this.ruleKey, "ruleKey is mandatory on issue");
Preconditions.checkState(!Strings.isNullOrEmpty(key), "Fail to generate issue key");
Preconditions.checkState(!locations.isEmpty(), "At least one location is mandatory on every issue");
Preconditions.checkState(primaryLocation != null, "Primary location is mandatory on every issue");
storage.store(this);
}

/**
* For testing only.
*/
public DefaultIssue withKey(String key) {
this.key = key;
return this;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DefaultIssue that = (DefaultIssue) o;
return !key.equals(that.key);
}

@Override
public int hashCode() {
return key.hashCode();
}

}

+ 11
- 38
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueLocation.java View File

@@ -20,10 +20,7 @@
package org.sonar.api.batch.sensor.issue.internal;

import com.google.common.base.Preconditions;
import javax.annotation.CheckForNull;
import org.sonar.api.batch.fs.InputDir;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.InputPath;
import org.sonar.api.batch.fs.InputComponent;
import org.sonar.api.batch.fs.TextRange;
import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.batch.sensor.issue.IssueLocation;
@@ -31,46 +28,23 @@ import org.sonar.api.batch.sensor.issue.NewIssueLocation;

public class DefaultIssueLocation implements NewIssueLocation, IssueLocation {

private static final String INPUT_DIR_SHOULD_BE_NON_NULL = "InputDir should be non null";
private static final String INPUT_FILE_SHOULD_BE_NON_NULL = "InputFile should be non null";
private static final String ON_FILE_OR_ON_DIR_ALREADY_CALLED = "onFile or onDir already called";
private static final String ON_PROJECT_ALREADY_CALLED = "onProject already called";

private boolean onProject = false;
private InputPath path;
private InputComponent component;
private TextRange textRange;
private String message;

@Override
public NewIssueLocation onFile(InputFile file) {
Preconditions.checkState(!this.onProject, ON_PROJECT_ALREADY_CALLED);
Preconditions.checkState(this.path == null, ON_FILE_OR_ON_DIR_ALREADY_CALLED);
Preconditions.checkNotNull(file, INPUT_FILE_SHOULD_BE_NON_NULL);
this.path = file;
return this;
}

@Override
public NewIssueLocation onDir(InputDir dir) {
Preconditions.checkState(!this.onProject, ON_PROJECT_ALREADY_CALLED);
Preconditions.checkState(this.path == null, ON_FILE_OR_ON_DIR_ALREADY_CALLED);
Preconditions.checkNotNull(dir, INPUT_DIR_SHOULD_BE_NON_NULL);
this.path = dir;
return this;
}

@Override
public NewIssueLocation onProject() {
Preconditions.checkState(!this.onProject, ON_PROJECT_ALREADY_CALLED);
Preconditions.checkState(this.path == null, ON_FILE_OR_ON_DIR_ALREADY_CALLED);
this.onProject = true;
public NewIssueLocation on(InputComponent component) {
Preconditions.checkArgument(component != null, "Component can't be null");
Preconditions.checkState(this.component == null, "on() already called");
this.component = component;
return this;
}

@Override
public NewIssueLocation at(TextRange location) {
Preconditions.checkState(this.path != null && this.path instanceof InputFile, "at() should be called after onFile.");
DefaultInputFile file = (DefaultInputFile) this.path;
Preconditions.checkState(this.component != null, "at() should be called after on()");
Preconditions.checkState(this.component.isFile(), "at() should be called only for an InputFile.");
DefaultInputFile file = (DefaultInputFile) this.component;
file.validate(location);
this.textRange = location;
return this;
@@ -86,9 +60,8 @@ public class DefaultIssueLocation implements NewIssueLocation, IssueLocation {
}

@Override
@CheckForNull
public InputPath inputPath() {
return this.path;
public InputComponent inputComponent() {
return this.component;
}

@Override

+ 8
- 2
sonar-plugin-api/src/main/java/org/sonar/api/issue/Issuable.java View File

@@ -67,7 +67,7 @@ public interface Issuable extends Perspective {

/**
* Optional line index, starting from 1. It must not be zero or negative.
* @deprecated since 5.2 use {@link #addLocation(NewIssueLocation)}
* @deprecated since 5.2 use {@link #at(NewIssueLocation)}
*/
@Deprecated
IssueBuilder line(@Nullable Integer line);
@@ -76,7 +76,7 @@ public interface Issuable extends Perspective {
* Optional, but recommended, plain-text message.
* <p/>
* Formats like Markdown or HTML are not supported. Size must not be greater than {@link Issue#MESSAGE_MAX_SIZE} characters.
* @deprecated since 5.2 use {@link #addLocation(NewIssueLocation)}
* @deprecated since 5.2 use {@link #at(NewIssueLocation)}
*/
@Deprecated
IssueBuilder message(@Nullable String message);
@@ -87,6 +87,12 @@ public interface Issuable extends Perspective {
*/
NewIssueLocation newLocation();

/**
* @since 5.2
* Register primary location for this issue.
*/
IssueBuilder at(NewIssueLocation primaryLocation);

/**
* @since 5.2
* Register a new secondary location for this issue.

+ 2
- 2
sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/internal/SensorContextTesterTest.java View File

@@ -89,12 +89,12 @@ public class SensorContextTesterTest {
assertThat(tester.allIssues()).isEmpty();
NewIssue newIssue = tester.newIssue();
newIssue
.addLocation(newIssue.newLocation().onFile(new DefaultInputFile("foo", "src/Foo.java")))
.at(newIssue.newLocation().on(new DefaultInputFile("foo", "src/Foo.java")))
.forRule(RuleKey.of("repo", "rule"))
.save();
newIssue = tester.newIssue();
newIssue
.addLocation(newIssue.newLocation().onFile(new DefaultInputFile("foo", "src/Foo.java")))
.at(newIssue.newLocation().on(new DefaultInputFile("foo", "src/Foo.java")))
.forRule(RuleKey.of("repo", "rule"))
.save();
assertThat(tester.allIssues()).hasSize(2);

+ 6
- 6
sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueLocationTest.java View File

@@ -35,19 +35,19 @@ public class DefaultIssueLocationTest {
private DefaultInputFile inputFile = new DefaultInputFile("foo", "src/Foo.php").initMetadata(new FileMetadata().readMetadata(new StringReader("Foo\nBar\n")));

@Test
public void not_allowed_to_call_onFile_and_onProject() {
public void not_allowed_to_call_on_twice() {
thrown.expect(IllegalStateException.class);
thrown.expectMessage("onProject already called");
thrown.expectMessage("on() already called");
new DefaultIssueLocation()
.onProject()
.onFile(inputFile)
.on(inputFile)
.on(inputFile)
.message("Wrong way!");
}

@Test
public void prevent_too_long_messages() {
new DefaultIssueLocation()
.onFile(inputFile)
.on(inputFile)
.message(StringUtils.repeat("a", 4000));

thrown.expect(IllegalArgumentException.class);
@@ -55,7 +55,7 @@ public class DefaultIssueLocationTest {
thrown.expectMessage("aaa] size is 4001");

new DefaultIssueLocation()
.onFile(inputFile)
.on(inputFile)
.message(StringUtils.repeat("a", 4001));

}

+ 16
- 15
sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueTest.java View File

@@ -23,6 +23,7 @@ import java.io.StringReader;
import org.junit.Test;
import org.sonar.api.batch.fs.internal.DefaultInputDir;
import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.batch.fs.internal.DefaultInputModule;
import org.sonar.api.batch.fs.internal.FileMetadata;
import org.sonar.api.batch.rule.Severity;
import org.sonar.api.batch.sensor.internal.SensorStorage;
@@ -40,18 +41,18 @@ public class DefaultIssueTest {
public void build_file_issue() {
SensorStorage storage = mock(SensorStorage.class);
DefaultIssue issue = new DefaultIssue(storage)
.addLocation(new DefaultIssueLocation()
.onFile(inputFile)
.at(new DefaultIssueLocation()
.on(inputFile)
.at(inputFile.selectLine(1))
.message("Wrong way!"))
.forRule(RuleKey.of("repo", "rule"))
.effortToFix(10.0);

assertThat(issue.locations().get(0).inputPath()).isEqualTo(inputFile);
assertThat(issue.primaryLocation().inputComponent()).isEqualTo(inputFile);
assertThat(issue.ruleKey()).isEqualTo(RuleKey.of("repo", "rule"));
assertThat(issue.locations().get(0).textRange().start().line()).isEqualTo(1);
assertThat(issue.primaryLocation().textRange().start().line()).isEqualTo(1);
assertThat(issue.effortToFix()).isEqualTo(10.0);
assertThat(issue.locations().get(0).message()).isEqualTo("Wrong way!");
assertThat(issue.primaryLocation().message()).isEqualTo("Wrong way!");

issue.save();

@@ -62,16 +63,16 @@ public class DefaultIssueTest {
public void build_directory_issue() {
SensorStorage storage = mock(SensorStorage.class);
DefaultIssue issue = new DefaultIssue(storage)
.addLocation(new DefaultIssueLocation()
.onDir(new DefaultInputDir("foo", "src"))
.at(new DefaultIssueLocation()
.on(new DefaultInputDir("foo", "src"))
.message("Wrong way!"))
.forRule(RuleKey.of("repo", "rule"))
.overrideSeverity(Severity.BLOCKER);

assertThat(issue.locations().get(0).inputPath()).isEqualTo(new DefaultInputDir("foo", "src"));
assertThat(issue.primaryLocation().inputComponent()).isEqualTo(new DefaultInputDir("foo", "src"));
assertThat(issue.ruleKey()).isEqualTo(RuleKey.of("repo", "rule"));
assertThat(issue.locations().get(0).textRange()).isNull();
assertThat(issue.locations().get(0).message()).isEqualTo("Wrong way!");
assertThat(issue.primaryLocation().textRange()).isNull();
assertThat(issue.primaryLocation().message()).isEqualTo("Wrong way!");
assertThat(issue.overriddenSeverity()).isEqualTo(Severity.BLOCKER);

issue.save();
@@ -83,17 +84,17 @@ public class DefaultIssueTest {
public void build_project_issue() {
SensorStorage storage = mock(SensorStorage.class);
DefaultIssue issue = new DefaultIssue(storage)
.addLocation(new DefaultIssueLocation()
.onProject()
.at(new DefaultIssueLocation()
.on(new DefaultInputModule("foo"))
.message("Wrong way!"))
.forRule(RuleKey.of("repo", "rule"))
.effortToFix(10.0);

assertThat(issue.locations().get(0).inputPath()).isNull();
assertThat(issue.primaryLocation().inputComponent()).isEqualTo(new DefaultInputModule("foo"));
assertThat(issue.ruleKey()).isEqualTo(RuleKey.of("repo", "rule"));
assertThat(issue.locations().get(0).textRange()).isNull();
assertThat(issue.primaryLocation().textRange()).isNull();
assertThat(issue.effortToFix()).isEqualTo(10.0);
assertThat(issue.locations().get(0).message()).isEqualTo("Wrong way!");
assertThat(issue.primaryLocation().message()).isEqualTo("Wrong way!");

issue.save();


Loading…
Cancel
Save