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.

SensorContextTester.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2020 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.api.batch.sensor.internal;
  21. import java.io.File;
  22. import java.io.Serializable;
  23. import java.nio.charset.Charset;
  24. import java.nio.file.Path;
  25. import java.util.ArrayList;
  26. import java.util.Collection;
  27. import java.util.Collections;
  28. import java.util.List;
  29. import java.util.Map;
  30. import java.util.Objects;
  31. import java.util.Set;
  32. import java.util.stream.Stream;
  33. import javax.annotation.CheckForNull;
  34. import javax.annotation.Nullable;
  35. import org.sonar.api.SonarQubeSide;
  36. import org.sonar.api.SonarRuntime;
  37. import org.sonar.api.batch.bootstrap.ProjectDefinition;
  38. import org.sonar.api.batch.fs.InputFile;
  39. import org.sonar.api.batch.fs.InputModule;
  40. import org.sonar.api.batch.fs.TextRange;
  41. import org.sonar.api.batch.fs.internal.DefaultFileSystem;
  42. import org.sonar.api.batch.fs.internal.DefaultInputFile;
  43. import org.sonar.api.batch.fs.internal.DefaultInputModule;
  44. import org.sonar.api.batch.fs.internal.DefaultInputProject;
  45. import org.sonar.api.batch.fs.internal.DefaultTextPointer;
  46. import org.sonar.api.batch.rule.ActiveRules;
  47. import org.sonar.api.batch.rule.internal.ActiveRulesBuilder;
  48. import org.sonar.api.batch.sensor.Sensor;
  49. import org.sonar.api.batch.sensor.SensorContext;
  50. import org.sonar.api.batch.sensor.code.NewSignificantCode;
  51. import org.sonar.api.batch.sensor.code.internal.DefaultSignificantCode;
  52. import org.sonar.api.batch.sensor.coverage.NewCoverage;
  53. import org.sonar.api.batch.sensor.coverage.internal.DefaultCoverage;
  54. import org.sonar.api.batch.sensor.cpd.NewCpdTokens;
  55. import org.sonar.api.batch.sensor.cpd.internal.DefaultCpdTokens;
  56. import org.sonar.api.batch.sensor.cpd.internal.TokensLine;
  57. import org.sonar.api.batch.sensor.error.AnalysisError;
  58. import org.sonar.api.batch.sensor.error.NewAnalysisError;
  59. import org.sonar.api.batch.sensor.error.internal.DefaultAnalysisError;
  60. import org.sonar.api.batch.sensor.highlighting.NewHighlighting;
  61. import org.sonar.api.batch.sensor.highlighting.TypeOfText;
  62. import org.sonar.api.batch.sensor.highlighting.internal.DefaultHighlighting;
  63. import org.sonar.api.batch.sensor.highlighting.internal.SyntaxHighlightingRule;
  64. import org.sonar.api.batch.sensor.issue.ExternalIssue;
  65. import org.sonar.api.batch.sensor.issue.Issue;
  66. import org.sonar.api.batch.sensor.issue.NewExternalIssue;
  67. import org.sonar.api.batch.sensor.issue.NewIssue;
  68. import org.sonar.api.batch.sensor.issue.internal.DefaultExternalIssue;
  69. import org.sonar.api.batch.sensor.issue.internal.DefaultIssue;
  70. import org.sonar.api.batch.sensor.measure.Measure;
  71. import org.sonar.api.batch.sensor.measure.NewMeasure;
  72. import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure;
  73. import org.sonar.api.batch.sensor.rule.AdHocRule;
  74. import org.sonar.api.batch.sensor.rule.NewAdHocRule;
  75. import org.sonar.api.batch.sensor.rule.internal.DefaultAdHocRule;
  76. import org.sonar.api.batch.sensor.symbol.NewSymbolTable;
  77. import org.sonar.api.batch.sensor.symbol.internal.DefaultSymbolTable;
  78. import org.sonar.api.config.Configuration;
  79. import org.sonar.api.config.internal.ConfigurationBridge;
  80. import org.sonar.api.config.internal.MapSettings;
  81. import org.sonar.api.internal.MetadataLoader;
  82. import org.sonar.api.internal.SonarRuntimeImpl;
  83. import org.sonar.api.measures.Metric;
  84. import org.sonar.api.scanner.fs.InputProject;
  85. import org.sonar.api.utils.System2;
  86. import org.sonar.api.utils.Version;
  87. import static java.util.Collections.unmodifiableMap;
  88. /**
  89. * Utility class to help testing {@link Sensor}. This is not an API and method signature may evolve.
  90. * <p>
  91. * Usage: call {@link #create(File)} to create an "in memory" implementation of {@link SensorContext} with a filesystem initialized with provided baseDir.
  92. * <p>
  93. * You have to manually register inputFiles using:
  94. * <pre>
  95. * sensorContextTester.fileSystem().add(new DefaultInputFile("myProjectKey", "src/Foo.java")
  96. * .setLanguage("java")
  97. * .initMetadata("public class Foo {\n}"));
  98. * </pre>
  99. * <p>
  100. * Then pass it to your {@link Sensor}. You can then query elements provided by your sensor using methods {@link #allIssues()}, ...
  101. */
  102. public class SensorContextTester implements SensorContext {
  103. private MapSettings settings;
  104. private DefaultFileSystem fs;
  105. private ActiveRules activeRules;
  106. private InMemorySensorStorage sensorStorage;
  107. private DefaultInputProject project;
  108. private DefaultInputModule module;
  109. private SonarRuntime runtime;
  110. private boolean cancelled;
  111. private SensorContextTester(Path moduleBaseDir) {
  112. this.settings = new MapSettings();
  113. this.fs = new DefaultFileSystem(moduleBaseDir).setEncoding(Charset.defaultCharset());
  114. this.activeRules = new ActiveRulesBuilder().build();
  115. this.sensorStorage = new InMemorySensorStorage();
  116. this.project = new DefaultInputProject(ProjectDefinition.create().setKey("projectKey").setBaseDir(moduleBaseDir.toFile()).setWorkDir(moduleBaseDir.resolve(".sonar").toFile()));
  117. this.module = new DefaultInputModule(ProjectDefinition.create().setKey("projectKey").setBaseDir(moduleBaseDir.toFile()).setWorkDir(moduleBaseDir.resolve(".sonar").toFile()));
  118. this.runtime = SonarRuntimeImpl.forSonarQube(MetadataLoader.loadVersion(System2.INSTANCE), SonarQubeSide.SCANNER, MetadataLoader.loadEdition(System2.INSTANCE));
  119. }
  120. public static SensorContextTester create(File moduleBaseDir) {
  121. return new SensorContextTester(moduleBaseDir.toPath());
  122. }
  123. public static SensorContextTester create(Path moduleBaseDir) {
  124. return new SensorContextTester(moduleBaseDir);
  125. }
  126. @Override
  127. public MapSettings settings() {
  128. return settings;
  129. }
  130. @Override
  131. public Configuration config() {
  132. return new ConfigurationBridge(settings);
  133. }
  134. public SensorContextTester setSettings(MapSettings settings) {
  135. this.settings = settings;
  136. return this;
  137. }
  138. @Override
  139. public DefaultFileSystem fileSystem() {
  140. return fs;
  141. }
  142. public SensorContextTester setFileSystem(DefaultFileSystem fs) {
  143. this.fs = fs;
  144. return this;
  145. }
  146. @Override
  147. public ActiveRules activeRules() {
  148. return activeRules;
  149. }
  150. public SensorContextTester setActiveRules(ActiveRules activeRules) {
  151. this.activeRules = activeRules;
  152. return this;
  153. }
  154. /**
  155. * Default value is the version of this API at compilation time. You can override it
  156. * using {@link #setRuntime(SonarRuntime)} to test your Sensor behaviour.
  157. */
  158. @Override
  159. public Version getSonarQubeVersion() {
  160. return runtime().getApiVersion();
  161. }
  162. /**
  163. * @see #setRuntime(SonarRuntime) to override defaults (SonarQube scanner with version
  164. * of this API as used at compilation time).
  165. */
  166. @Override
  167. public SonarRuntime runtime() {
  168. return runtime;
  169. }
  170. public SensorContextTester setRuntime(SonarRuntime runtime) {
  171. this.runtime = runtime;
  172. return this;
  173. }
  174. @Override
  175. public boolean isCancelled() {
  176. return cancelled;
  177. }
  178. public void setCancelled(boolean cancelled) {
  179. this.cancelled = cancelled;
  180. }
  181. @Override
  182. public InputModule module() {
  183. return module;
  184. }
  185. @Override
  186. public InputProject project() {
  187. return project;
  188. }
  189. @Override
  190. public <G extends Serializable> NewMeasure<G> newMeasure() {
  191. return new DefaultMeasure<>(sensorStorage);
  192. }
  193. public Collection<Measure> measures(String componentKey) {
  194. return sensorStorage.measuresByComponentAndMetric.getOrDefault(componentKey, Collections.emptyMap()).values();
  195. }
  196. public <G extends Serializable> Measure<G> measure(String componentKey, Metric<G> metric) {
  197. return measure(componentKey, metric.key());
  198. }
  199. public <G extends Serializable> Measure<G> measure(String componentKey, String metricKey) {
  200. return sensorStorage.measuresByComponentAndMetric.getOrDefault(componentKey, Collections.emptyMap()).get(metricKey);
  201. }
  202. @Override
  203. public NewIssue newIssue() {
  204. return new DefaultIssue(project, sensorStorage);
  205. }
  206. public Collection<Issue> allIssues() {
  207. return sensorStorage.allIssues;
  208. }
  209. @Override
  210. public NewExternalIssue newExternalIssue() {
  211. return new DefaultExternalIssue(project, sensorStorage);
  212. }
  213. @Override
  214. public NewAdHocRule newAdHocRule() {
  215. return new DefaultAdHocRule(sensorStorage);
  216. }
  217. public Collection<ExternalIssue> allExternalIssues() {
  218. return sensorStorage.allExternalIssues;
  219. }
  220. public Collection<AdHocRule> allAdHocRules() {
  221. return sensorStorage.allAdHocRules;
  222. }
  223. public Collection<AnalysisError> allAnalysisErrors() {
  224. return sensorStorage.allAnalysisErrors;
  225. }
  226. @CheckForNull
  227. public Integer lineHits(String fileKey, int line) {
  228. return sensorStorage.coverageByComponent.getOrDefault(fileKey, Collections.emptyList()).stream()
  229. .map(c -> c.hitsByLine().get(line))
  230. .flatMap(Stream::of)
  231. .filter(Objects::nonNull)
  232. .reduce(null, SensorContextTester::sumOrNull);
  233. }
  234. @CheckForNull
  235. public static Integer sumOrNull(@Nullable Integer o1, @Nullable Integer o2) {
  236. return o1 == null ? o2 : (o1 + o2);
  237. }
  238. @CheckForNull
  239. public Integer conditions(String fileKey, int line) {
  240. return sensorStorage.coverageByComponent.getOrDefault(fileKey, Collections.emptyList()).stream()
  241. .map(c -> c.conditionsByLine().get(line))
  242. .flatMap(Stream::of)
  243. .filter(Objects::nonNull)
  244. .reduce(null, SensorContextTester::maxOrNull);
  245. }
  246. @CheckForNull
  247. public Integer coveredConditions(String fileKey, int line) {
  248. return sensorStorage.coverageByComponent.getOrDefault(fileKey, Collections.emptyList()).stream()
  249. .map(c -> c.coveredConditionsByLine().get(line))
  250. .flatMap(Stream::of)
  251. .filter(Objects::nonNull)
  252. .reduce(null, SensorContextTester::maxOrNull);
  253. }
  254. @CheckForNull
  255. public TextRange significantCodeTextRange(String fileKey, int line) {
  256. if (sensorStorage.significantCodePerComponent.containsKey(fileKey)) {
  257. return sensorStorage.significantCodePerComponent.get(fileKey)
  258. .significantCodePerLine()
  259. .get(line);
  260. }
  261. return null;
  262. }
  263. @CheckForNull
  264. public static Integer maxOrNull(@Nullable Integer o1, @Nullable Integer o2) {
  265. return o1 == null ? o2 : Math.max(o1, o2);
  266. }
  267. @CheckForNull
  268. public List<TokensLine> cpdTokens(String componentKey) {
  269. DefaultCpdTokens defaultCpdTokens = sensorStorage.cpdTokensByComponent.get(componentKey);
  270. return defaultCpdTokens != null ? defaultCpdTokens.getTokenLines() : null;
  271. }
  272. @Override
  273. public NewHighlighting newHighlighting() {
  274. return new DefaultHighlighting(sensorStorage);
  275. }
  276. @Override
  277. public NewCoverage newCoverage() {
  278. return new DefaultCoverage(sensorStorage);
  279. }
  280. @Override
  281. public NewCpdTokens newCpdTokens() {
  282. return new DefaultCpdTokens(sensorStorage);
  283. }
  284. @Override
  285. public NewSymbolTable newSymbolTable() {
  286. return new DefaultSymbolTable(sensorStorage);
  287. }
  288. @Override
  289. public NewAnalysisError newAnalysisError() {
  290. return new DefaultAnalysisError(sensorStorage);
  291. }
  292. /**
  293. * Return list of syntax highlighting applied for a given position in a file. The result is a list because in theory you
  294. * can apply several styles to the same range.
  295. *
  296. * @param componentKey Key of the file like 'myProjectKey:src/foo.php'
  297. * @param line Line you want to query
  298. * @param lineOffset Offset you want to query.
  299. * @return List of styles applied to this position or empty list if there is no highlighting at this position.
  300. */
  301. public List<TypeOfText> highlightingTypeAt(String componentKey, int line, int lineOffset) {
  302. DefaultHighlighting syntaxHighlightingData = (DefaultHighlighting) sensorStorage.highlightingByComponent.get(componentKey);
  303. if (syntaxHighlightingData == null) {
  304. return Collections.emptyList();
  305. }
  306. List<TypeOfText> result = new ArrayList<>();
  307. DefaultTextPointer location = new DefaultTextPointer(line, lineOffset);
  308. for (SyntaxHighlightingRule sortedRule : syntaxHighlightingData.getSyntaxHighlightingRuleSet()) {
  309. if (sortedRule.range().start().compareTo(location) <= 0 && sortedRule.range().end().compareTo(location) > 0) {
  310. result.add(sortedRule.getTextType());
  311. }
  312. }
  313. return result;
  314. }
  315. /**
  316. * Return list of symbol references ranges for the symbol at a given position in a file.
  317. *
  318. * @param componentKey Key of the file like 'myProjectKey:src/foo.php'
  319. * @param line Line you want to query
  320. * @param lineOffset Offset you want to query.
  321. * @return List of references for the symbol (potentially empty) or null if there is no symbol at this position.
  322. */
  323. @CheckForNull
  324. public Collection<TextRange> referencesForSymbolAt(String componentKey, int line, int lineOffset) {
  325. DefaultSymbolTable symbolTable = sensorStorage.symbolsPerComponent.get(componentKey);
  326. if (symbolTable == null) {
  327. return null;
  328. }
  329. DefaultTextPointer location = new DefaultTextPointer(line, lineOffset);
  330. for (Map.Entry<TextRange, Set<TextRange>> symbol : symbolTable.getReferencesBySymbol().entrySet()) {
  331. if (symbol.getKey().start().compareTo(location) <= 0 && symbol.getKey().end().compareTo(location) > 0) {
  332. return symbol.getValue();
  333. }
  334. }
  335. return null;
  336. }
  337. @Override
  338. public void addContextProperty(String key, String value) {
  339. sensorStorage.storeProperty(key, value);
  340. }
  341. /**
  342. * @return an immutable map of the context properties defined with {@link SensorContext#addContextProperty(String, String)}.
  343. * @since 6.1
  344. */
  345. public Map<String, String> getContextProperties() {
  346. return unmodifiableMap(sensorStorage.contextProperties);
  347. }
  348. @Override
  349. public void markForPublishing(InputFile inputFile) {
  350. DefaultInputFile file = (DefaultInputFile) inputFile;
  351. file.setPublished(true);
  352. }
  353. @Override
  354. public NewSignificantCode newSignificantCode() {
  355. return new DefaultSignificantCode(sensorStorage);
  356. }
  357. }