From cfba7fcb6500d8217bd81ecfcb8f47ec48ad55f2 Mon Sep 17 00:00:00 2001 From: Julien HENRY Date: Tue, 4 Sep 2018 16:08:53 +0200 Subject: [PATCH] SONAR-11209 Allow sensors to provide ad hoc rule metadata for external issues --- .../main/java/org/sonar/xoo/XooPlugin.java | 4 +- .../rule/OneExternalIssuePerLineSensor.java | 39 ++--- ...efinedRuleExternalIssuePerLineSensor.java} | 33 ++-- .../sonar/xoo/rule/XooRulesDefinition.java | 9 +- .../xoo/rule/XooRulesDefinitionTest.java | 5 +- .../projectanalysis/issue/RuleRepository.java | 6 +- .../issue/RuleRepositoryImpl.java | 38 ++--- .../issue/TrackerRawInputFactory.java | 39 +++-- .../step/PersistExternalRulesStep.java | 2 +- .../issue/RuleRepositoryImplTest.java | 16 +- .../issue/RuleRepositoryRule.java | 8 +- .../issue/TrackerRawInputFactoryTest.java | 8 +- .../step/PersistExternalRulesStepTest.java | 12 +- .../step/PersistIssuesStepTest.java | 6 +- .../container/ComputeEngineContainerImpl.java | 4 +- ...uleCreator.java => AddHocRuleCreator.java} | 17 +- ...ewExternalRule.java => NewAddHocRule.java} | 8 +- ...orTest.java => AddHocRuleCreatorTest.java} | 6 +- ...alRuleTest.java => NewAddHocRuleTest.java} | 8 +- .../sonar/api/batch/sensor/SensorContext.java | 9 +- .../internal/InMemorySensorStorage.java | 11 +- .../sensor/internal/SensorContextTester.java | 12 ++ .../batch/sensor/internal/SensorStorage.java | 7 +- .../api/batch/sensor/issue/ExternalIssue.java | 10 ++ .../batch/sensor/issue/NewExternalIssue.java | 14 ++ .../issue/internal/AbstractDefaultIssue.java | 13 +- .../issue/internal/DefaultExternalIssue.java | 43 ++++- .../sensor/issue/internal/DefaultIssue.java | 12 ++ .../issue/internal/DefaultIssueLocation.java | 6 - .../api/batch/sensor/rule/AdHocRule.java | 62 ++++++++ .../api/batch/sensor/rule/NewAdHocRule.java | 70 +++++++++ .../rule/internal/DefaultAdHocRule.java | 126 +++++++++++++++ .../sensor/rule/internal/package-info.java | 21 +++ .../api/batch/sensor/rule/package-info.java | 21 +++ .../main/java/org/sonar/api/rule/RuleKey.java | 12 -- .../api/server/rule/RulesDefinition.java | 8 +- .../internal/DefaultExternalIssueTest.java | 4 +- .../rule/internal/DefaultAdHocRuleTest.java | 148 ++++++++++++++++++ .../externalissue/ExternalIssueImporter.java | 4 +- .../org/sonar/scanner/issue/ModuleIssues.java | 4 +- .../sonar/scanner/mediumtest/TaskResult.java | 12 ++ .../scanner/sensor/DefaultSensorContext.java | 11 ++ .../scanner/sensor/DefaultSensorStorage.java | 19 ++- .../scanner/sensor/noop/NoOpNewAdHocRule.java | 69 ++++++++ .../sensor/noop/NoOpNewExternalIssue.java | 12 ++ .../issues/ExternalIssuesMediumTest.java | 46 +++++- .../mediumtest/issues/IssuesMediumTest.java | 2 +- .../sensor/DefaultSensorContextTest.java | 2 + .../protocol/output/FileStructure.java | 4 + .../protocol/output/ScannerReportReader.java | 8 + .../protocol/output/ScannerReportWriter.java | 9 ++ .../viewer/ScannerReportViewerApp.java | 43 +++++ .../src/main/protobuf/scanner_report.proto | 16 +- 53 files changed, 944 insertions(+), 194 deletions(-) rename plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/{OneExternalIssueWithDetailsPerLineSensor.java => OnePredefinedRuleExternalIssuePerLineSensor.java} (68%) rename server/sonar-server-common/src/main/java/org/sonar/server/rule/{ExternalRuleCreator.java => AddHocRuleCreator.java} (80%) rename server/sonar-server-common/src/main/java/org/sonar/server/rule/{NewExternalRule.java => NewAddHocRule.java} (92%) rename server/sonar-server-common/src/test/java/org/sonar/server/rule/{ExternalRuleCreatorTest.java => AddHocRuleCreatorTest.java} (90%) rename server/sonar-server/src/test/java/org/sonar/server/rule/{NewExternalRuleTest.java => NewAddHocRuleTest.java} (90%) create mode 100644 sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/AdHocRule.java create mode 100644 sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/NewAdHocRule.java create mode 100644 sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/internal/DefaultAdHocRule.java create mode 100644 sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/internal/package-info.java create mode 100644 sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/package-info.java create mode 100644 sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/rule/internal/DefaultAdHocRuleTest.java create mode 100644 sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/noop/NoOpNewAdHocRule.java diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java index 50907147b201..cc66f2d9c1d5 100644 --- a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java @@ -49,7 +49,7 @@ import org.sonar.xoo.rule.OneBugIssuePerLineSensor; import org.sonar.xoo.rule.OneDayDebtPerFileSensor; import org.sonar.xoo.rule.OneExternalIssuePerLineSensor; -import org.sonar.xoo.rule.OneExternalIssueWithDetailsPerLineSensor; +import org.sonar.xoo.rule.OnePredefinedRuleExternalIssuePerLineSensor; import org.sonar.xoo.rule.OneIssueOnDirPerFileSensor; import org.sonar.xoo.rule.OneIssuePerDirectorySensor; import org.sonar.xoo.rule.OneIssuePerFileSensor; @@ -168,7 +168,7 @@ public void define(Context context) { if (context.getSonarQubeVersion().isGreaterThanOrEqual(Version.create(7, 2))) { context.addExtensions( OneExternalIssuePerLineSensor.class, - OneExternalIssueWithDetailsPerLineSensor.class, + OnePredefinedRuleExternalIssuePerLineSensor.class, SignificantCodeSensor.class); } if (context.getSonarQubeVersion().isGreaterThanOrEqual(Version.create(7, 3))) { diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneExternalIssuePerLineSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneExternalIssuePerLineSensor.java index c8d097043711..c75a8c7a1693 100644 --- a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneExternalIssuePerLineSensor.java +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneExternalIssuePerLineSensor.java @@ -22,54 +22,57 @@ import org.sonar.api.batch.fs.FilePredicates; import org.sonar.api.batch.fs.FileSystem; import org.sonar.api.batch.fs.InputFile; -import org.sonar.api.batch.fs.InputFile.Type; import org.sonar.api.batch.rule.Severity; 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.NewExternalIssue; -import org.sonar.api.rule.RuleKey; import org.sonar.api.rules.RuleType; import org.sonar.xoo.Xoo; -import org.sonar.xoo.Xoo2; public class OneExternalIssuePerLineSensor implements Sensor { - public static final String RULE_KEY = "OneExternalIssuePerLine"; - public static final String ENGINE_KEY = "XooEngine"; + public static final String RULE_ID = "OneExternalIssuePerLine"; + public static final String ENGINE_ID = "XooEngine"; public static final String SEVERITY = "MAJOR"; public static final Long EFFORT = 10l; public static final RuleType TYPE = RuleType.BUG; - public static final String ACTIVATE_EXTERNAL_ISSUES = "sonar.oneExternalIssuePerLine.activate"; + public static final String ACTIVATE = "sonar.oneExternalIssuePerLine.activate"; + public static final String REGISTER_AD_HOC_RULE = "sonar.oneExternalIssuePerLine.adhocRule"; private static final String NAME = "One External Issue Per Line"; @Override public void describe(SensorDescriptor descriptor) { descriptor .name(NAME) - .onlyOnLanguages(Xoo.KEY, Xoo2.KEY) - .onlyWhenConfiguration(c -> c.getBoolean(ACTIVATE_EXTERNAL_ISSUES).orElse(false)); + .onlyOnLanguages(Xoo.KEY) + .onlyWhenConfiguration(c -> c.getBoolean(ACTIVATE).orElse(false)); } @Override public void execute(SensorContext context) { - analyse(context, Xoo.KEY, XooRulesDefinition.XOO_REPOSITORY); - analyse(context, Xoo2.KEY, XooRulesDefinition.XOO2_REPOSITORY); - } - - private void analyse(SensorContext context, String language, String repo) { FileSystem fs = context.fileSystem(); FilePredicates p = fs.predicates(); - for (InputFile file : fs.inputFiles(p.and(p.hasLanguages(language), p.hasType(Type.MAIN)))) { - createIssues(file, context, repo); + for (InputFile file : fs.inputFiles(p.and(p.hasLanguages(Xoo.KEY), p.hasType(InputFile.Type.MAIN)))) { + createIssues(file, context); + } + if (context.config().getBoolean(REGISTER_AD_HOC_RULE).orElse(false)) { + context.newAdHocRule() + .engineId(ENGINE_ID) + .ruleId(RULE_ID) + .name("An ad hoc rule") + .description("blah blah") + .severity(Severity.BLOCKER) + .type(RuleType.BUG) + .save(); } } - private void createIssues(InputFile file, SensorContext context, String repo) { - RuleKey ruleKey = RuleKey.of(repo, RULE_KEY); + private static void createIssues(InputFile file, SensorContext context) { for (int line = 1; line <= file.lines(); line++) { NewExternalIssue newIssue = context.newExternalIssue(); newIssue - .forRule(ruleKey) + .engineId(ENGINE_ID) + .ruleId(RULE_ID) .at(newIssue.newLocation() .on(file) .at(file.selectLine(line)) diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneExternalIssueWithDetailsPerLineSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OnePredefinedRuleExternalIssuePerLineSensor.java similarity index 68% rename from plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneExternalIssueWithDetailsPerLineSensor.java rename to plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OnePredefinedRuleExternalIssuePerLineSensor.java index 0598b7a8d40d..779674808cc7 100644 --- a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneExternalIssueWithDetailsPerLineSensor.java +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OnePredefinedRuleExternalIssuePerLineSensor.java @@ -28,52 +28,45 @@ import org.sonar.api.batch.sensor.SensorContext; import org.sonar.api.batch.sensor.SensorDescriptor; import org.sonar.api.batch.sensor.issue.NewExternalIssue; -import org.sonar.api.rule.RuleKey; import org.sonar.api.rules.RuleType; import org.sonar.xoo.Xoo; -import org.sonar.xoo.Xoo2; -public class OneExternalIssueWithDetailsPerLineSensor implements Sensor { - public static final String RULE_KEY = "OneExternalIssueWithDetailsPerLine"; - public static final String ENGINE_KEY = "XooEngine"; +public class OnePredefinedRuleExternalIssuePerLineSensor implements Sensor { + public static final String RULE_ID = "OnePredefinedRuleExternalIssuePerLine"; + public static final String ENGINE_ID = "XooEngine"; public static final String SEVERITY = "MAJOR"; public static final Long EFFORT = 10l; public static final RuleType TYPE = RuleType.BUG; - public static final String ACTIVATE_EXTERNAL_ISSUES = "sonar.oneExternalIssueWithDetailsPerLine.activate"; - private static final String NAME = "One External Issue Per Line"; + public static final String ACTIVATE = "sonar.onePredefinedRuleExternalIssuePerLine.activate"; + private static final String NAME = "One External Issue Per Line With A Predefined Rule"; @Override public void describe(SensorDescriptor descriptor) { descriptor .name(NAME) - .onlyOnLanguages(Xoo.KEY, Xoo2.KEY) - .onlyWhenConfiguration(c -> c.getBoolean(ACTIVATE_EXTERNAL_ISSUES).orElse(false)); + .onlyOnLanguages(Xoo.KEY) + .onlyWhenConfiguration(c -> c.getBoolean(ACTIVATE).orElse(false)); } @Override public void execute(SensorContext context) { - analyse(context, Xoo.KEY, XooRulesDefinition.XOO_REPOSITORY); - analyse(context, Xoo2.KEY, XooRulesDefinition.XOO2_REPOSITORY); - } - - private void analyse(SensorContext context, String language, String repo) { FileSystem fs = context.fileSystem(); FilePredicates p = fs.predicates(); - for (InputFile file : fs.inputFiles(p.and(p.hasLanguages(language), p.hasType(Type.MAIN)))) { - createIssues(file, context, repo); + for (InputFile file : fs.inputFiles(p.and(p.hasLanguages(Xoo.KEY), p.hasType(Type.MAIN)))) { + createIssues(file, context); } } - private void createIssues(InputFile file, SensorContext context, String repo) { - RuleKey ruleKey = RuleKey.of(repo, RULE_KEY); + private static void createIssues(InputFile file, SensorContext context) { for (int line = 1; line <= file.lines(); line++) { NewExternalIssue newIssue = context.newExternalIssue(); newIssue - .forRule(ruleKey) + .engineId(ENGINE_ID) + .ruleId(RULE_ID) .at(newIssue.newLocation() .on(file) .at(file.selectLine(line)) - .message("This issue is generated on each line and the rule contains details")) + .message("This issue is generated on each line and the rule is predefined")) .severity(Severity.valueOf(SEVERITY)) .remediationEffortMinutes(EFFORT) .type(TYPE) diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooRulesDefinition.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooRulesDefinition.java index 8991b04bc28a..aee1ba3c3fa2 100644 --- a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooRulesDefinition.java +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooRulesDefinition.java @@ -38,7 +38,6 @@ public class XooRulesDefinition implements RulesDefinition { public static final String XOO_REPOSITORY = "xoo"; public static final String XOO2_REPOSITORY = "xoo2"; - public static final String XOO_EXTERNAL_REPOSITORY = "xoo"; private static final String TEN_MIN = "10min"; @@ -183,11 +182,11 @@ private void defineRulesXoo(Context context) { } private static void defineRulesXooExternal(Context context) { - NewRepository repo = context.createExternalRepository(XOO_EXTERNAL_REPOSITORY, Xoo.KEY).setName("XooExternal"); + NewRepository repo = context.createExternalRepository(OneExternalIssuePerLineSensor.ENGINE_ID, Xoo.KEY).setName(OneExternalIssuePerLineSensor.ENGINE_ID); - repo.createRule(OneExternalIssueWithDetailsPerLineSensor.RULE_KEY) - .setSeverity(OneExternalIssueWithDetailsPerLineSensor.SEVERITY) - .setType(OneExternalIssueWithDetailsPerLineSensor.TYPE) + repo.createRule(OnePredefinedRuleExternalIssuePerLineSensor.RULE_ID) + .setSeverity(OnePredefinedRuleExternalIssuePerLineSensor.SEVERITY) + .setType(OnePredefinedRuleExternalIssuePerLineSensor.TYPE) .setScope(RuleScope.ALL) .setHtmlDescription("Generates one external issue in each line") .setName("One external issue per line"); diff --git a/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/XooRulesDefinitionTest.java b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/XooRulesDefinitionTest.java index 0ded44031fca..d5df1600b567 100644 --- a/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/XooRulesDefinitionTest.java +++ b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/XooRulesDefinitionTest.java @@ -21,7 +21,6 @@ import org.junit.Before; import org.junit.Test; -import org.sonar.api.SonarProduct; import org.sonar.api.SonarQubeSide; import org.sonar.api.internal.SonarRuntimeImpl; import org.sonar.api.server.debt.DebtRemediationFunction; @@ -73,9 +72,9 @@ public void define_xoo_hotspot_rule() { @Test public void define_xooExternal_rules() { - RulesDefinition.Repository repo = context.repository("external_xoo"); + RulesDefinition.Repository repo = context.repository("external_XooEngine"); assertThat(repo).isNotNull(); - assertThat(repo.name()).isEqualTo("XooExternal"); + assertThat(repo.name()).isEqualTo("XooEngine"); assertThat(repo.language()).isEqualTo("xoo"); assertThat(repo.rules()).hasSize(1); } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleRepository.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleRepository.java index 123dc51d93c2..a780920f7339 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleRepository.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleRepository.java @@ -23,7 +23,7 @@ import java.util.function.Supplier; import org.sonar.api.rule.RuleKey; import org.sonar.db.DbSession; -import org.sonar.server.rule.NewExternalRule; +import org.sonar.server.rule.NewAddHocRule; /** * Repository of every rule in DB (including manual rules) whichever their status. @@ -48,8 +48,8 @@ public interface RuleRepository { Optional findById(int id); - void insertNewExternalRuleIfAbsent(RuleKey ruleKey, Supplier ruleSupplier); + void addNewAddHocRuleIfAbsent(RuleKey ruleKey, Supplier ruleSupplier); - void persistNewExternalRules(DbSession dbSession); + void persistNewAddHocRules(DbSession dbSession); } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleRepositoryImpl.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleRepositoryImpl.java index 65be96fdfa7a..7a99e846c1c3 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleRepositoryImpl.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleRepositoryImpl.java @@ -37,8 +37,8 @@ import org.sonar.db.rule.DeprecatedRuleKeyDto; import org.sonar.db.rule.RuleDto; import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder; -import org.sonar.server.rule.ExternalRuleCreator; -import org.sonar.server.rule.NewExternalRule; +import org.sonar.server.rule.AddHocRuleCreator; +import org.sonar.server.rule.NewAddHocRule; import static com.google.common.base.Preconditions.checkArgument; import static java.util.Objects.requireNonNull; @@ -50,34 +50,34 @@ public class RuleRepositoryImpl implements RuleRepository { @CheckForNull private Map rulesById; - private final ExternalRuleCreator creator; + private final AddHocRuleCreator creator; private final DbClient dbClient; private final AnalysisMetadataHolder analysisMetadataHolder; - public RuleRepositoryImpl(ExternalRuleCreator creator, DbClient dbClient, AnalysisMetadataHolder analysisMetadataHolder) { + public RuleRepositoryImpl(AddHocRuleCreator creator, DbClient dbClient, AnalysisMetadataHolder analysisMetadataHolder) { this.creator = creator; this.dbClient = dbClient; this.analysisMetadataHolder = analysisMetadataHolder; } - public void insertNewExternalRuleIfAbsent(RuleKey ruleKey, Supplier ruleSupplier) { + public void addNewAddHocRuleIfAbsent(RuleKey ruleKey, Supplier ruleSupplier) { ensureInitialized(); if (!rulesByKey.containsKey(ruleKey)) { - rulesByKey.computeIfAbsent(ruleKey, s -> new ExternalRuleWrapper(ruleSupplier.get())); + rulesByKey.computeIfAbsent(ruleKey, s -> new AdHocRuleWrapper(ruleSupplier.get())); } } @Override - public void persistNewExternalRules(DbSession dbSession) { + public void persistNewAddHocRules(DbSession dbSession) { ensureInitialized(); rulesByKey.values().stream() - .filter(ExternalRuleWrapper.class::isInstance) - .forEach(extRule -> persistAndIndex(dbSession, (ExternalRuleWrapper) extRule)); + .filter(AdHocRuleWrapper.class::isInstance) + .forEach(r -> persistAndIndex(dbSession, (AdHocRuleWrapper) r)); } - private void persistAndIndex(DbSession dbSession, ExternalRuleWrapper external) { + private void persistAndIndex(DbSession dbSession, AdHocRuleWrapper external) { Rule rule = new RuleImpl(creator.persistAndIndex(dbSession, external.getDelegate())); rulesById.put(rule.getId(), rule); rulesByKey.put(external.getKey(), rule); @@ -145,15 +145,15 @@ private void loadRulesFromDb(DbSession dbSession) { } } - private static class ExternalRuleWrapper implements Rule { - private final NewExternalRule externalRule; + private static class AdHocRuleWrapper implements Rule { + private final NewAddHocRule addHocRule; - private ExternalRuleWrapper(NewExternalRule externalRule) { - this.externalRule = externalRule; + private AdHocRuleWrapper(NewAddHocRule addHocRule) { + this.addHocRule = addHocRule; } - public NewExternalRule getDelegate() { - return externalRule; + public NewAddHocRule getDelegate() { + return addHocRule; } @Override @@ -163,12 +163,12 @@ public int getId() { @Override public RuleKey getKey() { - return externalRule.getKey(); + return addHocRule.getKey(); } @Override public String getName() { - return externalRule.getName(); + return addHocRule.getName(); } @Override @@ -201,7 +201,7 @@ public DebtRemediationFunction getRemediationFunction() { @CheckForNull @Override public String getPluginKey() { - return externalRule.getPluginKey(); + return addHocRule.getPluginKey(); } } } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactory.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactory.java index 58801ef4de61..767627d6a796 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactory.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactory.java @@ -40,14 +40,13 @@ import org.sonar.scanner.protocol.output.ScannerReport; import org.sonar.scanner.protocol.output.ScannerReport.IssueType; import org.sonar.ce.task.projectanalysis.batch.BatchReportReader; -import org.sonar.ce.task.projectanalysis.component.Component; import org.sonar.ce.task.projectanalysis.component.TreeRootHolder; import org.sonar.ce.task.projectanalysis.issue.commonrule.CommonRuleEngine; import org.sonar.ce.task.projectanalysis.issue.filter.IssueFilter; import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRulesHolder; import org.sonar.ce.task.projectanalysis.source.SourceLinesHashRepository; import org.sonar.server.rule.CommonRuleKeys; -import org.sonar.server.rule.NewExternalRule; +import org.sonar.server.rule.NewAddHocRule; import static org.apache.commons.lang.StringUtils.isNotEmpty; @@ -182,30 +181,30 @@ private DefaultIssue toIssue(LineHashSequence lineHashSeq, ScannerReport.Issue r return issue; } - private DefaultIssue toExternalIssue(LineHashSequence lineHashSeq, ScannerReport.ExternalIssue reportIssue) { + private DefaultIssue toExternalIssue(LineHashSequence lineHashSeq, ScannerReport.ExternalIssue reportExternalIssue) { DefaultIssue issue = new DefaultIssue(); init(issue); - issue.setRuleKey(RuleKey.of(RuleKey.EXTERNAL_RULE_REPO_PREFIX + reportIssue.getRuleRepository(), reportIssue.getRuleKey())); - if (reportIssue.hasTextRange()) { - int startLine = reportIssue.getTextRange().getStartLine(); + issue.setRuleKey(RuleKey.of(RuleKey.EXTERNAL_RULE_REPO_PREFIX + reportExternalIssue.getEngineId(), reportExternalIssue.getRuleId())); + if (reportExternalIssue.hasTextRange()) { + int startLine = reportExternalIssue.getTextRange().getStartLine(); issue.setLine(startLine); issue.setChecksum(lineHashSeq.getHashForLine(startLine)); } else { issue.setChecksum(""); } - if (isNotEmpty(reportIssue.getMsg())) { - issue.setMessage(reportIssue.getMsg()); + if (isNotEmpty(reportExternalIssue.getMsg())) { + issue.setMessage(reportExternalIssue.getMsg()); } - if (reportIssue.getSeverity() != Severity.UNSET_SEVERITY) { - issue.setSeverity(reportIssue.getSeverity().name()); + if (reportExternalIssue.getSeverity() != Severity.UNSET_SEVERITY) { + issue.setSeverity(reportExternalIssue.getSeverity().name()); } - issue.setEffort(Duration.create(reportIssue.getEffort() != 0 ? reportIssue.getEffort() : DEFAULT_EXTERNAL_ISSUE_EFFORT)); + issue.setEffort(Duration.create(reportExternalIssue.getEffort() != 0 ? reportExternalIssue.getEffort() : DEFAULT_EXTERNAL_ISSUE_EFFORT)); DbIssues.Locations.Builder dbLocationsBuilder = DbIssues.Locations.newBuilder(); - if (reportIssue.hasTextRange()) { - dbLocationsBuilder.setTextRange(convertTextRange(reportIssue.getTextRange())); + if (reportExternalIssue.hasTextRange()) { + dbLocationsBuilder.setTextRange(convertTextRange(reportExternalIssue.getTextRange())); } - for (ScannerReport.Flow flow : reportIssue.getFlowList()) { + for (ScannerReport.Flow flow : reportExternalIssue.getFlowList()) { if (flow.getLocationCount() > 0) { DbIssues.Flow.Builder dbFlowBuilder = DbIssues.Flow.newBuilder(); for (ScannerReport.IssueLocation location : flow.getLocationList()) { @@ -216,16 +215,16 @@ private DefaultIssue toExternalIssue(LineHashSequence lineHashSeq, ScannerReport } issue.setIsFromExternalRuleEngine(true); issue.setLocations(dbLocationsBuilder.build()); - issue.setType(toRuleType(reportIssue.getType())); + issue.setType(toRuleType(reportExternalIssue.getType())); - ruleRepository.insertNewExternalRuleIfAbsent(issue.getRuleKey(), () -> toExternalRule(reportIssue)); + ruleRepository.addNewAddHocRuleIfAbsent(issue.getRuleKey(), () -> toAdHocRule(reportExternalIssue)); return issue; } - private NewExternalRule toExternalRule(ScannerReport.ExternalIssue reportIssue) { - NewExternalRule.Builder builder = new NewExternalRule.Builder() - .setName(RuleKey.of(reportIssue.getRuleRepository(), reportIssue.getRuleKey()).toString()) - .setKey(RuleKey.of(RuleKey.EXTERNAL_RULE_REPO_PREFIX + reportIssue.getRuleRepository(), reportIssue.getRuleKey())); + private NewAddHocRule toAdHocRule(ScannerReport.ExternalIssue reportIssue) { + NewAddHocRule.Builder builder = new NewAddHocRule.Builder() + .setName(reportIssue.getEngineId() + " " + reportIssue.getRuleId()) + .setKey(RuleKey.of(RuleKey.EXTERNAL_RULE_REPO_PREFIX + reportIssue.getEngineId(), reportIssue.getRuleId())); return builder.build(); } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistExternalRulesStep.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistExternalRulesStep.java index 67baee66f5c8..28c871d75faf 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistExternalRulesStep.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistExternalRulesStep.java @@ -37,7 +37,7 @@ public PersistExternalRulesStep(DbClient dbClient, RuleRepository ruleRepository @Override public void execute(ComputationStep.Context context) { try (DbSession dbSession = dbClient.openSession(false)) { - ruleRepository.persistNewExternalRules(dbSession); + ruleRepository.persistNewAddHocRules(dbSession); } } diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/RuleRepositoryImplTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/RuleRepositoryImplTest.java index 623c082f094e..c359b6919d12 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/RuleRepositoryImplTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/RuleRepositoryImplTest.java @@ -37,8 +37,8 @@ import org.sonar.db.rule.RuleDao; import org.sonar.db.rule.RuleDefinitionDto; import org.sonar.db.rule.RuleDto; -import org.sonar.server.rule.ExternalRuleCreator; -import org.sonar.server.rule.NewExternalRule; +import org.sonar.server.rule.AddHocRuleCreator; +import org.sonar.server.rule.NewAddHocRule; import org.sonar.server.rule.index.RuleIndexer; import static org.assertj.core.api.Assertions.assertThat; @@ -78,8 +78,8 @@ public class RuleRepositoryImplTest { private RuleDao ruleDao = mock(RuleDao.class); private RuleIndexer ruleIndexer = mock(RuleIndexer.class); - private ExternalRuleCreator externalRuleCreator = new ExternalRuleCreator(db.getDbClient(), System2.INSTANCE, ruleIndexer); - private RuleRepositoryImpl underTest = new RuleRepositoryImpl(externalRuleCreator, dbClient, analysisMetadataHolder); + private AddHocRuleCreator addHocRuleCreator = new AddHocRuleCreator(db.getDbClient(), System2.INSTANCE, ruleIndexer); + private RuleRepositoryImpl underTest = new RuleRepositoryImpl(addHocRuleCreator, dbClient, analysisMetadataHolder); @Before public void setUp() throws Exception { @@ -270,7 +270,7 @@ public void findById_returns_Rule_if_it_exists_in_DB() { public void accept_new_externally_defined_Rules() { RuleKey ruleKey = RuleKey.of("eslint", "no-cond-assign"); - underTest.insertNewExternalRuleIfAbsent(ruleKey, () -> new NewExternalRule.Builder() + underTest.addNewAddHocRuleIfAbsent(ruleKey, () -> new NewAddHocRule.Builder() .setKey(ruleKey) .setPluginKey("eslint") .build()); @@ -286,15 +286,15 @@ public void accept_new_externally_defined_Rules() { @Test public void persist_new_externally_defined_Rules() { - underTest = new RuleRepositoryImpl(externalRuleCreator, db.getDbClient(), analysisMetadataHolder); + underTest = new RuleRepositoryImpl(addHocRuleCreator, db.getDbClient(), analysisMetadataHolder); RuleKey ruleKey = RuleKey.of("eslint", "no-cond-assign"); - underTest.insertNewExternalRuleIfAbsent(ruleKey, () -> new NewExternalRule.Builder() + underTest.addNewAddHocRuleIfAbsent(ruleKey, () -> new NewAddHocRule.Builder() .setKey(ruleKey) .setPluginKey("eslint") .build()); - underTest.persistNewExternalRules(db.getSession()); + underTest.persistNewAddHocRules(db.getSession()); db.commit(); Optional ruleDefinitionDto = db.getDbClient().ruleDao().selectDefinitionByKey(db.getSession(), ruleKey); diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/RuleRepositoryRule.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/RuleRepositoryRule.java index 5044fcd2c77b..ee875f212870 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/RuleRepositoryRule.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/RuleRepositoryRule.java @@ -26,7 +26,7 @@ import org.junit.rules.ExternalResource; import org.sonar.api.rule.RuleKey; import org.sonar.db.DbSession; -import org.sonar.server.rule.NewExternalRule; +import org.sonar.server.rule.NewAddHocRule; import static com.google.common.base.Preconditions.checkArgument; import static java.util.Objects.requireNonNull; @@ -35,7 +35,7 @@ public class RuleRepositoryRule extends ExternalResource implements RuleReposito private final Map rulesByKey = new HashMap<>(); private final Map rulesById = new HashMap<>(); - private final Map newExternalRulesById = new HashMap<>(); + private final Map newExternalRulesById = new HashMap<>(); @Override protected void after() { @@ -68,7 +68,7 @@ public Optional findById(int id) { } @Override - public void persistNewExternalRules(DbSession dbSession) { + public void persistNewAddHocRules(DbSession dbSession) { throw new UnsupportedOperationException(); } @@ -87,7 +87,7 @@ public RuleRepositoryRule add(DumbRule rule) { } @Override - public void insertNewExternalRuleIfAbsent(RuleKey ruleKey, Supplier ruleSupplier) { + public void addNewAddHocRuleIfAbsent(RuleKey ruleKey, Supplier ruleSupplier) { newExternalRulesById.computeIfAbsent(ruleKey, k -> ruleSupplier.get()); } diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactoryTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactoryTest.java index c977257a5435..390f6d8035f6 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactoryTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactoryTest.java @@ -191,8 +191,8 @@ public void load_external_issues_from_report() { ScannerReport.ExternalIssue reportIssue = ScannerReport.ExternalIssue.newBuilder() .setTextRange(TextRange.newBuilder().setStartLine(2).build()) .setMsg("the message") - .setRuleRepository("eslint") - .setRuleKey("S001") + .setEngineId("eslint") + .setRuleId("S001") .setSeverity(Constants.Severity.BLOCKER) .setEffort(20l) .setType(ScannerReport.IssueType.SECURITY_HOTSPOT) @@ -224,8 +224,8 @@ public void load_external_issues_from_report_with_default_effort() { ScannerReport.ExternalIssue reportIssue = ScannerReport.ExternalIssue.newBuilder() .setTextRange(TextRange.newBuilder().setStartLine(2).build()) .setMsg("the message") - .setRuleRepository("eslint") - .setRuleKey("S001") + .setEngineId("eslint") + .setRuleId("S001") .setSeverity(Constants.Severity.BLOCKER) .setType(ScannerReport.IssueType.BUG) .build(); diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistExternalRulesStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistExternalRulesStepTest.java index 87d44ddb3430..2832628b1939 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistExternalRulesStepTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistExternalRulesStepTest.java @@ -34,8 +34,8 @@ import org.sonar.db.rule.RuleDao; import org.sonar.db.rule.RuleDefinitionDto; import org.sonar.server.es.EsTester; -import org.sonar.server.rule.ExternalRuleCreator; -import org.sonar.server.rule.NewExternalRule; +import org.sonar.server.rule.AddHocRuleCreator; +import org.sonar.server.rule.NewAddHocRule; import org.sonar.server.rule.index.RuleIndexDefinition; import org.sonar.server.rule.index.RuleIndexer; @@ -59,7 +59,7 @@ public class PersistExternalRulesStepTest extends BaseStepTest { public EsTester es = EsTester.create(); private RuleIndexer indexer = new RuleIndexer(es.client(), dbClient); - private ExternalRuleCreator externalRuleCreator = new ExternalRuleCreator(dbClient, System2.INSTANCE, indexer); + private AddHocRuleCreator addHocRuleCreator = new AddHocRuleCreator(dbClient, System2.INSTANCE, indexer); @Override protected ComputationStep step() { @@ -68,7 +68,7 @@ protected ComputationStep step() { @Before public void setup() { - ruleRepository = new RuleRepositoryImpl(externalRuleCreator, dbClient, analysisMetadataHolder); + ruleRepository = new RuleRepositoryImpl(addHocRuleCreator, dbClient, analysisMetadataHolder); underTest = new PersistExternalRulesStep(dbClient, ruleRepository); } @@ -76,7 +76,7 @@ public void setup() { public void persist_and_index_new_external_rules() { RuleKey ruleKey = RuleKey.of("eslint", "no-cond-assign"); - ruleRepository.insertNewExternalRuleIfAbsent(ruleKey, () -> new NewExternalRule.Builder() + ruleRepository.addNewAddHocRuleIfAbsent(ruleKey, () -> new NewAddHocRule.Builder() .setKey(ruleKey) .setPluginKey("eslint") .setName("eslint:no-cond-assign") @@ -105,7 +105,7 @@ public void persist_and_index_new_external_rules() { public void do_not_persist_existing_external_rules() { RuleKey ruleKey = RuleKey.of("eslint", "no-cond-assign"); db.rules().insert(ruleKey, r -> r.setIsExternal(true)); - ruleRepository.insertNewExternalRuleIfAbsent(ruleKey, () -> new NewExternalRule.Builder() + ruleRepository.addNewAddHocRuleIfAbsent(ruleKey, () -> new NewAddHocRule.Builder() .setKey(ruleKey) .setPluginKey("eslint") .build()); diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistIssuesStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistIssuesStepTest.java index a5a7552b0ac3..e5cdb6f6a12c 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistIssuesStepTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistIssuesStepTest.java @@ -53,7 +53,7 @@ import org.sonar.db.rule.RuleTesting; import org.sonar.scanner.protocol.output.ScannerReport; import org.sonar.server.issue.IssueStorage; -import org.sonar.server.rule.ExternalRuleCreator; +import org.sonar.server.rule.AddHocRuleCreator; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; @@ -85,7 +85,7 @@ public class PersistIssuesStepTest extends BaseStepTest { private IssueCache issueCache; private ComputationStep underTest; - private ExternalRuleCreator externalRuleCreator = mock(ExternalRuleCreator.class); + private AddHocRuleCreator addHocRuleCreator = mock(AddHocRuleCreator.class); @Override protected ComputationStep step() { @@ -99,7 +99,7 @@ public void setup() throws Exception { when(system2.now()).thenReturn(NOW); reportReader.setMetadata(ScannerReport.Metadata.getDefaultInstance()); - underTest = new PersistIssuesStep(dbClient, system2, new UpdateConflictResolver(), new RuleRepositoryImpl(externalRuleCreator, dbClient, analysisMetadataHolder), issueCache, + underTest = new PersistIssuesStep(dbClient, system2, new UpdateConflictResolver(), new RuleRepositoryImpl(addHocRuleCreator, dbClient, analysisMetadataHolder), issueCache, new IssueStorage()); } diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java index 6a70c5d6b796..dacec29f0b0e 100644 --- a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java +++ b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java @@ -141,7 +141,7 @@ import org.sonar.server.qualitygate.QualityGateFinder; import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; import org.sonar.server.rule.DefaultRuleFinder; -import org.sonar.server.rule.ExternalRuleCreator; +import org.sonar.server.rule.AddHocRuleCreator; import org.sonar.server.rule.index.RuleIndex; import org.sonar.server.rule.index.RuleIndexer; import org.sonar.server.setting.DatabaseSettingLoader; @@ -370,7 +370,7 @@ private static void populateLevel4(ComponentContainer container, Props props) { XMLRuleParser.class, DefaultRuleFinder.class, RulesDefinitionXmlLoader.class, - ExternalRuleCreator.class, + AddHocRuleCreator.class, RuleIndexer.class, // languages diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/rule/ExternalRuleCreator.java b/server/sonar-server-common/src/main/java/org/sonar/server/rule/AddHocRuleCreator.java similarity index 80% rename from server/sonar-server-common/src/main/java/org/sonar/server/rule/ExternalRuleCreator.java rename to server/sonar-server-common/src/main/java/org/sonar/server/rule/AddHocRuleCreator.java index 9044ebff3a18..a2f938f9e7a4 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/rule/ExternalRuleCreator.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/rule/AddHocRuleCreator.java @@ -30,35 +30,36 @@ import static org.sonar.api.rule.RuleStatus.READY; import static org.sonar.db.rule.RuleDto.Scope.ALL; -public class ExternalRuleCreator { +public class AddHocRuleCreator { private final DbClient dbClient; private final System2 system2; private final RuleIndexer ruleIndexer; - public ExternalRuleCreator(DbClient dbClient, System2 system2, RuleIndexer ruleIndexer) { + public AddHocRuleCreator(DbClient dbClient, System2 system2, RuleIndexer ruleIndexer) { this.dbClient = dbClient; this.system2 = system2; this.ruleIndexer = ruleIndexer; } /** - * Persists a rule in the DB and indexes it. + * Persists a new add hoc rule in the DB and indexes it. * @return the rule that was inserted in the DB, which includes the generated ID. */ - public RuleDto persistAndIndex(DbSession dbSession, NewExternalRule external) { + public RuleDto persistAndIndex(DbSession dbSession, NewAddHocRule adHoc) { RuleDao dao = dbClient.ruleDao(); dao.insert(dbSession, new RuleDefinitionDto() - .setRuleKey(external.getKey()) - .setPluginKey(external.getPluginKey()) + .setRuleKey(adHoc.getKey()) + .setPluginKey(adHoc.getPluginKey()) .setIsExternal(true) - .setName(external.getName()) + .setName(adHoc.getName()) + .setIsAdHoc(true) .setScope(ALL) .setStatus(READY) .setCreatedAt(system2.now()) .setUpdatedAt(system2.now())); - RuleDto ruleDto = dao.selectOrFailByKey(dbSession, external.getKey()); + RuleDto ruleDto = dao.selectOrFailByKey(dbSession, adHoc.getKey()); ruleIndexer.commitAndIndex(dbSession, ruleDto.getId()); return ruleDto; } diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/rule/NewExternalRule.java b/server/sonar-server-common/src/main/java/org/sonar/server/rule/NewAddHocRule.java similarity index 92% rename from server/sonar-server-common/src/main/java/org/sonar/server/rule/NewExternalRule.java rename to server/sonar-server-common/src/main/java/org/sonar/server/rule/NewAddHocRule.java index a968a43d7ac6..c84896b77915 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/rule/NewExternalRule.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/rule/NewAddHocRule.java @@ -24,12 +24,12 @@ import org.sonar.api.rule.RuleKey; @Immutable -public class NewExternalRule { +public class NewAddHocRule { private final RuleKey key; private final String name; private final String pluginKey; - private NewExternalRule(Builder builder) { + private NewAddHocRule(Builder builder) { Objects.requireNonNull(builder.key, "'key' not expected to be null for an external rule"); this.key = builder.key; this.pluginKey = builder.pluginKey; @@ -67,8 +67,8 @@ public String name() { return name; } - public NewExternalRule build() { - return new NewExternalRule(this); + public NewAddHocRule build() { + return new NewAddHocRule(this); } public Builder setPluginKey(String pluginKey) { diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/rule/ExternalRuleCreatorTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/rule/AddHocRuleCreatorTest.java similarity index 90% rename from server/sonar-server-common/src/test/java/org/sonar/server/rule/ExternalRuleCreatorTest.java rename to server/sonar-server-common/src/test/java/org/sonar/server/rule/AddHocRuleCreatorTest.java index 59d4d28fcfef..db5269e1b4bf 100644 --- a/server/sonar-server-common/src/test/java/org/sonar/server/rule/ExternalRuleCreatorTest.java +++ b/server/sonar-server-common/src/test/java/org/sonar/server/rule/AddHocRuleCreatorTest.java @@ -30,7 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat; -public class ExternalRuleCreatorTest { +public class AddHocRuleCreatorTest { @org.junit.Rule public DbTester dbTester = DbTester.create(System2.INSTANCE); @@ -38,13 +38,13 @@ public class ExternalRuleCreatorTest { public EsTester es = EsTester.create(); private RuleIndexer indexer = new RuleIndexer(es.client(), dbTester.getDbClient()); - private ExternalRuleCreator underTest = new ExternalRuleCreator(dbTester.getDbClient(), System2.INSTANCE, indexer); + private AddHocRuleCreator underTest = new AddHocRuleCreator(dbTester.getDbClient(), System2.INSTANCE, indexer); private DbSession dbSession = dbTester.getSession(); @Test public void create_external_rule() { RuleKey ruleKey = RuleKey.of("eslint", "no-cond-assign"); - NewExternalRule externalRule = new NewExternalRule.Builder() + NewAddHocRule externalRule = new NewAddHocRule.Builder() .setKey(ruleKey) .setPluginKey("eslint") .setName("name") diff --git a/server/sonar-server/src/test/java/org/sonar/server/rule/NewExternalRuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/rule/NewAddHocRuleTest.java similarity index 90% rename from server/sonar-server/src/test/java/org/sonar/server/rule/NewExternalRuleTest.java rename to server/sonar-server/src/test/java/org/sonar/server/rule/NewAddHocRuleTest.java index 85816da9e6b8..11917668f11b 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/rule/NewExternalRuleTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/rule/NewAddHocRuleTest.java @@ -25,20 +25,20 @@ import static org.assertj.core.api.Assertions.assertThat; -public class NewExternalRuleTest { +public class NewAddHocRuleTest { @org.junit.Rule public ExpectedException exception = ExpectedException.none(); @Test public void should_build_new_external_rule() { - NewExternalRule.Builder builder = new NewExternalRule.Builder() + NewAddHocRule.Builder builder = new NewAddHocRule.Builder() .setKey(RuleKey.of("repo", "rule")) .setPluginKey("repo") .setName("name"); assertThat(builder.name()).isEqualTo("name"); - NewExternalRule rule = builder.build(); + NewAddHocRule rule = builder.build(); assertThat(rule.getName()).isEqualTo("name"); assertThat(rule.getPluginKey()).isEqualTo("repo"); @@ -49,7 +49,7 @@ public void fail_if_rule_key_is_not_set() { exception.expect(NullPointerException.class); exception.expectMessage("'key' not expected to be null for an external rule"); - new NewExternalRule.Builder() + new NewAddHocRule.Builder() .build(); } } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java index ed4ffe22b3c9..00dae1fdd651 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java @@ -20,7 +20,6 @@ package org.sonar.api.batch.sensor; import java.io.Serializable; - import org.sonar.api.SonarRuntime; import org.sonar.api.batch.fs.FileSystem; import org.sonar.api.batch.fs.InputFile; @@ -38,6 +37,8 @@ import org.sonar.api.batch.sensor.issue.NewIssue; import org.sonar.api.batch.sensor.measure.Measure; import org.sonar.api.batch.sensor.measure.NewMeasure; +import org.sonar.api.batch.sensor.rule.AdHocRule; +import org.sonar.api.batch.sensor.rule.NewAdHocRule; import org.sonar.api.batch.sensor.symbol.NewSymbolTable; import org.sonar.api.config.Configuration; import org.sonar.api.config.Settings; @@ -122,6 +123,12 @@ public interface SensorContext { */ NewExternalIssue newExternalIssue(); + /** + * Fluent builder to create a new {@link AdHocRule}. Don't forget to call {@link NewAdHocRule#save()} once all parameters are provided. + * @since 7.4 + */ + NewAdHocRule newAdHocRule(); + // ------------ HIGHLIGHTING ------------ /** diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/InMemorySensorStorage.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/InMemorySensorStorage.java index e46b81218300..681005b5ff73 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/InMemorySensorStorage.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/InMemorySensorStorage.java @@ -34,7 +34,10 @@ import org.sonar.api.batch.sensor.highlighting.internal.DefaultHighlighting; import org.sonar.api.batch.sensor.issue.ExternalIssue; import org.sonar.api.batch.sensor.issue.Issue; +import org.sonar.api.batch.sensor.issue.internal.DefaultExternalIssue; import org.sonar.api.batch.sensor.measure.Measure; +import org.sonar.api.batch.sensor.rule.AdHocRule; +import org.sonar.api.batch.sensor.rule.internal.DefaultAdHocRule; import org.sonar.api.batch.sensor.symbol.internal.DefaultSymbolTable; import static com.google.common.base.Preconditions.checkArgument; @@ -45,6 +48,7 @@ class InMemorySensorStorage implements SensorStorage { Collection allIssues = new ArrayList<>(); Collection allExternalIssues = new ArrayList<>(); + Collection allAdHocRules = new ArrayList<>(); Collection allAnalysisErrors = new ArrayList<>(); Map highlightingByComponent = new HashMap<>(); @@ -70,6 +74,11 @@ public void store(Issue issue) { allIssues.add(issue); } + @Override + public void store(DefaultAdHocRule adHocRule) { + allAdHocRules.add(adHocRule); + } + @Override public void store(DefaultHighlighting highlighting) { String fileKey = highlighting.inputFile().key(); @@ -119,7 +128,7 @@ public void storeProperty(String key, String value) { } @Override - public void store(ExternalIssue issue) { + public void store(DefaultExternalIssue issue) { allExternalIssues.add(issue); } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java index 3559ebdb2582..8549c5943c54 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java @@ -70,6 +70,9 @@ import org.sonar.api.batch.sensor.measure.Measure; import org.sonar.api.batch.sensor.measure.NewMeasure; import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure; +import org.sonar.api.batch.sensor.rule.AdHocRule; +import org.sonar.api.batch.sensor.rule.NewAdHocRule; +import org.sonar.api.batch.sensor.rule.internal.DefaultAdHocRule; import org.sonar.api.batch.sensor.symbol.NewSymbolTable; import org.sonar.api.batch.sensor.symbol.internal.DefaultSymbolTable; import org.sonar.api.config.Configuration; @@ -229,10 +232,19 @@ public NewExternalIssue newExternalIssue() { return new DefaultExternalIssue(sensorStorage); } + @Override + public NewAdHocRule newAdHocRule() { + return new DefaultAdHocRule(sensorStorage); + } + public Collection allExternalIssues() { return sensorStorage.allExternalIssues; } + public Collection allAdHocRules() { + return sensorStorage.allAdHocRules; + } + public Collection allAnalysisErrors() { return sensorStorage.allAnalysisErrors; } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorStorage.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorStorage.java index ede3c9e99105..69177b4d9240 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorStorage.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorStorage.java @@ -25,9 +25,10 @@ import org.sonar.api.batch.sensor.cpd.internal.DefaultCpdTokens; import org.sonar.api.batch.sensor.error.AnalysisError; import org.sonar.api.batch.sensor.highlighting.internal.DefaultHighlighting; -import org.sonar.api.batch.sensor.issue.ExternalIssue; import org.sonar.api.batch.sensor.issue.Issue; +import org.sonar.api.batch.sensor.issue.internal.DefaultExternalIssue; import org.sonar.api.batch.sensor.measure.Measure; +import org.sonar.api.batch.sensor.rule.internal.DefaultAdHocRule; import org.sonar.api.batch.sensor.symbol.internal.DefaultSymbolTable; /** @@ -41,7 +42,9 @@ public interface SensorStorage { void store(Issue issue); - void store(ExternalIssue issue); + void store(DefaultExternalIssue issue); + + void store(DefaultAdHocRule adHocRule); void store(DefaultHighlighting highlighting); diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/ExternalIssue.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/ExternalIssue.java index b151a6582aa9..9f0cb1fd76d1 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/ExternalIssue.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/ExternalIssue.java @@ -30,6 +30,16 @@ */ public interface ExternalIssue extends IIssue { + /** + * @since 7.4 + */ + String engineId(); + + /** + * @since 7.4 + */ + String ruleId(); + Severity severity(); /** diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/NewExternalIssue.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/NewExternalIssue.java index ba9a4d7f3ec7..99e63eb4d09f 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/NewExternalIssue.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/NewExternalIssue.java @@ -34,9 +34,23 @@ public interface NewExternalIssue { /** * The {@link RuleKey} of the issue. + * @deprecated since 7.4. It is misleading, because of the "external_" prefix that is added on server side. Use {@link #engineId(String)} and {@link #ruleId(String)} */ + @Deprecated NewExternalIssue forRule(RuleKey ruleKey); + /** + * Unique identifier of the external analyzer (e.g. eslint, pmd, ...) + * @since 7.4 + */ + NewExternalIssue engineId(String engineId); + + /** + * Unique rule identifier for a given {@link #engineId(String)} + * @since 7.4 + */ + NewExternalIssue ruleId(String ruleId); + /** * Type of issue. */ diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/AbstractDefaultIssue.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/AbstractDefaultIssue.java index c845eb1ab7f1..b44534f78d2a 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/AbstractDefaultIssue.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/AbstractDefaultIssue.java @@ -26,17 +26,15 @@ import javax.annotation.Nullable; import org.sonar.api.batch.sensor.internal.DefaultStorable; import org.sonar.api.batch.sensor.internal.SensorStorage; +import org.sonar.api.batch.sensor.issue.Issue.Flow; import org.sonar.api.batch.sensor.issue.IssueLocation; import org.sonar.api.batch.sensor.issue.NewIssueLocation; -import org.sonar.api.batch.sensor.issue.Issue.Flow; -import org.sonar.api.rule.RuleKey; import static com.google.common.base.Preconditions.checkState; import static java.util.Collections.unmodifiableList; import static java.util.stream.Collectors.toList; public abstract class AbstractDefaultIssue extends DefaultStorable { - protected RuleKey ruleKey; protected IssueLocation primaryLocation; protected List> flows = new ArrayList<>(); @@ -48,15 +46,6 @@ public AbstractDefaultIssue(@Nullable SensorStorage storage) { super(storage); } - public T forRule(RuleKey ruleKey) { - this.ruleKey = ruleKey; - return (T) this; - } - - public RuleKey ruleKey() { - return this.ruleKey; - } - public IssueLocation primaryLocation() { return primaryLocation; } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultExternalIssue.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultExternalIssue.java index 9118a4a373cb..c4554bcd8adb 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultExternalIssue.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultExternalIssue.java @@ -25,6 +25,7 @@ import org.sonar.api.batch.sensor.internal.SensorStorage; import org.sonar.api.batch.sensor.issue.ExternalIssue; import org.sonar.api.batch.sensor.issue.NewExternalIssue; +import org.sonar.api.rule.RuleKey; import org.sonar.api.rules.RuleType; import static com.google.common.base.Preconditions.checkState; @@ -35,6 +36,8 @@ public class DefaultExternalIssue extends AbstractDefaultIssue implements Issue, NewIssue { + private RuleKey ruleKey; private Double gap; private Severity overriddenSeverity; @@ -43,6 +45,16 @@ public DefaultIssue(@Nullable SensorStorage storage) { super(storage); } + public DefaultIssue forRule(RuleKey ruleKey) { + this.ruleKey = ruleKey; + return this; + } + + public RuleKey ruleKey() { + return this.ruleKey; + } + + @Override public DefaultIssue gap(@Nullable Double gap) { Preconditions.checkArgument(gap == null || gap >= 0, format("Gap must be greater than or equal 0 (got %s)", gap)); diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueLocation.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueLocation.java index fbb20c0353e6..140b90593fe9 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueLocation.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueLocation.java @@ -90,10 +90,4 @@ public String message() { return this.message; } - public static void main (String[] args) { - - new DefaultIssueLocation().message("pipo"); - - } - } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/AdHocRule.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/AdHocRule.java new file mode 100644 index 000000000000..6708bcd8533c --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/AdHocRule.java @@ -0,0 +1,62 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.sensor.rule; + +import org.sonar.api.batch.rule.Severity; +import org.sonar.api.batch.sensor.Sensor; +import org.sonar.api.rules.RuleType; + +/** + * Represents a rule imported from an external rule engine by a {@link Sensor}. + * @since 7.4 + */ +public interface AdHocRule { + + /** + * Unique identifier of the external analyzer (e.g. eslint, pmd, ...) + */ + String engineId(); + + /** + * Unique rule identifier for a given {@link #engineId()} + */ + String ruleId(); + + /** + * Name of the rule. + */ + String name(); + + /** + * Description of the rule. + */ + String description(); + + /** + * Default severity of the rule. + */ + Severity severity(); + + /** + * Type of the rule. + */ + RuleType type(); + +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/NewAdHocRule.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/NewAdHocRule.java new file mode 100644 index 000000000000..993b19eeb021 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/NewAdHocRule.java @@ -0,0 +1,70 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.sensor.rule; + +import org.sonar.api.batch.rule.Severity; +import org.sonar.api.batch.sensor.Sensor; +import org.sonar.api.rules.RuleType; + +/** + * Builder for a rule imported from an external rule engine by a {@link Sensor}. This allows to provide more metadata + * for rules associated to {@link org.sonar.api.batch.sensor.issue.ExternalIssue}. + * Don't forget to {@link #save()} after setting the fields. + * + * @since 7.4 + */ +public interface NewAdHocRule { + + /** + * Unique identifier of the external analyzer (e.g. eslint, pmd, ...) + */ + NewAdHocRule engineId(String engineId); + + /** + * Unique rule identifier for a given {@link #engineId(String)} + */ + NewAdHocRule ruleId(String ruleId); + + /** + * The name of the rule. + */ + NewAdHocRule name(String name); + + /** + * The description of the rule. + */ + NewAdHocRule description(String description); + + /** + * Type of the rule. + */ + NewAdHocRule type(RuleType type); + + /** + * Set the severity of the rule. + */ + NewAdHocRule severity(Severity severity); + + /** + * Save the rule. There is almost no validation, except that no duplicated ad hoc rule keys are permitted. + */ + void save(); + +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/internal/DefaultAdHocRule.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/internal/DefaultAdHocRule.java new file mode 100644 index 000000000000..0251f5ce839f --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/internal/DefaultAdHocRule.java @@ -0,0 +1,126 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.sensor.rule.internal; + +import javax.annotation.Nullable; +import org.sonar.api.batch.rule.Severity; +import org.sonar.api.batch.sensor.internal.DefaultStorable; +import org.sonar.api.batch.sensor.internal.SensorStorage; +import org.sonar.api.batch.sensor.rule.AdHocRule; +import org.sonar.api.batch.sensor.rule.NewAdHocRule; +import org.sonar.api.rules.RuleType; + +import static com.google.common.base.Preconditions.checkState; +import static org.apache.commons.lang.StringUtils.isNotBlank; + +public class DefaultAdHocRule extends DefaultStorable implements AdHocRule, NewAdHocRule { + private Severity severity; + private RuleType type; + private String name; + private String description; + private String engineId; + private String ruleId; + + public DefaultAdHocRule() { + super(null); + } + + public DefaultAdHocRule(@Nullable SensorStorage storage) { + super(storage); + } + + @Override + public DefaultAdHocRule severity(Severity severity) { + this.severity = severity; + return this; + } + + @Override + public String engineId() { + return engineId; + } + + @Override + public String ruleId() { + return ruleId; + } + + @Override + public String name() { + return name; + } + + @Override + public String description() { + return description; + } + + @Override + public Severity severity() { + return this.severity; + } + + @Override + public void doSave() { + checkState(isNotBlank(engineId), "Engine id is mandatory on ad hoc rule"); + checkState(isNotBlank(ruleId), "Rule id is mandatory on ad hoc rule"); + checkState(isNotBlank(name), "Name is mandatory on every ad hoc rule"); + checkState(isNotBlank(description), "Description is mandatory on every ad hoc rule"); + checkState(severity != null, "Severity is mandatory on every ad hoc rule"); + checkState(type != null, "Type is mandatory on every ad hoc rule"); + storage.store(this); + } + + @Override + public RuleType type() { + return type; + } + + @Override + public DefaultAdHocRule engineId(String engineId) { + this.engineId = engineId; + return this; + } + + @Override + public DefaultAdHocRule ruleId(String ruleId) { + this.ruleId = ruleId; + return this; + } + + @Override + public DefaultAdHocRule name(String name) { + this.name = name; + return this; + } + + @Override + public DefaultAdHocRule description(String description) { + this.description = description; + return this; + } + + @Override + public DefaultAdHocRule type(RuleType type) { + this.type = type; + return this; + } + +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/internal/package-info.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/internal/package-info.java new file mode 100644 index 000000000000..72502a544bb3 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/internal/package-info.java @@ -0,0 +1,21 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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. + */ +@javax.annotation.ParametersAreNonnullByDefault +package org.sonar.api.batch.sensor.rule.internal; diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/package-info.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/package-info.java new file mode 100644 index 000000000000..aebc1b314708 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/package-info.java @@ -0,0 +1,21 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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. + */ +@javax.annotation.ParametersAreNonnullByDefault +package org.sonar.api.batch.sensor.rule; diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/rule/RuleKey.java b/sonar-plugin-api/src/main/java/org/sonar/api/rule/RuleKey.java index 1adb4eaf3d20..0c429837af89 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/rule/RuleKey.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/rule/RuleKey.java @@ -34,11 +34,6 @@ @Immutable public class RuleKey implements Serializable, Comparable { - /** - * @deprecated since 5.5, manual rule feature has been dropped - */ - @Deprecated - public static final String MANUAL_REPOSITORY_KEY = "manual"; public static final String EXTERNAL_RULE_REPO_PREFIX = "external_"; private final String repository; @@ -86,13 +81,6 @@ public String rule() { return rule; } - /** - * @deprecated since 5.5, manual rule feature has been dropped - */ - @Deprecated - public boolean isManual() { - return false; - } @Override public boolean equals(@Nullable Object o) { diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinition.java b/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinition.java index 4d1a5304c556..eb7089dc97e0 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinition.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinition.java @@ -403,12 +403,12 @@ public NewRepository createRepository(String key, String language) { /** * Creates a repository of rules from external rule engines. - * The key will always be prefixed with "external_". + * The repository key will be "external_[engineId]". * * @since 7.2 */ - public NewRepository createExternalRepository(String key, String language) { - return new NewRepositoryImpl(this, key, language, true); + public NewRepository createExternalRepository(String engineId, String language) { + return new NewRepositoryImpl(this, RuleKey.EXTERNAL_RULE_REPO_PREFIX + engineId, language, true); } /** @@ -504,7 +504,7 @@ class NewRepositoryImpl implements NewRepository { private NewRepositoryImpl(Context context, String key, String language, boolean isExternal) { this.context = context; - this.key = isExternal ? (RuleKey.EXTERNAL_RULE_REPO_PREFIX + key) : key; + this.key = key; this.name = key; this.language = language; this.isExternal = isExternal; diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/issue/internal/DefaultExternalIssueTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/issue/internal/DefaultExternalIssueTest.java index dc77fbc63fba..d0d38a0f5473 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/issue/internal/DefaultExternalIssueTest.java +++ b/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/issue/internal/DefaultExternalIssueTest.java @@ -56,7 +56,9 @@ public void build_file_issue() { .severity(Severity.BLOCKER); assertThat(issue.primaryLocation().inputComponent()).isEqualTo(inputFile); - assertThat(issue.ruleKey()).isEqualTo(RuleKey.of("repo", "rule")); + assertThat(issue.ruleKey()).isEqualTo(RuleKey.of("external_repo", "rule")); + assertThat(issue.engineId()).isEqualTo("repo"); + assertThat(issue.ruleId()).isEqualTo("rule"); assertThat(issue.primaryLocation().textRange().start().line()).isEqualTo(1); assertThat(issue.remediationEffort()).isEqualTo(10l); assertThat(issue.type()).isEqualTo(RuleType.BUG); diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/rule/internal/DefaultAdHocRuleTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/rule/internal/DefaultAdHocRuleTest.java new file mode 100644 index 000000000000..7a2b38084bfe --- /dev/null +++ b/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/rule/internal/DefaultAdHocRuleTest.java @@ -0,0 +1,148 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.sensor.rule.internal; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.batch.rule.Severity; +import org.sonar.api.batch.sensor.internal.SensorStorage; +import org.sonar.api.batch.sensor.rule.NewAdHocRule; +import org.sonar.api.rules.RuleType; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class DefaultAdHocRuleTest { + + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Test + public void store() { + SensorStorage storage = mock(SensorStorage.class); + new DefaultAdHocRule(storage) + .engineId("engine") + .ruleId("ruleId") + .name("name") + .description("desc") + .severity(Severity.BLOCKER) + .type(RuleType.CODE_SMELL) + .save(); + + verify(storage).store(any(DefaultAdHocRule.class)); + } + + @Test + public void fail_to_store_if_no_engine_id() { + SensorStorage storage = mock(SensorStorage.class); + NewAdHocRule rule = new DefaultAdHocRule(storage) + .engineId(" ") + .ruleId("ruleId") + .name("name") + .description("desc") + .severity(Severity.BLOCKER) + .type(RuleType.CODE_SMELL); + + exception.expect(IllegalStateException.class); + exception.expectMessage("Engine id is mandatory"); + rule.save(); + } + + @Test + public void fail_to_store_if_no_rule_id() { + SensorStorage storage = mock(SensorStorage.class); + NewAdHocRule rule = new DefaultAdHocRule(storage) + .engineId("engine") + .ruleId(" ") + .name("name") + .description("desc") + .severity(Severity.BLOCKER) + .type(RuleType.CODE_SMELL); + + exception.expect(IllegalStateException.class); + exception.expectMessage("Rule id is mandatory"); + rule.save(); + } + + @Test + public void fail_to_store_if_no_name() { + SensorStorage storage = mock(SensorStorage.class); + NewAdHocRule rule = new DefaultAdHocRule(storage) + .engineId("engine") + .ruleId("ruleId") + .name(" ") + .description("desc") + .severity(Severity.BLOCKER) + .type(RuleType.CODE_SMELL); + + exception.expect(IllegalStateException.class); + exception.expectMessage("Name is mandatory"); + rule.save(); + } + + @Test + public void fail_to_store_if_no_description() { + SensorStorage storage = mock(SensorStorage.class); + NewAdHocRule rule = new DefaultAdHocRule(storage) + .engineId("engine") + .ruleId("ruleId") + .name("name") + .description(" ") + .severity(Severity.BLOCKER) + .type(RuleType.CODE_SMELL); + + exception.expect(IllegalStateException.class); + exception.expectMessage("Description is mandatory"); + rule.save(); + } + + @Test + public void fail_to_store_if_no_severity() { + SensorStorage storage = mock(SensorStorage.class); + NewAdHocRule rule = new DefaultAdHocRule(storage) + .engineId("engine") + .ruleId("ruleId") + .name("name") + .description("desc") + .type(RuleType.CODE_SMELL); + + exception.expect(IllegalStateException.class); + exception.expectMessage("Severity is mandatory"); + rule.save(); + } + + @Test + public void fail_to_store_if_no_type() { + SensorStorage storage = mock(SensorStorage.class); + NewAdHocRule rule = new DefaultAdHocRule(storage) + .engineId("engine") + .ruleId("ruleId") + .name("name") + .description("desc") + .severity(Severity.BLOCKER); + + exception.expect(IllegalStateException.class); + exception.expectMessage("Type is mandatory"); + rule.save(); + } + +} \ No newline at end of file diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/externalissue/ExternalIssueImporter.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/externalissue/ExternalIssueImporter.java index 74cf2e6434d6..46d495bd4588 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/externalissue/ExternalIssueImporter.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/externalissue/ExternalIssueImporter.java @@ -29,7 +29,6 @@ import org.sonar.api.batch.sensor.SensorContext; import org.sonar.api.batch.sensor.issue.NewExternalIssue; import org.sonar.api.batch.sensor.issue.NewIssueLocation; -import org.sonar.api.rule.RuleKey; import org.sonar.api.rules.RuleType; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; @@ -70,7 +69,8 @@ public void execute() { private boolean importIssue(Issue issue) { NewExternalIssue externalIssue = context.newExternalIssue() - .forRule(RuleKey.of(issue.engineId, issue.ruleId)) + .engineId(issue.engineId) + .ruleId(issue.ruleId) .severity(Severity.valueOf(issue.severity)) .type(RuleType.valueOf(issue.type)); diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/ModuleIssues.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/ModuleIssues.java index d064b302f480..a0b8974443d8 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/ModuleIssues.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/ModuleIssues.java @@ -123,8 +123,8 @@ private static ScannerReport.ExternalIssue createReportExternalIssue(ExternalIss // non-null fields builder.setSeverity(severity); builder.setType(issueType); - builder.setRuleRepository(issue.ruleKey().repository()); - builder.setRuleKey(issue.ruleKey().rule()); + builder.setEngineId(issue.engineId()); + builder.setRuleId(issue.ruleId()); builder.setMsg(primaryMessage); locationBuilder.setMsg(primaryMessage); diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/mediumtest/TaskResult.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/mediumtest/TaskResult.java index 80a33078927a..79b08aa9dc25 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/mediumtest/TaskResult.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/mediumtest/TaskResult.java @@ -305,4 +305,16 @@ public ScannerReport.CoverageDetail coveragePerTestFor(InputFile testFile, Strin } return null; } + + public List adHocRules() { + List result = new ArrayList<>(); + try (CloseableIterator it = getReportReader().readAdHocRules()) { + while (it.hasNext()) { + result.add(it.next()); + } + } catch (Exception e) { + throw new IllegalStateException(e); + } + return result; + } } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorContext.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorContext.java index 299e7eadb98a..f79f09d4f2a7 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorContext.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorContext.java @@ -45,12 +45,15 @@ import org.sonar.api.batch.sensor.issue.internal.DefaultIssue; import org.sonar.api.batch.sensor.measure.NewMeasure; import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure; +import org.sonar.api.batch.sensor.rule.NewAdHocRule; +import org.sonar.api.batch.sensor.rule.internal.DefaultAdHocRule; import org.sonar.api.batch.sensor.symbol.NewSymbolTable; import org.sonar.api.batch.sensor.symbol.internal.DefaultSymbolTable; import org.sonar.api.config.Configuration; import org.sonar.api.config.Settings; import org.sonar.api.utils.Version; import org.sonar.scanner.scan.branch.BranchConfiguration; +import org.sonar.scanner.sensor.noop.NoOpNewAdHocRule; import org.sonar.scanner.sensor.noop.NoOpNewAnalysisError; import org.sonar.scanner.sensor.noop.NoOpNewCpdTokens; import org.sonar.scanner.sensor.noop.NoOpNewExternalIssue; @@ -66,6 +69,7 @@ public class DefaultSensorContext implements SensorContext { static final NoOpNewCpdTokens NO_OP_NEW_CPD_TOKENS = new NoOpNewCpdTokens(); static final NoOpNewAnalysisError NO_OP_NEW_ANALYSIS_ERROR = new NoOpNewAnalysisError(); static final NoOpNewExternalIssue NO_OP_NEW_EXTERNAL_ISSUE = new NoOpNewExternalIssue(); + static final NoOpNewAdHocRule NO_OP_NEW_AD_HOC_RULE = new NoOpNewAdHocRule(); static final NoOpNewSignificantCode NO_OP_NEW_SIGNIFICANT_CODE = new NoOpNewSignificantCode(); private final Settings mutableSettings; @@ -142,7 +146,14 @@ public NewExternalIssue newExternalIssue() { return NO_OP_NEW_EXTERNAL_ISSUE; } return new DefaultExternalIssue(sensorStorage); + } + @Override + public NewAdHocRule newAdHocRule() { + if (analysisMode.isIssues()) { + return NO_OP_NEW_AD_HOC_RULE; + } + return new DefaultAdHocRule(sensorStorage); } @Override diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorStorage.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorStorage.java index 859cda8b75dd..f7f3b9898d6a 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorStorage.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorStorage.java @@ -42,10 +42,11 @@ import org.sonar.api.batch.sensor.error.AnalysisError; import org.sonar.api.batch.sensor.highlighting.internal.DefaultHighlighting; import org.sonar.api.batch.sensor.internal.SensorStorage; -import org.sonar.api.batch.sensor.issue.ExternalIssue; import org.sonar.api.batch.sensor.issue.Issue; +import org.sonar.api.batch.sensor.issue.internal.DefaultExternalIssue; import org.sonar.api.batch.sensor.measure.Measure; import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure; +import org.sonar.api.batch.sensor.rule.internal.DefaultAdHocRule; import org.sonar.api.batch.sensor.symbol.internal.DefaultSymbolTable; import org.sonar.api.config.Configuration; import org.sonar.api.utils.KeyValueFormat; @@ -56,6 +57,7 @@ import org.sonar.duplications.internal.pmd.PmdBlockChunker; import org.sonar.scanner.cpd.index.SonarCpdBlockIndex; import org.sonar.scanner.issue.ModuleIssues; +import org.sonar.scanner.protocol.Constants; import org.sonar.scanner.protocol.output.FileStructure; import org.sonar.scanner.protocol.output.ScannerReport; import org.sonar.scanner.protocol.output.ScannerReportWriter; @@ -390,7 +392,7 @@ public void store(Issue issue) { * Thread safe assuming that each issues for each file are only written once. */ @Override - public void store(ExternalIssue externalIssue) { + public void store(DefaultExternalIssue externalIssue) { if (externalIssue.primaryLocation().inputComponent() instanceof DefaultInputFile) { DefaultInputFile defaultInputFile = (DefaultInputFile) externalIssue.primaryLocation().inputComponent(); defaultInputFile.setPublished(true); @@ -398,6 +400,19 @@ public void store(ExternalIssue externalIssue) { moduleIssues.initAndAddExternalIssue(externalIssue); } + @Override + public void store(DefaultAdHocRule adHocRule) { + ScannerReportWriter writer = reportPublisher.getWriter(); + final ScannerReport.AdHocRule.Builder builder = ScannerReport.AdHocRule.newBuilder(); + builder.setEngineId(adHocRule.engineId()); + builder.setRuleId(adHocRule.ruleId()); + builder.setName(adHocRule.name()); + builder.setDescription(adHocRule.description()); + builder.setSeverity(Constants.Severity.valueOf(adHocRule.severity().name())); + builder.setType(ScannerReport.IssueType.valueOf(adHocRule.type().name())); + writer.appendAdHocRule(builder.build()); + } + @Override public void store(DefaultHighlighting highlighting) { ScannerReportWriter writer = reportPublisher.getWriter(); diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/noop/NoOpNewAdHocRule.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/noop/NoOpNewAdHocRule.java new file mode 100644 index 000000000000..cd679665d90e --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/noop/NoOpNewAdHocRule.java @@ -0,0 +1,69 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.scanner.sensor.noop; + +import org.sonar.api.batch.rule.Severity; +import org.sonar.api.batch.sensor.rule.NewAdHocRule; +import org.sonar.api.rules.RuleType; + +public class NoOpNewAdHocRule implements NewAdHocRule { + + @Override + public NoOpNewAdHocRule engineId(String engineId) { + // no op + return this; + } + + @Override + public NoOpNewAdHocRule ruleId(String ruleId) { + // no op + return this; + } + + @Override + public NewAdHocRule name(String name) { + // no op + return this; + } + + @Override + public NewAdHocRule description(String description) { + // no op + return this; + } + + @Override + public NoOpNewAdHocRule type(RuleType type) { + // no op + return this; + } + + @Override + public NoOpNewAdHocRule severity(Severity severity) { + // no op + return this; + } + + @Override + public void save() { + // no op + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/noop/NoOpNewExternalIssue.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/noop/NoOpNewExternalIssue.java index 555bb7f94f99..e66f026090a8 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/noop/NoOpNewExternalIssue.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/noop/NoOpNewExternalIssue.java @@ -34,6 +34,18 @@ public NewExternalIssue forRule(RuleKey ruleKey) { return this; } + @Override + public NewExternalIssue engineId(String engineId) { + // no op + return this; + } + + @Override + public NewExternalIssue ruleId(String ruleId) { + // no op + return this; + } + @Override public NewExternalIssue type(RuleType type) { // no op diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issues/ExternalIssuesMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issues/ExternalIssuesMediumTest.java index dddb88040ffd..fc3ce5b17981 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issues/ExternalIssuesMediumTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issues/ExternalIssuesMediumTest.java @@ -31,12 +31,15 @@ import org.sonar.scanner.mediumtest.ScannerMediumTester; import org.sonar.scanner.mediumtest.TaskResult; import org.sonar.scanner.protocol.Constants.Severity; +import org.sonar.scanner.protocol.output.ScannerReport; import org.sonar.scanner.protocol.output.ScannerReport.ExternalIssue; import org.sonar.scanner.protocol.output.ScannerReport.Issue; import org.sonar.scanner.protocol.output.ScannerReport.IssueType; import org.sonar.xoo.XooPlugin; +import org.sonar.xoo.rule.OneExternalIssuePerLineSensor; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; public class ExternalIssuesMediumTest { @Rule @@ -56,7 +59,7 @@ public void testOneIssuePerLine() throws Exception { TaskResult result = tester .newScanTask(new File(tmpDir, "sonar-project.properties")) - .property("sonar.oneExternalIssuePerLine.activate", "true") + .property(OneExternalIssuePerLineSensor.ACTIVATE, "true") .execute(); List issues = result.issuesFor(result.inputFile("xources/hello/HelloJava.xoo")); @@ -67,6 +70,37 @@ public void testOneIssuePerLine() throws Exception { ExternalIssue issue = externalIssues.get(0); assertThat(issue.getTextRange().getStartLine()).isEqualTo(issue.getTextRange().getStartLine()); + + assertThat(result.adHocRules()).isEmpty(); + } + + @Test + public void testOneIssuePerLine_register_ad_hoc_rule() throws Exception { + File projectDir = new File(IssuesMediumTest.class.getResource("/mediumtest/xoo/sample").toURI()); + File tmpDir = temp.newFolder(); + FileUtils.copyDirectory(projectDir, tmpDir); + + TaskResult result = tester + .newScanTask(new File(tmpDir, "sonar-project.properties")) + .property(OneExternalIssuePerLineSensor.ACTIVATE, "true") + .property(OneExternalIssuePerLineSensor.REGISTER_AD_HOC_RULE, "true") + .execute(); + + assertThat(result.adHocRules()).extracting( + ScannerReport.AdHocRule::getEngineId, + ScannerReport.AdHocRule::getRuleId, + ScannerReport.AdHocRule::getName, + ScannerReport.AdHocRule::getDescription, + ScannerReport.AdHocRule::getSeverity, + ScannerReport.AdHocRule::getType) + .containsExactlyInAnyOrder( + tuple( + OneExternalIssuePerLineSensor.ENGINE_ID, + OneExternalIssuePerLineSensor.RULE_ID, + "An ad hoc rule", + "blah blah", + Severity.BLOCKER, + IssueType.BUG)); } @Test @@ -90,7 +124,8 @@ public void testLoadIssuesFromJsonReport() throws URISyntaxException, IOExceptio ExternalIssue issue = externalIssues.get(0); assertThat(issue.getFlowCount()).isZero(); assertThat(issue.getMsg()).isEqualTo("fix the issue here"); - assertThat(issue.getRuleKey()).isEqualTo("rule1"); + assertThat(issue.getEngineId()).isEqualTo("externalXoo"); + assertThat(issue.getRuleId()).isEqualTo("rule1"); assertThat(issue.getSeverity()).isEqualTo(Severity.MAJOR); assertThat(issue.getEffort()).isEqualTo(50l); assertThat(issue.getType()).isEqualTo(IssueType.CODE_SMELL); @@ -103,7 +138,8 @@ public void testLoadIssuesFromJsonReport() throws URISyntaxException, IOExceptio issue = externalIssues.get(1); assertThat(issue.getFlowCount()).isZero(); assertThat(issue.getMsg()).isEqualTo("fix the bug here"); - assertThat(issue.getRuleKey()).isEqualTo("rule2"); + assertThat(issue.getEngineId()).isEqualTo("externalXoo"); + assertThat(issue.getRuleId()).isEqualTo("rule2"); assertThat(issue.getSeverity()).isEqualTo(Severity.CRITICAL); assertThat(issue.getType()).isEqualTo(IssueType.BUG); assertThat(issue.getEffort()).isZero(); @@ -119,7 +155,8 @@ public void testLoadIssuesFromJsonReport() throws URISyntaxException, IOExceptio issue = externalIssues2.iterator().next(); assertThat(issue.getFlowCount()).isEqualTo(2); assertThat(issue.getMsg()).isEqualTo("fix the bug here"); - assertThat(issue.getRuleKey()).isEqualTo("rule3"); + assertThat(issue.getEngineId()).isEqualTo("externalXoo"); + assertThat(issue.getRuleId()).isEqualTo("rule3"); assertThat(issue.getSeverity()).isEqualTo(Severity.MAJOR); assertThat(issue.getType()).isEqualTo(IssueType.BUG); assertThat(issue.hasTextRange()).isFalse(); @@ -128,7 +165,6 @@ public void testLoadIssuesFromJsonReport() throws URISyntaxException, IOExceptio assertThat(issue.getFlow(1).getLocationCount()).isOne(); assertThat(issue.getFlow(1).getLocation(0).getTextRange().getStartLine()).isEqualTo(3); - // one issue is located in a non-existing file assertThat(logs.logs()).contains("External issues ignored for 1 unknown files, including: invalidFile"); diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issues/IssuesMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issues/IssuesMediumTest.java index 0226398fdd9c..84de3e207e36 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issues/IssuesMediumTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issues/IssuesMediumTest.java @@ -78,7 +78,7 @@ public void testOneExternalIssuePerLine() throws Exception { TaskResult result = tester .newScanTask(new File(tmpDir, "sonar-project.properties")) - .property(OneExternalIssuePerLineSensor.ACTIVATE_EXTERNAL_ISSUES, "true") + .property(OneExternalIssuePerLineSensor.ACTIVATE, "true") .execute(); List externalIssues = result.externalIssuesFor(result.inputFile("xources/hello/HelloJava.xoo")); diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorContextTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorContextTest.java index e7f82f77ab51..dc58ee42d946 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorContextTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorContextTest.java @@ -85,6 +85,7 @@ public void shouldProvideComponents() { assertThat(adaptor.newIssue()).isNotNull(); assertThat(adaptor.newExternalIssue()).isNotNull(); + assertThat(adaptor.newAdHocRule()).isNotNull(); assertThat(adaptor.newMeasure()).isNotNull(); assertThat(adaptor.newAnalysisError()).isEqualTo(DefaultSensorContext.NO_OP_NEW_ANALYSIS_ERROR); assertThat(adaptor.isCancelled()).isFalse(); @@ -105,6 +106,7 @@ public void shouldSkipSeveralObjectsInPreviewMode() { assertThat(adaptor.newCpdTokens()).isEqualTo(DefaultSensorContext.NO_OP_NEW_CPD_TOKENS); assertThat(adaptor.newSymbolTable()).isEqualTo(DefaultSensorContext.NO_OP_NEW_SYMBOL_TABLE); assertThat(adaptor.newExternalIssue()).isEqualTo(DefaultSensorContext.NO_OP_NEW_EXTERNAL_ISSUE); + assertThat(adaptor.newAdHocRule()).isEqualTo(DefaultSensorContext.NO_OP_NEW_AD_HOC_RULE); assertThat(adaptor.newHighlighting()).isEqualTo(DefaultSensorContext.NO_OP_NEW_HIGHLIGHTING); } diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/FileStructure.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/FileStructure.java index bab729b95b0a..5b7e6ddb8512 100644 --- a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/FileStructure.java +++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/FileStructure.java @@ -77,6 +77,10 @@ public File activeRules() { return new File(dir, "activerules.pb"); } + public File adHocRules() { + return new File(dir, "adhocrules.pb"); + } + public File fileFor(Domain domain, int componentRef) { return new File(dir, domain.filePrefix + componentRef + domain.fileSuffix); } diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportReader.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportReader.java index 65c7bb79a05d..c52726e48c1e 100644 --- a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportReader.java +++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportReader.java @@ -50,6 +50,14 @@ public CloseableIterator readActiveRules() { return Protobuf.readStream(file, ScannerReport.ActiveRule.parser()); } + public CloseableIterator readAdHocRules() { + File file = fileStructure.adHocRules(); + if (!fileExists(file)) { + return emptyCloseableIterator(); + } + return Protobuf.readStream(file, ScannerReport.AdHocRule.parser()); + } + public CloseableIterator readComponentMeasures(int componentRef) { File file = fileStructure.fileFor(FileStructure.Domain.MEASURES, componentRef); if (fileExists(file)) { diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportWriter.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportWriter.java index 1ccb6876e887..5fa891516892 100644 --- a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportWriter.java +++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportWriter.java @@ -109,6 +109,15 @@ public void appendComponentExternalIssue(int componentRef, ScannerReport.Externa } } + public void appendAdHocRule(ScannerReport.AdHocRule adHocRule) { + File file = fileStructure.adHocRules(); + try (OutputStream out = new BufferedOutputStream(new FileOutputStream(file, true))) { + adHocRule.writeDelimitedTo(out); + } catch (Exception e) { + throw ContextException.of("Unable to write ad hoc rule", e).addContext("file", file); + } + } + public File writeComponentMeasures(int componentRef, Iterable measures) { File file = fileStructure.fileFor(FileStructure.Domain.MEASURES, componentRef); Protobuf.writeStream(measures, file, false); diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/viewer/ScannerReportViewerApp.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/viewer/ScannerReportViewerApp.java index c7ab72ff6d34..4df7d77c080b 100644 --- a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/viewer/ScannerReportViewerApp.java +++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/viewer/ScannerReportViewerApp.java @@ -92,12 +92,16 @@ public class ScannerReportViewerApp { private JEditorPane duplicationEditor; private JScrollPane issuesTab; private JEditorPane issuesEditor; + private JScrollPane externalIssuesTab; + private JEditorPane externalIssuesEditor; private JScrollPane measuresTab; private JEditorPane measuresEditor; private JScrollPane scmTab; private JEditorPane scmEditor; private JScrollPane activeRuleTab; private JEditorPane activeRuleEditor; + private JScrollPane adHocRuleTab; + private JEditorPane adHocRuleEditor; private JScrollPane qualityProfileTab; private JEditorPane qualityProfileEditor; private JScrollPane pluginTab; @@ -196,6 +200,7 @@ private void loadReport(File file) { updateTitle(); loadComponents(); updateActiveRules(); + updateAdHocRules(); updateQualityProfiles(); updatePlugins(); } @@ -254,6 +259,7 @@ private void updateDetails(Component component) { updateTests(component); updateDuplications(component); updateIssues(component); + updateExternalIssues(component); updateMeasures(component); updateScm(component); updateCpdTextBlocks(component); @@ -317,6 +323,19 @@ private void updateIssues(Component component) { } } + private void updateExternalIssues(Component component) { + externalIssuesEditor.setText(""); + try (CloseableIterator it = reader.readComponentExternalIssues(component.getRef())) { + while (it.hasNext()) { + ScannerReport.ExternalIssue issue = it.next(); + int offset = externalIssuesEditor.getDocument().getEndPosition().getOffset(); + externalIssuesEditor.getDocument().insertString(offset, issue.toString(), null); + } + } catch (Exception e) { + throw new IllegalStateException("Can't read external issues for " + getNodeName(component), e); + } + } + private void updateCoverage(Component component) { coverageEditor.setText(""); try (CloseableIterator it = reader.readComponentCoverage(component.getRef())) { @@ -375,6 +394,18 @@ private void updateActiveRules() { } } + private void updateAdHocRules() { + adHocRuleEditor.setText(""); + + StringBuilder builder = new StringBuilder(); + try (CloseableIterator adHocRuleCloseableIterator = reader.readAdHocRules()) { + while (adHocRuleCloseableIterator.hasNext()) { + builder.append(adHocRuleCloseableIterator.next().toString()).append("\n"); + } + adHocRuleEditor.setText(builder.toString()); + } + } + private void updateQualityProfiles() { qualityProfileEditor.setText(""); @@ -536,6 +567,12 @@ private void initialize() { issuesEditor = new JEditorPane(); issuesTab.setViewportView(issuesEditor); + externalIssuesTab = new JScrollPane(); + tabbedPane.addTab("External Issues", null, externalIssuesTab, null); + + externalIssuesEditor = new JEditorPane(); + externalIssuesTab.setViewportView(externalIssuesEditor); + measuresTab = new JScrollPane(); tabbedPane.addTab("Measures", null, measuresTab, null); @@ -554,6 +591,12 @@ private void initialize() { activeRuleEditor = new JEditorPane(); activeRuleTab.setViewportView(activeRuleEditor); + adHocRuleTab = new JScrollPane(); + tabbedPane.addTab("Add Hoc Rules", null, adHocRuleTab, null); + + adHocRuleEditor = new JEditorPane(); + adHocRuleTab.setViewportView(adHocRuleEditor); + qualityProfileTab = new JScrollPane(); tabbedPane.addTab("Quality Profiles", null, qualityProfileTab, null); diff --git a/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto b/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto index ac4c89980b8d..0913fc179e96 100644 --- a/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto +++ b/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto @@ -187,19 +187,25 @@ message Issue { } message ExternalIssue { - string rule_repository = 1; - string rule_key = 2; - // Only when issue component is a file. Can also be empty for a file if this is an issue global to the file. + string engine_id = 1; + string rule_id = 2; string msg = 3; Severity severity = 4; int64 effort = 5; - // Only when issue component is a file. Can also be empty for a file if this is an issue global to the file. - // Will be identical to the first location of the first flow TextRange text_range = 6; repeated Flow flow = 7; IssueType type = 8; } +message AdHocRule { + string engine_id = 1; + string rule_id = 2; + string name = 3; + string description = 4; + Severity severity = 5; + IssueType type = 6; +} + enum IssueType { UNSET = 0; CODE_SMELL = 1;