選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

MultilineIssuesSensor.java 9.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2022 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.xoo.rule;
  21. import java.io.IOException;
  22. import java.util.Collection;
  23. import java.util.HashMap;
  24. import java.util.LinkedList;
  25. import java.util.List;
  26. import java.util.Map;
  27. import java.util.TreeMap;
  28. import java.util.regex.Matcher;
  29. import java.util.regex.Pattern;
  30. import javax.annotation.CheckForNull;
  31. import javax.annotation.Nullable;
  32. import org.sonar.api.batch.fs.FilePredicates;
  33. import org.sonar.api.batch.fs.FileSystem;
  34. import org.sonar.api.batch.fs.InputFile;
  35. import org.sonar.api.batch.fs.InputFile.Type;
  36. import org.sonar.api.batch.fs.TextPointer;
  37. import org.sonar.api.batch.sensor.Sensor;
  38. import org.sonar.api.batch.sensor.SensorContext;
  39. import org.sonar.api.batch.sensor.SensorDescriptor;
  40. import org.sonar.api.batch.sensor.issue.NewIssue;
  41. import org.sonar.api.batch.sensor.issue.NewIssue.FlowType;
  42. import org.sonar.api.batch.sensor.issue.NewIssueLocation;
  43. import org.sonar.api.rule.RuleKey;
  44. import org.sonar.xoo.Xoo;
  45. import static org.sonar.api.batch.sensor.issue.NewIssue.FlowType.DATA;
  46. import static org.sonar.api.batch.sensor.issue.NewIssue.FlowType.EXECUTION;
  47. public class MultilineIssuesSensor implements Sensor {
  48. public static final String RULE_KEY = "MultilineIssue";
  49. private static final Pattern START_ISSUE_PATTERN = Pattern.compile("\\{xoo-start-issue:([0-9]+)\\}");
  50. private static final Pattern END_ISSUE_PATTERN = Pattern.compile("\\{xoo-end-issue:([0-9]+)\\}");
  51. private static final Pattern START_FLOW_PATTERN = Pattern.compile("\\{xoo-start-flow:([0-9]+):([0-9]+):([0-9]+)\\}");
  52. private static final Pattern END_FLOW_PATTERN = Pattern.compile("\\{xoo-end-flow:([0-9]+):([0-9]+):([0-9]+)\\}");
  53. private static final Pattern START_DATA_FLOW_PATTERN = Pattern.compile("\\{xoo-start-data-flow:([0-9]+):([0-9]+):([0-9]+)\\}");
  54. private static final Pattern END_DATA_FLOW_PATTERN = Pattern.compile("\\{xoo-end-data-flow:([0-9]+):([0-9]+):([0-9]+)\\}");
  55. private static final Pattern START_EXECUTION_FLOW_PATTERN = Pattern.compile("\\{xoo-start-execution-flow:([0-9]+):([0-9]+):([0-9]+)\\}");
  56. private static final Pattern END_EXECUTION_FLOW_PATTERN = Pattern.compile("\\{xoo-end-execution-flow:([0-9]+):([0-9]+):([0-9]+)\\}");
  57. @Override
  58. public void describe(SensorDescriptor descriptor) {
  59. descriptor
  60. .name("Multiline Issues")
  61. .onlyOnLanguages(Xoo.KEY)
  62. .createIssuesForRuleRepositories(XooRulesDefinition.XOO_REPOSITORY);
  63. }
  64. @Override
  65. public void execute(SensorContext context) {
  66. FileSystem fs = context.fileSystem();
  67. FilePredicates p = fs.predicates();
  68. for (InputFile file : fs.inputFiles(p.and(p.hasLanguages(Xoo.KEY), p.hasType(Type.MAIN)))) {
  69. createIssues(file, context);
  70. }
  71. }
  72. private void createIssues(InputFile file, SensorContext context) {
  73. Collection<ParsedIssue> issues = parseIssues(file);
  74. FlowIndex flowIndex = new FlowIndex();
  75. parseFlows(flowIndex, file, START_FLOW_PATTERN, END_FLOW_PATTERN, null);
  76. parseFlows(flowIndex, file, START_DATA_FLOW_PATTERN, END_DATA_FLOW_PATTERN, DATA);
  77. parseFlows(flowIndex, file, START_EXECUTION_FLOW_PATTERN, END_EXECUTION_FLOW_PATTERN, EXECUTION);
  78. createIssues(file, context, issues, flowIndex);
  79. }
  80. private static void createIssues(InputFile file, SensorContext context, Collection<ParsedIssue> parsedIssues, FlowIndex flowIndex) {
  81. RuleKey ruleKey = RuleKey.of(XooRulesDefinition.XOO_REPOSITORY, RULE_KEY);
  82. for (ParsedIssue parsedIssue : parsedIssues) {
  83. NewIssue newIssue = context.newIssue().forRule(ruleKey);
  84. NewIssueLocation primaryLocation = newIssue.newLocation()
  85. .on(file)
  86. .at(file.newRange(parsedIssue.start, parsedIssue.end));
  87. newIssue.at(primaryLocation.message("Primary location"));
  88. IssueFlows issueFlows = flowIndex.getFlows(parsedIssue.issueId);
  89. if (issueFlows != null) {
  90. for (ParsedFlow flow : issueFlows.getFlows()) {
  91. List<NewIssueLocation> flowLocations = new LinkedList<>();
  92. for (ParsedFlowLocation flowLocation : flow.getLocations()) {
  93. flowLocations.add(newIssue.newLocation()
  94. .on(file)
  95. .at(file.newRange(flowLocation.start, flowLocation.end))
  96. .message("Flow step #" + flowLocation.flowLocationId));
  97. }
  98. if (flow.getType() != null) {
  99. newIssue.addFlow(flowLocations, flow.getType(), "flow #" + flow.getFlowId());
  100. } else {
  101. newIssue.addFlow(flowLocations);
  102. }
  103. }
  104. }
  105. newIssue.save();
  106. }
  107. }
  108. private static Collection<ParsedIssue> parseIssues(InputFile file) {
  109. Map<Integer, ParsedIssue> issuesById = new HashMap<>();
  110. int currentLine = 0;
  111. try {
  112. for (String lineStr : file.contents().split("\\r?\\n")) {
  113. currentLine++;
  114. Matcher m = START_ISSUE_PATTERN.matcher(lineStr);
  115. while (m.find()) {
  116. Integer issueId = Integer.parseInt(m.group(1));
  117. issuesById.computeIfAbsent(issueId, ParsedIssue::new).start = file.newPointer(currentLine, m.end());
  118. }
  119. m = END_ISSUE_PATTERN.matcher(lineStr);
  120. while (m.find()) {
  121. Integer issueId = Integer.parseInt(m.group(1));
  122. issuesById.computeIfAbsent(issueId, ParsedIssue::new).end = file.newPointer(currentLine, m.start());
  123. }
  124. }
  125. } catch (IOException e) {
  126. throw new IllegalStateException("Unable to read file", e);
  127. }
  128. return issuesById.values();
  129. }
  130. private void parseFlows(FlowIndex flowIndex, InputFile file, Pattern flowStartPattern, Pattern flowEndPattern, @Nullable FlowType flowType) {
  131. int currentLine = 0;
  132. try {
  133. for (String lineStr : file.contents().split("\\r?\\n")) {
  134. currentLine++;
  135. Matcher m = flowStartPattern.matcher(lineStr);
  136. while (m.find()) {
  137. ParsedFlowLocation flowLocation = new ParsedFlowLocation(Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2)), Integer.parseInt(m.group(3)));
  138. flowLocation.start = file.newPointer(currentLine, m.end());
  139. flowIndex.addLocation(flowLocation, flowType);
  140. }
  141. m = flowEndPattern.matcher(lineStr);
  142. while (m.find()) {
  143. ParsedFlowLocation flowLocation = new ParsedFlowLocation(Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2)), Integer.parseInt(m.group(3)));
  144. flowLocation.end = file.newPointer(currentLine, m.start());
  145. flowIndex.addLocation(flowLocation, flowType);
  146. }
  147. }
  148. } catch (IOException e) {
  149. throw new IllegalStateException("Unable to read file", e);
  150. }
  151. }
  152. private static class ParsedIssue {
  153. private final int issueId;
  154. private TextPointer start;
  155. private TextPointer end;
  156. private ParsedIssue(int issueId) {
  157. this.issueId = issueId;
  158. }
  159. }
  160. private static class FlowIndex {
  161. private final Map<Integer, IssueFlows> flowsByIssueId = new HashMap<>();
  162. public void addLocation(ParsedFlowLocation flowLocation, @Nullable FlowType type) {
  163. flowsByIssueId.computeIfAbsent(flowLocation.issueId, issueId -> new IssueFlows()).addLocation(flowLocation, type);
  164. }
  165. @CheckForNull
  166. public IssueFlows getFlows(int issueId) {
  167. return flowsByIssueId.get(issueId);
  168. }
  169. }
  170. private static class IssueFlows {
  171. private final Map<Integer, ParsedFlow> flowById = new TreeMap<>();
  172. private void addLocation(ParsedFlowLocation flowLocation, @Nullable FlowType type) {
  173. flowById.computeIfAbsent(flowLocation.flowId, flowId -> new ParsedFlow(flowId, type)).addLocation(flowLocation);
  174. }
  175. Collection<ParsedFlow> getFlows() {
  176. return flowById.values();
  177. }
  178. }
  179. private static class ParsedFlow {
  180. private final int id;
  181. private final FlowType type;
  182. private final Map<Integer, ParsedFlowLocation> locationsById = new TreeMap<>();
  183. private String description;
  184. private ParsedFlow(int id, @Nullable FlowType type) {
  185. this.id = id;
  186. this.type = type;
  187. }
  188. private void addLocation(ParsedFlowLocation flowLocation) {
  189. if (locationsById.containsKey(flowLocation.flowLocationId)) {
  190. if (flowLocation.start != null) {
  191. locationsById.get(flowLocation.flowLocationId).start = flowLocation.start;
  192. } else if (flowLocation.end != null) {
  193. locationsById.get(flowLocation.flowLocationId).end = flowLocation.end;
  194. }
  195. } else {
  196. locationsById.put(flowLocation.flowLocationId, flowLocation);
  197. }
  198. }
  199. public Collection<ParsedFlowLocation> getLocations() {
  200. return locationsById.values();
  201. }
  202. public void setDescription(@Nullable String description) {
  203. this.description = description;
  204. }
  205. @CheckForNull
  206. public String getDescription() {
  207. return description;
  208. }
  209. @CheckForNull
  210. FlowType getType() {
  211. return type;
  212. }
  213. int getFlowId() {
  214. return id;
  215. }
  216. }
  217. private static class ParsedFlowLocation {
  218. private final int issueId;
  219. private final int flowId;
  220. private final int flowLocationId;
  221. private TextPointer start;
  222. private TextPointer end;
  223. public ParsedFlowLocation(int issueId, int flowId, int flowLocationId) {
  224. this.issueId = issueId;
  225. this.flowId = flowId;
  226. this.flowLocationId = flowLocationId;
  227. }
  228. }
  229. }