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 000000000..72f2b636e
Binary files /dev/null and b/org.sonarlint.eclipse.ui/icons/full/annotation16/vulnerability.png differ
diff --git a/org.sonarlint.eclipse.ui/icons/full/annotation16/vulnerability.xcf b/org.sonarlint.eclipse.ui/icons/full/annotation16/vulnerability.xcf
new file mode 100644
index 000000000..f53280c62
Binary files /dev/null and b/org.sonarlint.eclipse.ui/icons/full/annotation16/vulnerability.xcf differ
diff --git a/org.sonarlint.eclipse.ui/icons/full/annotation16/vulnerability@2x.png b/org.sonarlint.eclipse.ui/icons/full/annotation16/vulnerability@2x.png
new file mode 100644
index 000000000..2ccfde93f
Binary files /dev/null and b/org.sonarlint.eclipse.ui/icons/full/annotation16/vulnerability@2x.png differ
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 000000000..5bac0dcf5
Binary files /dev/null and b/org.sonarlint.eclipse.ui/icons/full/annotation16/vulnerability@2x.xcf differ
diff --git a/org.sonarlint.eclipse.ui/icons/full/eview16/vulnerabilities.png b/org.sonarlint.eclipse.ui/icons/full/eview16/vulnerabilities.png
new file mode 100644
index 000000000..a99007e25
Binary files /dev/null and b/org.sonarlint.eclipse.ui/icons/full/eview16/vulnerabilities.png differ
diff --git a/org.sonarlint.eclipse.ui/icons/full/eview16/vulnerabilities.xcf b/org.sonarlint.eclipse.ui/icons/full/eview16/vulnerabilities.xcf
new file mode 100644
index 000000000..cebd8b1d7
Binary files /dev/null and b/org.sonarlint.eclipse.ui/icons/full/eview16/vulnerabilities.xcf differ
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 000000000..b0286a45c
Binary files /dev/null and b/org.sonarlint.eclipse.ui/icons/full/eview16/vulnerabilities@2x.png differ
diff --git a/org.sonarlint.eclipse.ui/icons/full/eview16/vulnerabilities@2x.xcf b/org.sonarlint.eclipse.ui/icons/full/eview16/vulnerabilities@2x.xcf
new file mode 100644
index 000000000..38c6b5316
Binary files /dev/null and b/org.sonarlint.eclipse.ui/icons/full/eview16/vulnerabilities@2x.xcf differ
diff --git a/org.sonarlint.eclipse.ui/icons/sonarcloud-16.png b/org.sonarlint.eclipse.ui/icons/sonarcloud-16.png
new file mode 100644
index 000000000..7abe416f2
Binary files /dev/null and b/org.sonarlint.eclipse.ui/icons/sonarcloud-16.png differ
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 000000000..da8464643
Binary files /dev/null and b/org.sonarlint.eclipse.ui/icons/sonarqube-16.png differ
diff --git a/org.sonarlint.eclipse.ui/plugin.xml b/org.sonarlint.eclipse.ui/plugin.xml
index 8765673d4..d9be1e787 100644
--- a/org.sonarlint.eclipse.ui/plugin.xml
+++ b/org.sonarlint.eclipse.ui/plugin.xml
@@ -143,6 +143,14 @@
id="org.sonarlint.eclipse.ui.views.HotspotsView"
name="SonarLint Security Hotspots">
+
+
@@ -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));
}
}