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 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 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 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 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"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - @@ -630,6 +736,43 @@ + + + + + + + + + + + + + + + + @@ -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)); } }