From f3909f8c27ce76ccc42edf78aea767e8d1559f38 Mon Sep 17 00:00:00 2001
From: Dam <64742703+damien-urruty-sonarsource@users.noreply.github.com>
Date: Thu, 11 Mar 2021 13:22:36 +0100
Subject: [PATCH] SLE-430 Display taint vulnerabilities from
SonarQube/SonarCloud (#298)
---
.../sonarlint/eclipse/its/LocalLeakTest.java | 1 -
.../eclipse/its/SecondaryLocationsTest.java | 10 +-
.../internal/markers/MarkerFlowsTest.java | 70 ++++++
.../META-INF/MANIFEST.MF | 1 +
org.sonarlint.eclipse.core/plugin.xml | 33 +++
.../core/internal/SonarLintCorePlugin.java | 2 +
.../connected/ConnectedEngineFacade.java | 4 +-
.../jobs/AnalyzeConnectedProjectJob.java | 12 +-
.../internal/jobs/SonarLintMarkerUpdater.java | 187 +++++++++++++++-
.../core/internal/markers/MarkerFlow.java | 7 +
.../internal/markers/MarkerFlowLocation.java | 19 +-
.../core/internal/markers/MarkerFlows.java | 92 ++++++++
.../core/internal/markers/MarkerUtils.java | 23 +-
.../SonarLintGlobalConfiguration.java | 9 +
.../telemetry/SonarLintTelemetry.java | 12 +
.../tracking/ServerIssueTrackable.java | 2 +-
.../internal/tracking/ServerIssueUpdater.java | 2 +
.../TaintVulnerabilitiesListener.java | 24 ++
org.sonarlint.eclipse.ui/build.properties | 6 +-
.../icons/full/annotation16/vulnerability.png | Bin 0 -> 822 bytes
.../icons/full/annotation16/vulnerability.xcf | Bin 0 -> 1406 bytes
.../full/annotation16/vulnerability@2x.png | Bin 0 -> 1084 bytes
.../full/annotation16/vulnerability@2x.xcf | Bin 0 -> 1965 bytes
.../icons/full/eview16/vulnerabilities.png | Bin 0 -> 2701 bytes
.../icons/full/eview16/vulnerabilities.xcf | Bin 0 -> 3773 bytes
.../icons/full/eview16/vulnerabilities@2x.png | Bin 0 -> 4973 bytes
.../icons/full/eview16/vulnerabilities@2x.xcf | Bin 0 -> 5482 bytes
.../icons/sonarcloud-16.png | Bin 0 -> 1005 bytes
.../icons/sonarqube-16.png | Bin 0 -> 1203 bytes
org.sonarlint.eclipse.ui/plugin.xml | 205 +++++++++++++++++-
.../DeleteTaintMarkersOnEditorClosed.java | 80 +++++++
.../eclipse/ui/internal/SonarLintImages.java | 4 +
.../ui/internal/SonarLintUiPlugin.java | 15 ++
.../ui/internal/WindowOpenCloseListener.java | 3 +
.../SonarLintCodeMiningProvider.java | 32 ++-
...SonarLintFlowLocationNumberCodeMining.java | 2 +-
.../command/AbstractIssueCommand.java | 15 +-
.../command/OpenInBrowserCommand.java | 82 +++++++
.../flowlocations/SonarLintFlowAnnotator.java | 25 ++-
.../SonarLintFlowLocationsService.java | 30 ++-
.../ShowHideIssueFlowsMarkerResolver.java | 2 +-
.../markers/SonarLintMarkerImageProvider.java | 3 +
.../SonarLintMarkerResolutionGenerator.java | 2 +-
.../TaintVulnerabilityAvailablePopup.java | 68 ++++++
.../properties/AboutPropertyPage.java | 4 +
.../ui/internal/util/SelectionUtils.java | 4 +-
.../views/issues/IssueDescriptionField.java | 20 +-
.../issues/TaintVulnerabilitiesView.java | 61 ++++++
.../views/locations/IssueLocationsView.java | 182 +++++++++++-----
49 files changed, 1189 insertions(+), 166 deletions(-)
create mode 100644 org.sonarlint.eclipse.core.tests/src/test/java/org/sonarlint/eclipse/core/internal/markers/MarkerFlowsTest.java
create mode 100644 org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/markers/MarkerFlows.java
create mode 100644 org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/listener/TaintVulnerabilitiesListener.java
create mode 100644 org.sonarlint.eclipse.ui/icons/full/annotation16/vulnerability.png
create mode 100644 org.sonarlint.eclipse.ui/icons/full/annotation16/vulnerability.xcf
create mode 100644 org.sonarlint.eclipse.ui/icons/full/annotation16/vulnerability@2x.png
create mode 100644 org.sonarlint.eclipse.ui/icons/full/annotation16/vulnerability@2x.xcf
create mode 100644 org.sonarlint.eclipse.ui/icons/full/eview16/vulnerabilities.png
create mode 100644 org.sonarlint.eclipse.ui/icons/full/eview16/vulnerabilities.xcf
create mode 100644 org.sonarlint.eclipse.ui/icons/full/eview16/vulnerabilities@2x.png
create mode 100644 org.sonarlint.eclipse.ui/icons/full/eview16/vulnerabilities@2x.xcf
create mode 100644 org.sonarlint.eclipse.ui/icons/sonarcloud-16.png
create mode 100644 org.sonarlint.eclipse.ui/icons/sonarqube-16.png
create mode 100644 org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/DeleteTaintMarkersOnEditorClosed.java
create mode 100644 org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/command/OpenInBrowserCommand.java
create mode 100644 org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/popup/TaintVulnerabilityAvailablePopup.java
create mode 100644 org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/views/issues/TaintVulnerabilitiesView.java
diff --git a/its/org.sonarlint.eclipse.its/src/org/sonarlint/eclipse/its/LocalLeakTest.java b/its/org.sonarlint.eclipse.its/src/org/sonarlint/eclipse/its/LocalLeakTest.java
index d29d497ec..fcb6ad521 100644
--- a/its/org.sonarlint.eclipse.its/src/org/sonarlint/eclipse/its/LocalLeakTest.java
+++ b/its/org.sonarlint.eclipse.its/src/org/sonarlint/eclipse/its/LocalLeakTest.java
@@ -46,7 +46,6 @@ public void shouldComputeLocalLeak() throws Exception {
SWTBotView view = new OnTheFlyViewBot(bot).show();
assertThat(view.bot().tree().columns()).containsExactly("Date", "Description", "Resource");
- assertThat(view.bot().tree().getAllItems()).isEmpty();
IProject project = importEclipseProject("java/leak", "leak");
JobHelpers.waitForJobsToComplete(bot);
diff --git a/its/org.sonarlint.eclipse.its/src/org/sonarlint/eclipse/its/SecondaryLocationsTest.java b/its/org.sonarlint.eclipse.its/src/org/sonarlint/eclipse/its/SecondaryLocationsTest.java
index d8974e549..5f7f7fc2a 100644
--- a/its/org.sonarlint.eclipse.its/src/org/sonarlint/eclipse/its/SecondaryLocationsTest.java
+++ b/its/org.sonarlint.eclipse.its/src/org/sonarlint/eclipse/its/SecondaryLocationsTest.java
@@ -64,11 +64,11 @@ public void closeActiveEditor() {
}
@Test
- public void shouldShowSingleFlow() throws Exception {
+ public void shouldShowSingleFlow() {
SWTBotEclipseEditor helloEditor = openAndAnalyzeFile("SingleFlow.java");
String issueTitle = "\"NullPointerException\" will be thrown when invoking method \"doAnotherThingWith()\".";
- waitUntilOnTheFlyViewHasItemWithTitle(issueTitle + " [+1 flow]");
+ waitUntilOnTheFlyViewHasItemWithTitle(issueTitle + " [+5 locations]");
onTheFly.bot().tree().getAllItems()[0].select();
SWTBotView issueLocationsView = getIssueLocationsView();
@@ -86,7 +86,7 @@ public void shouldShowSingleFlow() throws Exception {
}
@Test
- public void shouldShowHighlightsOnly() throws Exception {
+ public void shouldShowHighlightsOnly() {
openAndAnalyzeFile("HighlightOnly.java");
String issueTitle = "Remove these useless parentheses.";
@@ -104,7 +104,7 @@ public void shouldShowHighlightsOnly() throws Exception {
}
@Test
- public void shouldShowMultipleFlows() throws Exception {
+ public void shouldShowMultipleFlows() {
SWTBotEclipseEditor helloEditor = openAndAnalyzeFile("MultiFlows.java");
String issueTitle = "\"NullPointerException\" will be thrown when invoking method \"doAnotherThingWith()\".";
@@ -140,7 +140,7 @@ public void shouldShowMultipleFlows() throws Exception {
}
@Test
- public void shouldShowFlattenedFlows() throws Exception {
+ public void shouldShowFlattenedFlows() {
SWTBotEclipseEditor cognitiveComplexityEditor = openAndAnalyzeFile("CognitiveComplexity.java");
String issueTitle = "Refactor this method to reduce its Cognitive Complexity from 24 to the 15 allowed.";
diff --git a/org.sonarlint.eclipse.core.tests/src/test/java/org/sonarlint/eclipse/core/internal/markers/MarkerFlowsTest.java b/org.sonarlint.eclipse.core.tests/src/test/java/org/sonarlint/eclipse/core/internal/markers/MarkerFlowsTest.java
new file mode 100644
index 000000000..368195024
--- /dev/null
+++ b/org.sonarlint.eclipse.core.tests/src/test/java/org/sonarlint/eclipse/core/internal/markers/MarkerFlowsTest.java
@@ -0,0 +1,70 @@
+/*
+ * SonarLint for Eclipse
+ * Copyright (C) 2015-2021 SonarSource SA
+ * sonarlint@sonarsource.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.sonarlint.eclipse.core.internal.markers;
+
+import java.util.Arrays;
+import org.junit.Test;
+import org.sonarlint.eclipse.tests.common.SonarTestCase;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class MarkerFlowsTest extends SonarTestCase {
+
+ @Test
+ public void display_a_correct_summary_for_secondary_locations_only_flows() {
+ MarkerFlow flow1 = new MarkerFlow(0);
+ new MarkerFlowLocation(flow1, "message1");
+ MarkerFlow flow2 = new MarkerFlow(0);
+ new MarkerFlowLocation(flow2, "message2");
+ MarkerFlow flow3 = new MarkerFlow(0);
+ new MarkerFlowLocation(flow3, "message3");
+ MarkerFlows markerFlows = new MarkerFlows(Arrays.asList(flow1, flow2, flow3));
+
+ assertThat(markerFlows.getSummaryDescription()).isEqualTo(" [+3 locations]");
+ }
+
+ @Test
+ public void display_a_correct_summary_for_multiple_flows() {
+ MarkerFlow flow1 = new MarkerFlow(0);
+ new MarkerFlowLocation(flow1, "message1");
+ new MarkerFlowLocation(flow1, "message11");
+ MarkerFlow flow2 = new MarkerFlow(0);
+ new MarkerFlowLocation(flow2, "message2");
+ new MarkerFlowLocation(flow2, "message22");
+ MarkerFlow flow3 = new MarkerFlow(0);
+ new MarkerFlowLocation(flow3, "message3");
+ new MarkerFlowLocation(flow3, "message33");
+ MarkerFlows markerFlows = new MarkerFlows(Arrays.asList(flow1, flow2, flow3));
+
+ assertThat(markerFlows.getSummaryDescription()).isEqualTo(" [+3 flows]");
+ }
+
+ @Test
+ public void display_a_correct_summary_for_single_flow() {
+ MarkerFlow flow1 = new MarkerFlow(0);
+ new MarkerFlowLocation(flow1, "message1");
+ new MarkerFlowLocation(flow1, "message11");
+ new MarkerFlowLocation(flow1, "message111");
+ MarkerFlows markerFlows = new MarkerFlows(Arrays.asList(flow1));
+
+ assertThat(markerFlows.getSummaryDescription()).isEqualTo(" [+3 locations]");
+ }
+
+}
diff --git a/org.sonarlint.eclipse.core/META-INF/MANIFEST.MF b/org.sonarlint.eclipse.core/META-INF/MANIFEST.MF
index 69b0846dd..5de79c9f7 100644
--- a/org.sonarlint.eclipse.core/META-INF/MANIFEST.MF
+++ b/org.sonarlint.eclipse.core/META-INF/MANIFEST.MF
@@ -12,6 +12,7 @@ Bundle-Localization: OSGI-INF/l10n/bundle
Export-Package: org.sonarlint.eclipse.core,
org.sonarlint.eclipse.core.analysis,
org.sonarlint.eclipse.core.configurator,
+ org.sonarlint.eclipse.core.listener,
org.sonarlint.eclipse.core.internal;x-friends:="org.sonarlint.eclipse.core.tests,org.sonarlint.eclipse.ui",
org.sonarlint.eclipse.core.internal.adapter;x-friends:="org.sonarlint.eclipse.ui",
org.sonarlint.eclipse.core.internal.engine;x-friends:="org.sonarlint.eclipse.ui,org.sonarlint.eclipse.core.tests",
diff --git a/org.sonarlint.eclipse.core/plugin.xml b/org.sonarlint.eclipse.core/plugin.xml
index e1df22260..404cab599 100644
--- a/org.sonarlint.eclipse.core/plugin.xml
+++ b/org.sonarlint.eclipse.core/plugin.xml
@@ -88,6 +88,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
getBoundProjects(String projectKey) {
public void updateProjectStorage(String projectKey, IProgressMonitor monitor) {
doWithEngine(engine -> {
engine.updateProject(createEndpointParams(), buildClientWithProxyAndCredentials(), projectKey,
- false, new WrappedProgressMonitor(monitor, "Update configuration from server '" + getId() + "' for project '" + projectKey + "'"));
+ true, new WrappedProgressMonitor(monitor, "Update configuration from server '" + getId() + "' for project '" + projectKey + "'"));
getBoundProjects(projectKey).forEach(p -> {
ProjectBinding projectBinding = engine.calculatePathPrefixes(projectKey, p.files().stream().map(ISonarLintFile::getProjectRelativePath).collect(toList()));
String idePathPrefix = projectBinding.idePathPrefix();
@@ -647,7 +647,7 @@ public void downloadServerIssues(String projectKey, IProgressMonitor monitor) {
public List downloadServerIssues(ProjectBinding projectBinding, String filePath, IProgressMonitor monitor) {
return withEngine(
engine -> engine.downloadServerIssues(createEndpointParams(), buildClientWithProxyAndCredentials(), projectBinding, filePath,
- false, new WrappedProgressMonitor(monitor, "Fetch issues")))
+ true, new WrappedProgressMonitor(monitor, "Fetch issues")))
.orElse(emptyList());
}
diff --git a/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/jobs/AnalyzeConnectedProjectJob.java b/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/jobs/AnalyzeConnectedProjectJob.java
index 8bfdc1189..0a74dbd4f 100644
--- a/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/jobs/AnalyzeConnectedProjectJob.java
+++ b/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/jobs/AnalyzeConnectedProjectJob.java
@@ -79,10 +79,7 @@ protected void trackIssues(Map docPerFile, Map filesWithAtLeastOneIssue = filesWithAtLeastOneIssue(rawIssuesPerResource);
- if (!filesWithAtLeastOneIssue.isEmpty()) {
- trackServerIssuesAsync(engineFacade, filesWithAtLeastOneIssue, docPerFile, triggerType);
- }
+ trackServerIssuesAsync(engineFacade, rawIssuesPerResource.keySet(), docPerFile, triggerType);
}
}
@@ -97,13 +94,6 @@ protected Collection trackFileIssues(ISonarLintFile file, List filesWithAtLeastOneIssue(Map> rawIssuesPerResource) {
- return rawIssuesPerResource.entrySet().stream()
- .filter(e -> !e.getValue().isEmpty())
- .map(Map.Entry::getKey)
- .collect(Collectors.toList());
- }
-
private void trackServerIssuesAsync(ConnectedEngineFacade engineFacade, Collection resources, Map docPerFile,
TriggerType triggerType) {
SonarLintCorePlugin.getInstance().getServerIssueUpdater().updateAsync(engineFacade, getProject(),
diff --git a/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/jobs/SonarLintMarkerUpdater.java b/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/jobs/SonarLintMarkerUpdater.java
index b0373b7e2..19d957cfc 100644
--- a/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/jobs/SonarLintMarkerUpdater.java
+++ b/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/jobs/SonarLintMarkerUpdater.java
@@ -35,28 +35,45 @@
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.Position;
import org.sonarlint.eclipse.core.SonarLintLogger;
import org.sonarlint.eclipse.core.internal.SonarLintCorePlugin;
import org.sonarlint.eclipse.core.internal.TriggerType;
+import org.sonarlint.eclipse.core.internal.engine.connected.ConnectedEngineFacade;
import org.sonarlint.eclipse.core.internal.markers.MarkerFlow;
import org.sonarlint.eclipse.core.internal.markers.MarkerFlowLocation;
+import org.sonarlint.eclipse.core.internal.markers.MarkerFlows;
import org.sonarlint.eclipse.core.internal.markers.MarkerUtils;
import org.sonarlint.eclipse.core.internal.markers.TextRange;
import org.sonarlint.eclipse.core.internal.preferences.SonarLintGlobalConfiguration;
+import org.sonarlint.eclipse.core.internal.preferences.SonarLintProjectConfiguration;
+import org.sonarlint.eclipse.core.internal.preferences.SonarLintProjectConfiguration.EclipseProjectBinding;
import org.sonarlint.eclipse.core.internal.resources.ProjectsProviderUtils;
+import org.sonarlint.eclipse.core.internal.tracking.ServerIssueTrackable;
import org.sonarlint.eclipse.core.internal.tracking.Trackable;
+import org.sonarlint.eclipse.core.internal.utils.StringUtils;
+import org.sonarlint.eclipse.core.listener.TaintVulnerabilitiesListener;
import org.sonarlint.eclipse.core.resource.ISonarLintFile;
import org.sonarlint.eclipse.core.resource.ISonarLintIssuable;
import org.sonarlint.eclipse.core.resource.ISonarLintProject;
import org.sonarsource.sonarlint.core.client.api.common.analysis.IssueLocation;
+import org.sonarsource.sonarlint.core.client.api.connected.ServerIssue;
+import org.sonarsource.sonarlint.core.client.api.connected.ServerIssue.Flow;
+import org.sonarsource.sonarlint.core.client.api.connected.ServerIssueLocation;
public class SonarLintMarkerUpdater {
+ private static TaintVulnerabilitiesListener taintVulnerabilitiesListener;
+
private SonarLintMarkerUpdater() {
}
+ public static void setTaintVulnerabilitiesListener(TaintVulnerabilitiesListener listener) {
+ taintVulnerabilitiesListener = listener;
+ }
+
public static void createOrUpdateMarkers(ISonarLintFile file, Optional openedDocument, Collection issues, TriggerType triggerType) {
try {
Set previousMarkersToDelete;
@@ -77,6 +94,64 @@ public static void createOrUpdateMarkers(ISonarLintFile file, Optional {
+ List taintVulnerabilities = facade.getServerIssues(binding, currentFile.getProjectRelativePath())
+ .stream()
+ .filter(i -> i.ruleKey().contains("security"))
+ .filter(i -> StringUtils.isEmpty(i.resolution()))
+ .collect(Collectors.toList());
+
+ List boundSiblingProjects = facade.getBoundProjects(binding.projectKey());
+ Map bindings = boundSiblingProjects.stream()
+ .collect(Collectors.toMap(p -> p, p -> SonarLintCorePlugin.loadConfig(p).getProjectBinding().get()));
+
+ for (ServerIssue taintIssue : taintVulnerabilities) {
+ Optional primaryLocationFile = findFileForLocationInBoundProjects(bindings, taintIssue.getFilePath());
+ if (primaryLocationFile.isPresent()) {
+ createTaintMarker(primaryLocationFile.get().getDocument(), primaryLocationFile.get(), taintIssue, bindings);
+ }
+ }
+ if (!taintVulnerabilities.isEmpty() && taintVulnerabilitiesListener != null) {
+ taintVulnerabilitiesListener.markersCreated(facade.isSonarCloud());
+ }
+ });
+
+ }
+
+ public static void deleteTaintMarkers(ISonarLintFile currentFile) {
+ try {
+ Set markersToDelete = new HashSet<>(Arrays.asList(currentFile.getResource().findMarkers(SonarLintCorePlugin.MARKER_TAINT_ID, false, IResource.DEPTH_ZERO)));
+ for (IMarker primaryLocationMarker : markersToDelete) {
+ MarkerUtils.getIssueFlows(primaryLocationMarker).deleteAllMarkers();
+ primaryLocationMarker.delete();
+ }
+ } catch (CoreException e) {
+ SonarLintLogger.get().error(e.getMessage(), e);
+ }
+ }
+
+ private static Optional findFileForLocationInBoundProjects(Map bindingsPerProjects, @Nullable String serverIssuePath) {
+ if (serverIssuePath == null) {
+ // Should never occur, no taint issues are at file level
+ return Optional.empty();
+ }
+ for (Map.Entry entry : bindingsPerProjects.entrySet()) {
+ Optional idePath = entry.getValue().serverPathToIdePath(serverIssuePath);
+ if (idePath.isPresent()) {
+ Optional primaryLocationFile = entry.getKey().find(idePath.get());
+ if (primaryLocationFile.isPresent()) {
+ return primaryLocationFile;
+ }
+ }
+ }
+ return Optional.empty();
+ }
+
public static Set getResourcesWithMarkers(ISonarLintProject project) throws CoreException {
return Arrays.stream(project.getResource().findMarkers(SonarLintCorePlugin.MARKER_ON_THE_FLY_ID, false, IResource.DEPTH_INFINITE))
.map(IMarker::getResource)
@@ -136,7 +211,8 @@ private static void createOrUpdateMarkers(ISonarLintFile file, Optional bindingsPerProjects) {
+ try {
+ IMarker marker = issuable.getResource().createMarker(SonarLintCorePlugin.MARKER_TAINT_ID);
+
+ setMarkerViewUtilsAttributes(issuable, marker);
+
+ updateMarkerAttributes(document, new ServerIssueTrackable(taintIssue), marker);
+ createFlowMarkersForTaint(taintIssue, marker, bindingsPerProjects);
+ } catch (CoreException e) {
+ SonarLintLogger.get().error("Unable to create marker", e);
+ }
+ }
+
+ private static void setMarkerViewUtilsAttributes(ISonarLintIssuable issuable, IMarker marker) throws CoreException {
+ // See MarkerViewUtil
marker.setAttribute("org.eclipse.ui.views.markers.name", issuable.getResourceNameForMarker());
marker.setAttribute("org.eclipse.ui.views.markers.path", issuable.getResourceContainerForMarker());
-
- updateMarkerAttributes(document, issuable, trackable, marker, triggerType);
}
- private static void updateMarkerAttributes(IDocument document, ISonarLintIssuable issuable, Trackable trackable, IMarker marker, TriggerType triggerType) throws CoreException {
+ private static void updateMarkerAttributes(IDocument document, Trackable trackable, IMarker marker) throws CoreException {
Map existingAttributes = marker.getAttributes();
setMarkerAttributeIfDifferent(marker, existingAttributes, MarkerUtils.SONAR_MARKER_RULE_KEY_ATTR, trackable.getRuleKey());
@@ -177,23 +277,23 @@ private static void updateMarkerAttributes(IDocument document, ISonarLintIssuabl
setMarkerAttributeIfDifferent(marker, existingAttributes, IMarker.CHAR_END, position.getOffset() + position.getLength());
}
- createFlowMarkers(document, issuable, trackable, marker, triggerType);
-
updateServerMarkerAttributes(trackable, marker);
}
- private static void createFlowMarkers(IDocument document, ISonarLintIssuable issuable, Trackable trackable, IMarker marker, TriggerType triggerType) throws CoreException {
- List flowsMarkers = new ArrayList<>();
+ private static void createFlowMarkersForLocalIssues(IDocument document, ISonarLintIssuable issuable, Trackable trackable, IMarker marker, String flowMarkerId)
+ throws CoreException {
+ List flows = new ArrayList<>();
int i = 1;
for (org.sonarsource.sonarlint.core.client.api.common.analysis.Issue.Flow engineFlow : trackable.getFlows()) {
MarkerFlow flow = new MarkerFlow(i);
- flowsMarkers.add(flow);
+ flows.add(flow);
List locations = new ArrayList<>(engineFlow.locations());
Collections.reverse(locations);
for (IssueLocation l : locations) {
+ l.getInputFile();
MarkerFlowLocation flowLocation = new MarkerFlowLocation(flow, l.getMessage());
try {
- IMarker m = issuable.getResource().createMarker(triggerType.isOnTheFly() ? SonarLintCorePlugin.MARKER_ON_THE_FLY_FLOW_ID : SonarLintCorePlugin.MARKER_REPORT_FLOW_ID);
+ IMarker m = issuable.getResource().createMarker(flowMarkerId);
m.setAttribute(IMarker.MESSAGE, l.getMessage());
m.setAttribute(IMarker.LINE_NUMBER, l.getStartLine() != null ? l.getStartLine() : 1);
Position flowPosition = MarkerUtils.getPosition(document, TextRange.get(l.getStartLine(), l.getStartLineOffset(), l.getEndLine(), l.getEndLineOffset()));
@@ -208,7 +308,61 @@ private static void createFlowMarkers(IDocument document, ISonarLintIssuable iss
}
i++;
}
- marker.setAttribute(MarkerUtils.SONAR_MARKER_EXTRA_LOCATIONS_ATTR, flowsMarkers);
+ marker.setAttribute(MarkerUtils.SONAR_MARKER_EXTRA_LOCATIONS_ATTR, new MarkerFlows(flows));
+ }
+
+ private static void createFlowMarkersForTaint(ServerIssue taintIssue, IMarker primaryLocationMarker, Map bindingsPerProjects)
+ throws CoreException {
+ List flows = new ArrayList<>();
+ int i = 1;
+ for (Flow engineFlow : taintIssue.getFlows()) {
+ MarkerFlow flow = new MarkerFlow(i);
+ flows.add(flow);
+ List locations = new ArrayList<>(engineFlow.locations());
+ Collections.reverse(locations);
+ for (ServerIssueLocation l : locations) {
+ MarkerFlowLocation flowLocation = new MarkerFlowLocation(flow, l.getMessage(), l.getFilePath());
+
+ Optional locationFile = findFileForLocationInBoundProjects(bindingsPerProjects, l.getFilePath());
+ if (!locationFile.isPresent()) {
+ continue;
+ }
+ ISonarLintFile file = locationFile.get();
+ try {
+ IMarker marker = createMarkerIfCodeMatches(file, l);
+ if (marker != null) {
+ flowLocation.setMarker(marker);
+ } else {
+ flowLocation.setDeleted(true);
+ }
+ } catch (Exception e) {
+ SonarLintLogger.get().debug("Unable to create flow marker", e);
+ }
+ }
+ i++;
+ }
+ primaryLocationMarker.setAttribute(MarkerUtils.SONAR_MARKER_EXTRA_LOCATIONS_ATTR, new MarkerFlows(flows));
+ }
+
+ @Nullable
+ private static IMarker createMarkerIfCodeMatches(ISonarLintFile file, ServerIssueLocation location) throws BadLocationException, CoreException {
+ IDocument document = file.getDocument();
+ int startOffset = document.getLineOffset(location.getStartLine() - 1) + location.getStartLineOffset();
+ int endOffset = document.getLineOffset(location.getEndLine() - 1) + location.getEndLineOffset();
+ String inEditorCode = document.get(startOffset, endOffset - startOffset);
+ if (inEditorCode.equals(location.getCodeSnippet())) {
+ IMarker marker = file.getResource().createMarker(SonarLintCorePlugin.MARKER_TAINT_FLOW_ID);
+ marker.setAttribute(IMarker.MESSAGE, location.getMessage());
+ marker.setAttribute(IMarker.LINE_NUMBER, location.getStartLine() != null ? location.getStartLine() : 1);
+ Position flowPosition = MarkerUtils.getPosition(document,
+ TextRange.get(location.getStartLine(), location.getStartLineOffset(), location.getEndLine(), location.getEndLineOffset()));
+ if (flowPosition != null) {
+ marker.setAttribute(IMarker.CHAR_START, flowPosition.getOffset());
+ marker.setAttribute(IMarker.CHAR_END, flowPosition.getOffset() + flowPosition.getLength());
+ }
+ return marker;
+ }
+ return null;
}
/**
@@ -265,4 +419,13 @@ public static void deleteAllMarkersFromReport() {
p.deleteAllMarkers(SonarLintCorePlugin.MARKER_REPORT_FLOW_ID);
});
}
+
+ public static void deleteAllMarkersFromTaint() {
+ ProjectsProviderUtils.allProjects().stream()
+ .filter(ISonarLintProject::isOpen)
+ .forEach(p -> {
+ p.deleteAllMarkers(SonarLintCorePlugin.MARKER_TAINT_ID);
+ p.deleteAllMarkers(SonarLintCorePlugin.MARKER_TAINT_FLOW_ID);
+ });
+ }
}
diff --git a/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/markers/MarkerFlow.java b/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/markers/MarkerFlow.java
index 9122345c4..48f9d3fc8 100644
--- a/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/markers/MarkerFlow.java
+++ b/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/markers/MarkerFlow.java
@@ -38,4 +38,11 @@ public List getLocations() {
return locations;
}
+ public boolean areAllLocationsInSameFile() {
+ return locations.stream()
+ .map(MarkerFlowLocation::getFilePath)
+ .distinct()
+ .count() == 1;
+ }
+
}
diff --git a/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/markers/MarkerFlowLocation.java b/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/markers/MarkerFlowLocation.java
index e2a0bb3ec..bfa81ed7f 100644
--- a/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/markers/MarkerFlowLocation.java
+++ b/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/markers/MarkerFlowLocation.java
@@ -20,21 +20,31 @@
package org.sonarlint.eclipse.core.internal.markers;
import org.eclipse.core.resources.IMarker;
+import org.eclipse.jdt.annotation.Nullable;
public class MarkerFlowLocation {
private final MarkerFlow parent;
private final int number;
+ @Nullable
private final String message;
+ @Nullable
private IMarker marker;
private boolean deleted;
+ @Nullable
+ private String filePath;
- public MarkerFlowLocation(MarkerFlow parent, String message) {
+ public MarkerFlowLocation(MarkerFlow parent, @Nullable String message) {
this.parent = parent;
this.parent.locations.add(this);
this.number = this.parent.locations.size();
this.message = message;
}
+ public MarkerFlowLocation(MarkerFlow parent, @Nullable String message, @Nullable String filePath) {
+ this(parent, message);
+ this.filePath = filePath;
+ }
+
public MarkerFlow getParent() {
return parent;
}
@@ -43,6 +53,7 @@ public int getNumber() {
return number;
}
+ @Nullable
public String getMessage() {
return message;
}
@@ -51,6 +62,7 @@ public void setMarker(IMarker marker) {
this.marker = marker;
}
+ @Nullable
public IMarker getMarker() {
return marker;
}
@@ -62,4 +74,9 @@ public boolean isDeleted() {
public void setDeleted(boolean deleted) {
this.deleted = deleted;
}
+
+ @Nullable
+ public String getFilePath() {
+ return filePath;
+ }
}
diff --git a/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/markers/MarkerFlows.java b/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/markers/MarkerFlows.java
new file mode 100644
index 000000000..fc4300d27
--- /dev/null
+++ b/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/markers/MarkerFlows.java
@@ -0,0 +1,92 @@
+/*
+ * SonarLint for Eclipse
+ * Copyright (C) 2015-2021 SonarSource SA
+ * sonarlint@sonarsource.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.sonarlint.eclipse.core.internal.markers;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Stream;
+import org.eclipse.core.runtime.CoreException;
+import org.sonarlint.eclipse.core.SonarLintLogger;
+
+public class MarkerFlows {
+
+ private final List flows;
+
+ public MarkerFlows(List flows) {
+ this.flows = flows;
+ }
+
+ public List getFlows() {
+ return flows;
+ }
+
+ public void deleteAllMarkers() {
+ flows.stream()
+ .flatMap(f -> f.getLocations().stream())
+ .map(MarkerFlowLocation::getMarker)
+ .filter(Objects::nonNull)
+ .forEach(m -> {
+ try {
+ m.delete();
+ } catch (CoreException e) {
+ SonarLintLogger.get().error(e.getMessage(), e);
+ }
+ });
+ }
+
+ /**
+ * Special case when all flows have a single location, this is called "secondary locations"
+ */
+ public boolean isSecondaryLocations() {
+ return !flows.isEmpty() && flows.stream().allMatch(f -> f.getLocations().size() == 1);
+ }
+
+ public boolean isEmpty() {
+ return flows.isEmpty();
+ }
+
+ public Stream allLocationsAsStream() {
+ return flows.stream().flatMap(f -> f.getLocations().stream());
+ }
+
+ public int count() {
+ return flows.size();
+ }
+
+ public String getSummaryDescription() {
+ if (!isEmpty()) {
+ String kind;
+ int count;
+ if (isSecondaryLocations() || count() == 1) {
+ kind = "location";
+ count = (int) flows.stream().flatMap(flow -> flow.locations.stream()).count();
+ } else {
+ kind = "flow";
+ count = count();
+ }
+ return " [+" + count + " " + pluralize(kind, count) + "]";
+ }
+ return "";
+ }
+
+ private static String pluralize(String str, int count) {
+ return count == 1 ? str : (str + "s");
+ }
+}
diff --git a/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/markers/MarkerUtils.java b/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/markers/MarkerUtils.java
index bbc29a636..50d51a486 100644
--- a/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/markers/MarkerUtils.java
+++ b/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/markers/MarkerUtils.java
@@ -19,8 +19,10 @@
*/
package org.sonarlint.eclipse.core.internal.markers;
-import java.util.List;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.Optional;
+import java.util.Set;
import java.util.function.BiFunction;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
@@ -37,7 +39,7 @@
import org.sonarlint.eclipse.core.internal.preferences.SonarLintGlobalConfiguration;
import org.sonarsource.sonarlint.core.client.api.common.RuleKey;
-import static java.util.Collections.emptyList;
+import static java.util.Arrays.asList;
public final class MarkerUtils {
@@ -50,6 +52,9 @@ public final class MarkerUtils {
public static final String SONAR_MARKER_SERVER_ISSUE_KEY_ATTR = "serverissuekey";
public static final String SONAR_MARKER_EXTRA_LOCATIONS_ATTR = "extralocations";
+ public static final Set SONARLINT_PRIMARY_MARKER_IDS = new HashSet<>(
+ asList(SonarLintCorePlugin.MARKER_ON_THE_FLY_ID, SonarLintCorePlugin.MARKER_REPORT_ID, SonarLintCorePlugin.MARKER_TAINT_ID));
+
private MarkerUtils() {
}
@@ -121,20 +126,12 @@ public static RuleKey getRuleKey(IMarker marker) {
return RuleKey.parse(repositoryAndKey);
}
- public static List getIssueFlows(IMarker marker) {
- List flowsMarkers;
+ public static MarkerFlows getIssueFlows(IMarker marker) {
try {
- flowsMarkers = Optional.ofNullable((List) marker.getAttribute(SONAR_MARKER_EXTRA_LOCATIONS_ATTR)).orElse(emptyList());
+ return Optional.ofNullable((MarkerFlows) marker.getAttribute(SONAR_MARKER_EXTRA_LOCATIONS_ATTR)).orElseGet(() -> new MarkerFlows(Collections.emptyList()));
} catch (CoreException e) {
- flowsMarkers = emptyList();
+ return new MarkerFlows(Collections.emptyList());
}
- return flowsMarkers;
}
- /**
- * Special case when all flows have a single location, this is called "secondary locations"
- */
- public static boolean isSecondaryLocations(List flowsMarkers) {
- return !flowsMarkers.isEmpty() && flowsMarkers.stream().allMatch(f -> f.getLocations().size() == 1);
- }
}
diff --git a/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/preferences/SonarLintGlobalConfiguration.java b/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/preferences/SonarLintGlobalConfiguration.java
index 92fedd739..24adfe030 100644
--- a/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/preferences/SonarLintGlobalConfiguration.java
+++ b/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/preferences/SonarLintGlobalConfiguration.java
@@ -78,6 +78,7 @@ public class SonarLintGlobalConfiguration {
public static final String PREF_TEST_FILE_REGEXPS_DEFAULT = ""; //$NON-NLS-1$
public static final String PREF_SKIP_CONFIRM_ANALYZE_MULTIPLE_FILES = "skipConfirmAnalyzeMultipleFiles"; //$NON-NLS-1$
public static final String PREF_NODEJS_PATH = "nodeJsPath"; //$NON-NLS-1$
+ private static final String PREF_TAINT_VULNERABILITY_DISPLAYED = "taintVulnerabilityDisplayed";
private SonarLintGlobalConfiguration() {
// Utility class
@@ -313,4 +314,12 @@ public static void setNodeJsPath(String path) {
public static String getNodejsPath() {
return getPreferenceString(PREF_NODEJS_PATH);
}
+
+ public static boolean taintVulnerabilityNeverBeenDisplayed() {
+ return !getPreferenceBoolean(PREF_TAINT_VULNERABILITY_DISPLAYED);
+ }
+
+ public static void setTaintVulnerabilityDisplayed() {
+ setPreferenceBoolean(PREF_TAINT_VULNERABILITY_DISPLAYED, true);
+ }
}
diff --git a/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/telemetry/SonarLintTelemetry.java b/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/telemetry/SonarLintTelemetry.java
index 68fcb6a1d..46b30dd27 100644
--- a/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/telemetry/SonarLintTelemetry.java
+++ b/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/telemetry/SonarLintTelemetry.java
@@ -196,6 +196,18 @@ public void showHotspotRequestReceived() {
}
}
+ public void taintVulnerabilitiesInvestigatedLocally() {
+ if (enabled()) {
+ telemetry.taintVulnerabilitiesInvestigatedLocally();
+ }
+ }
+
+ public void taintVulnerabilitiesInvestigatedRemotely() {
+ if (enabled()) {
+ telemetry.taintVulnerabilitiesInvestigatedRemotely();
+ }
+ }
+
public void stop() {
if (scheduledJob != null) {
scheduledJob.cancel();
diff --git a/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/tracking/ServerIssueTrackable.java b/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/tracking/ServerIssueTrackable.java
index 134a588c4..a3bbdf498 100644
--- a/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/tracking/ServerIssueTrackable.java
+++ b/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/tracking/ServerIssueTrackable.java
@@ -74,7 +74,7 @@ public String getRuleKey() {
@Override
public String getRuleName() {
- throw new UnsupportedOperationException();
+ return "";
}
@Override
diff --git a/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/tracking/ServerIssueUpdater.java b/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/tracking/ServerIssueUpdater.java
index 9d9e9ed29..2ad1cdb09 100644
--- a/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/tracking/ServerIssueUpdater.java
+++ b/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/tracking/ServerIssueUpdater.java
@@ -36,6 +36,7 @@
import org.sonarlint.eclipse.core.internal.TriggerType;
import org.sonarlint.eclipse.core.internal.engine.connected.ConnectedEngineFacade;
import org.sonarlint.eclipse.core.internal.jobs.AsyncServerMarkerUpdaterJob;
+import org.sonarlint.eclipse.core.internal.jobs.SonarLintMarkerUpdater;
import org.sonarlint.eclipse.core.resource.ISonarLintFile;
import org.sonarlint.eclipse.core.resource.ISonarLintIssuable;
import org.sonarlint.eclipse.core.resource.ISonarLintProject;
@@ -97,6 +98,7 @@ protected IStatus run(IProgressMonitor monitor) {
Collection tracked = issueTracker.matchAndTrackServerIssues(file, serverIssuesTrackable);
issueTracker.updateCache(file, tracked);
trackedIssues.put(issuable, tracked);
+ SonarLintMarkerUpdater.refreshMarkersForTaint(file, engineFacade);
}
}
if (!trackedIssues.isEmpty()) {
diff --git a/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/listener/TaintVulnerabilitiesListener.java b/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/listener/TaintVulnerabilitiesListener.java
new file mode 100644
index 000000000..1d6b92685
--- /dev/null
+++ b/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/listener/TaintVulnerabilitiesListener.java
@@ -0,0 +1,24 @@
+/*
+ * SonarLint for Eclipse
+ * Copyright (C) 2015-2021 SonarSource SA
+ * sonarlint@sonarsource.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.sonarlint.eclipse.core.listener;
+
+public interface TaintVulnerabilitiesListener {
+ void markersCreated(boolean comeFromSonarCloud);
+}
diff --git a/org.sonarlint.eclipse.ui/build.properties b/org.sonarlint.eclipse.ui/build.properties
index 71881feb6..3f2120f71 100644
--- a/org.sonarlint.eclipse.ui/build.properties
+++ b/org.sonarlint.eclipse.ui/build.properties
@@ -23,4 +23,8 @@ bin.excludes = icons/full/eview16/rule.xcf,\
icons/full/eview16/hotspots@2x.xcf,\
icons/priority/high.xcf,\
icons/priority/low.xcf,\
- icons/priority/medium.xcf
+ icons/priority/medium.xcf,\
+ icons/full/eview16/vulnerabilities.xcf,\
+ icons/full/eview16/vulnerabilities@2x.xcf,\
+ icons/full/annotation16/vulnerability.xcf,\
+ icons/full/annotation16/vulnerability@2x.xcf
diff --git a/org.sonarlint.eclipse.ui/icons/full/annotation16/vulnerability.png b/org.sonarlint.eclipse.ui/icons/full/annotation16/vulnerability.png
new file mode 100644
index 0000000000000000000000000000000000000000..72f2b636e879a77f1a222c5c3d3638e078694e0f
GIT binary patch
literal 822
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
literal 0
HcmV?d00001
diff --git a/org.sonarlint.eclipse.ui/icons/full/annotation16/vulnerability@2x.xcf b/org.sonarlint.eclipse.ui/icons/full/annotation16/vulnerability@2x.xcf
new file mode 100644
index 0000000000000000000000000000000000000000..5bac0dcf56b62487d916caaf82366506d30e5814
GIT binary patch
literal 1965
zcmb7F&x>1C6ux;slIb*^Br~0{9gDnaIc&e{9@zQ7D4oPS8aW{1Xb|qM*=Sp%g1KnS1)3``$}m!Hp-Jd%o|SbI-l!
zyyT@DZEbsd?M-htP)b0&L>Yettw3`*EDO+?*M1U$6>=KtKrQG}f;n#y`a87TMQIsr
zwYnYI-HA5f5*)v^IvnlyI^J;5i#9IJu5NbQgWh1rTQx@Rbk|#6U_R;+!t4JLR-XBA
zcyGAfYDfL9rvkA3r&vPdxF3zYsNepeGvpnE&COwFP4e{IgaYX-7aEgSZwo99v(r@GrsaERty-*elBt%!7ySagU@B~
zYzBK7d@_TJ8GI^(OTc_yDg^*Oo9n-jU>I?MF+pIT&*1DA0Pa+*cT)uGCCYUn)?RGQ
z>S1MtSl41JL|+_NW&Nz6sv~txE(!8yBsCTU)o0~`uqD;48rEt>IMPO%C8@^PL9Bu-
zOS*z;7GtbgoEQ3nnm6ucQ9_ARf)s2G)-TH^tBV6IlyZ&FL53ry_*DlHCyi9WYB6*9
zI)0p2kh)sSPfJ!9t?^NFLO1F~G;tt4=U$s}ulc|<8YGPd3&IJG{XjVZ+K_gD%F1S}
zEac!!*opx5(V)+mS657GLEy(E?H2xJL`#9`7CTIz%=~;nYQ`WJRnf6>4Rhc}$0-?~wbpL*RxLqEi)yNdV-fDM{PLMzpQwPz+a5<&ZB|LNT^^6e}bc
zcQLIy31`a##x+bXI0lf3;u*s2l|!N*a+WxH
z@gJh^FpD=WqE$ysz9za;5l8P4J+eU8iSAX5d4TCWbC*6#7{*gro{uBTLVxnGNcvAq
zNH6L+qFWfw#S+o4Y+X9~WAFWMj_}hr+#&tjtR?j86MO2R%NW&-l-r~y4jkL516!X1
zzn0
zaB^>EX>4U6ba`-PAZ2)IW&i+q+O1bxlH@21{O1&V1SA21P
z?ooN^^JP!Xck^+;DNt%23kh=<5{oAbZwqAJBpI!&Cqs@TpijbP4P7V#kWcR20NRfL
zeFFLFnP>Fx-V^wo&$v8c84gD1yqzHb9{dIIhlS;~h(AxHVfDqG3*^z_7=3$}T>zrt
zM3glbIt%^L0K1d*S-_>ZJ^H$?ge%%?D=2Ta+JdW6A_gnNkfTPK2KCiesxZ-UXGP0g
zLDU?JqMW$OPAbK)e(fj)(#DW;TiW|Of{hOP|xjFG}ciYvZ^q9vA8az*%5SA7jtYpki}
z#!VX1L<>z@Y^mi=rXy2y*L@FNd+e#_fmjtp}{&66`5$vK|PP0nm$YzogX(ob$oqsbU3be-4_y19EW
z_buLxkKf{rzhcfz>i!Sr45)kM?G0;f*15VE+q-aLY6ZIwGMS^nZ7m+}TIt@cZmAKP
z>#yYO*NSr4ikiw0`L?2;Snwl@eqy2d6HDGHH2)RkOR;%p>Q{=sZPC{jXgdob1RG*7
zFGaZ``0A2D)Qz0`>SAovT1WDGYOoy*`TEE`$(#FwXMwBD$$4@(n`un5gEAY3=&~q9
zf-y_Z*xvG`Tes~fk68WL_7~GXx8}vhFHOYW+j0T5x?Ei&jdmXer?q8zv_ryWdMTG@
zjkl97;iw>fx#_#ZmPURdvz)fa`KzVjecOU14U~z&`TCGX4$t$qB8ug)7>E5$K#t71
zRmUuw@QB)^av&?Hx|egnXq@=SF-ecK`z}k3{dcrKwdR)s!*7a9sRIU8yVZ7dgS2DE
zTugGjmi=Srw;tZx$nTnE9%8Cpe+0
z>DH8?2~bvZ29WqUb;RC!K?O-Yw(}L$w&72yRWPzfJ+%`7Id!FK`YqFyk0)S@HRVlk
zjf#WfQ+&L}iTPEaxN+VKjf7j={=fkv-c|{H(EC0h?*;P)mh(O;#YJPJuU*|^Aw
zC}cN&)c!X1B8lVh@<*p7Gu2$F(Ak;7CYehuJ5;#vO*JE#&j?>?
zN<*y7ih9aY!qK}uZR`yk5b}GkL5Tg8A?TVcdT;wS4%DIESqO*I1b6-g*^NxOb+f55
z0004mX+uL$Nkc;*aB^>EX>4Tx0C=2zkv&MmKp2MKrbN_;mtPe_uLwdwgjUGRGUg>I4d3x~j{x8A;yla$-k)PYEm#Z)
zh{SWuFm2)u;+aj`;Ji;9Wo204C(LTvt4P<6LrC;F(b~n@JK!iN#VED_zXW
zrbawX98)!&@`aqoD(5ZETD8vF@8mxW7xa~7uG1Vr3X52R1Q81AsGtfPaoTlKEM(|B
z;o~26{Svtpa#g^{v49#h$gUr}2fu4;6{jY>q;LZ0eQ}(RQ6RJnv>J}{ee5``6Cn5u
zT)3^~VNQ3M$elp(46RQ}kF+
zT9iamcCn|TNYW#vO&3lCr>vJ0XL6dxbez*e=fogw*s%Hc`WOHD*IE)IjwUO!fJ#6G
zyf$pz2G!6%CC5uiL+a6G+uZD1
zRbFQ7*|xPYEi3DD?)==+y2D47J$uSLxVN{ht;92NLS%AA2BUhu`>eKlMN&Jr0}610RA;r6J+lE|jMF(I!?WP>
zP&l09?CmYOeZ4-AH%(3-yJNE(K+r
z?F4h@%#8>kOn~g{?0y>`bh)%lkLQtX;(6dca7B}qdh_Sc3_I{nQ`3a*?yu?LP-slk
zty{Bg{I|>f0Wednx)YkLlv?9?Jl@&0)(-4^6HF~BE%S^tQV1yl2zwW>wkx2?$^+Mo
z>1TGYC@=W(?OP^5T#{mOAqEB}xUjxoRb7&9pVt-X800000NkvXX
Hu0mjfk-R#6-gx2nf;60EIh@znDgalXb=5IuRXF+t&e};!u=q@64
zCv~PX@+)r!~
zhKElx9JAby4DTp!OOE6USu5ti+W8yHGwiWKu43g1BU{sD$tY$r<#fezIIv>JXx_RxT`K2_h2=f5h|^=G3nRr8rnJ0g=$eb|Wj!kf
zTL%2Z!4}IJs3fyiAvu~}-o0i=E)%mMC&ju2Q}#U0RnM-Ih;x4=%It8HghE_fy=&
z%hQEyWo!3fEb(WeH{?=(D){O&wr8w@n;OF6br1Ic0r|#L37Cqrbjh;l1+hQKx%MyR
zT(z}WDMnUHsjS16V~KqJf#|8#m5pg?np88I@litur#E!V`3>#&({Jz*bPHq}M2gi|
zKb9{*Qvf2>&X98juecLEei0DyOK`};P}JRo=Qd#>mw0Iw#K)~oST64SWx_t50W8-u
zJfG=l;a})sC|V$x(V*uyVgDEFEeb~5rhALx8vf0D*VGqzL8AOIKz)_(SEwH8Q*Bp`eMF0Gn2W@W+7Xq0UP&}$+VyC!W^+WdCwpx>GDMQztJ(?(8mJzL
zPP3;X)EBU+5AKNS)T42Y6o+G~<6gpXh+=DAj)ZCr
zPb3Opn@&AOrX&3INK6Ae_v>-tzAzpKqja$b>L*$fkNe#5`jUhimtgS6r-rCsjZY@x
zaXdYjzz<)&KdQxXg*dJdfVEViTI-%4YOgQQ=*xZl)-xwR$97MjI!ILDt1)rDm1xe7
zL%1N{qS8sMz4oEOQ{TrrRsQUgH|oq=I+@?lXlDc6L$tDij>OM?4XDH9sm*ks@ml@a
z+1MD_U=bAo+TSf^IhLAQ;L|3>XSP5i!BkW<=o5VIV
zywIRvrk+xC3T3!WCteoJunj#3twOJcZj5TWgl>^%i@#)2I&E(0mJo58n_9>68ReQ~@?oSR#T&
zI_RH`W_22!RMyTfw}z;uTpBR@y8+sJ&(Z@I0CZg+)|ELSnw<0SuKVwOP(xWI^hY@e
zeJ6AX<)j#o@;um^U_;lG_PH}#Lo?-O{gshAnX#@|NR}=~p;-B1wkX5@rvmYdUhWI6
zb)cc$-d_frLv*XkFfB{IVKH2UUPeV4(UJvzmj(P_
zHG#n&27eg*Vep5+A4X&t{9$B6^l+77hQp073=g@Z648j3EbzN5;0LP-tkJ~6T?dXH
zed&(*1|8S7pEz-7_wGXz)q;wHQG12=cW6}Ye1Z>PS4zCuq{$I3PM5x1B{)Mw}(&c$8Y4R>U`SHTTH5^IFIMxL4JIuMkhO;
z;ukTo<7%Ng`TpKV-k<#GCe@&c1-HL&(*q~{SxWAB3a%(mbOy(>+|;0`Sggq
zT}yyhJHP&BcENY9_*cm2Uw_54kmGa8$tKu+6M%MSL=*;;x6J0|za2_wQ)`nqNdF$Z
zu{LjJZkRs?qiutqn4sDNwd03*J)Ryn8q7BasID9Z_ztJjnE~c+#Zfc3emr0h&o-GK
m!+FK^sdI2%F=uc-y&YtJ33?p*Jm^oLyWG(j&MU4@T=5I7gfbog
literal 0
HcmV?d00001
diff --git a/org.sonarlint.eclipse.ui/icons/full/eview16/vulnerabilities@2x.png b/org.sonarlint.eclipse.ui/icons/full/eview16/vulnerabilities@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..b0286a45caf3e04c7b121e7a6ffbace2758f3e67
GIT binary patch
literal 4973
zcmV-z6O!zSP)
zaB^>EX>4U6ba`-PAZ2)IW&i+q+O3#rcH}q?g#Ysta|CYSavZMj^$q6u^MO)sF1xC`
z-&l4^sf7Z8Kt#}H{r9hJ{=-l3*_$Z2q?(fDC)QYf*LfH3f$F*rQZ4yC
z)P9FL?i)`i_un4=j3@oNq~Fc^iI~aK6kZg(U%>^-rw{&$An2b9jeR(up8H%#9QzQ=
z+2uPD67n&<$KKla1brm=ab&(`|9SK!`M%xf!2LFu3o%h!B
zzTpaQo{n3+V=$*s_-Ql0yZBEwv-i*xIa{8&A}+5WBn+*besUKDar>^DoCH6%yZrVI
zKR}fV%1Lu!g3b18h%WAht#IlbxKG$$E#yp|_W-1bcMHL|Km}}aL0ob+d!L;nkA(s?
zBKINsP6eEZp9JY+2+5o5i`RGaOm^p9FFtua4L4B;p_?+bsi9G<7zO;~Sin$|AW=k;
zWGP0fv=mZIl2Xb^Nv`FPW0st9&L!8}N+_{NNhOz3YH8Ki00&A3#WGHO`uxA452$6VaMcF&`Zj&sqTh?X_mM
zxEQ@wPHSc>Y!<{5q-br-bXqY=7?<<1-LrPD%Kfdn87Y6OZvIb|Gg`X;N97Eq`?YT0
zRBfuxjZ?9!3pJ-U(0$y~d@=l4%f}DZ$=49Ap}!W~z>;z}p3x^vLH-i7IH5yVb3?Ti#~
zj2xT>;5S)j%c~Q>T=5w|cPsszHlvu3D{Ey+6H+x>1rb8B_Kaj{PC!q$&@wZli*slu
zPU&|jyI1=a*Z_C(YjJFi(!3u*m4y&48v~6YW(YM@Fsyv=9py~su
zN$i?(0nj&2YCbrbV}g@3+cYcgUT&n8y6vACMQWGAbi*4lwkX4(95DTHgtmL%-wn58~
zv=GZ!BNv<##Z#);qA&@gAyE`*#7tYQnjT!>bF^Qn*4z0SizaYpru1*`B?=IwJiZa{4A^5pFO#sX-@&reb1Pms68_LYg
z9%j9DgLyW+=rjksJ@t?=?5!q87p;l#gSzBfS!9=zcFsiYx5C@&)NJM|)7A-81|9-}
zU@C()qG~OTLMHJZ;GPf=xIhQlBj9zEQb9x?(_xCSoDmQ@DWnz%B|Lx9tYz9~JF<=e_25M7(pNus73@Jc~qNWX)TeyNqxLivI4j%45V3F9QnF2^|O
z;uYwupndTa#HIB7(iO5qhAePfW*4#AzInhfL7m-D*`_7=9ZtPt25uCVLMA3hji5
zASppOj;)8(O-v1uiv|?^p!6#1y7p{b?6TDUldM!Ua)kha91S2>$
z*8nuMd$5G!VxSTCkQ*T{UWn@+wRJbQB?Ne$Yyw+#z0`Ile7EH8%$(KJ#0mEh5S>LB
zb=<5%bI$J!$Za}+I0FXUX5Q1JaIuWgnYz4vqnL16N@bpd7$IqA_t@_7a*BDzc8rCUx+n_M|IcMo;|l9hxs&yw&`oy6c|j2u~cO?2ua4kw)H;e*H
zfe@EkD)BWCK11*Vx^q6Lkj0%8lUODJQmki-Z}3$cmx_q6sD5(!icXX8dhL_x5g1+i
z3^Jes16v{#$JH$ID29R_S3{WPWjvvklAf?;z5G#)+FW>AJaA36wM8)e*taZ+-!fUv
z|B_7@KaUm26(O@U{6L*xv$AE~55YtXgIB?#^5@w%R6RpEF5|e{E|<%=+$}Cc-L0AF
z*xRk{w!TvJz7+drX?z&zn8BlupwnD#@Dk>H3-eZCPDhv*g#F>r=Y+5OD?^WFHW(HV
z(srS5SOr5D7#32Fc6EgaxUFy{eZdM9#z1GG`l|mkKz-eFNCPW(R)Oy9;z-X#c~^cR
z+21Vg%s*P)-Cey_FH!%A(t`fmZWaXIpyl((M-OtTE)~sVA7u}j^{WePnwP5%NL2eq
zZOtFn9jZRI9=k$vR`pj(?D*=x&DRsxgf5K3|BC4f1UJTf?-|-gL6&{>)PR==7=I+g
z&lrxoo|LyG{Wn6uBqy8ZFvPq1v|-kT1OBygt*FgLTQkWxlcgz<0R!
z>@d?xZvF=-3`bN<}Ii
z>>%Qhp*mR*6>-#7C_;r$E41oha_Jv5X-HCB90k{cgFlN^2N!2u9b5%L@CU@r%}LQk
zO8hP;gU<|Qc&-|=;i
z0N?N8Jj?&ypJPBRSPTe=#B2$nN8c^yiXiuWmzRYCmuKHfy9qoS3G{>Tyk09
znNc&FNfJki#Znh5UChd+Mm$X%Q#GCPg`CGK=Pk}!wa(h_^Q9xAovVi=^cNq3CwRA0fwFo*_2(0dJmyk
z1m4f6O?hDO7U*B|>efET=>w3ZTBUD*gF|4fMA_>;|L*Iq+rM|(_4@&)IC53y)^T?L
z000JJOGiWi{{a60|De66lK=n!32;bRa{vG?BLDy{BLR4&KXw2B00(qQO+^Rg0|E~h
z9DOPcE&u=qi%CR5R9M5Mmwiwa=N-pCd)#rn2wpHMAmS2WH3>#B3m#J6Di9KsAj#xq
ztTBx?PNw;*nMo!Rr+?I`F-_CybhL#@Cyk_zu|t}+6Eul$ZA?hgO+Z80WJW+zncl91I56@K>mbt{{|inXALO~cn(kx%(aL=0$x%Ly(z+Y
z$pdx)t1aU5z(cB`pP7_^CM(Ine}Ob18UmhH4gGISacQz50Z#&d24aNR0P?JskS6&2
z7*HTYmw+^V7Q#JH!K?`iR
zfFGO!cvf?B^QLXDY=8d3#m?{4h_SJ8so{$+vl_nmGOMzoA$Qv+$EsUY_i^*a&?+EN
zSo{}~@O=-EAgbEpNK9{IIVR
zB2^NE@I<732lA_HYAUVZ%L@zI)~#K8ENR9JZ|mvSyWXyS=b>JYCk|lWy9ZVlmlQX|
zJ@H7((Ehp{US2%1pG@!`mX0*`>f$9Oj82zoIJTA%XLR`s4;lGv5tgk
z?G}BhRszF7za}dSfxn9s9Rzl0vT~;;D;c6OVNXdx^7%rgdYZ!#C5f6g($}}7ucf76
z#OqBQGK}(!lw@+}rkMcif4sUaK0Q4h7!MM#hjH-^(cM;vNNoX*3qlgG(N+2Yo!=U7
z0?rT#C3~;K<+{?}*!1j;Hht4zd;5qvZaR|l^8c`>wDjVo!$)39aJk~fM@GK3g4x6%
zfv8fmMNqz=8hV3j=(^~9_lhB8yK3m~q*bmz>rYDhG}MzNM(?=qzE7v6r*}+Wyr>3=
zadX^sIFpl)BxYx|#3v>7&na-%j13NEjQM=4ouzAR;%Jwo$;vTcl@R0t3xETvp^uAr
zNCS2Qr+{DHf4(iMy1M2-?O*qmdItuaQ*Bar-<5Iw<+3stb?-6Osvoigtrh}nR73x*
z^?+*VoubDB(0=x8(Vps>a#IA_RM$7>&by8_HSyrW9EMu;R-2gNf~(^D^4~Pg{2vM)
z%7&pKqEk}={$Up_*emLDp(ZPfZqb76w^F>!o$srtsOWZ0pB`w_^~68^sb=<&&li@g
zW7f<8;4`~w=mAYuYJs1NDBlH?PJ&^Z47{Rzt@o9xs#DR?(c=Jax4YZzcK2+2>gk+e
zzyBr@v2)WuR}FnMsPI=J=K&tjWaY7`nsv+O%^gAT;F_-}*Pr?6rk
zNpFTtE7j0@fFd!sc|*Z-=ebr<|Np>h9}DW8G_O@d52%K|6UYSK0DAEa!#8Ir
z%*?zk0!wmpTZ055E~%QV*u`D3+_vGdWt~Y$3j+S(H~`aT8#Zk=-Pwh?G_pR#62=ASf+u+J**5L!20F@11^g?_Fo9S^hAIvUtlq`%2|-SPOpnj=
zWF5STNZ$e8(1*~e@<4ZAG1WIx?nM(xoX6Tm$9D7=)#y-vx%bSvwo+fuQ2)?~YI9g>
zq;GS#$~wQ?nJ{nuH|Csjeq!D}I^5k;9_&-OEVLb;&^#rzb+A09%7Z;yilfqFs8kv)
zjw#2#Ms~$dI?I?7H8=)}W8J;oW8KmYEZX;0tlU1(uWl-ijFyK6&s~&DXBVmBV9!u5
zhIHGBwwFuk3*-@{&nu6OURoU4*gY`Z
zUu^AKrtn*lf$M+n;&i;QIM_F~rM0V&|192@mwP{x^M)d(XX_Y1ZNkGt^iK(X#fc8B
zUAgSij%AtUxlieH<>%^i>6W3fq4a85DjPB7ctq9sD>5gZu1ri#F&bRO|EkMF8JE{P
zGkC%={-YBzH9|fKgb60=vaS7tV)KDywKHge$Sc2r8ao0c?cyBtWH4&2k>}UQbv3f8
zkwZ0dxJIt8k)x0$pV<8#Np6XM21sNn3qdBMfS+C?S9h#z>6&6nj%ulb_hOl6Rt?})
z8$|O<)pkEs7N^o7;>BjlZchb>Gl3KrEaG%s$|57hX}ulwVuQ?-0XIFx?IN47K~L-O
z;&xqR)U~pY?1eegX%15~WiQU5YR@#J*dYsPbOZp;m$WXvL24CMP{~=$>z)~f}{+ob8fA}2Ptwo
z+8}2z{*1^{TQu9A&1PJs3I^?ASfg3aMYCF_0eyUs1JW0<04N8ZG0_G|TBm1lv};m_MS%#EGS1@jnc&dl4{f&rt0
zD=)b8Jg~VO*$inb=y0CR<@25a%F;Qd3Je_qEEhqca`b3hDort|Dnx8FDp%Bj5dF;$tt)(>-qK_KC^B3mI
z{!0R6=MMPTe#ipS`3D2c_XF$0I#2E0aQ5h7X1mj%>DV;$*>_gygeuuy9X#nm&*>(6ZO!lplVV?bcw#kmL{a7-}_VY=L
zXWra?{rHS23aS_Dbh1~^8m`J6cm@wU3aVKX+Hlo$DwKPx%KZk}ryTaQY5NDMmdoF9
zLQ0a>C|8~}UXSqX7$t59Z?i9wovA#MW2r&TFtxe6=f=K~p{;|xXd-!0OZi_q*4z=f
zN1Gf!pZ8dkv7_R!IFKo9wUx~
zO1uI5%i$8A5hd2^OZ*jZANcv;QSc?;r$$Pslh6v_%OWLK^{s(Go{+1TASOaXSt@
zJyzni;6CuH!2^w@xjN#=38z+0opNRP_J+vTiwJTVf~COMA$nUeQnF8z)uB9+trc;$
zR(Wu&I8q)OLBkhnL&^KTri43d6a`tx+fd>4kgG*O5JCx;U5*v550k~sRL2Pld%H
zluM0hi({81CY-NA7_Jx+G)ZvQy1Ya-iRZ;jhKwPyO*QftS)b&0h`;46=0
z#Y8s_4R()Qh$W-?hx&%l@`YM5)%S|vLWyi~xl=3DLf(>z+zz=~6a*oZ$b&A&Y0ROC
z7)K}SIX+RxO%nn3O$0$cwkE>hCU_KNun9kNLI=e`8XFS{a0WGTV4|7*4r#2x$8hZ8
z!)RwmcYT>Jp0kNvLw?~p>mF5O1rS&IxOp_ll2`J}I1F!GCdL?Y#_Ntd01S1&QV&cq
z#11kLDj-xqsDMxbp#nk$gbD~15Go*4KnOomA%j9Dg;6OWR6wYJPywMsKnNehv5OB7
zf<$+HfpE?yb|7?gAVfc*YODa_N*_0m^q0JnU&aB1&bWY30U^fgjvEMpBMv-K_=SK4
z2$MjV1i~Z`CV?;sgh?Pw0wHci7Vb(`5(wdECXpeDOi7F`34}=?OafsN2ps}K_!y2|
ze1H%ny6X#sb2hO9p`!yG`UzEI1rS&IxOt?%K1%yc;#CY9t10it4fhP*T
z5U>Pk(pr7*&tLuE{a2p3XO$VFO7ne(gIsz4o>FqWjO}G4+`Pil~eXLzI#r%haNz?vc
zF(1f8X}aTY%=>(}rhb!oG99Ct4!G>VZT6&N%p2o$>}uvCxDh?T{A7ZTX?vOPA^(SL
zr<-W1gZVvk9rFv#^wCSqE8#TKOp~`T-~BvunrQh3ou1dOz0n{t?t2{Ho!W!X`DrQh
zQ*SUg(7I{vBf5k6XBur|epI6}^N+QYn16Sar?k}6%;cfOf*GAFJJ(F={K)mqhxiz;
zHY$8b?0cq{C#PS1ZT6Z~hj16~Ir{)l@dfd>o;G1e{1zS{dJ1TWE@b{L(K)kxFVT_@
z<|97Fm%Rd~%=|sK8#-0)cmh}I%GzPSPE(6ye7l%Of$CO3IiGodGfiQecKMF5ooS-u
zOPCLtbU*Xc37XXQG2cbhJjr}noMx_N{zn`S@axRSb9kUjKVsg7?*#uZng5!{BVYP1
z^IprOkLSGumn*dpCgwUgy%oooz*&F9to@>Z`CIw|q_>io)20_N)i2$;>=e_Qzx=k}
zVyd5OtfPu=#}SJ)8!O`QRtrlcarb*zE`PtXF-DVS*IiG({O-Y*pSbfJ!?~m?9EL(o
z7j=(*ZEI2W;*YcakMtLNbJrrd7XdB+jr4ZW(eNi;M7RDmPg|M}r`EBsk;BSE62)+;e`w<)_5qxq%
ze-8S7e5mc#@a1-%9c3=zvbV2+`KP!ew#M)Y2)-8lDEP&VxX$x>r{3b!w>b4~r{3e#
d45bDP46hOx7_4S6Fo+k-*%fF5)R!9I
z6XFU~@V|iJe;UL83Wooc4F78w{^v9NuVVOL&G5gF;eR?<3Mfl;1hJG1V}Y&$B9gSj
z)dO__*<@IXUkiT6{QocFy7D?Opvy{v{6H}X1A=-S$%n5eu{7MRFg2^Jckm0iW7o5q
zbzPV0`9>G^&Le7!p;wO0-*mx(Y2n)l*INRtDm+$^#hyDCuqmCHId|`?Fm)fPMB(5!
z{)xOk?@n3X>^K|Ge0!^G)&0dqt>@YbYuH(5?OIs9s?F}Ey?}hmfdHjU!%c0$%8Qd93pdO?w?s{M)~UAT
zH_yD0F_?c=cSiS?Epxtq`{|V!x?*eTts5@Prg^!^>E-~5yQRnWRrrQ&P8a|aj{oQ`NX^Q`O{g!0ch|SQ(jG85-ysm{=JYoQx|yhoT`jKP5A*61RpO%x8;$8W=oX{an^LB{Ts5fnAuU
literal 0
HcmV?d00001
diff --git a/org.sonarlint.eclipse.ui/icons/sonarqube-16.png b/org.sonarlint.eclipse.ui/icons/sonarqube-16.png
new file mode 100644
index 0000000000000000000000000000000000000000..da846464303abd83e46c06378b93738e5b8c69fc
GIT binary patch
literal 1203
zcmaJ>U2NM_7`21P{=s&M
zTM){a(AeOi6JzuNp=z7ZCZTD28$;EEl!p!Rz{938)Cnm<@PGtNNWjFja@{q72ZCk$
z{^*?Zec%0#?$L?yec|rMx-kq3=dxlRjS>F~b)tXoZ)-9d9(B`G?xb0EtFjF-K{ZPd
z%fw)9TRti?M?7R&p=f?}m{EU)NiP61a#N&{F4qX{|`a#3tyd<%%%b~fyO%h-o
z;?5+A`$U5X55xQH7#uz_KxVFwU}cKVT@DAu05-QF@Szvm7%7HXZ^+v(++}
z7c(2N&?-q(T-V}AvRbXissl08o+jx;BH?Q=OcWua&Ya=OUes`QHWftZD7I#~nrQ%E
zQ7)OYZjwNm-Y-G7TC#?-aZKpINKdv%I!5^=HGz`!|4?0Tp&d65|H=11g`L8j1<5>g
z%voDOjVtf;Ls?wPhO%qg1=Bp(JjIEM>6%W(v_NW-1$z!?hH6%wC)e?k#N`afl??^v
z#3X?fVw$FM1F3X|9i}qESVExbG&3f!gQ?+6S{xcm$EhKz$rVjyR)>b$BIjwyzVq3ipuG%&iD&3Y#qGU+FvHxmxa|^l0pSEm^7A@mL
z1G(Kswq}+-cn-txCvu`t@V+hGU0WLs?5Z7qs*_@vQ=!XWEuRRV*&DchEO~AIO6_hx
z_-ot3@}^HZ%k($c%G`(5z)kYdpG3s$Y8>Hr;Pq$u)y>y01uNOD3(+lVRT<#7Ts_%Q
z+6ErnRC*Jf?`oXpSA+HM4qfVaN2x7tuUs8@IB>xef>{Y`{K$W_xqg)Y^nvS(T_*yc
z6@UM^@74>$VV*gQW$$1Ydm}xm@QZl;79U`QR~CPLoBBGl@bt-!+YB&*4GjDcXaqe4
zfA^W)__?3>H+Fpg_|g}5E>J~+xh&wJV_&|`_dhhaaJa9Be+lp2Uk3N^`btf
+
+
@@ -361,6 +369,96 @@
scope="ON_ANY">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -650,6 +793,12 @@
name="Deactivate rule"
categoryId="org.sonarlint.eclipse.ui.command.category">
+
+
+
+
@@ -781,6 +934,9 @@
+
+
@@ -866,6 +1046,10 @@
class="org.sonarlint.eclipse.ui.internal.markers.SonarLintMarkerResolutionGenerator"
markerType="org.sonarlint.eclipse.core.sonarlintOnTheFlyProblem">
+
+
+
+
+
+
+
+
+
+
+
diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/DeleteTaintMarkersOnEditorClosed.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/DeleteTaintMarkersOnEditorClosed.java
new file mode 100644
index 000000000..c39050f0e
--- /dev/null
+++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/DeleteTaintMarkersOnEditorClosed.java
@@ -0,0 +1,80 @@
+/*
+ * SonarLint for Eclipse
+ * Copyright (C) 2015-2021 SonarSource SA
+ * sonarlint@sonarsource.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.sonarlint.eclipse.ui.internal;
+
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IFileEditorInput;
+import org.eclipse.ui.IPartListener2;
+import org.eclipse.ui.IWorkbenchPart;
+import org.eclipse.ui.IWorkbenchPartReference;
+import org.sonarlint.eclipse.core.internal.adapter.Adapters;
+import org.sonarlint.eclipse.core.internal.jobs.SonarLintMarkerUpdater;
+import org.sonarlint.eclipse.core.resource.ISonarLintFile;
+
+public class DeleteTaintMarkersOnEditorClosed implements IPartListener2 {
+ @Override
+ public void partOpened(IWorkbenchPartReference partRef) {
+ // Nothing to do
+ }
+
+ @Override
+ public void partVisible(IWorkbenchPartReference partRef) {
+ // Nothing to do
+ }
+
+ @Override
+ public void partInputChanged(IWorkbenchPartReference partRef) {
+ // Nothing to do
+ }
+
+ @Override
+ public void partHidden(IWorkbenchPartReference partRef) {
+ // Nothing to do
+ }
+
+ @Override
+ public void partDeactivated(IWorkbenchPartReference partRef) {
+ // Nothing to do
+ }
+
+ @Override
+ public void partClosed(IWorkbenchPartReference partRef) {
+ IWorkbenchPart part = partRef.getPart(true);
+ if (part instanceof IEditorPart) {
+ IEditorInput input = ((IEditorPart) part).getEditorInput();
+ if (input instanceof IFileEditorInput) {
+ ISonarLintFile sonarLintFile = Adapters.adapt(input, ISonarLintFile.class);
+ SonarLintMarkerUpdater.deleteTaintMarkers(sonarLintFile);
+ }
+ }
+ }
+
+ @Override
+ public void partBroughtToTop(IWorkbenchPartReference partRef) {
+ // Nothing to do
+ }
+
+ @Override
+ public void partActivated(IWorkbenchPartReference partRef) {
+ // Nothing to do
+ }
+
+}
diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/SonarLintImages.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/SonarLintImages.java
index 22656c059..0e151b38f 100644
--- a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/SonarLintImages.java
+++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/SonarLintImages.java
@@ -43,6 +43,7 @@ public final class SonarLintImages {
public static final Image ISSUE_ANNOTATION = createImage("full/annotation16/issue.png"); //$NON-NLS-1$
public static final Image HOTSPOT_ANNOTATION = createImage("full/annotation16/hotspot.png"); //$NON-NLS-1$
+ public static final Image VULNERABILITY_ANNOTATION = createImage("full/annotation16/vulnerability.png"); //$NON-NLS-1$
public static final Image RESOLUTION_SHOW_RULE = createImage("full/marker_resolution16/showrule.png"); //$NON-NLS-1$
public static final Image RESOLUTION_SHOW_LOCATIONS = createImage("full/marker_resolution16/showlocations.png"); //$NON-NLS-1$
public static final Image RESOLUTION_DISABLE_RULE = createImage("full/marker_resolution16/disablerule.png"); //$NON-NLS-1$
@@ -66,6 +67,9 @@ public final class SonarLintImages {
public static final Image IMG_SONARQUBE_LOGO = createImage("logo/sonarqube-black-256px.png"); //$NON-NLS-1$
public static final Image IMG_SONARCLOUD_LOGO = createImage("logo/sonarcloud-black-256px.png"); //$NON-NLS-1$
+ public static final ImageDescriptor SONARCLOUD_16 = createImageDescriptor("sonarcloud-16.png"); //$NON-NLS-1$
+ public static final ImageDescriptor SONARQUBE_16 = createImageDescriptor("sonarqube-16.png"); //$NON-NLS-1$
+
public static final Image IMG_OPEN_EXTERNAL = createImage("external-link-16.png"); //$NON-NLS-1$
public static final ImageDescriptor DEBUG = createImageDescriptor("debug.gif"); //$NON-NLS-1$
diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/SonarLintUiPlugin.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/SonarLintUiPlugin.java
index 851c5d85e..56048cbc0 100644
--- a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/SonarLintUiPlugin.java
+++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/SonarLintUiPlugin.java
@@ -43,6 +43,7 @@
import org.sonarlint.eclipse.core.internal.SonarLintCorePlugin;
import org.sonarlint.eclipse.core.internal.TriggerType;
import org.sonarlint.eclipse.core.internal.engine.connected.IConnectedEngineFacade;
+import org.sonarlint.eclipse.core.internal.jobs.SonarLintMarkerUpdater;
import org.sonarlint.eclipse.core.internal.markers.MarkerUtils;
import org.sonarlint.eclipse.core.internal.notifications.ListenerFactory;
import org.sonarlint.eclipse.core.internal.preferences.SonarLintGlobalConfiguration;
@@ -58,6 +59,7 @@
import org.sonarlint.eclipse.ui.internal.popup.GenericNotificationPopup;
import org.sonarlint.eclipse.ui.internal.popup.MissingNodePopup;
import org.sonarlint.eclipse.ui.internal.popup.ServerStorageNeedUpdatePopup;
+import org.sonarlint.eclipse.ui.internal.popup.TaintVulnerabilityAvailablePopup;
import org.sonarsource.sonarlint.core.client.api.connected.ConnectedSonarLintEngine.State;
import org.sonarsource.sonarlint.core.client.api.notifications.ServerNotification;
import org.sonarsource.sonarlint.core.client.api.notifications.ServerNotificationListener;
@@ -164,11 +166,24 @@ public void start(final BundleContext context) throws Exception {
getPreferenceStore().addPropertyChangeListener(prefListener);
+ SonarLintMarkerUpdater.setTaintVulnerabilitiesListener(SonarLintUiPlugin::notifyTaintVulnerabilitiesDisplayed);
+
new CheckForUpdatesJob().schedule((long) 10 * 1000);
startupAsync();
}
+ private static void notifyTaintVulnerabilitiesDisplayed(boolean comeFromSonarCloud) {
+ if (SonarLintGlobalConfiguration.taintVulnerabilityNeverBeenDisplayed()) {
+ SonarLintGlobalConfiguration.setTaintVulnerabilityDisplayed();
+ Display.getDefault().syncExec(() -> showTaintVulnerabitilityNotification(comeFromSonarCloud));
+ }
+ }
+
+ private static void showTaintVulnerabitilityNotification(boolean comeFromSonarCloud) {
+ new TaintVulnerabilityAvailablePopup(comeFromSonarCloud).open();
+ }
+
@Override
public void stop(final BundleContext context) throws Exception {
hotspotsHandlerServer.shutdown();
diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/WindowOpenCloseListener.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/WindowOpenCloseListener.java
index 5954c3541..ae24ed829 100644
--- a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/WindowOpenCloseListener.java
+++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/WindowOpenCloseListener.java
@@ -28,6 +28,7 @@
class WindowOpenCloseListener implements IWindowListener {
private static final OpenEditorAnalysisTrigger OPEN_EDITOR_ANALYSIS_TRIGGER = new OpenEditorAnalysisTrigger();
+ private static final DeleteTaintMarkersOnEditorClosed DELETE_TAINT_MARKERS_ON_EDITOR_CLOSED = new DeleteTaintMarkersOnEditorClosed();
private static final IPageListener PAGE_OPEN_CLOSE_LISTENER = new IPageListener() {
@@ -79,6 +80,7 @@ static void addListenerToAllPages(IWorkbenchWindow window) {
private static void addListenersToPage(IWorkbenchPage page) {
page.addPartListener(OPEN_EDITOR_ANALYSIS_TRIGGER);
+ page.addPartListener(DELETE_TAINT_MARKERS_ON_EDITOR_CLOSED);
page.addPartListener(SonarLintFlowAnnotator.PART_LISTENER);
page.addPostSelectionListener(SonarLintUiPlugin.getSonarlintMarkerSelectionService());
}
@@ -91,6 +93,7 @@ static void removeListenerFromAllPages(IWorkbenchWindow window) {
private static void removeListenersFromPage(IWorkbenchPage page) {
page.removePartListener(OPEN_EDITOR_ANALYSIS_TRIGGER);
+ page.removePartListener(DELETE_TAINT_MARKERS_ON_EDITOR_CLOSED);
page.removePartListener(SonarLintFlowAnnotator.PART_LISTENER);
page.removePostSelectionListener(SonarLintUiPlugin.getSonarlintMarkerSelectionService());
}
diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/codemining/SonarLintCodeMiningProvider.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/codemining/SonarLintCodeMiningProvider.java
index 387a459a4..72c267c0c 100644
--- a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/codemining/SonarLintCodeMiningProvider.java
+++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/codemining/SonarLintCodeMiningProvider.java
@@ -46,6 +46,7 @@
import org.sonarlint.eclipse.core.SonarLintLogger;
import org.sonarlint.eclipse.core.internal.markers.MarkerFlow;
import org.sonarlint.eclipse.core.internal.markers.MarkerFlowLocation;
+import org.sonarlint.eclipse.core.internal.markers.MarkerFlows;
import org.sonarlint.eclipse.core.internal.markers.MarkerUtils;
import org.sonarlint.eclipse.ui.internal.SonarLintUiPlugin;
import org.sonarlint.eclipse.ui.internal.flowlocations.SonarLintFlowLocationSelectionListener;
@@ -133,10 +134,7 @@ public void dispose() {
@Override
public void markerSelected(Optional marker) {
- forceRefreshCodeMiningsIfNecessary(marker, m -> {
- List flowsMarkers = MarkerUtils.getIssueFlows(m);
- return flowsMarkers.stream().flatMap(f -> f.getLocations().stream());
- });
+ forceRefreshCodeMiningsIfNecessary(marker, m -> MarkerUtils.getIssueFlows(m).allLocationsAsStream());
}
@Override
@@ -181,17 +179,13 @@ public CompletableFuture> provideCodeMinings(ITextVi
if (markerToUse == null) {
return CompletableFuture.completedFuture(emptyList());
}
- // Return fast if the marker is not for the current editor
ITextEditor textEditor = super.getAdapter(ITextEditor.class);
IFileEditorInput editorInput = textEditor.getEditorInput().getAdapter(IFileEditorInput.class);
- if (editorInput == null || !editorInput.getFile().equals(markerToUse.getResource())) {
- return CompletableFuture.completedFuture(emptyList());
- }
- List flowsMarkers = MarkerUtils.getIssueFlows(markerToUse);
+ MarkerFlows flowsMarkers = MarkerUtils.getIssueFlows(markerToUse);
if (flowsMarkers.isEmpty()) {
return CompletableFuture.completedFuture(emptyList());
}
- boolean isSecondaryLocation = MarkerUtils.isSecondaryLocations(flowsMarkers);
+ boolean isSecondaryLocation = flowsMarkers.isSecondaryLocations();
Optional lastSelectedFlow = SonarLintUiPlugin.getSonarlintMarkerSelectionService().getLastSelectedFlow();
if (!isSecondaryLocation && !lastSelectedFlow.isPresent()) {
return CompletableFuture.completedFuture(emptyList());
@@ -204,7 +198,7 @@ public CompletableFuture> provideCodeMinings(ITextVi
List locations;
if (isSecondaryLocation) {
// Flatten all locations
- locations = flowsMarkers.stream().flatMap(f -> f.getLocations().stream()).collect(toList());
+ locations = flowsMarkers.allLocationsAsStream().collect(toList());
} else if (lastSelectedFlow.isPresent()) {
locations = lastSelectedFlow.get().getLocations();
} else {
@@ -222,13 +216,15 @@ private List createMiningsForLocations(ITextEditor textEditor, List
List result = new ArrayList<>();
for (MarkerFlowLocation l : locations) {
try {
- @Nullable
- Position position = LocationsUtils.getMarkerPosition(l.getMarker(), textEditor);
- if (position != null && !position.isDeleted()) {
- result.add(new SonarLintFlowMessageCodeMining(l, doc, position, this));
- result.add(
- new SonarLintFlowLocationNumberCodeMining(l, position, this, number,
- l.equals(SonarLintUiPlugin.getSonarlintMarkerSelectionService().getLastSelectedFlowLocation().orElse(null))));
+ IMarker marker = l.getMarker();
+ if (marker != null && !l.isDeleted()) {
+ Position position = LocationsUtils.getMarkerPosition(marker, textEditor);
+ if (position != null && !position.isDeleted()) {
+ result.add(new SonarLintFlowMessageCodeMining(l, doc, position, this));
+ result.add(
+ new SonarLintFlowLocationNumberCodeMining(l, position, this, number,
+ l.equals(SonarLintUiPlugin.getSonarlintMarkerSelectionService().getLastSelectedFlowLocation().orElse(null))));
+ }
}
number++;
} catch (BadLocationException e) {
diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/codemining/SonarLintFlowLocationNumberCodeMining.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/codemining/SonarLintFlowLocationNumberCodeMining.java
index a3f15bc34..798b25bbf 100644
--- a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/codemining/SonarLintFlowLocationNumberCodeMining.java
+++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/codemining/SonarLintFlowLocationNumberCodeMining.java
@@ -68,7 +68,7 @@ public Point draw(GC gc, StyledText textWidget, Color color, int x, int y) {
gc.setAntialias(SWT.ON);
String numberStr = Integer.toString(number);
Point numberExtent = gc.stringExtent(numberStr);
- // Compute all sizes based on text size to adapt to zoom in/ou
+ // Compute all sizes based on text size to adapt to zoom in/out
int arcRadius = (int) (numberExtent.y * ARC_RADIUS_RATIO);
int horizontalPadding = (int) (numberExtent.y * HORIZONTAL_PADDING_RATIO);
int horizontalMargin = (int) (numberExtent.y * HORIZONTAL_MARGIN_RATIO);
diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/command/AbstractIssueCommand.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/command/AbstractIssueCommand.java
index 7a3af4897..30523b7fc 100644
--- a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/command/AbstractIssueCommand.java
+++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/command/AbstractIssueCommand.java
@@ -45,10 +45,7 @@ public Display getDisplay() {
}
@Nullable
- @Override
- public Object execute(ExecutionEvent event) throws ExecutionException {
- IStructuredSelection selection = (IStructuredSelection) HandlerUtil.getCurrentSelectionChecked(event);
-
+ protected static IMarker getSelectedMarker(IStructuredSelection selection) {
List selectedSonarMarkers = new ArrayList<>();
@SuppressWarnings("rawtypes")
@@ -59,12 +56,16 @@ public Object execute(ExecutionEvent event) throws ExecutionException {
selectedSonarMarkers.add(marker);
}
}
+ return !selectedSonarMarkers.isEmpty() ? selectedSonarMarkers.get(0) : null;
+ }
- if (!selectedSonarMarkers.isEmpty()) {
- IMarker marker = selectedSonarMarkers.get(0);
+ @Nullable
+ @Override
+ public Object execute(ExecutionEvent event) throws ExecutionException {
+ IMarker marker = getSelectedMarker((IStructuredSelection) HandlerUtil.getCurrentSelectionChecked(event));
+ if (marker != null) {
execute(marker);
}
-
return null;
}
diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/command/OpenInBrowserCommand.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/command/OpenInBrowserCommand.java
new file mode 100644
index 000000000..b0d7c4ab3
--- /dev/null
+++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/command/OpenInBrowserCommand.java
@@ -0,0 +1,82 @@
+/*
+ * SonarLint for Eclipse
+ * Copyright (C) 2015-2021 SonarSource SA
+ * sonarlint@sonarsource.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.sonarlint.eclipse.ui.internal.command;
+
+import java.net.URL;
+import java.util.Map;
+import java.util.Optional;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.commands.IElementUpdater;
+import org.eclipse.ui.menus.UIElement;
+import org.sonarlint.eclipse.core.SonarLintLogger;
+import org.sonarlint.eclipse.core.internal.SonarLintCorePlugin;
+import org.sonarlint.eclipse.core.internal.adapter.Adapters;
+import org.sonarlint.eclipse.core.internal.engine.connected.ResolvedBinding;
+import org.sonarlint.eclipse.core.internal.markers.MarkerUtils;
+import org.sonarlint.eclipse.core.internal.utils.StringUtils;
+import org.sonarlint.eclipse.core.resource.ISonarLintProject;
+import org.sonarlint.eclipse.ui.internal.SonarLintImages;
+
+public class OpenInBrowserCommand extends AbstractIssueCommand implements IElementUpdater {
+
+ @Override
+ public void updateElement(UIElement element, Map parameters) {
+ IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
+ if (window != null) {
+ IStructuredSelection selection = (IStructuredSelection) window.getSelectionService().getSelection();
+ Optional binding = getBinding(getSelectedMarker(selection));
+ if (binding.isPresent()) {
+ element.setIcon(binding.get().getEngineFacade().isSonarCloud() ? SonarLintImages.SONARCLOUD_16 : SonarLintImages.SONARQUBE_16);
+ }
+ }
+ }
+
+ @Override
+ protected void execute(IMarker selectedMarker) {
+ try {
+ Optional binding = getBinding(selectedMarker);
+ if (!binding.isPresent()) {
+ SonarLintLogger.get().info("Unable to open issue in browser: project is not bound");
+ return;
+ }
+ SonarLintCorePlugin.getTelemetry().taintVulnerabilitiesInvestigatedRemotely();
+ String issueKey = (String) selectedMarker.getAttribute(MarkerUtils.SONAR_MARKER_SERVER_ISSUE_KEY_ATTR);
+ String serverIssueLink = buildLink(binding.get().getEngineFacade().getHost(), binding.get().getProjectBinding().projectKey(), issueKey);
+ PlatformUI.getWorkbench().getBrowserSupport().getExternalBrowser().openURL(new URL(serverIssueLink));
+ } catch (Exception e) {
+ SonarLintLogger.get().error("Unable to open issue in browser", e);
+ }
+ }
+
+ private static Optional getBinding(IMarker marker) {
+ ISonarLintProject project = Adapters.adapt(marker.getResource().getProject(), ISonarLintProject.class);
+ return SonarLintCorePlugin.getServersManager().resolveBinding(project);
+ }
+
+ private static String buildLink(String serverUrl, String projectKey, String issueKey) {
+ String urlEncodedProjectKey = StringUtils.urlEncode(projectKey);
+ String urlEncodedIssueKey = StringUtils.urlEncode(issueKey);
+ return serverUrl + "/project/issues?id=" + urlEncodedProjectKey + "&open=" + urlEncodedIssueKey;
+ }
+
+}
diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/flowlocations/SonarLintFlowAnnotator.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/flowlocations/SonarLintFlowAnnotator.java
index ad0e89793..7ecfaecc2 100644
--- a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/flowlocations/SonarLintFlowAnnotator.java
+++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/flowlocations/SonarLintFlowAnnotator.java
@@ -42,6 +42,7 @@
import org.eclipse.ui.texteditor.ITextEditor;
import org.sonarlint.eclipse.core.internal.markers.MarkerFlow;
import org.sonarlint.eclipse.core.internal.markers.MarkerFlowLocation;
+import org.sonarlint.eclipse.core.internal.markers.MarkerFlows;
import org.sonarlint.eclipse.core.internal.markers.MarkerUtils;
import org.sonarlint.eclipse.ui.internal.SonarLintUiPlugin;
import org.sonarlint.eclipse.ui.internal.util.LocationsUtils;
@@ -123,9 +124,9 @@ public SonarLintFlowAnnotator(ITextEditor textEditor) {
public void documentChanged(DocumentEvent event) {
Optional lastSelectedMarker = SonarLintUiPlugin.getSonarlintMarkerSelectionService().getLastSelectedMarker();
if (lastSelectedMarker.isPresent()) {
- List issueFlows = MarkerUtils.getIssueFlows(lastSelectedMarker.get());
+ MarkerFlows issueFlows = MarkerUtils.getIssueFlows(lastSelectedMarker.get());
IssueLocationsView view = (IssueLocationsView) PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().findView(IssueLocationsView.ID);
- issueFlows.stream().flatMap(f -> f.getLocations().stream()).forEach(l -> {
+ issueFlows.allLocationsAsStream().forEach(l -> {
Position markerPosition = LocationsUtils.getMarkerPosition(l.getMarker(), textEditor);
if (markerPosition != null && markerPosition.isDeleted() != l.isDeleted()) {
l.setDeleted(markerPosition.isDeleted());
@@ -192,16 +193,15 @@ private static Map createAnnotations(ITextEditor textEdito
if (markerToUse == null) {
return emptyMap();
}
- List flowsMarkers = MarkerUtils.getIssueFlows(markerToUse);
+ MarkerFlows flowsMarkers = MarkerUtils.getIssueFlows(markerToUse);
if (flowsMarkers.isEmpty()) {
return emptyMap();
}
- boolean isSecondaryLocation = MarkerUtils.isSecondaryLocations(flowsMarkers);
Optional lastSelectedFlow = SonarLintUiPlugin.getSonarlintMarkerSelectionService().getLastSelectedFlow();
List locations;
- if (isSecondaryLocation) {
+ if (flowsMarkers.isSecondaryLocations()) {
// Flatten all locations
- locations = flowsMarkers.stream().flatMap(f -> f.getLocations().stream()).collect(toList());
+ locations = flowsMarkers.allLocationsAsStream().collect(toList());
} else if (lastSelectedFlow.isPresent()) {
locations = lastSelectedFlow.get().getLocations();
} else {
@@ -209,11 +209,14 @@ private static Map createAnnotations(ITextEditor textEdito
}
Map result = new HashMap<>();
locations.forEach(location -> {
- Position markerPosition = LocationsUtils.getMarkerPosition(location.getMarker(), textEditor);
- if (markerPosition != null && !markerPosition.isDeleted()) {
- Annotation annotation = new Annotation(ISSUE_FLOW_ANNOTATION_TYPE, false, location.getMessage());
- // Copy the position to avoid having it updated twice when document is updated
- result.put(annotation, new Position(markerPosition.getOffset(), markerPosition.getLength()));
+ IMarker marker = location.getMarker();
+ if (marker != null && !location.isDeleted()) {
+ Position markerPosition = LocationsUtils.getMarkerPosition(marker, textEditor);
+ if (markerPosition != null && !markerPosition.isDeleted()) {
+ Annotation annotation = new Annotation(ISSUE_FLOW_ANNOTATION_TYPE, false, location.getMessage());
+ // Copy the position to avoid having it updated twice when document is updated
+ result.put(annotation, new Position(markerPosition.getOffset(), markerPosition.getLength()));
+ }
}
});
return result;
diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/flowlocations/SonarLintFlowLocationsService.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/flowlocations/SonarLintFlowLocationsService.java
index 0f98a5897..0bc36ae59 100644
--- a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/flowlocations/SonarLintFlowLocationsService.java
+++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/flowlocations/SonarLintFlowLocationsService.java
@@ -21,12 +21,12 @@
import java.util.Arrays;
import java.util.HashSet;
-import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.swt.widgets.Display;
@@ -37,14 +37,17 @@
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.sonarlint.eclipse.core.SonarLintLogger;
+import org.sonarlint.eclipse.core.internal.SonarLintCorePlugin;
import org.sonarlint.eclipse.core.internal.event.AnalysisEvent;
import org.sonarlint.eclipse.core.internal.event.AnalysisListener;
import org.sonarlint.eclipse.core.internal.markers.MarkerFlow;
import org.sonarlint.eclipse.core.internal.markers.MarkerFlowLocation;
+import org.sonarlint.eclipse.core.internal.markers.MarkerFlows;
import org.sonarlint.eclipse.core.internal.markers.MarkerUtils;
import org.sonarlint.eclipse.ui.internal.util.SelectionUtils;
import org.sonarlint.eclipse.ui.internal.views.issues.OnTheFlyIssuesView;
import org.sonarlint.eclipse.ui.internal.views.issues.SonarLintReportView;
+import org.sonarlint.eclipse.ui.internal.views.issues.TaintVulnerabilitiesView;
import org.sonarlint.eclipse.ui.internal.views.locations.IssueLocationsView;
public class SonarLintFlowLocationsService implements ISelectionListener, AnalysisListener {
@@ -57,7 +60,7 @@ public class SonarLintFlowLocationsService implements ISelectionListener, Analys
private Optional lastSelectedFlowLocation = Optional.empty();
private boolean showAnnotationsInEditor = true;
- private static final Set sonarlintMarkerViewsIds = new HashSet<>(Arrays.asList(SonarLintReportView.ID, OnTheFlyIssuesView.ID));
+ private static final Set sonarlintMarkerViewsIds = new HashSet<>(Arrays.asList(SonarLintReportView.ID, OnTheFlyIssuesView.ID, TaintVulnerabilitiesView.ID));
@Override
public void selectionChanged(IWorkbenchPart part, ISelection selection) {
@@ -76,11 +79,11 @@ public void usedAnalysis(AnalysisEvent event) {
// Marker has been deleted during the last analysis
markerSelected(null, false, false);
} else {
- List newIssueFlows = MarkerUtils.getIssueFlows(lastSelectedMarker.get());
+ MarkerFlows newIssueFlows = MarkerUtils.getIssueFlows(lastSelectedMarker.get());
// Try to reselect the same flow number than before
Integer pastFlowNum = lastSelectedFlow.map(MarkerFlow::getNumber).orElse(null);
- if (pastFlowNum != null && newIssueFlows.size() >= pastFlowNum) {
- lastSelectedFlow = Optional.of(newIssueFlows.get(pastFlowNum - 1));
+ if (pastFlowNum != null && newIssueFlows.count() >= pastFlowNum) {
+ lastSelectedFlow = Optional.of(newIssueFlows.getFlows().get(pastFlowNum - 1));
}
// Try to select the same flow location
Integer pastFlowLocationNum = lastSelectedFlowLocation.map(MarkerFlowLocation::getNumber).orElse(null);
@@ -159,11 +162,12 @@ public void markerSelected(@Nullable IMarker selectedMarker, boolean forceShowAn
lastSelectedFlow = Optional.empty();
lastSelectedFlowLocation = Optional.empty();
if (selectedMarker != null) {
- List issueFlow = MarkerUtils.getIssueFlows(selectedMarker);
- if (!MarkerUtils.isSecondaryLocations(issueFlow) && !issueFlow.isEmpty()) {
+ MarkerFlows issueFlow = MarkerUtils.getIssueFlows(selectedMarker);
+ if (!issueFlow.isSecondaryLocations() && !issueFlow.isEmpty()) {
// Select the first flow
- lastSelectedFlow = Optional.of(issueFlow.get(0));
+ lastSelectedFlow = Optional.of(issueFlow.getFlows().get(0));
}
+ triggerTelemetryOnShowTaintVulnerability(selectedMarker);
}
notifyAllOfMarkerChange();
}
@@ -201,4 +205,14 @@ public void setShowAnnotationsInEditor(boolean enabled) {
notifyAllOfMarkerChange();
}
}
+
+ private static void triggerTelemetryOnShowTaintVulnerability(IMarker marker) {
+ try {
+ if (marker.getType().equals(SonarLintCorePlugin.MARKER_TAINT_ID)) {
+ SonarLintCorePlugin.getTelemetry().taintVulnerabilitiesInvestigatedLocally();
+ }
+ } catch (CoreException e) {
+ SonarLintLogger.get().debug("Cannot get marker type", e);
+ }
+ }
}
diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/markers/ShowHideIssueFlowsMarkerResolver.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/markers/ShowHideIssueFlowsMarkerResolver.java
index 6afb029f5..4dde762f7 100644
--- a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/markers/ShowHideIssueFlowsMarkerResolver.java
+++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/markers/ShowHideIssueFlowsMarkerResolver.java
@@ -35,7 +35,7 @@ public class ShowHideIssueFlowsMarkerResolver implements IMarkerResolution2 {
public ShowHideIssueFlowsMarkerResolver(IMarker marker) {
this.marker = marker;
this.alreadySelected = marker.equals(SonarLintUiPlugin.getSonarlintMarkerSelectionService().getLastSelectedMarker().orElse(null));
- isSecondaryLocation = MarkerUtils.isSecondaryLocations(MarkerUtils.getIssueFlows(marker));
+ isSecondaryLocation = MarkerUtils.getIssueFlows(marker).isSecondaryLocations();
}
@Override
diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/markers/SonarLintMarkerImageProvider.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/markers/SonarLintMarkerImageProvider.java
index e495e105e..1c85fd945 100644
--- a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/markers/SonarLintMarkerImageProvider.java
+++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/markers/SonarLintMarkerImageProvider.java
@@ -42,6 +42,9 @@ public Image getManagedImage(Annotation annotation) {
if (annotation.getType().equals("org.sonarlint.eclipse.hotspotAnnotationType")) {
return SonarLintImages.HOTSPOT_ANNOTATION;
}
+ if (annotation.getType().equals("org.sonarlint.eclipse.taintAnnotationType")) {
+ return SonarLintImages.VULNERABILITY_ANNOTATION;
+ }
return SonarLintImages.ISSUE_ANNOTATION;
}
diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/markers/SonarLintMarkerResolutionGenerator.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/markers/SonarLintMarkerResolutionGenerator.java
index 2ab9be5bf..770b6a69c 100644
--- a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/markers/SonarLintMarkerResolutionGenerator.java
+++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/markers/SonarLintMarkerResolutionGenerator.java
@@ -56,7 +56,7 @@ public IMarkerResolution[] getResolutions(final IMarker marker) {
private static boolean isSonarLintIssueMarker(IMarker marker) {
try {
- return SonarLintCorePlugin.MARKER_ON_THE_FLY_ID.equals(marker.getType()) || SonarLintCorePlugin.MARKER_REPORT_ID.equals(marker.getType());
+ return MarkerUtils.SONARLINT_PRIMARY_MARKER_IDS.contains(marker.getType());
} catch (final CoreException e) {
return false;
}
diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/popup/TaintVulnerabilityAvailablePopup.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/popup/TaintVulnerabilityAvailablePopup.java
new file mode 100644
index 000000000..25def7d60
--- /dev/null
+++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/popup/TaintVulnerabilityAvailablePopup.java
@@ -0,0 +1,68 @@
+/*
+ * SonarLint for Eclipse
+ * Copyright (C) 2015-2021 SonarSource SA
+ * sonarlint@sonarsource.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.sonarlint.eclipse.ui.internal.popup;
+
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+import org.sonarlint.eclipse.core.SonarLintLogger;
+import org.sonarlint.eclipse.ui.internal.SonarLintImages;
+import org.sonarlint.eclipse.ui.internal.views.issues.TaintVulnerabilitiesView;
+
+public class TaintVulnerabilityAvailablePopup extends AbstractSonarLintPopup {
+
+ private boolean comeFromSonarCloud;
+
+ public TaintVulnerabilityAvailablePopup(boolean comeFromSonarCloud) {
+ this.comeFromSonarCloud = comeFromSonarCloud;
+ }
+
+ @Override
+ protected String getMessage() {
+ return "Taint vulnerabilities have been detected by " + (comeFromSonarCloud ? "SonarCloud" : "SonarQube")
+ + " on this file. SonarLint can show you those vulnerabilities in your local code.";
+ }
+
+ @Override
+ protected void createContentArea(Composite composite) {
+ super.createContentArea(composite);
+
+ addLink("Show in view", e -> Display.getDefault().asyncExec(() -> {
+ close();
+ try {
+ PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().showView(TaintVulnerabilitiesView.ID);
+ } catch (PartInitException exception) {
+ SonarLintLogger.get().debug("Cannot show taint vulnerabilitites view", exception);
+ }
+ }));
+ }
+
+ @Override
+ protected String getPopupShellTitle() {
+ return "SonarLint - Taint vulnerability found";
+ }
+
+ @Override
+ protected Image getPopupShellImage(int maximumHeight) {
+ return SonarLintImages.BALLOON_IMG;
+ }
+}
diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/properties/AboutPropertyPage.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/properties/AboutPropertyPage.java
index ffcc0520c..8a3054d3c 100644
--- a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/properties/AboutPropertyPage.java
+++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/properties/AboutPropertyPage.java
@@ -92,6 +92,10 @@ protected Control createContents(final Composite parent) {
" },\n" +
" \"show_hotspot\": {\n" +
" \"requests_count\": 3\n" +
+ " },\n" +
+ " \"taint_vulnerabilities\": {\n" +
+ " \"investigated_locally_count\": 3,\n" +
+ " \"investigated_remotely_count\": 4\n" +
" }\n"
+ "}");
diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/util/SelectionUtils.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/util/SelectionUtils.java
index f1ce313a9..0f3a5cdb0 100644
--- a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/util/SelectionUtils.java
+++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/util/SelectionUtils.java
@@ -31,8 +31,8 @@
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.ui.handlers.HandlerUtil;
-import org.sonarlint.eclipse.core.internal.SonarLintCorePlugin;
import org.sonarlint.eclipse.core.internal.adapter.Adapters;
+import org.sonarlint.eclipse.core.internal.markers.MarkerUtils;
import org.sonarlint.eclipse.core.resource.ISonarLintFile;
import org.sonarlint.eclipse.core.resource.ISonarLintProject;
import org.sonarlint.eclipse.core.resource.ISonarLintProjectContainer;
@@ -160,7 +160,7 @@ private static void processElement(List selectedSonarMarkers, Object el
}
private static boolean isSonarLintMarker(IMarker marker) throws CoreException {
- return SonarLintCorePlugin.MARKER_ON_THE_FLY_ID.equals(marker.getType()) || SonarLintCorePlugin.MARKER_REPORT_ID.equals(marker.getType());
+ return MarkerUtils.SONARLINT_PRIMARY_MARKER_IDS.contains(marker.getType());
}
}
diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/views/issues/IssueDescriptionField.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/views/issues/IssueDescriptionField.java
index a63659188..5bbb33399 100644
--- a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/views/issues/IssueDescriptionField.java
+++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/views/issues/IssueDescriptionField.java
@@ -19,7 +19,6 @@
*/
package org.sonarlint.eclipse.ui.internal.views.issues;
-import java.util.List;
import java.util.Locale;
import org.eclipse.core.resources.IMarker;
import org.eclipse.jdt.annotation.Nullable;
@@ -29,7 +28,7 @@
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.views.markers.MarkerField;
import org.eclipse.ui.views.markers.MarkerItem;
-import org.sonarlint.eclipse.core.internal.markers.MarkerFlow;
+import org.sonarlint.eclipse.core.internal.markers.MarkerFlows;
import org.sonarlint.eclipse.core.internal.markers.MarkerUtils;
import org.sonarlint.eclipse.core.internal.utils.CompatibilityUtils;
import org.sonarlint.eclipse.ui.internal.SonarLintImages;
@@ -65,25 +64,12 @@ public String getValue(MarkerItem item) {
IMarker marker = item.getMarker();
// When grouping by severity, MarkerItem will be a MarkerCategory, that doesn't have an attached marker
if (marker != null) {
- List issueFlows = MarkerUtils.getIssueFlows(marker);
- if (!issueFlows.isEmpty()) {
- boolean isSecondary = MarkerUtils.isSecondaryLocations(issueFlows);
- String kind;
- if (isSecondary) {
- kind = "location";
- } else {
- kind = "flow";
- }
- sb.append(" [+").append(issueFlows.size()).append(" ").append(pluralize(kind, issueFlows.size())).append("]");
- }
+ MarkerFlows issueFlows = MarkerUtils.getIssueFlows(marker);
+ sb.append(issueFlows.getSummaryDescription());
}
return sb.toString();
}
- private static String pluralize(String str, int count) {
- return count == 1 ? str : (str + "s");
- }
-
@Override
public int compare(MarkerItem item1, MarkerItem item2) {
int severity1 = getSeverity(item1);
diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/views/issues/TaintVulnerabilitiesView.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/views/issues/TaintVulnerabilitiesView.java
new file mode 100644
index 000000000..91e0e07bd
--- /dev/null
+++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/views/issues/TaintVulnerabilitiesView.java
@@ -0,0 +1,61 @@
+/*
+ * SonarLint for Eclipse
+ * Copyright (C) 2015-2021 SonarSource SA
+ * sonarlint@sonarsource.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.sonarlint.eclipse.ui.internal.views.issues;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.RowLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Link;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+import org.sonarlint.eclipse.core.SonarLintLogger;
+import org.sonarlint.eclipse.ui.internal.SonarLintUiPlugin;
+
+public class TaintVulnerabilitiesView extends MarkerViewWithBottomPanel {
+
+ public static final String ID = SonarLintUiPlugin.PLUGIN_ID + ".views.issues.TaintVulnerabilitiesView";
+
+ public TaintVulnerabilitiesView() {
+ super(SonarLintUiPlugin.PLUGIN_ID + ".views.issues.taintIssueMarkerGenerator");
+ }
+
+ @Override
+ protected void populateBottomPanel(Composite bottom) {
+ RowLayout bottomLayout = new RowLayout();
+ bottomLayout.center = true;
+ bottom.setLayout(bottomLayout);
+ GridData bottomLayoutData = new GridData(SWT.FILL, SWT.FILL, true, false);
+ bottom.setLayoutData(bottomLayoutData);
+
+ Link label = new Link(bottom, SWT.NONE);
+ label.setText("This view displays taint vulnerabilities found by SonarQube or SonarCloud on the main branch during last analysis. Learn more");
+ label.addListener(SWT.Selection, e -> {
+ try {
+ PlatformUI.getWorkbench().getBrowserSupport().getExternalBrowser().openURL(new URL("https://github.com/SonarSource/sonarlint-eclipse/wiki/Taint-Vulnerabilities"));
+ } catch (PartInitException | MalformedURLException ex) {
+ SonarLintLogger.get().error("Unable to open the browser", ex);
+ }
+ });
+ }
+
+}
diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/views/locations/IssueLocationsView.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/views/locations/IssueLocationsView.java
index 970bf65ed..f3b7c9a76 100644
--- a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/views/locations/IssueLocationsView.java
+++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/views/locations/IssueLocationsView.java
@@ -19,6 +19,8 @@
*/
package org.sonarlint.eclipse.ui.internal.views.locations;
+import java.nio.file.Paths;
+import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@@ -51,6 +53,7 @@
import org.sonarlint.eclipse.core.SonarLintLogger;
import org.sonarlint.eclipse.core.internal.markers.MarkerFlow;
import org.sonarlint.eclipse.core.internal.markers.MarkerFlowLocation;
+import org.sonarlint.eclipse.core.internal.markers.MarkerFlows;
import org.sonarlint.eclipse.core.internal.markers.MarkerUtils;
import org.sonarlint.eclipse.core.internal.utils.StringUtils;
import org.sonarlint.eclipse.ui.internal.SonarLintImages;
@@ -71,12 +74,16 @@ public class IssueLocationsView extends ViewPart implements SonarLintMarkerSelec
private ToggleAnnotationsAction showAnnotationsAction;
- private static class FlowNode {
+ private interface LocationNode {
+ boolean isValid();
+ }
+
+ private static class FlowLocationNode implements LocationNode {
private final String label;
private final MarkerFlowLocation location;
- public FlowNode(MarkerFlowLocation location) {
+ public FlowLocationNode(MarkerFlowLocation location) {
this.label = location.getParent().getLocations().size() > 1 ? (location.getNumber() + ": " + location.getMessage()) : location.getMessage();
this.location = location;
}
@@ -89,6 +96,12 @@ public MarkerFlowLocation getLocation() {
return location;
}
+ @Override
+ public boolean isValid() {
+ IMarker marker = location.getMarker();
+ return marker != null && marker.exists() && !location.isDeleted();
+ }
+
@Override
public int hashCode() {
return Objects.hash(location.getParent().getNumber(), location.getNumber());
@@ -99,10 +112,10 @@ public boolean equals(Object obj) {
if (this == obj) {
return true;
}
- if (!(obj instanceof FlowNode)) {
+ if (!(obj instanceof FlowLocationNode)) {
return false;
}
- FlowNode other = (FlowNode) obj;
+ FlowLocationNode other = (FlowLocationNode) obj;
return Objects.equals(location.getParent().getNumber(), other.location.getParent().getNumber()) && Objects.equals(location.getNumber(), other.location.getNumber());
}
@@ -110,16 +123,28 @@ public boolean equals(Object obj) {
private static class FlowRootNode {
- private final List children;
+ private final List children;
private final MarkerFlow flow;
public FlowRootNode(MarkerFlow flow) {
this.flow = flow;
- children = flow.getLocations().stream()
- // SLE-388 - "Highlight-only" locations don't have a message
- .filter(l -> !StringUtils.isEmpty(l.getMessage()))
- .map(FlowNode::new)
- .collect(toList());
+ if (flow.areAllLocationsInSameFile()) {
+ children = flow.getLocations().stream()
+ // SLE-388 - "Highlight-only" locations don't have a message
+ .filter(l -> !StringUtils.isEmpty(l.getMessage()))
+ .map(FlowLocationNode::new)
+ .collect(toList());
+ } else {
+ children = new ArrayList<>();
+ LocationFileGroupNode lastNode = null;
+ for (MarkerFlowLocation location : flow.getLocations()) {
+ if (lastNode == null || !lastNode.getFilePath().equals(location.getFilePath())) {
+ lastNode = new LocationFileGroupNode(children.size(), location.getFilePath());
+ children.add(lastNode);
+ }
+ lastNode.addLocation(new FlowLocationNode(location));
+ }
+ }
}
public MarkerFlow getFlow() {
@@ -130,8 +155,8 @@ public String getLabel() {
return "Flow " + flow.getNumber();
}
- public FlowNode[] getChildren() {
- return children.toArray(new FlowNode[0]);
+ public LocationNode[] getChildren() {
+ return children.toArray(new LocationNode[0]);
}
@Override
@@ -153,22 +178,69 @@ public boolean equals(Object obj) {
}
+ private static class LocationFileGroupNode implements LocationNode {
+
+ private final int groupIndex;
+ private final String filePath;
+ private List children = new ArrayList<>();
+
+ public LocationFileGroupNode(int groupIndex, String filePath) {
+ this.groupIndex = groupIndex;
+ this.filePath = filePath;
+ }
+
+ public void addLocation(FlowLocationNode flowLocationNode) {
+ children.add(flowLocationNode);
+ }
+
+ public @Nullable String getFilePath() {
+ return filePath;
+ }
+
+ public List getChildren() {
+ return children;
+ }
+
+ @Override
+ public boolean isValid() {
+ return children.get(0).isValid();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(filePath, groupIndex);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof FlowRootNode)) {
+ return false;
+ }
+ LocationFileGroupNode other = (LocationFileGroupNode) obj;
+ return Objects.equals(filePath, other.filePath) && Objects.equals(groupIndex, other.groupIndex);
+ }
+
+ }
+
private static class RootNode {
private final IMarker rootMarker;
- private final List flowsMarkers;
+ private final MarkerFlows flows;
- public RootNode(IMarker rootMarker, List flowsMarkers) {
+ public RootNode(IMarker rootMarker, MarkerFlows flows) {
this.rootMarker = rootMarker;
- this.flowsMarkers = flowsMarkers;
+ this.flows = flows;
}
public IMarker getMarker() {
return rootMarker;
}
- public List getFlows() {
- return flowsMarkers;
+ public MarkerFlows getFlows() {
+ return flows;
}
}
@@ -178,7 +250,7 @@ private static class LocationsProvider implements ITreeContentProvider {
@Override
public Object[] getElements(Object inputElement) {
IMarker sonarlintMarker = (IMarker) inputElement;
- List flowsMarkers = MarkerUtils.getIssueFlows(sonarlintMarker);
+ MarkerFlows flowsMarkers = MarkerUtils.getIssueFlows(sonarlintMarker);
if (!flowsMarkers.isEmpty()) {
return new Object[] {new RootNode(sonarlintMarker, flowsMarkers)};
} else {
@@ -189,22 +261,24 @@ public Object[] getElements(Object inputElement) {
@Override
public Object[] getChildren(Object parentElement) {
if (parentElement instanceof RootNode) {
- List flows = ((RootNode) parentElement).getFlows();
- if (flows.size() > 1) {
+ MarkerFlows flows = ((RootNode) parentElement).getFlows();
+ if (flows.count() > 1) {
// Flatten if all flows have a single location
- if (flows.stream().allMatch(f -> f.getLocations().size() <= 1)) {
- return flows.stream().map(FlowRootNode::new).flatMap(f -> Stream.of(f.getChildren())).toArray();
+ if (flows.isSecondaryLocations()) {
+ return flows.getFlows().stream().map(FlowRootNode::new).flatMap(f -> Stream.of(f.getChildren())).toArray();
} else {
- return flows.stream().map(FlowRootNode::new).toArray();
+ return flows.getFlows().stream().map(FlowRootNode::new).toArray();
}
- } else if (flows.size() == 1) {
+ } else if (flows.count() == 1) {
// Don't show flow number
- return new FlowRootNode(flows.get(0)).getChildren();
+ return new FlowRootNode(flows.getFlows().get(0)).getChildren();
} else {
return new Object[0];
}
} else if (parentElement instanceof FlowRootNode) {
return ((FlowRootNode) parentElement).getChildren();
+ } else if (parentElement instanceof LocationFileGroupNode) {
+ return ((LocationFileGroupNode) parentElement).getChildren().toArray();
} else {
return new Object[0];
}
@@ -247,8 +321,10 @@ private static String getText(Object element) {
return ((RootNode) element).getMarker().getAttribute(IMarker.MESSAGE, "No message");
} else if (element instanceof FlowRootNode) {
return ((FlowRootNode) element).getLabel();
- } else if (element instanceof FlowNode) {
- return ((FlowNode) element).getLabel();
+ } else if (element instanceof FlowLocationNode) {
+ return ((FlowLocationNode) element).getLabel();
+ } else if (element instanceof LocationFileGroupNode) {
+ return Paths.get(((LocationFileGroupNode) element).getFilePath()).getFileName().toString();
} else if (element instanceof String) {
return (String) element;
}
@@ -276,23 +352,21 @@ private StyledString getStyledString(Object element) {
return new StyledString(getText(element), isValidLocation(element) ? null : invalidLocationStyler);
}
- }
-
- private static boolean isValidLocation(Object element) {
- if (element instanceof RootNode) {
- return ((RootNode) element).getMarker().exists();
- } else if (element instanceof FlowRootNode) {
- return Stream.of(((FlowRootNode) element).getChildren()).anyMatch(IssueLocationsView::isValidFlowLocation);
- } else if (element instanceof FlowNode) {
- return isValidFlowLocation(((FlowNode) element));
- } else if (element instanceof String) {
- return true;
- }
- throw new IllegalArgumentException("Unknow node type: " + element);
- }
+ private static boolean isValidLocation(Object element) {
+ if (element instanceof RootNode) {
+ return ((RootNode) element).getMarker().exists();
+ } else if (element instanceof FlowRootNode) {
+ return Stream.of(((FlowRootNode) element).getChildren()).anyMatch(LocationNode::isValid);
+ } else if (element instanceof FlowLocationNode) {
+ return ((FlowLocationNode) element).isValid();
+ } else if (element instanceof LocationFileGroupNode) {
+ return ((LocationFileGroupNode) element).isValid();
+ } else if (element instanceof String) {
+ return true;
+ }
+ throw new IllegalArgumentException("Unknown node type: " + element);
+ }
- private static boolean isValidFlowLocation(FlowNode flowNode) {
- return flowNode.getLocation().getMarker().exists() && !flowNode.getLocation().isDeleted();
}
@Override
@@ -353,8 +427,10 @@ private static void onTreeNodeSelected(Object selectedNode) {
SonarLintUiPlugin.getSonarlintMarkerSelectionService().flowSelected(null);
} else if (selectedNode instanceof FlowRootNode) {
SonarLintUiPlugin.getSonarlintMarkerSelectionService().flowSelected(((FlowRootNode) selectedNode).getFlow());
- } else if (selectedNode instanceof FlowNode) {
- SonarLintUiPlugin.getSonarlintMarkerSelectionService().flowLocationSelected(((FlowNode) selectedNode).getLocation());
+ } else if (selectedNode instanceof FlowLocationNode) {
+ SonarLintUiPlugin.getSonarlintMarkerSelectionService().flowLocationSelected(((FlowLocationNode) selectedNode).getLocation());
+ } else if (selectedNode instanceof LocationFileGroupNode) {
+ SonarLintUiPlugin.getSonarlintMarkerSelectionService().flowLocationSelected(((LocationFileGroupNode) selectedNode).getChildren().get(0).getLocation());
} else {
throw new IllegalStateException("Unsupported node type");
}
@@ -362,9 +438,15 @@ private static void onTreeNodeSelected(Object selectedNode) {
private static void onTreeNodeDoubleClick(Object node) {
IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
- if (node instanceof FlowNode) {
- IMarker flowMarker = ((FlowNode) node).getLocation().getMarker();
- if (flowMarker.exists()) {
+ MarkerFlowLocation location = null;
+ if (node instanceof FlowLocationNode) {
+ location = ((FlowLocationNode) node).getLocation();
+ } else if (node instanceof LocationFileGroupNode) {
+ location = ((LocationFileGroupNode) node).getChildren().get(0).getLocation();
+ }
+ if (location != null) {
+ IMarker flowMarker = location.getMarker();
+ if (flowMarker != null && flowMarker.exists()) {
try {
IDE.openEditor(page, flowMarker);
} catch (PartInitException e) {
@@ -423,7 +505,7 @@ public void setShowAnnotations(boolean b) {
}
public void selectLocation(MarkerFlowLocation location) {
- locationsViewer.setSelection(new StructuredSelection(new FlowNode(location)), true);
+ locationsViewer.setSelection(new StructuredSelection(new FlowLocationNode(location)), true);
}
public void selectFlow(MarkerFlow flow) {
@@ -431,7 +513,7 @@ public void selectFlow(MarkerFlow flow) {
}
public void refreshLabel(MarkerFlowLocation location) {
- locationsViewer.refresh(new FlowNode(location));
+ locationsViewer.refresh(new FlowLocationNode(location));
}
}