You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

IssuePublisher.java 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2023 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.scanner.issue;
  21. import java.util.Collection;
  22. import java.util.List;
  23. import java.util.function.Consumer;
  24. import java.util.stream.Collectors;
  25. import javax.annotation.Nullable;
  26. import javax.annotation.concurrent.ThreadSafe;
  27. import org.apache.commons.lang.StringUtils;
  28. import org.sonar.api.batch.fs.TextRange;
  29. import org.sonar.api.batch.fs.internal.DefaultInputComponent;
  30. import org.sonar.api.batch.fs.internal.DefaultInputFile;
  31. import org.sonar.api.batch.rule.ActiveRule;
  32. import org.sonar.api.batch.rule.ActiveRules;
  33. import org.sonar.api.batch.sensor.issue.ExternalIssue;
  34. import org.sonar.api.batch.sensor.issue.Issue;
  35. import org.sonar.api.batch.sensor.issue.Issue.Flow;
  36. import org.sonar.api.batch.sensor.issue.MessageFormatting;
  37. import org.sonar.api.batch.sensor.issue.NewIssue.FlowType;
  38. import org.sonar.api.batch.sensor.issue.internal.DefaultIssueFlow;
  39. import org.sonar.scanner.protocol.Constants.Severity;
  40. import org.sonar.scanner.protocol.output.ScannerReport;
  41. import org.sonar.scanner.protocol.output.ScannerReport.IssueLocation;
  42. import org.sonar.scanner.protocol.output.ScannerReport.IssueType;
  43. import org.sonar.scanner.report.ReportPublisher;
  44. /**
  45. * Initialize the issues raised during scan.
  46. */
  47. @ThreadSafe
  48. public class IssuePublisher {
  49. private final ActiveRules activeRules;
  50. private final IssueFilters filters;
  51. private final ReportPublisher reportPublisher;
  52. public IssuePublisher(ActiveRules activeRules, IssueFilters filters, ReportPublisher reportPublisher) {
  53. this.activeRules = activeRules;
  54. this.filters = filters;
  55. this.reportPublisher = reportPublisher;
  56. }
  57. public boolean initAndAddIssue(Issue issue) {
  58. DefaultInputComponent inputComponent = (DefaultInputComponent) issue.primaryLocation().inputComponent();
  59. if (noSonar(inputComponent, issue)) {
  60. return false;
  61. }
  62. ActiveRule activeRule = activeRules.find(issue.ruleKey());
  63. if (activeRule == null) {
  64. // rule does not exist or is not enabled -> ignore the issue
  65. return false;
  66. }
  67. ScannerReport.Issue rawIssue = createReportIssue(issue, inputComponent.scannerId(), activeRule.severity());
  68. if (filters.accept(inputComponent, rawIssue)) {
  69. write(inputComponent.scannerId(), rawIssue);
  70. return true;
  71. }
  72. return false;
  73. }
  74. private static boolean noSonar(DefaultInputComponent inputComponent, Issue issue) {
  75. TextRange textRange = issue.primaryLocation().textRange();
  76. return inputComponent.isFile()
  77. && textRange != null
  78. && ((DefaultInputFile) inputComponent).hasNoSonarAt(textRange.start().line())
  79. && !StringUtils.containsIgnoreCase(issue.ruleKey().rule(), "nosonar");
  80. }
  81. public void initAndAddExternalIssue(ExternalIssue issue) {
  82. DefaultInputComponent inputComponent = (DefaultInputComponent) issue.primaryLocation().inputComponent();
  83. ScannerReport.ExternalIssue rawExternalIssue = createReportExternalIssue(issue, inputComponent.scannerId());
  84. write(inputComponent.scannerId(), rawExternalIssue);
  85. }
  86. private static String nullToEmpty(@Nullable String str) {
  87. if (str == null) {
  88. return "";
  89. }
  90. return str;
  91. }
  92. private static ScannerReport.Issue createReportIssue(Issue issue, int componentRef, String activeRuleSeverity) {
  93. String primaryMessage = nullToEmpty(issue.primaryLocation().message());
  94. org.sonar.api.batch.rule.Severity overriddenSeverity = issue.overriddenSeverity();
  95. Severity severity = overriddenSeverity != null ? Severity.valueOf(overriddenSeverity.name()) : Severity.valueOf(activeRuleSeverity);
  96. ScannerReport.Issue.Builder builder = ScannerReport.Issue.newBuilder();
  97. ScannerReport.IssueLocation.Builder locationBuilder = IssueLocation.newBuilder();
  98. ScannerReport.TextRange.Builder textRangeBuilder = ScannerReport.TextRange.newBuilder();
  99. // non-null fields
  100. builder.setSeverity(severity);
  101. builder.setRuleRepository(issue.ruleKey().repository());
  102. builder.setRuleKey(issue.ruleKey().rule());
  103. builder.setMsg(primaryMessage);
  104. builder.addAllMsgFormatting(toProtobufMessageFormattings(issue.primaryLocation().messageFormattings()));
  105. locationBuilder.setMsg(primaryMessage);
  106. locationBuilder.addAllMsgFormatting(toProtobufMessageFormattings(issue.primaryLocation().messageFormattings()));
  107. locationBuilder.setComponentRef(componentRef);
  108. TextRange primaryTextRange = issue.primaryLocation().textRange();
  109. if (primaryTextRange != null) {
  110. builder.setTextRange(toProtobufTextRange(textRangeBuilder, primaryTextRange));
  111. }
  112. Double gap = issue.gap();
  113. if (gap != null) {
  114. builder.setGap(gap);
  115. }
  116. applyFlows(builder::addFlow, locationBuilder, textRangeBuilder, issue.flows());
  117. builder.setQuickFixAvailable(issue.isQuickFixAvailable());
  118. issue.ruleDescriptionContextKey().ifPresent(builder::setRuleDescriptionContextKey);
  119. List<String> codeVariants = issue.codeVariants();
  120. if (codeVariants != null) {
  121. builder.addAllCodeVariants(codeVariants);
  122. }
  123. return builder.build();
  124. }
  125. private static List<ScannerReport.MessageFormatting> toProtobufMessageFormattings(List<MessageFormatting> messageFormattings) {
  126. return messageFormattings.stream()
  127. .map(m -> ScannerReport.MessageFormatting.newBuilder()
  128. .setStart(m.start())
  129. .setEnd(m.end())
  130. .setType(ScannerReport.MessageFormattingType.valueOf(m.type().name()))
  131. .build())
  132. .collect(Collectors.toList());
  133. }
  134. private static ScannerReport.ExternalIssue createReportExternalIssue(ExternalIssue issue, int componentRef) {
  135. // primary location of an external issue must have a message
  136. String primaryMessage = issue.primaryLocation().message();
  137. Severity severity = Severity.valueOf(issue.severity().name());
  138. IssueType issueType = IssueType.valueOf(issue.type().name());
  139. ScannerReport.ExternalIssue.Builder builder = ScannerReport.ExternalIssue.newBuilder();
  140. ScannerReport.IssueLocation.Builder locationBuilder = IssueLocation.newBuilder();
  141. ScannerReport.TextRange.Builder textRangeBuilder = ScannerReport.TextRange.newBuilder();
  142. // non-null fields
  143. builder.setSeverity(severity);
  144. builder.setType(issueType);
  145. builder.setEngineId(issue.engineId());
  146. builder.setRuleId(issue.ruleId());
  147. builder.setMsg(primaryMessage);
  148. builder.addAllMsgFormatting(toProtobufMessageFormattings(issue.primaryLocation().messageFormattings()));
  149. locationBuilder.setMsg(primaryMessage);
  150. locationBuilder.addAllMsgFormatting(toProtobufMessageFormattings(issue.primaryLocation().messageFormattings()));
  151. locationBuilder.setComponentRef(componentRef);
  152. TextRange primaryTextRange = issue.primaryLocation().textRange();
  153. if (primaryTextRange != null) {
  154. builder.setTextRange(toProtobufTextRange(textRangeBuilder, primaryTextRange));
  155. }
  156. Long effort = issue.remediationEffort();
  157. if (effort != null) {
  158. builder.setEffort(effort);
  159. }
  160. applyFlows(builder::addFlow, locationBuilder, textRangeBuilder, issue.flows());
  161. return builder.build();
  162. }
  163. private static void applyFlows(Consumer<ScannerReport.Flow> consumer, ScannerReport.IssueLocation.Builder locationBuilder,
  164. ScannerReport.TextRange.Builder textRangeBuilder, Collection<Flow> flows) {
  165. ScannerReport.Flow.Builder flowBuilder = ScannerReport.Flow.newBuilder();
  166. for (Flow f : flows) {
  167. DefaultIssueFlow flow = (DefaultIssueFlow) f;
  168. if (flow.locations().isEmpty()) {
  169. return;
  170. }
  171. flowBuilder.clear();
  172. for (org.sonar.api.batch.sensor.issue.IssueLocation location : flow.locations()) {
  173. int locationComponentRef = ((DefaultInputComponent) location.inputComponent()).scannerId();
  174. locationBuilder.clear();
  175. locationBuilder.setComponentRef(locationComponentRef);
  176. String message = location.message();
  177. if (message != null) {
  178. locationBuilder.setMsg(message);
  179. locationBuilder.addAllMsgFormatting(toProtobufMessageFormattings(location.messageFormattings()));
  180. }
  181. TextRange textRange = location.textRange();
  182. if (textRange != null) {
  183. locationBuilder.setTextRange(toProtobufTextRange(textRangeBuilder, textRange));
  184. }
  185. flowBuilder.addLocation(locationBuilder.build());
  186. }
  187. if (flow.description() != null) {
  188. flowBuilder.setDescription(flow.description());
  189. }
  190. flowBuilder.setType(toProtobufFlowType(flow.type()));
  191. consumer.accept(flowBuilder.build());
  192. }
  193. }
  194. private static ScannerReport.FlowType toProtobufFlowType(FlowType flowType) {
  195. switch (flowType) {
  196. case EXECUTION:
  197. return ScannerReport.FlowType.EXECUTION;
  198. case DATA:
  199. return ScannerReport.FlowType.DATA;
  200. case UNDEFINED:
  201. return ScannerReport.FlowType.UNDEFINED;
  202. default:
  203. throw new IllegalArgumentException("Unrecognized flow type: " + flowType);
  204. }
  205. }
  206. private static ScannerReport.TextRange toProtobufTextRange(ScannerReport.TextRange.Builder textRangeBuilder, TextRange primaryTextRange) {
  207. textRangeBuilder.clear();
  208. textRangeBuilder.setStartLine(primaryTextRange.start().line());
  209. textRangeBuilder.setStartOffset(primaryTextRange.start().lineOffset());
  210. textRangeBuilder.setEndLine(primaryTextRange.end().line());
  211. textRangeBuilder.setEndOffset(primaryTextRange.end().lineOffset());
  212. return textRangeBuilder.build();
  213. }
  214. public void write(int batchId, ScannerReport.Issue rawIssue) {
  215. reportPublisher.getWriter().appendComponentIssue(batchId, rawIssue);
  216. }
  217. public void write(int batchId, ScannerReport.ExternalIssue rawIssue) {
  218. reportPublisher.getWriter().appendComponentExternalIssue(batchId, rawIssue);
  219. }
  220. }