diff --git a/org.sonarlint.eclipse.core.tests/src/test/java/org/sonarlint/eclipse/jdt/internal/SuppressWarningsQuickFixTest.java b/org.sonarlint.eclipse.core.tests/src/test/java/org/sonarlint/eclipse/jdt/internal/SuppressWarningsQuickFixTest.java new file mode 100644 index 000000000..9ff3c4934 --- /dev/null +++ b/org.sonarlint.eclipse.core.tests/src/test/java/org/sonarlint/eclipse/jdt/internal/SuppressWarningsQuickFixTest.java @@ -0,0 +1,146 @@ +/* + * SonarLint for Eclipse + * Copyright (C) SonarSource Sàrl + * 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.jdt.internal; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jdt.core.JavaCore; +import org.junit.After; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class SuppressWarningsQuickFixTest { + private static final SuppressWarningsQuickFixGenerator generator = new SuppressWarningsQuickFixGenerator(); + + @After + public void after() { + var options = JavaCore.getOptions(); + options.put(JavaCore.COMPILER_PB_UNHANDLED_WARNING_TOKEN, JavaCore.WARNING); + JavaCore.setOptions(options); + } + + @Test + public void testIrrelevantMarkers() throws CoreException { + var marker = mock(IMarker.class); + when(marker.getType()).thenReturn("noJavaProblem"); + + assertThat(generator.hasResolutions(marker)).isFalse(); + assertThat(generator.getResolutions(marker)).isEmpty(); + + when(marker.getType()).thenReturn(SuppressWarningsQuickFixGenerator.JAVA_PROBLEM_TYPE); + when(marker.getAttribute(IMarker.MESSAGE)).thenReturn(null); + + assertThat(generator.hasResolutions(marker)).isFalse(); + + when(marker.getAttribute(IMarker.MESSAGE)).thenReturn(SuppressWarningsQuickFixGenerator.MARKER_MESSAGE_GENERAL); + + assertThat(generator.hasResolutions(marker)).isFalse(); + } + + @Test + public void testRelevantMarkers() throws CoreException { + var marker = getRelevantMarker(); + assertThat(generator.hasResolutions(marker)).isTrue(); + + when(marker.getAttribute(IMarker.MESSAGE)).thenReturn(SuppressWarningsQuickFixGenerator.MARKER_MESSAGE_GENERAL + + SuppressWarningsQuickFixGenerator.MARKER_MESSAGE_JAVA_SECURITY_ISSUE); + assertThat(generator.hasResolutions(marker)).isTrue(); + + when(marker.getAttribute(IMarker.MESSAGE)).thenReturn(SuppressWarningsQuickFixGenerator.MARKER_MESSAGE_GENERAL + + SuppressWarningsQuickFixGenerator.MARKER_MESSAGE_JAVA_BUGS_ISSUE); + assertThat(generator.hasResolutions(marker)).isTrue(); + + when(marker.getAttribute(IMarker.MESSAGE)).thenReturn(SuppressWarningsQuickFixGenerator.MARKER_MESSAGE_GENERAL + + SuppressWarningsQuickFixGenerator.MARKER_MESSAGE_JAVA_ARCHITECTURE_ISSUE); + assertThat(generator.hasResolutions(marker)).isTrue(); + } + + @Test + public void testQuickFix() throws CoreException { + var marker = getRelevantMarker(); + + var quickFix = getQuickFix(marker); + assertThat(quickFix.getLabel()).contains("SonarQube"); + assertThat(quickFix.getDescription()).contains("SonarQube"); + + quickFix.run(marker); + assertThat(JavaCore.getOption(JavaCore.COMPILER_PB_UNHANDLED_WARNING_TOKEN)).isEqualTo(JavaCore.IGNORE); + } + + /** + * This tests the conditions to check whether the project related resource has a project and therefore can delegate + * it to the JDT logic to create a Java project. As there is no project, it is not possible. + */ + @Test + public void testWithoutProjectMarker() throws CoreException { + var resourceWithoutProject = mock(IResource.class); + when(resourceWithoutProject.getProject()).thenReturn(null); + + var markerWithResource = getRelevantMarker(); + when(markerWithResource.getResource()).thenReturn(resourceWithoutProject); + + var quickFix = getQuickFix(markerWithResource); + quickFix.run(markerWithResource); + assertThat(JavaCore.getOption(JavaCore.COMPILER_PB_UNHANDLED_WARNING_TOKEN)).isEqualTo(JavaCore.IGNORE); + } + + /** + * This tests the marker with a project, but the JavaModel cannot be mocked and therefore the Java project created + * by JDT logic is closed. The quickfix therefore falls back to the global options. + * + * The JavaModel and the related logic can only be tested by an integration test which is quite costly. + */ + @Test + public void testClosedJavaProjectMarker() throws CoreException { + var closedProject = mock(IProject.class); + when(closedProject.getType()).thenReturn(IResource.PROJECT); + + var resourceWithProject = mock(IResource.class); + when(resourceWithProject.getProject()).thenReturn(closedProject); + + var markerWithResource = getRelevantMarker(); + when(markerWithResource.getResource()).thenReturn(resourceWithProject); + + var quickFix = getQuickFix(markerWithResource); + quickFix.run(markerWithResource); + + // This means the option is not applied on the project but global level. + assertThat(JavaCore.getOption(JavaCore.COMPILER_PB_UNHANDLED_WARNING_TOKEN)).isEqualTo(JavaCore.IGNORE); + } + + private IMarker getRelevantMarker() throws CoreException { + var marker = mock(IMarker.class); + when(marker.getType()).thenReturn(SuppressWarningsQuickFixGenerator.JAVA_PROBLEM_TYPE); + when(marker.getAttribute(IMarker.MESSAGE)).thenReturn(SuppressWarningsQuickFixGenerator.MARKER_MESSAGE_GENERAL + + SuppressWarningsQuickFixGenerator.MARKER_MESSAGE_JAVA_ISSUE); + return marker; + } + + private SuppressWarningsQuickFix getQuickFix(IMarker marker) { + var quickFixes = generator.getResolutions(marker); + assertThat(quickFixes).hasSize(1); + return (SuppressWarningsQuickFix) quickFixes[0]; + } +} diff --git a/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/backend/SonarLintBackendService.java b/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/backend/SonarLintBackendService.java index 32d3d139c..0d9d47700 100644 --- a/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/backend/SonarLintBackendService.java +++ b/org.sonarlint.eclipse.core/src/org/sonarlint/eclipse/core/internal/backend/SonarLintBackendService.java @@ -187,7 +187,16 @@ protected IStatus run(IProgressMonitor monitor) { embeddedPlugins.put("web", requireNonNull(PluginPathHelper.findEmbeddedHtmlPlugin(), "HTML plugin not found")); embeddedPlugins.put("xml", requireNonNull(PluginPathHelper.findEmbeddedXmlPlugin(), "XML plugin not found")); embeddedPlugins.put("text", requireNonNull(PluginPathHelper.findEmbeddedSecretsPlugin(), "Secrets plugin not found")); - embeddedPlugins.put("cpp", requireNonNull(PluginPathHelper.findEmbeddedCFamilyPlugin(), "CFamily plugin not found")); + + // In forks of SonarQube for Eclipse the CFamily analyzer is not available (Maven option + // "-Dskip-sonarsource-repo"). In this case the plug-in would not start when it is + // required, we therefore keep this not strictly required. + var cFamilyPlugin = PluginPathHelper.findEmbeddedCFamilyPlugin(); + if (cFamilyPlugin != null) { + embeddedPlugins.put("cpp", cFamilyPlugin); + } else { + SonarLintLogger.get().info("CFamily plug-in not found - C/C++ analysis will not be available"); + } var sqConnections = ConnectionSynchronizer.buildSqConnectionDtos(); var scConnections = ConnectionSynchronizer.buildScConnectionDtos(); diff --git a/org.sonarlint.eclipse.jdt/plugin.xml b/org.sonarlint.eclipse.jdt/plugin.xml index 5d1ef0187..19b703612 100644 --- a/org.sonarlint.eclipse.jdt/plugin.xml +++ b/org.sonarlint.eclipse.jdt/plugin.xml @@ -36,4 +36,15 @@ class="org.sonarlint.eclipse.jdt.internal.JavaProjectConfiguratorExtension"> + + + + + diff --git a/org.sonarlint.eclipse.jdt/src/org/sonarlint/eclipse/jdt/internal/JavaProjectConfiguratorExtension.java b/org.sonarlint.eclipse.jdt/src/org/sonarlint/eclipse/jdt/internal/JavaProjectConfiguratorExtension.java index cb7d01a59..807acd208 100644 --- a/org.sonarlint.eclipse.jdt/src/org/sonarlint/eclipse/jdt/internal/JavaProjectConfiguratorExtension.java +++ b/org.sonarlint.eclipse.jdt/src/org/sonarlint/eclipse/jdt/internal/JavaProjectConfiguratorExtension.java @@ -63,7 +63,7 @@ public JavaProjectConfiguratorExtension() { javaProjectConfigurator = jdtPresent ? new JdtUtils() : null; } - private static boolean isJdtPresent() { + static boolean isJdtPresent() { return isClassPresentAtRuntime("org.eclipse.jdt.core.JavaCore"); } diff --git a/org.sonarlint.eclipse.jdt/src/org/sonarlint/eclipse/jdt/internal/SuppressWarningsQuickFix.java b/org.sonarlint.eclipse.jdt/src/org/sonarlint/eclipse/jdt/internal/SuppressWarningsQuickFix.java new file mode 100644 index 000000000..1540dc075 --- /dev/null +++ b/org.sonarlint.eclipse.jdt/src/org/sonarlint/eclipse/jdt/internal/SuppressWarningsQuickFix.java @@ -0,0 +1,66 @@ +/* + * SonarLint for Eclipse + * Copyright (C) SonarSource Sàrl + * 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.jdt.internal; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.swt.graphics.Image; +import org.eclipse.ui.IMarkerResolution2; +import org.sonarlint.eclipse.ui.internal.SonarLintImages; + +public class SuppressWarningsQuickFix implements IMarkerResolution2 { + @Override + public String getLabel() { + return "Disable warnings on \"@SuppressWarnings(...)\" for SonarQube issues"; + } + + @Override + public String getDescription() { + return "SonarQube uses custom ids that can be set in \"@SuppressWarnings\" to be picked up by the language " + + "analyzers to suppress potential rule violations. Eclipse JDT does not recognize these IDs and reports them " + + "as unsupported. This quick fix will disable this compiler warning on the project level if project specific " + + "Eclipse JDT configurations are available, otherwise on the workspace level."; + } + + @Override + public Image getImage() { + return SonarLintImages.RESOLUTION_QUICKFIX_CHANGE; + } + + @Override + public void run(IMarker marker) { + var resource = marker.getResource(); + if (resource != null) { + var javaProject = JavaCore.create(resource.getProject()); + if (javaProject != null && javaProject.exists()) { + var projectOptions = javaProject.getOptions(false); + if (!projectOptions.isEmpty()) { + projectOptions.put(JavaCore.COMPILER_PB_UNHANDLED_WARNING_TOKEN, JavaCore.IGNORE); + javaProject.setOptions(projectOptions); + return; + } + } + } + + var options = JavaCore.getOptions(); + options.put(JavaCore.COMPILER_PB_UNHANDLED_WARNING_TOKEN, JavaCore.IGNORE); + JavaCore.setOptions(options); + } +} diff --git a/org.sonarlint.eclipse.jdt/src/org/sonarlint/eclipse/jdt/internal/SuppressWarningsQuickFixGenerator.java b/org.sonarlint.eclipse.jdt/src/org/sonarlint/eclipse/jdt/internal/SuppressWarningsQuickFixGenerator.java new file mode 100644 index 000000000..cf60166c4 --- /dev/null +++ b/org.sonarlint.eclipse.jdt/src/org/sonarlint/eclipse/jdt/internal/SuppressWarningsQuickFixGenerator.java @@ -0,0 +1,71 @@ +/* + * SonarLint for Eclipse + * Copyright (C) SonarSource Sàrl + * 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.jdt.internal; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.ui.IMarkerResolution; +import org.eclipse.ui.IMarkerResolutionGenerator2; +import org.sonarlint.eclipse.core.SonarLintLogger; + +/** + * Generator for the Quick Fox on the "Unsupported @SuppressWarnings(...)" marker that is added when the value(s) + * provided is one indicating a SonarQube-related rule - these are unknown to Eclipse JDT. + */ +public class SuppressWarningsQuickFixGenerator implements IMarkerResolutionGenerator2 { + static final String JAVA_PROBLEM_TYPE = "org.eclipse.jdt.core.problem"; //$NON-NLS-1$ + static final String MARKER_MESSAGE_GENERAL = "Unsupported @SuppressWarnings"; //$NON-NLS-1$ + static final String MARKER_MESSAGE_JAVA_ISSUE = "java:"; //$NON-NLS-1$ + static final String MARKER_MESSAGE_JAVA_SECURITY_ISSUE = "javasecurity:"; //$NON-NLS-1$ + static final String MARKER_MESSAGE_JAVA_BUGS_ISSUE = "javabugs:"; //$NON-NLS-1$ + static final String MARKER_MESSAGE_JAVA_ARCHITECTURE_ISSUE = "javaarchitecture:"; //$NON-NLS-1$ + + @Override + public IMarkerResolution[] getResolutions(IMarker marker) { + if (isSuppressWarningsJavaProblem(marker)) { + return new IMarkerResolution[] {new SuppressWarningsQuickFix()}; + } + return new IMarkerResolution[0]; + } + + @Override + public boolean hasResolutions(IMarker marker) { + return isSuppressWarningsJavaProblem(marker); + } + + private boolean isSuppressWarningsJavaProblem(IMarker marker) { + if (!JavaProjectConfiguratorExtension.isJdtPresent()) { + return false; + } + + try { + var isJavaProblem = marker.getType().equals(JAVA_PROBLEM_TYPE); + var message = (String) marker.getAttribute(IMarker.MESSAGE); + return isJavaProblem && message != null && message.contains(MARKER_MESSAGE_GENERAL) + && (message.toLowerCase().contains(MARKER_MESSAGE_JAVA_ISSUE) + || message.toLowerCase().contains(MARKER_MESSAGE_JAVA_SECURITY_ISSUE) + || message.toLowerCase().contains(MARKER_MESSAGE_JAVA_BUGS_ISSUE) + || message.toLowerCase().contains(MARKER_MESSAGE_JAVA_ARCHITECTURE_ISSUE)); + } catch (CoreException err) { + SonarLintLogger.get().error(err.getMessage(), err); + return false; + } + } +} diff --git a/org.sonarlint.eclipse.ui/META-INF/MANIFEST.MF b/org.sonarlint.eclipse.ui/META-INF/MANIFEST.MF index b67b8624d..aae740bd3 100644 --- a/org.sonarlint.eclipse.ui/META-INF/MANIFEST.MF +++ b/org.sonarlint.eclipse.ui/META-INF/MANIFEST.MF @@ -28,7 +28,7 @@ Require-Bundle: org.eclipse.core.runtime, org.eclipse.compare, org.eclipse.core.expressions, org.sonarsource.sonarlint.core.sonarlint-java-client-osgi;bundle-version="[11.2.0,11.3.0)" -Export-Package: org.sonarlint.eclipse.ui.internal;x-friends:="org.sonarlint.eclipse.core.tests", +Export-Package: org.sonarlint.eclipse.ui.internal;x-friends:="org.sonarlint.eclipse.core.tests,org.sonarlint.eclipse.jdt", org.sonarlint.eclipse.ui.internal.backend;x-friends:="org.sonarlint.eclipse.core.tests", org.sonarlint.eclipse.ui.internal.notifications;x-friends:="org.sonarlint.eclipse.core.tests", org.sonarlint.eclipse.ui.internal.popup;x-friends:="org.sonarlint.eclipse.core.tests", diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/ExtendedSonarLintImages.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/ExtendedSonarLintImages.java new file mode 100644 index 000000000..c3fafe14d --- /dev/null +++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/ExtendedSonarLintImages.java @@ -0,0 +1,160 @@ +/* + * SonarLint for Eclipse + * Copyright (C) SonarSource Sàrl + * 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 java.util.Locale; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.swt.graphics.Image; +import org.sonarlint.eclipse.core.internal.markers.MarkerUtils.FindingMatchingStatus; +import org.sonarsource.sonarlint.core.rpc.protocol.common.ImpactSeverity; +import org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity; +import org.sonarsource.sonarlint.core.rpc.protocol.common.RuleType; + +/** + * Purpose of this class is to remove the dependencies to "SonarLint Core" from the base "SonarLintImages" class so it + * can be imported safely in the "org.sonarlint.eclipse.jdt" bundle. + */ +public final class ExtendedSonarLintImages extends SonarLintImages { + private ExtendedSonarLintImages() { + super(); + } + + /** + * Mapping of the matching status of an issue to the specific image + * + * @param matchingStatus specific matching status of an issue + * @param isResolved resolution status of an issue + * @return the corresponding connection mode icon (including standalone) + */ + private static ImageDescriptor matchingStatusToImageDescriptor(FindingMatchingStatus matchingStatus, + boolean isResolved) { + if (matchingStatus == FindingMatchingStatus.NOT_MATCHED) { + return isResolved ? STANDALONE_RESOLVED_16 : STANDALONE_16; + } else if (matchingStatus == FindingMatchingStatus.MATCHED_WITH_SC) { + return isResolved ? SONARCLOUD_RESOLVED_16 : SONARCLOUD_16; + } + return isResolved ? SONARQUBE_RESOLVED_16 : SONARQUBE_16; + } + + /** + * Create a composite image with the markers' project connection mode, issue severity and type + * + * @param matchingStatus matching status of an issue (nullable when grouped) + * @param issueSeverity issue severity (NOT impact severity) + * @param type issue type + * @param isResolved issue status + * @return composite image if found, null otherwise + */ + @Nullable + public static Image getIssueImage(@Nullable FindingMatchingStatus matchingStatus, String issueSeverity, + @Nullable String type, boolean isResolved) { + var key = matchingStatus + "/" + issueSeverity + "/" + type + "/" + isResolved; + var imageRegistry = SonarLintUiPlugin.getDefault().getImageRegistry(); + var image = imageRegistry.get(key); + if (image == null) { + ImageDescriptor matchingStatusImage = null; + if (matchingStatus != null) { + matchingStatusImage = matchingStatusToImageDescriptor(matchingStatus, isResolved); + } + var severityImage = getImpactSeverityImageDescriptor(issueSeverity); + ImageDescriptor typeImage = null; + if (type != null) { + typeImage = createImageDescriptor("type/" + type.toLowerCase(Locale.ENGLISH) + ".png"); + } + imageRegistry.put(key, new CompositeIssueImage(matchingStatusImage, severityImage, typeImage)); + } + return imageRegistry.get(key); + } + + /** + * Create a composite image for the new clean code taxonomy with the markers' connection mode and highest impact + * + * @param matchingStatus matching status of an issue (nullable when grouped) + * @param impact highest issue impact + * @param isResolved issue status + * @return composite image if found, null otherwise + */ + @Nullable + public static Image getIssueImage(@Nullable FindingMatchingStatus matchingStatus, String impact, + boolean isResolved) { + var key = matchingStatus + "/" + impact + "/" + isResolved; + var imageRegistry = SonarLintUiPlugin.getDefault().getImageRegistry(); + var image = imageRegistry.get(key); + if (image == null) { + ImageDescriptor matchingStatusImage = null; + if (matchingStatus != null) { + matchingStatusImage = matchingStatusToImageDescriptor(matchingStatus, isResolved); + } + var impactImage = createImageDescriptor(IMPACT_FOLDER_PREFIX + impact.toLowerCase(Locale.ENGLISH) + ".png"); + imageRegistry.put(key, new CompositeIssueImage(matchingStatusImage, impactImage, null)); + } + return imageRegistry.get(key); + } + + @Nullable + public static ImageDescriptor getImpactSeverityImageDescriptor(String issueSeverity) { + ImpactSeverity impactSeverity; + if (IssueSeverity.BLOCKER.name().equals(issueSeverity)) { + impactSeverity = ImpactSeverity.BLOCKER; + } else if (IssueSeverity.CRITICAL.name().equals(issueSeverity)) { + impactSeverity = ImpactSeverity.HIGH; + } else if (IssueSeverity.MAJOR.name().equals(issueSeverity)) { + impactSeverity = ImpactSeverity.MEDIUM; + } else if (IssueSeverity.MINOR.name().equals(issueSeverity)) { + impactSeverity = ImpactSeverity.LOW; + } else { + impactSeverity = ImpactSeverity.INFO; + } + return createImageDescriptor(IMPACT_FOLDER_PREFIX + impactSeverity.name().toLowerCase(Locale.ENGLISH) + ".png"); + } + + @Nullable + public static Image getSeverityImage(IssueSeverity severity) { + ImpactSeverity impactSeverity; + switch (severity) { + case BLOCKER: + impactSeverity = ImpactSeverity.BLOCKER; + break; + case CRITICAL: + impactSeverity = ImpactSeverity.HIGH; + break; + case MAJOR: + impactSeverity = ImpactSeverity.MEDIUM; + break; + case MINOR: + impactSeverity = ImpactSeverity.LOW; + break; + default: + impactSeverity = ImpactSeverity.INFO; + } + return getImpactImage(impactSeverity); + } + + @Nullable + public static Image getTypeImage(RuleType type) { + return createImage("type/" + type.name().toLowerCase(Locale.ENGLISH) + ".png"); + } + + @Nullable + public static Image getImpactImage(ImpactSeverity impact) { + return createImage(IMPACT_FOLDER_PREFIX + impact.name().toLowerCase(Locale.ENGLISH) + ".png"); + } +} 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 c0566d47a..d211eb19f 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 @@ -20,19 +20,14 @@ package org.sonarlint.eclipse.ui.internal; import java.net.URL; -import java.util.Locale; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jface.resource.CompositeImageDescriptor; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; -import org.sonarlint.eclipse.core.internal.markers.MarkerUtils.FindingMatchingStatus; -import org.sonarsource.sonarlint.core.rpc.protocol.common.ImpactSeverity; -import org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity; -import org.sonarsource.sonarlint.core.rpc.protocol.common.RuleType; -public final class SonarLintImages { - private static final String IMPACT_FOLDER_PREFIX = "impact/"; +public class SonarLintImages { + protected static final String IMPACT_FOLDER_PREFIX = "impact/"; public static final ImageDescriptor IMG_WIZBAN_NEW_CONNECTION = createImageDescriptor("new_server_wiz.png"); //$NON-NLS-1$ public static final ImageDescriptor UPDATE_IMG = createImageDescriptor("update.gif"); //$NON-NLS-1$ @@ -92,84 +87,14 @@ public final class SonarLintImages { public static final Image NOTIFICATION_CLOSE = createImage("notifications/eview16/notification-close.png"); //$NON-NLS-1$ public static final Image NOTIFICATION_CLOSE_HOVER = createImage("notifications/eview16/notification-close-active.png"); //$NON-NLS-1$ - private SonarLintImages() { - } - - /** - * Mapping of the matching status of an issue to the specific image - * - * @param matchingStatus specific matching status of an issue - * @param isResolved resolution status of an issue - * @return the corresponding connection mode icon (including standalone) - */ - private static ImageDescriptor matchingStatusToImageDescriptor(FindingMatchingStatus matchingStatus, - boolean isResolved) { - if (matchingStatus == FindingMatchingStatus.NOT_MATCHED) { - return isResolved ? STANDALONE_RESOLVED_16 : STANDALONE_16; - } else if (matchingStatus == FindingMatchingStatus.MATCHED_WITH_SC) { - return isResolved ? SONARCLOUD_RESOLVED_16 : SONARCLOUD_16; - } - return isResolved ? SONARQUBE_RESOLVED_16 : SONARQUBE_16; - } - - /** - * Create a composite image with the markers' project connection mode, issue severity and type - * - * @param matchingStatus matching status of an issue (nullable when grouped) - * @param issueSeverity issue severity (NOT impact severity) - * @param type issue type - * @param isResolved issue status - * @return composite image if found, null otherwise - */ - @Nullable - public static Image getIssueImage(@Nullable FindingMatchingStatus matchingStatus, String issueSeverity, - @Nullable String type, boolean isResolved) { - var key = matchingStatus + "/" + issueSeverity + "/" + type + "/" + isResolved; - var imageRegistry = SonarLintUiPlugin.getDefault().getImageRegistry(); - var image = imageRegistry.get(key); - if (image == null) { - ImageDescriptor matchingStatusImage = null; - if (matchingStatus != null) { - matchingStatusImage = matchingStatusToImageDescriptor(matchingStatus, isResolved); - } - var severityImage = getImpactSeverityImageDescriptor(issueSeverity); - ImageDescriptor typeImage = null; - if (type != null) { - typeImage = createImageDescriptor("type/" + type.toLowerCase(Locale.ENGLISH) + ".png"); - } - imageRegistry.put(key, new CompositeIssueImage(matchingStatusImage, severityImage, typeImage)); - } - return imageRegistry.get(key); - } - - /** - * Create a composite image for the new clean code taxonomy with the markers' connection mode and highest impact - * - * @param matchingStatus matching status of an issue (nullable when grouped) - * @param impact highest issue impact - * @param isResolved issue status - * @return composite image if found, null otherwise - */ - @Nullable - public static Image getIssueImage(@Nullable FindingMatchingStatus matchingStatus, String impact, - boolean isResolved) { - var key = matchingStatus + "/" + impact + "/" + isResolved; - var imageRegistry = SonarLintUiPlugin.getDefault().getImageRegistry(); - var image = imageRegistry.get(key); - if (image == null) { - ImageDescriptor matchingStatusImage = null; - if (matchingStatus != null) { - matchingStatusImage = matchingStatusToImageDescriptor(matchingStatus, isResolved); - } - var impactImage = createImageDescriptor(IMPACT_FOLDER_PREFIX + impact.toLowerCase(Locale.ENGLISH) + ".png"); - imageRegistry.put(key, new CompositeIssueImage(matchingStatusImage, impactImage, null)); - } - return imageRegistry.get(key); + protected SonarLintImages() { + // utility class } /** For issue markers where no grouping can be applied (e.g. new CCT unavailable for old SonarQube connections) */ public static Image getNotAvailableImage() { var key = "notAvailable"; + var imageRegistry = SonarLintUiPlugin.getDefault().getImageRegistry(); var image = imageRegistry.get(key); if (image == null) { @@ -178,61 +103,12 @@ public static Image getNotAvailableImage() { return imageRegistry.get(key); } - @Nullable - public static ImageDescriptor getImpactSeverityImageDescriptor(String issueSeverity) { - ImpactSeverity impactSeverity; - if (IssueSeverity.BLOCKER.name().equals(issueSeverity)) { - impactSeverity = ImpactSeverity.BLOCKER; - } else if (IssueSeverity.CRITICAL.name().equals(issueSeverity)) { - impactSeverity = ImpactSeverity.HIGH; - } else if (IssueSeverity.MAJOR.name().equals(issueSeverity)) { - impactSeverity = ImpactSeverity.MEDIUM; - } else if (IssueSeverity.MINOR.name().equals(issueSeverity)) { - impactSeverity = ImpactSeverity.LOW; - } else { - impactSeverity = ImpactSeverity.INFO; - } - return createImageDescriptor(IMPACT_FOLDER_PREFIX + impactSeverity.name().toLowerCase(Locale.ENGLISH) + ".png"); - } - - @Nullable - public static Image getSeverityImage(IssueSeverity severity) { - ImpactSeverity impactSeverity; - switch (severity) { - case BLOCKER: - impactSeverity = ImpactSeverity.BLOCKER; - break; - case CRITICAL: - impactSeverity = ImpactSeverity.HIGH; - break; - case MAJOR: - impactSeverity = ImpactSeverity.MEDIUM; - break; - case MINOR: - impactSeverity = ImpactSeverity.LOW; - break; - default: - impactSeverity = ImpactSeverity.INFO; - } - return getImpactImage(impactSeverity); - } - - @Nullable - public static Image getTypeImage(RuleType type) { - return createImage("type/" + type.name().toLowerCase(Locale.ENGLISH) + ".png"); - } - - @Nullable - public static Image getImpactImage(ImpactSeverity impact) { - return createImage(IMPACT_FOLDER_PREFIX + impact.name().toLowerCase(Locale.ENGLISH) + ".png"); - } - /** * Due to Eclipse platform restrictions on the composite marker images: Even though the number of icons on the old * and new CCT differ, we can't display them without "empty" icons. When the old CCT will be removed at some point * the composite image will be correctly displayed at last! */ - private static class CompositeIssueImage extends CompositeImageDescriptor { + protected static class CompositeIssueImage extends CompositeImageDescriptor { @Nullable private final ImageDescriptor matchingStatus; private final ImageDescriptor requiredPart; @@ -282,13 +158,13 @@ private static URL getIconUrl(String key) { return SonarLintUiPlugin.getDefault().getBundle().getEntry("icons/" + key); } - private static Image createImage(String key) { + protected static Image createImage(String key) { createImageDescriptor(key); var imageRegistry = SonarLintUiPlugin.getDefault().getImageRegistry(); return imageRegistry.get(key); } - private static ImageDescriptor createImageDescriptor(String key) { + protected static ImageDescriptor createImageDescriptor(String key) { var imageRegistry = SonarLintUiPlugin.getDefault().getImageRegistry(); var imageDescriptor = imageRegistry.getDescriptor(key); if (imageDescriptor == null) { @@ -302,5 +178,4 @@ private static ImageDescriptor createImageDescriptor(String key) { } return imageDescriptor; } - } diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/dialog/AbstractFixSuggestionDialog.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/dialog/AbstractFixSuggestionDialog.java index 32419b8ca..45a7e91ed 100644 --- a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/dialog/AbstractFixSuggestionDialog.java +++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/dialog/AbstractFixSuggestionDialog.java @@ -21,12 +21,12 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.Locale; import org.eclipse.compare.CompareConfiguration; import org.eclipse.compare.IEncodedStreamContentAccessor; import org.eclipse.compare.ITypedElement; import org.eclipse.compare.contentmergeviewer.TextMergeViewer; -import org.eclipse.compare.internal.Utilities; import org.eclipse.compare.structuremergeviewer.DiffNode; import org.eclipse.core.runtime.CoreException; import org.eclipse.jdt.annotation.Nullable; @@ -161,7 +161,7 @@ public CodeNode(String content) { @Override public InputStream getContents() throws CoreException { - return new ByteArrayInputStream(Utilities.getBytes(content, "UTF-8")); + return new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)); } @Override diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/rule/LegacyRuleHeaderPanel.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/rule/LegacyRuleHeaderPanel.java index 6f87229cf..6eac79189 100644 --- a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/rule/LegacyRuleHeaderPanel.java +++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/rule/LegacyRuleHeaderPanel.java @@ -23,7 +23,7 @@ import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Label; -import org.sonarlint.eclipse.ui.internal.SonarLintImages; +import org.sonarlint.eclipse.ui.internal.ExtendedSonarLintImages; import org.sonarsource.sonarlint.core.rpc.protocol.common.StandardModeDetails; /** Rule header for the old CCT */ @@ -51,11 +51,11 @@ public LegacyRuleHeaderPanel(Composite parent, StandardModeDetails details, Stri @Override public void updateRule() { var type = details.getType(); - ruleTypeIcon.setImage(SonarLintImages.getTypeImage(type)); + ruleTypeIcon.setImage(ExtendedSonarLintImages.getTypeImage(type)); ruleTypeLabel.setText(clean(type.toString())); var severity = details.getSeverity(); - ruleSeverityIcon.setImage(SonarLintImages.getSeverityImage(severity)); + ruleSeverityIcon.setImage(ExtendedSonarLintImages.getSeverityImage(severity)); ruleSeverityLabel.setText(clean(severity.toString())); ruleKeyLabel.setText(ruleKey); diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/rule/RuleHeaderPanel.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/rule/RuleHeaderPanel.java index ab3199384..95864aafe 100644 --- a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/rule/RuleHeaderPanel.java +++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/rule/RuleHeaderPanel.java @@ -25,7 +25,7 @@ import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Label; -import org.sonarlint.eclipse.ui.internal.SonarLintImages; +import org.sonarlint.eclipse.ui.internal.ExtendedSonarLintImages; import org.sonarsource.sonarlint.core.client.utils.ImpactSeverity; import org.sonarsource.sonarlint.core.client.utils.SoftwareQuality; import org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.ImpactDto; @@ -94,7 +94,7 @@ public void updateImpact(ImpactDto impact) { softwareQualityLabel.setText(sqWithLabel.getLabel()); softwareQualityLabel.setToolTipText(tooltip); - impactSeverityIcon.setImage(SonarLintImages.getImpactImage(impact.getImpactSeverity())); + impactSeverityIcon.setImage(ExtendedSonarLintImages.getImpactImage(impact.getImpactSeverity())); impactSeverityIcon.setToolTipText(tooltip); } } 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 823d7bb2f..7469ee0b8 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 @@ -29,6 +29,7 @@ import org.eclipse.ui.views.markers.MarkerField; import org.eclipse.ui.views.markers.MarkerItem; import org.sonarlint.eclipse.core.internal.markers.MarkerUtils; +import org.sonarlint.eclipse.ui.internal.ExtendedSonarLintImages; import org.sonarlint.eclipse.ui.internal.SonarLintImages; import org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity; @@ -118,13 +119,13 @@ private static Image getImage(MarkerItem item) { var highestImpact = MarkerUtils.decodeHighestImpact( item.getAttributeValue(MarkerUtils.SONAR_MARKER_ISSUE_HIGHEST_IMPACT_ATTR, null)); if (cleanCodeAttribute == null || highestImpact == null) { - return SonarLintImages.getIssueImage(matchingStatus, + return ExtendedSonarLintImages.getIssueImage(matchingStatus, item.getAttributeValue(MarkerUtils.SONAR_MARKER_ISSUE_SEVERITY_ATTR, "major"), item.getAttributeValue(MarkerUtils.SONAR_MARKER_ISSUE_TYPE_ATTR, "code_smell"), isResolved); } - return SonarLintImages.getIssueImage(matchingStatus, highestImpact.name(), isResolved); + return ExtendedSonarLintImages.getIssueImage(matchingStatus, highestImpact.name(), isResolved); } else { // It is no actual marker but a groupBy item which groups the headers var groupByTitle = item.getAttributeValue(IMarker.MESSAGE, "") @@ -133,16 +134,16 @@ private static Image getImage(MarkerItem item) { .trim(); // As we offer multiple different grouping options (severity / impacts), we have to display - // the correct grouping icon. + // the correct grouping icon. if (groupByTitle.endsWith(" IMPACT")) { - return SonarLintImages.getIssueImage(null, groupByTitle.replace(" IMPACT", ""), false); + return ExtendedSonarLintImages.getIssueImage(null, groupByTitle.replace(" IMPACT", ""), false); } else if (IssueSeverity.BLOCKER.name().equals(groupByTitle) || IssueSeverity.CRITICAL.name().equals(groupByTitle) || IssueSeverity.MAJOR.name().equals(groupByTitle) || IssueSeverity.MINOR.name().equals(groupByTitle) || IssueSeverity.INFO.name().equals(groupByTitle)) { - return SonarLintImages.getIssueImage(null, groupByTitle, null, false); - } + return ExtendedSonarLintImages.getIssueImage(null, groupByTitle, null, false); + } return SonarLintImages.getNotAvailableImage(); } }