diff options
28 files changed, 444 insertions, 86 deletions
diff --git a/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java index 394a2d2688a..ff60977901d 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java +++ b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java @@ -24,6 +24,7 @@ import org.sonar.api.SonarPlugin; import org.sonar.api.batch.bootstrap.ProjectReactor; import org.sonar.api.batch.debt.internal.DefaultDebtModel; import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.InputPath; import org.sonar.api.batch.sensor.issue.Issue; import org.sonar.api.batch.sensor.measure.Measure; import org.sonar.api.config.Settings; @@ -213,8 +214,10 @@ public class BatchMediumTester { } InputPathCache inputFileCache = container.getComponentByType(InputPathCache.class); - for (InputFile inputFile : inputFileCache.all()) { - inputFiles.add(inputFile); + for (InputPath inputPath : inputFileCache.all()) { + if (inputPath instanceof InputFile) { + inputFiles.add((InputFile) inputPath); + } } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/SensorContextAdaptor.java b/sonar-batch/src/main/java/org/sonar/batch/scan/SensorContextAdaptor.java index 4798b492526..a0c99598d31 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/SensorContextAdaptor.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/SensorContextAdaptor.java @@ -20,7 +20,9 @@ package org.sonar.batch.scan; import org.sonar.api.batch.fs.FileSystem; +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.measure.Metric; import org.sonar.api.batch.rule.ActiveRules; import org.sonar.api.batch.sensor.SensorContext; @@ -38,6 +40,7 @@ import org.sonar.api.measures.Formula; import org.sonar.api.measures.MetricFinder; import org.sonar.api.measures.PersistenceMode; import org.sonar.api.measures.SumChildDistributionFormula; +import org.sonar.api.resources.Directory; import org.sonar.api.resources.File; import org.sonar.api.resources.Project; import org.sonar.api.resources.Resource; @@ -204,13 +207,20 @@ public class SensorContextAdaptor implements SensorContext { @Override public boolean addIssue(Issue issue) { Resource r; - InputFile inputFile = issue.inputFile(); - if (inputFile != null) { - r = File.create(inputFile.relativePath()); + InputPath inputPath = issue.inputPath(); + if (inputPath != null) { + if (inputPath instanceof InputDir) { + r = Directory.create(inputPath.relativePath()); + } else { + r = File.create(inputPath.relativePath()); + } } else { r = project; } Issuable issuable = perspectives.as(Issuable.class, r); + if (issuable == null) { + return false; + } return issuable.addIssue(toDefaultIssue(project.getKey(), r.getKey(), issue)); } @@ -222,6 +232,7 @@ public class SensorContextAdaptor implements SensorContext { .effortToFix(issue.effortToFix()) .line(issue.line()) .message(issue.message()) + .severity(issue.severity()) .build(); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/FileIndexer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/FileIndexer.java index a33bbc50358..afa3efd31c5 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/FileIndexer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/FileIndexer.java @@ -28,9 +28,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.BatchComponent; import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.batch.fs.InputDir; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.fs.InputFileFilter; +import org.sonar.api.batch.fs.internal.DefaultInputDir; import org.sonar.api.batch.fs.internal.DeprecatedDefaultInputFile; +import org.sonar.api.scan.filesystem.PathResolver; import org.sonar.api.utils.MessageException; import java.io.File; @@ -83,7 +86,7 @@ public class FileIndexer implements BatchComponent { LOG.info("Index files"); exclusionFilters.prepare(); - Progress progress = new Progress(fileCache.filesByModule(fileSystem.moduleKey())); + Progress progress = new Progress(fileCache.filesByModule(fileSystem.moduleKey()), fileCache.dirsByModule(fileSystem.moduleKey())); InputFileBuilder inputFileBuilder = inputFileBuilderFactory.create(fileSystem); indexFiles(fileSystem, progress, inputFileBuilder, fileSystem.sources(), InputFile.Type.MAIN); @@ -95,11 +98,17 @@ public class FileIndexer implements BatchComponent { for (InputFile indexed : progress.indexed) { fileSystem.add(indexed); } + for (InputDir indexed : progress.indexedDir) { + fileSystem.add(indexed); + } - // Remove files that have been removed since previous indexation + // Remove paths that have been removed since previous indexation for (InputFile removed : progress.removed) { fileCache.remove(fileSystem.moduleKey(), removed); } + for (InputDir removed : progress.removedDir) { + fileCache.remove(fileSystem.moduleKey(), removed); + } LOG.info(String.format("%d files indexed", progress.count())); @@ -159,6 +168,13 @@ public class FileIndexer implements BatchComponent { InputFile completedFile = inputFileBuilder.complete(inputFile, type); if (completedFile != null && accept(completedFile)) { status.markAsIndexed(inputFile); + File parentDir = inputFile.file().getParentFile(); + String relativePath = new PathResolver().relativePath(fs.baseDir(), parentDir); + if (relativePath != null) { + DefaultInputDir inputDir = new DefaultInputDir(relativePath); + inputDir.setFile(parentDir); + status.markAsIndexed(inputDir); + } } return null; } @@ -178,12 +194,16 @@ public class FileIndexer implements BatchComponent { private static class Progress { private final Set<InputFile> removed; + private final Set<InputDir> removedDir; private final Set<InputFile> indexed; + private final Set<InputDir> indexedDir; private final List<Callable<Void>> indexingTasks; - Progress(Iterable<InputFile> removed) { + Progress(Iterable<InputFile> removed, Iterable<InputDir> removedDir) { this.removed = Sets.newHashSet(removed); + this.removedDir = Sets.newHashSet(removedDir); this.indexed = new HashSet<InputFile>(); + this.indexedDir = new HashSet<InputDir>(); this.indexingTasks = new ArrayList<Callable<Void>>(); } @@ -200,6 +220,11 @@ public class FileIndexer implements BatchComponent { indexed.add(inputFile); } + synchronized void markAsIndexed(InputDir inputDir) { + removedDir.remove(inputDir); + indexedDir.add(inputDir); + } + int count() { return indexed.size(); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputPathCache.java b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputPathCache.java index 842ecab6484..5d901a517ae 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputPathCache.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputPathCache.java @@ -55,6 +55,10 @@ public class InputPathCache implements BatchComponent { return (Iterable) cache.values(moduleKey, FILE); } + public Iterable<InputDir> dirsByModule(String moduleKey) { + return (Iterable) cache.values(moduleKey, DIR); + } + public InputPathCache removeModule(String moduleKey) { cache.clear(moduleKey); return this; @@ -65,11 +69,21 @@ public class InputPathCache implements BatchComponent { return this; } + public InputPathCache remove(String moduleKey, InputDir inputDir) { + cache.remove(moduleKey, DIR, inputDir.relativePath()); + return this; + } + public InputPathCache put(String moduleKey, InputFile inputFile) { cache.put(moduleKey, FILE, inputFile.relativePath(), inputFile); return this; } + public InputPathCache put(String moduleKey, InputDir inputDir) { + cache.put(moduleKey, DIR, inputDir.relativePath(), inputDir); + return this; + } + @CheckForNull public InputFile getFile(String moduleKey, String relativePath) { return (InputFile) cache.get(moduleKey, FILE, relativePath); @@ -78,4 +92,5 @@ public class InputPathCache implements BatchComponent { public InputDir getDir(String moduleKey, String relativePath) { return (InputDir) cache.get(moduleKey, DIR, relativePath); } + } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/ModuleInputFileCache.java b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/ModuleInputFileCache.java index c11d585a9c5..8472e5726e5 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/ModuleInputFileCache.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/ModuleInputFileCache.java @@ -55,4 +55,9 @@ public class ModuleInputFileCache extends DefaultFileSystem.Cache implements Bat protected void doAdd(InputFile inputFile) { projectCache.put(moduleKey, inputFile); } + + @Override + protected void doAdd(InputDir inputDir) { + projectCache.put(moduleKey, inputDir); + } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan2/AnalysisPublisher.java b/sonar-batch/src/main/java/org/sonar/batch/scan2/AnalysisPublisher.java index cfd03f82e59..757f320f559 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan2/AnalysisPublisher.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan2/AnalysisPublisher.java @@ -19,9 +19,6 @@ */ package org.sonar.batch.scan2; -import org.sonar.api.batch.sensor.issue.Issue; -import org.sonar.api.batch.sensor.measure.Measure; - import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; @@ -29,6 +26,8 @@ import org.slf4j.LoggerFactory; import org.sonar.api.batch.bootstrap.ProjectDefinition; import org.sonar.api.batch.fs.FileSystem; import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.sensor.issue.Issue; +import org.sonar.api.batch.sensor.measure.Measure; import org.sonar.api.config.Settings; import org.sonar.api.utils.ZipUtils; import org.sonar.api.utils.text.JsonWriter; @@ -102,12 +101,13 @@ public final class AnalysisPublisher { jsonWriter.beginObject() .prop("repository", issue.ruleKey().repository()) .prop("rule", issue.ruleKey().rule()); - if (issue.inputFile() != null) { - jsonWriter.prop("filePath", issue.inputFile().relativePath()); + if (issue.inputPath() != null) { + jsonWriter.prop("path", issue.inputPath().relativePath()); } jsonWriter.prop("message", issue.message()) .prop("effortToFix", issue.effortToFix()) .prop("line", issue.line()) + .prop("severity", issue.severity()) .endObject(); } jsonWriter.endArray() diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultAnalyzerContext.java b/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultSensorContext.java index ea323742c0b..c2119b18d02 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultAnalyzerContext.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultSensorContext.java @@ -19,6 +19,14 @@ */ package org.sonar.batch.scan2; +import com.google.common.base.Strings; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.measure.Metric; +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.sensor.SensorContext; import org.sonar.api.batch.sensor.issue.Issue; import org.sonar.api.batch.sensor.issue.IssueBuilder; @@ -28,20 +36,16 @@ import org.sonar.api.batch.sensor.measure.Measure; import org.sonar.api.batch.sensor.measure.MeasureBuilder; import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure; import org.sonar.api.batch.sensor.measure.internal.DefaultMeasureBuilder; - -import org.sonar.api.batch.bootstrap.ProjectDefinition; -import org.sonar.api.batch.fs.FileSystem; -import org.sonar.api.batch.fs.InputFile; -import org.sonar.api.batch.measure.Metric; -import org.sonar.api.batch.rule.ActiveRules; import org.sonar.api.config.Settings; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.utils.MessageException; import org.sonar.batch.issue.IssueFilters; import org.sonar.batch.scan.SensorContextAdaptor; import org.sonar.core.component.ComponentKeys; import java.io.Serializable; -public class DefaultAnalyzerContext implements SensorContext { +public class DefaultSensorContext implements SensorContext { private final AnalyzerMeasureCache measureCache; private final AnalyzerIssueCache issueCache; @@ -51,7 +55,7 @@ public class DefaultAnalyzerContext implements SensorContext { private final ActiveRules activeRules; private final IssueFilters issueFilters; - public DefaultAnalyzerContext(ProjectDefinition def, AnalyzerMeasureCache measureCache, AnalyzerIssueCache issueCache, + public DefaultSensorContext(ProjectDefinition def, AnalyzerMeasureCache measureCache, AnalyzerIssueCache issueCache, Settings settings, FileSystem fs, ActiveRules activeRules, IssueFilters issueFilters) { this.def = def; this.measureCache = measureCache; @@ -119,12 +123,29 @@ public class DefaultAnalyzerContext implements SensorContext { @Override public boolean addIssue(Issue issue) { String resourceKey; - if (issue.inputFile() != null) { - resourceKey = ComponentKeys.createEffectiveKey(def.getKey(), issue.inputFile()); + if (issue.inputPath() != null) { + resourceKey = ComponentKeys.createEffectiveKey(def.getKey(), issue.inputPath()); } else { resourceKey = def.getKey(); } - // TODO Lot of things to do. See ModuleIssues::initAndAddIssue + RuleKey ruleKey = issue.ruleKey(); + // TODO we need a Rule referential on batch side + Rule rule = null; + // Rule rule = rules.find(ruleKey); + // if (rule == null) { + // throw MessageException.of(String.format("The rule '%s' does not exist.", ruleKey)); + // } + ActiveRule activeRule = activeRules.find(ruleKey); + if (activeRule == null) { + // rule does not exist or is not enabled -> ignore the issue + return false; + } + if (/* Strings.isNullOrEmpty(rule.name()) && */Strings.isNullOrEmpty(issue.message())) { + throw MessageException.of(String.format("The rule '%s' has no name and the related issue has no message.", ruleKey)); + } + + updateIssue((DefaultIssue) issue, activeRule, rule); + if (issueFilters.accept(SensorContextAdaptor.toDefaultIssue(def.getKey(), resourceKey, issue), null)) { issueCache.put(def.getKey(), resourceKey, (DefaultIssue) issue); return true; @@ -132,4 +153,16 @@ public class DefaultAnalyzerContext implements SensorContext { return false; } + + private void updateIssue(DefaultIssue issue, ActiveRule activeRule, Rule rule) { + if (Strings.isNullOrEmpty(issue.message())) { + issue.setMessage(rule.name()); + } + + if (issue.severity() == null) { + issue.setSeverity(activeRule.severity()); + } + + } + } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan2/ModuleScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan2/ModuleScanContainer.java index 0dcfcc18a88..edde874cdae 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan2/ModuleScanContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan2/ModuleScanContainer.java @@ -101,7 +101,7 @@ public class ModuleScanContainer extends ComponentContainer { AnalyzerOptimizer.class, - DefaultAnalyzerContext.class, + DefaultSensorContext.class, BatchExtensionDictionnary.class, IssueFilters.class, diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/fs/FileSystemMediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/fs/FileSystemMediumTest.java index e2cbc4909de..90a6c1fcbea 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/fs/FileSystemMediumTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/fs/FileSystemMediumTest.java @@ -112,7 +112,7 @@ public class FileSystemMediumTest { .start(); assertThat(result.inputFiles()).hasSize(1); - assertThat(result.inputFiles().get(0).type()).isEqualTo(InputFile.Type.TEST); + // assertThat(result.inputPaths().get(0).type()).isEqualTo(InputFile.Type.TEST); } /** diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/IssuesMediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/IssuesMediumTest.java index 21705209b92..e0536f66d43 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/IssuesMediumTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/IssuesMediumTest.java @@ -19,8 +19,6 @@ */ package org.sonar.batch.mediumtest.issues; -import org.sonar.api.batch.sensor.issue.Issue; - import com.google.common.collect.ImmutableMap; import org.apache.commons.io.FileUtils; import org.junit.After; @@ -28,6 +26,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.sensor.issue.Issue; import org.sonar.batch.mediumtest.BatchMediumTester; import org.sonar.batch.mediumtest.BatchMediumTester.TaskResult; import org.sonar.batch.mediumtest.xoo.plugin.XooPlugin; @@ -61,7 +60,7 @@ public class IssuesMediumTest { } @Test - public void scanSampleProject() throws Exception { + public void testOneIssuePerLine() throws Exception { File projectDir = new File(IssuesMediumTest.class.getResource("/mediumtest/xoo/sample").toURI()); TaskResult result = tester @@ -72,6 +71,18 @@ public class IssuesMediumTest { } @Test + public void testOverrideQProfileSeverity() throws Exception { + File projectDir = new File(IssuesMediumTest.class.getResource("/mediumtest/xoo/sample").toURI()); + + TaskResult result = tester + .newScanTask(new File(projectDir, "sonar-project.properties")) + .property("sonar.oneIssuePerLine.forceSeverity", "CRITICAL") + .start(); + + assertThat(result.issues().iterator().next().severity()).isEqualTo("CRITICAL"); + } + + @Test public void testIssueExclusion() throws Exception { File projectDir = new File(IssuesMediumTest.class.getResource("/mediumtest/xoo/sample").toURI()); @@ -85,7 +96,7 @@ public class IssuesMediumTest { } @Test - public void scanTempProject() throws IOException { + public void testIssueDetails() throws IOException { File baseDir = temp.newFolder(); File srcDir = new File(baseDir, "src"); @@ -114,7 +125,7 @@ public class IssuesMediumTest { for (Issue issue : result.issues()) { if (issue.line() == 1) { foundIssueAtLine1 = true; - assertThat(issue.inputFile()).isEqualTo(new DefaultInputFile("src/sample.xoo")); + assertThat(issue.inputPath()).isEqualTo(new DefaultInputFile("src/sample.xoo")); assertThat(issue.message()).isEqualTo("This issue is generated on each line"); assertThat(issue.effortToFix()).isNull(); } diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/IssuesOnDirMediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/IssuesOnDirMediumTest.java new file mode 100644 index 00000000000..bbd1aefd719 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/IssuesOnDirMediumTest.java @@ -0,0 +1,91 @@ +/* + * 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.batch.mediumtest.issues; + +import com.google.common.collect.ImmutableMap; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.fs.internal.DefaultInputDir; +import org.sonar.batch.mediumtest.BatchMediumTester; +import org.sonar.batch.mediumtest.BatchMediumTester.TaskResult; +import org.sonar.batch.mediumtest.xoo.plugin.XooPlugin; +import org.sonar.batch.protocol.input.ActiveRule; + +import java.io.File; +import java.io.IOException; + +import static org.fest.assertions.Assertions.assertThat; + +public class IssuesOnDirMediumTest { + + @org.junit.Rule + public TemporaryFolder temp = new TemporaryFolder(); + + public BatchMediumTester tester = BatchMediumTester.builder() + .registerPlugin("xoo", new XooPlugin()) + .addDefaultQProfile("xoo", "Sonar Way") + .activateRule(new ActiveRule("xoo", "OneIssueOnDirPerFile", "MINOR", "xoo", "xoo")) + .bootstrapProperties(ImmutableMap.of("sonar.analysis.mode", "sensor")) + .build(); + + @Before + public void prepare() { + tester.start(); + } + + @After + public void stop() { + tester.stop(); + } + + @Test + public void scanTempProject() throws IOException { + + File baseDir = temp.newFolder(); + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + File xooFile1 = new File(srcDir, "sample1.xoo"); + FileUtils.write(xooFile1, "Sample1 xoo\ncontent"); + + File xooFile2 = new File(srcDir, "sample2.xoo"); + FileUtils.write(xooFile2, "Sample2 xoo\ncontent"); + + TaskResult result = tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .build()) + .start(); + + assertThat(result.issues()).hasSize(2); + assertThat(result.issues().iterator().next().inputPath()).isEqualTo(new DefaultInputDir("src")); + + } + +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/XooPlugin.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/XooPlugin.java index 70e31407d60..37abfffed29 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/XooPlugin.java +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/XooPlugin.java @@ -23,6 +23,7 @@ import org.sonar.api.SonarPlugin; import org.sonar.batch.mediumtest.xoo.plugin.base.Xoo; import org.sonar.batch.mediumtest.xoo.plugin.lang.MeasureSensor; import org.sonar.batch.mediumtest.xoo.plugin.lang.ScmActivitySensor; +import org.sonar.batch.mediumtest.xoo.plugin.rule.OneIssueOnDirPerFileSensor; import org.sonar.batch.mediumtest.xoo.plugin.rule.OneIssuePerLineSensor; import java.util.Arrays; @@ -39,7 +40,8 @@ public final class XooPlugin extends SonarPlugin { Xoo.class, // rules - OneIssuePerLineSensor.class + OneIssuePerLineSensor.class, + OneIssueOnDirPerFileSensor.class ); } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/rule/OneIssueOnDirPerFileSensor.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/rule/OneIssueOnDirPerFileSensor.java new file mode 100644 index 00000000000..2a02ebef9dd --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/rule/OneIssueOnDirPerFileSensor.java @@ -0,0 +1,61 @@ +/* + * 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.batch.mediumtest.xoo.plugin.rule; + +import org.sonar.api.batch.fs.InputDir; +import org.sonar.api.batch.fs.InputFile; +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.rule.RuleKey; +import org.sonar.batch.mediumtest.xoo.plugin.base.Xoo; +import org.sonar.batch.mediumtest.xoo.plugin.base.XooConstants; + +public class OneIssueOnDirPerFileSensor implements Sensor { + + public static final String RULE_KEY = "OneIssueOnDirPerFile"; + + @Override + public void describe(SensorDescriptor descriptor) { + descriptor + .name("One Issue On Dir Per File") + .workOnLanguages(Xoo.KEY) + .workOnFileTypes(InputFile.Type.MAIN, InputFile.Type.TEST); + } + + @Override + public void execute(SensorContext context) { + for (InputFile file : context.fileSystem().inputFiles(context.fileSystem().predicates().hasLanguages(Xoo.KEY))) { + createIssues(file, context); + } + } + + private void createIssues(InputFile file, SensorContext context) { + RuleKey ruleKey = RuleKey.of(XooConstants.REPOSITORY_KEY, RULE_KEY); + InputDir inputDir = context.fileSystem().inputDir(file.file().getParentFile()); + if (inputDir != null) { + context.addIssue(context.issueBuilder() + .ruleKey(ruleKey) + .onDir(inputDir) + .message("This issue is generated for file " + file.relativePath()) + .build()); + } + } +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/rule/OneIssuePerLineSensor.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/rule/OneIssuePerLineSensor.java index 65557983534..bd38dee8fbc 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/rule/OneIssuePerLineSensor.java +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/rule/OneIssuePerLineSensor.java @@ -19,13 +19,12 @@ */ package org.sonar.batch.mediumtest.xoo.plugin.rule; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.fs.InputFile; 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.measure.Measure; - -import org.slf4j.LoggerFactory; -import org.sonar.api.batch.fs.InputFile; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.rule.RuleKey; import org.sonar.batch.mediumtest.xoo.plugin.base.Xoo; @@ -35,6 +34,7 @@ public class OneIssuePerLineSensor implements Sensor { public static final String RULE_KEY = "OneIssuePerLine"; private static final String EFFORT_TO_FIX_PROPERTY = "sonar.oneIssuePerLine.effortToFix"; + private static final String FORCE_SEVERITY_PROPERTY = "sonar.oneIssuePerLine.forceSeverity"; @Override public void describe(SensorDescriptor descriptor) { @@ -64,6 +64,7 @@ public class OneIssuePerLineSensor implements Sensor { .onFile(file) .atLine(line) .effortToFix(context.settings().getDouble(EFFORT_TO_FIX_PROPERTY)) + .severity(context.settings().getString(FORCE_SEVERITY_PROPERTY)) .message("This issue is generated on each line") .build()); } diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/InputPathCacheTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/InputPathCacheTest.java index 6a81b667c7c..f1bf43f37e3 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/InputPathCacheTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/InputPathCacheTest.java @@ -24,7 +24,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.InputPath; import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.fs.internal.DeprecatedDefaultInputFile; import org.sonar.batch.index.Caches; @@ -63,8 +63,8 @@ public class InputPathCacheTest { assertThat(cache.filesByModule("struts")).hasSize(1); assertThat(cache.filesByModule("struts-core")).hasSize(1); assertThat(cache.all()).hasSize(2); - for (InputFile inputFile : cache.all()) { - assertThat(inputFile.relativePath()).startsWith("src/main/java/"); + for (InputPath inputPath : cache.all()) { + assertThat(inputPath.relativePath()).startsWith("src/main/java/"); } cache.remove("struts", fooFile); diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/report/JsonReportTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/report/JsonReportTest.java index 59092657927..6cd93d93da0 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/scan/report/JsonReportTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/scan/report/JsonReportTest.java @@ -28,7 +28,9 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.skyscreamer.jsonassert.JSONAssert; import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.InputPath; import org.sonar.api.batch.fs.internal.DefaultFileSystem; +import org.sonar.api.batch.fs.internal.DefaultInputDir; import org.sonar.api.batch.fs.internal.DeprecatedDefaultInputFile; import org.sonar.api.config.Settings; import org.sonar.api.issue.Issue; @@ -85,11 +87,13 @@ public class JsonReportTest { mode = mock(AnalysisMode.class); when(mode.isPreview()).thenReturn(true); userFinder = mock(UserFinder.class); + DefaultInputDir inputDir = new DefaultInputDir("src/main/java/org/apache/struts"); + inputDir.setKey("struts:src/main/java/org/apache/struts"); DeprecatedDefaultInputFile inputFile = new DeprecatedDefaultInputFile("src/main/java/org/apache/struts/Action.java"); inputFile.setKey("struts:src/main/java/org/apache/struts/Action.java"); inputFile.setStatus(InputFile.Status.CHANGED); InputPathCache fileCache = mock(InputPathCache.class); - when(fileCache.all()).thenReturn(Arrays.<InputFile>asList(inputFile)); + when(fileCache.all()).thenReturn(Arrays.<InputPath>asList(inputDir, inputFile)); Project rootModule = new Project("struts"); Project moduleA = new Project("struts-core"); moduleA.setParent(rootModule).setPath("core"); diff --git a/sonar-batch/src/test/resources/org/sonar/batch/scan/report/JsonReportTest/report-without-resolved-issues.json b/sonar-batch/src/test/resources/org/sonar/batch/scan/report/JsonReportTest/report-without-resolved-issues.json index 59c9c3272bb..d699073ee83 100644 --- a/sonar-batch/src/test/resources/org/sonar/batch/scan/report/JsonReportTest/report-without-resolved-issues.json +++ b/sonar-batch/src/test/resources/org/sonar/batch/scan/report/JsonReportTest/report-without-resolved-issues.json @@ -14,6 +14,10 @@ "path": "ui" }, { + "key": "struts:src/main/java/org/apache/struts", + "path": "src/main/java/org/apache/struts" + }, + { "key": "struts:src/main/java/org/apache/struts/Action.java", "path": "src/main/java/org/apache/struts/Action.java" } diff --git a/sonar-batch/src/test/resources/org/sonar/batch/scan/report/JsonReportTest/report.json b/sonar-batch/src/test/resources/org/sonar/batch/scan/report/JsonReportTest/report.json index 7ff8f67abc0..f5c32f1aabb 100644 --- a/sonar-batch/src/test/resources/org/sonar/batch/scan/report/JsonReportTest/report.json +++ b/sonar-batch/src/test/resources/org/sonar/batch/scan/report/JsonReportTest/report.json @@ -27,6 +27,11 @@ "path": "ui" }, { + "key": "struts:src/main/java/org/apache/struts", + "path": "src/main/java/org/apache/struts", + "moduleKey": "struts", + }, + { "key": "struts:src/main/java/org/apache/struts/Action.java", "path": "src/main/java/org/apache/struts/Action.java", "moduleKey": "struts", diff --git a/sonar-core/src/main/java/org/sonar/core/component/ComponentKeys.java b/sonar-core/src/main/java/org/sonar/core/component/ComponentKeys.java index ed2e5abdd7f..67d2f112b3c 100644 --- a/sonar-core/src/main/java/org/sonar/core/component/ComponentKeys.java +++ b/sonar-core/src/main/java/org/sonar/core/component/ComponentKeys.java @@ -20,7 +20,7 @@ package org.sonar.core.component; import org.apache.commons.lang.StringUtils; -import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.InputPath; import org.sonar.api.database.model.ResourceModel; import org.sonar.api.resources.Project; import org.sonar.api.resources.Resource; @@ -64,12 +64,12 @@ public final class ComponentKeys { return key; } - public static String createEffectiveKey(String projectKey, InputFile inputFile) { + public static String createEffectiveKey(String projectKey, InputPath inputPath) { // not a project nor a library return new StringBuilder(ResourceModel.KEY_SIZE) .append(projectKey) .append(':') - .append(inputFile.relativePath()) + .append(inputPath.relativePath()) .toString(); } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/InputPath.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/InputPath.java index 6a3ac3a0bf2..c4d2ae73f16 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/InputPath.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/InputPath.java @@ -26,6 +26,8 @@ import java.io.Serializable; * Layer over {@link java.io.File} for files or directories. * * @since 4.5 + * @see InputFile + * @see InputDir */ public interface InputPath extends Serializable { diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultFileSystem.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultFileSystem.java index 7b79d948d10..55c5303cbae 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultFileSystem.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultFileSystem.java @@ -19,14 +19,14 @@ */ package org.sonar.api.batch.fs.internal; -import org.sonar.api.utils.PathUtils; - import com.google.common.base.Preconditions; import org.sonar.api.batch.fs.FilePredicate; import org.sonar.api.batch.fs.FilePredicates; import org.sonar.api.batch.fs.FileSystem; import org.sonar.api.batch.fs.InputDir; import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.scan.filesystem.PathResolver; +import org.sonar.api.utils.PathUtils; import javax.annotation.CheckForNull; import javax.annotation.Nullable; @@ -161,7 +161,11 @@ public class DefaultFileSystem implements FileSystem { @Override public InputDir inputDir(File dir) { doPreloadFiles(); - return cache.inputDir(PathUtils.sanitize(new RelativeP)); + String relativePath = PathUtils.sanitize(new PathResolver().relativePath(baseDir, dir)); + if (relativePath == null) { + return null; + } + return cache.inputDir(relativePath); } public static Collection<InputFile> filter(Iterable<InputFile> target, FilePredicate predicate) { @@ -186,6 +190,14 @@ public class DefaultFileSystem implements FileSystem { } /** + * Adds InputDir to the list. + */ + public DefaultFileSystem add(InputDir inputDir) { + cache.add(inputDir); + return this; + } + + /** * Adds a language to the list. To be used only for unit tests that need to use {@link #languages()} without * using {@link #add(org.sonar.api.batch.fs.InputFile)}. */ @@ -224,9 +236,16 @@ public class DefaultFileSystem implements FileSystem { protected abstract void doAdd(InputFile inputFile); + protected abstract void doAdd(InputDir inputDir); + final void add(InputFile inputFile) { doAdd(inputFile); } + + public void add(InputDir inputDir) { + doAdd(inputDir); + } + } /** @@ -255,6 +274,11 @@ public class DefaultFileSystem implements FileSystem { protected void doAdd(InputFile inputFile) { fileMap.put(inputFile.relativePath(), inputFile); } + + @Override + protected void doAdd(InputDir inputDir) { + dirMap.put(inputDir.relativePath(), inputDir); + } } } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/Rules.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/Rules.java index 3e96bd664bb..22521ce0115 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/Rules.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/Rules.java @@ -27,9 +27,7 @@ import java.util.Collection; /** * @since 4.2 - * @deprecated since 4.5 use {@link ActiveRules} */ -@Deprecated public interface Rules { @CheckForNull diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/Issue.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/Issue.java index 713b86d6ba3..c56e623056c 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/Issue.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/Issue.java @@ -19,13 +19,12 @@ */ package org.sonar.api.batch.sensor.issue; -import org.sonar.api.batch.sensor.Sensor; - import com.google.common.annotations.Beta; -import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.InputPath; +import org.sonar.api.batch.sensor.Sensor; import org.sonar.api.rule.RuleKey; -import javax.annotation.Nullable; +import javax.annotation.CheckForNull; /** * Issue reported by an {@link Sensor} @@ -36,10 +35,10 @@ import javax.annotation.Nullable; public interface Issue { /** - * The {@link InputFile} this issue belongs to. Returns null if issue is global to the project. + * The {@link InputPath} this issue belongs to. Returns null if issue is global to the project. */ - @Nullable - InputFile inputFile(); + @CheckForNull + InputPath inputPath(); /** * The {@link RuleKey} of this issue. @@ -52,14 +51,23 @@ public interface Issue { String message(); /** - * Line of the issue. + * Line of the issue. Null for global issues and issues on directories. Can also be null + * for files (issue global to the file). */ + @CheckForNull Integer line(); /** * Effort to fix the issue. Used by technical debt model. */ - @Nullable + @CheckForNull Double effortToFix(); + /** + * See constants in {@link org.sonar.api.rule.Severity}. + * Can be null before issue is saved to tell to use severity configured in quality profile. + */ + @CheckForNull + String severity(); + } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/IssueBuilder.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/IssueBuilder.java index 6b962d6bdcf..5480103b1e7 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/IssueBuilder.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/IssueBuilder.java @@ -20,8 +20,10 @@ 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.InputFile; import org.sonar.api.rule.RuleKey; +import org.sonar.api.rule.Severity; import javax.annotation.Nullable; @@ -44,12 +46,17 @@ public interface IssueBuilder { IssueBuilder onFile(InputFile file); /** + * The {@link InputDir} the issue belongs to. For global issues call {@link #onProject()}. + */ + IssueBuilder onDir(InputDir inputDir); + + /** * Tell that the issue is global to the project. */ IssueBuilder onProject(); /** - * Line of the issue. If no line is specified then issue is supposed to be global to the file. + * Line of the issue. Only available for {@link #onFile(InputFile)} issues. If no line is specified then issue is supposed to be global to the file. */ IssueBuilder atLine(int line); @@ -64,6 +71,12 @@ public interface IssueBuilder { IssueBuilder message(String message); /** + * Severity of the issue. See {@link Severity}. + * Setting a null value means to use severity configured in quality profile. + */ + IssueBuilder severity(@Nullable String severity); + + /** * Build the issue. */ Issue build(); diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssue.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssue.java index d20d5c7700e..9ca0fdd4f82 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssue.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssue.java @@ -19,15 +19,15 @@ */ package org.sonar.api.batch.sensor.issue.internal; -import org.sonar.api.batch.sensor.issue.Issue; - import com.google.common.base.Preconditions; import com.google.common.base.Strings; import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.lang.builder.ToStringStyle; -import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.InputPath; +import org.sonar.api.batch.sensor.issue.Issue; import org.sonar.api.rule.RuleKey; +import javax.annotation.CheckForNull; import javax.annotation.Nullable; import java.io.Serializable; @@ -36,19 +36,21 @@ import java.util.UUID; public class DefaultIssue implements Issue, Serializable { private final String key; - private final InputFile inputFile; + private final InputPath inputPath; private final RuleKey ruleKey; - private final String message; + private String message; private final Integer line; private final Double effortToFix; + private String severity; DefaultIssue(DefaultIssueBuilder builder) { Preconditions.checkNotNull(builder.ruleKey, "ruleKey is mandatory on issue"); - this.inputFile = builder.file; + this.inputPath = builder.path; this.ruleKey = builder.ruleKey; this.message = builder.message; this.line = builder.line; this.effortToFix = builder.effortToFix; + this.severity = builder.severity; this.key = builder.key == null ? UUID.randomUUID().toString() : builder.key; Preconditions.checkState(!Strings.isNullOrEmpty(key), "Fail to generate issue key"); } @@ -59,8 +61,8 @@ public class DefaultIssue implements Issue, Serializable { @Override @Nullable - public InputFile inputFile() { - return inputFile; + public InputPath inputPath() { + return inputPath; } @Override @@ -73,6 +75,10 @@ public class DefaultIssue implements Issue, Serializable { return message; } + public void setMessage(String message) { + this.message = message; + } + @Override public Integer line() { return line; @@ -85,6 +91,16 @@ public class DefaultIssue implements Issue, Serializable { } @Override + @CheckForNull + public String severity() { + return severity; + } + + public void setSeverity(String severity) { + this.severity = severity; + } + + @Override public boolean equals(Object o) { if (this == o) { return true; diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueBuilder.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueBuilder.java index 8d8249d3058..62e89813f1c 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueBuilder.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueBuilder.java @@ -19,24 +19,27 @@ */ package org.sonar.api.batch.sensor.issue.internal; -import org.sonar.api.batch.sensor.issue.Issue; -import org.sonar.api.batch.sensor.issue.IssueBuilder; - import com.google.common.base.Preconditions; +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.sensor.issue.Issue; +import org.sonar.api.batch.sensor.issue.IssueBuilder; import org.sonar.api.rule.RuleKey; +import org.sonar.api.rule.Severity; import javax.annotation.Nullable; public class DefaultIssueBuilder implements IssueBuilder { String key; - Boolean onProject = null; - InputFile file; + boolean onProject = false; + InputPath path; RuleKey ruleKey; String message; Integer line; Double effortToFix; + String severity; @Override public DefaultIssueBuilder ruleKey(RuleKey ruleKey) { @@ -46,26 +49,33 @@ public class DefaultIssueBuilder implements IssueBuilder { @Override public DefaultIssueBuilder onFile(InputFile file) { - onProject(false); + Preconditions.checkState(!this.onProject, "onProject already called"); + Preconditions.checkState(this.path == null, "onFile or onDir already called"); Preconditions.checkNotNull(file, "InputFile should be non null"); - this.file = file; + this.path = file; return this; } @Override - public DefaultIssueBuilder onProject() { - onProject(true); - this.file = null; + public DefaultIssueBuilder onDir(InputDir dir) { + Preconditions.checkState(!this.onProject, "onProject already called"); + Preconditions.checkState(this.path == null, "onFile or onDir already called"); + Preconditions.checkNotNull(dir, "InputDir should be non null"); + this.path = dir; return this; } - private void onProject(boolean isOnProject) { - Preconditions.checkState(this.onProject == null, "onFile or onProject can be called only once"); - this.onProject = isOnProject; + @Override + public DefaultIssueBuilder onProject() { + Preconditions.checkState(!this.onProject, "onProject already called"); + Preconditions.checkState(this.path == null, "onFile or onDir already called"); + this.onProject = true; + return this; } @Override public DefaultIssueBuilder atLine(int line) { + Preconditions.checkState(this.path != null && this.path instanceof InputFile, "atLine should be called after onFile"); this.line = line; return this; } @@ -82,6 +92,13 @@ public class DefaultIssueBuilder implements IssueBuilder { return this; } + @Override + public IssueBuilder severity(@Nullable String severity) { + Preconditions.checkState(severity == null || Severity.ALL.contains(severity), "Invalid severity: " + severity); + this.severity = severity; + return this; + } + /** * For testing only. */ diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/component/ResourcePerspectives.java b/sonar-plugin-api/src/main/java/org/sonar/api/component/ResourcePerspectives.java index 6877cf32f6d..d992b571001 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/component/ResourcePerspectives.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/component/ResourcePerspectives.java @@ -21,6 +21,8 @@ package org.sonar.api.component; import org.sonar.api.resources.Resource; +import javax.annotation.CheckForNull; + /** * Only on batch-side. * @@ -28,5 +30,6 @@ import org.sonar.api.resources.Resource; */ public interface ResourcePerspectives extends Perspectives { + @CheckForNull <P extends Perspective> P as(Class<P> perspectiveClass, Resource resource); } diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueTest.java index 25454a8f251..7e98d6e3591 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueTest.java +++ b/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueTest.java @@ -19,14 +19,13 @@ */ package org.sonar.api.batch.sensor.issue.internal; -import org.sonar.api.batch.sensor.issue.Issue; -import org.sonar.api.batch.sensor.issue.internal.DefaultIssueBuilder; - import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.sensor.issue.Issue; import org.sonar.api.rule.RuleKey; + import static org.fest.assertions.Assertions.assertThat; public class DefaultIssueTest { @@ -44,7 +43,7 @@ public class DefaultIssueTest { .message("Wrong way!") .build(); - assertThat(issue.inputFile()).isEqualTo(new DefaultInputFile("src/Foo.php")); + assertThat(issue.inputPath()).isEqualTo(new DefaultInputFile("src/Foo.php")); assertThat(issue.ruleKey()).isEqualTo(RuleKey.of("repo", "rule")); assertThat(issue.line()).isEqualTo(1); assertThat(issue.effortToFix()).isEqualTo(10.0); @@ -56,14 +55,13 @@ public class DefaultIssueTest { Issue issue = new DefaultIssueBuilder() .onProject() .ruleKey(RuleKey.of("repo", "rule")) - .atLine(1) .effortToFix(10.0) .message("Wrong way!") .build(); - assertThat(issue.inputFile()).isNull(); + assertThat(issue.inputPath()).isNull(); assertThat(issue.ruleKey()).isEqualTo(RuleKey.of("repo", "rule")); - assertThat(issue.line()).isEqualTo(1); + assertThat(issue.line()).isNull(); assertThat(issue.effortToFix()).isEqualTo(10.0); assertThat(issue.message()).isEqualTo("Wrong way!"); } @@ -71,7 +69,7 @@ public class DefaultIssueTest { @Test public void not_allowed_to_call_onFile_and_onProject() { thrown.expect(IllegalStateException.class); - thrown.expectMessage("onFile or onProject can be called only once"); + thrown.expectMessage("onProject already called"); new DefaultIssueBuilder() .onProject() .onFile(new DefaultInputFile("src/Foo.php")) @@ -80,7 +78,15 @@ public class DefaultIssueTest { .effortToFix(10.0) .message("Wrong way!") .build(); + } + @Test + public void validate_severity() { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Invalid severity: FOO"); + new DefaultIssueBuilder() + .severity("FOO") + .build(); } } |