diff --git a/.gitignore b/.gitignore index 514532d83..93587eaec 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ build/ out/ /bin/ +src/main/resources/stylesheets/*.css +src/main/resources/stylesheets/*.css.map ################################# # Gradle files # diff --git a/CHANGELOG.md b/CHANGELOG.md index 36aa602bd..74777bbbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,13 @@ # Snyk Security Changelog +## [2.8.1] +### Added +- Injects custom styling for the HTML panel used by Snyk Code for consistent ignores. + ## [2.8.0] ### Added -- Serve Snyk Code functionality via language server. This enables auto-scanning on startup / save, code actions for Snyk Learn and, if enabled, Snyk Auto-Fix. The number of uploaded files is not shown anymore. +- Serve Snyk Code functionality via language server. This enables auto-scanning on startup / save, code actions for Snyk Learn and, if enabled, Snyk Auto-Fix. The number of uploaded files is not shown anymore. ## [2.7.21] diff --git a/build.gradle.kts b/build.gradle.kts index 8d8c01a86..9ff2969c4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,6 +14,7 @@ plugins { id("org.jetbrains.kotlin.jvm") version "1.9.23" id("io.gitlab.arturbosch.detekt") version ("1.23.6") id("pl.allegro.tech.build.axion-release") version "1.17.0" + id("io.miret.etienne.sass") version "1.5.0" } version = scmVersion.version @@ -88,6 +89,19 @@ detekt { } tasks { + // plugin from https://github.com/EtienneMiret/sass-gradle-plugin + compileSass { + sourceDir = project.file("${projectDir}/src/main/resources/stylesheets") + outputDir = project.file("${projectDir}/src/main/resources/stylesheets") + } + + processResources{ + dependsOn(compileSass) + } + compileKotlin{ + dependsOn(processResources) + } + withType { kotlinOptions.jvmTarget = jdk kotlinOptions.languageVersion = "1.9" diff --git a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/JCEFDescriptionPanel.kt b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/JCEFDescriptionPanel.kt index 7c5724dc2..eb682afbd 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/JCEFDescriptionPanel.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/JCEFDescriptionPanel.kt @@ -2,8 +2,8 @@ package io.snyk.plugin.ui.toolwindow.panels import com.intellij.uiDesigner.core.GridLayoutManager import com.intellij.util.ui.JBUI -import io.snyk.plugin.pluginSettings import io.snyk.plugin.SnykFile +import io.snyk.plugin.pluginSettings import io.snyk.plugin.ui.DescriptionHeaderPanel import io.snyk.plugin.ui.SnykBalloonNotificationHelper import io.snyk.plugin.ui.baseGridConstraintsAnchorWest @@ -15,6 +15,7 @@ import io.snyk.plugin.ui.toolwindow.SnykToolWindowPanel import io.snyk.plugin.ui.wrapWithScrollPane import snyk.common.ProductType import snyk.common.lsp.ScanIssue +import stylesheets.SnykStylesheets import java.awt.BorderLayout import java.awt.Font import javax.swing.JLabel @@ -37,7 +38,8 @@ class SuggestionDescriptionPanelFromLS( issue.canLoadSuggestionPanelFromHTML() ) { val openFileLoadHandlerGenerator = OpenFileLoadHandlerGenerator(snykFile) - val jbCefBrowserComponent = JCEFUtils.getJBCefBrowserComponentIfSupported(issue.details()) { + val html = this.getStyledHTML() + val jbCefBrowserComponent = JCEFUtils.getJBCefBrowserComponentIfSupported(html) { openFileLoadHandlerGenerator.generate(it) } if (jbCefBrowserComponent == null) { @@ -111,6 +113,29 @@ class SuggestionDescriptionPanelFromLS( } return Pair(panel, lastRowToAddSpacer) } + + fun getStyledHTML(): String { + var html = issue.details() + var ideStyle = "" + if (issue.additionalData.getProductType() == ProductType.CODE_SECURITY || issue.additionalData.getProductType() == ProductType.CODE_QUALITY) { + ideStyle = SnykStylesheets.SnykCodeSuggestion + + } + html = html.replace("\${ideStyle}", "") + + val nonce = getNonce() + html = html.replace("\${nonce}", nonce) + + return html + } + + private fun getNonce(): String { + val allowedChars = ('A'..'Z') + ('a'..'z') + ('0'..'9') + return (1..32) + .map { allowedChars.random() } + .joinToString("") + } + } fun defaultFontLabel(labelText: String, bold: Boolean = false): JLabel { diff --git a/src/main/kotlin/stylesheets/SnykStylesheets.kt b/src/main/kotlin/stylesheets/SnykStylesheets.kt new file mode 100644 index 000000000..9ae59525c --- /dev/null +++ b/src/main/kotlin/stylesheets/SnykStylesheets.kt @@ -0,0 +1,11 @@ +package stylesheets + +object SnykStylesheets { + private fun getStylesheet(name: String): String { + return this::class.java.getResource(name)?.readText() + ?: "" + } + + val SnykCodeSuggestion = getStylesheet("/stylesheets/snyk_code_suggestion.css") +} + diff --git a/src/main/resources/stylesheets/snyk_code_suggestion.scss b/src/main/resources/stylesheets/snyk_code_suggestion.scss new file mode 100644 index 000000000..88e525e25 --- /dev/null +++ b/src/main/resources/stylesheets/snyk_code_suggestion.scss @@ -0,0 +1,77 @@ +::-webkit-scrollbar-thumb { + background: var(--scrollbar-thumb-color); +} + +::-webkit-scrollbar-thumb:hover { + background: #595a5c; +} + +body { + color: var(--text-color); +} + +a, +.link { + color: var(--link-color); +} + +.delimiter { + border: 1px solid #505254; +} + + +.ignore-warning { + background: #FFF4ED; + color: #B6540B; + border: 1px solid #E27122; +} + + +.ignore-badge { + background: #FFF4ED; + color: #B6540B; + border: 1px solid #E27122; +} + + +.data-flow-body { + background-color: var(--data-flow-body-color); +} + + +.data-flow-clickable-row { + color: var(--link-color); +} + + +.data-flow-delimiter { + color: #BBBBBB; +} + +.tabs-nav { + border-bottom: 1px solid var(--tabs-bottom-color); +} + +.tab-item-icon path { + fill: var(--tab-item-github-icon-color); +} + +.tab-item:hover { + background-color: var(--tab-item-hover-color); +} + +.tab-item.is-selected { + border-bottom: 3px solid #3474f0; +} + +.example-line.added { + background-color: var(--example-line-added-color); +} + +.example-line.removed { + background-color: var(--example-line-removed-color); +} + +.ignore-action-container { + display: none; +} diff --git a/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelFromLSCodeTest.kt b/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelFromLSCodeTest.kt index edbb1aca7..2da95054d 100644 --- a/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelFromLSCodeTest.kt +++ b/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelFromLSCodeTest.kt @@ -161,4 +161,18 @@ class SuggestionDescriptionPanelFromLSCodeTest : BasePlatformTestCase() { val actualBrowser = getJLabelByText(cut, "HTML message") assertNotNull(actualBrowser) } + + @Test + fun `test getStyledHTML should inject CSS into the HTML`() { + pluginSettings().isGlobalIgnoresFeatureEnabled = true + + every { issue.details() } returns "\${ideStyle}HTML message" + every { issue.canLoadSuggestionPanelFromHTML() } returns true + cut = SuggestionDescriptionPanelFromLS(snykFile, issue) + + val actual = cut.getStyledHTML() + assertFalse(actual.contains("\${ideStyle}")) + assertFalse(actual.contains("\${nonce}")) + assertTrue(actual.contains(".ignore-warning")) + } } diff --git a/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelFromLSOSSTest.kt b/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelFromLSOSSTest.kt index b04aac7b2..5e76b4212 100644 --- a/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelFromLSOSSTest.kt +++ b/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelFromLSOSSTest.kt @@ -7,32 +7,22 @@ import com.intellij.psi.PsiFile import com.intellij.testFramework.fixtures.BasePlatformTestCase import io.mockk.every import io.mockk.mockk -import io.mockk.mockkObject import io.mockk.unmockkAll import io.snyk.plugin.Severity import io.snyk.plugin.pluginSettings import io.snyk.plugin.resetSettings import io.snyk.plugin.SnykFile -import io.snyk.plugin.ui.jcef.JCEFUtils import io.snyk.plugin.ui.toolwindow.panels.SuggestionDescriptionPanelFromLS -import org.eclipse.lsp4j.Position -import org.eclipse.lsp4j.Range import org.junit.Before import org.junit.Test import snyk.UIComponentFinder.getActionLinkByText -import snyk.UIComponentFinder.getJBCEFBrowser -import snyk.UIComponentFinder.getJButtonByText import snyk.UIComponentFinder.getJLabelByText import snyk.UIComponentFinder.getJPanelByName import snyk.code.annotator.SnykCodeAnnotator import snyk.common.ProductType -import snyk.common.lsp.CommitChangeLine -import snyk.common.lsp.DataFlow -import snyk.common.lsp.ExampleCommitFix import snyk.common.lsp.IssueData import snyk.common.lsp.ScanIssue import java.nio.file.Paths -import javax.swing.JLabel class SuggestionDescriptionPanelFromLSOSSTest : BasePlatformTestCase() { private lateinit var cut: SuggestionDescriptionPanelFromLS @@ -115,4 +105,19 @@ class SuggestionDescriptionPanelFromLSOSSTest : BasePlatformTestCase() { val ossOverviewPanel = getJPanelByName(cut, "overviewPanel") assertNotNull(ossOverviewPanel) } + + @Test + fun `test getStyledHTML should inject CSS into the HTML`() { + pluginSettings().isGlobalIgnoresFeatureEnabled = true + + every { issue.details() } returns "HTML message" + every { issue.canLoadSuggestionPanelFromHTML() } returns true + cut = SuggestionDescriptionPanelFromLS(snykFile, issue) + + val actual = cut.getStyledHTML() + + // we don't apply any custom style for oss + assertFalse(actual.contains("\${ideStyle}")) + assertFalse(actual.contains(".ignore-warning")) + } }