diff --git a/src/main/kotlin/io/snyk/plugin/SnykBulkFileListener.kt b/src/main/kotlin/io/snyk/plugin/SnykBulkFileListener.kt index 77c51d6d0..360ea2e83 100644 --- a/src/main/kotlin/io/snyk/plugin/SnykBulkFileListener.kt +++ b/src/main/kotlin/io/snyk/plugin/SnykBulkFileListener.kt @@ -2,6 +2,7 @@ package io.snyk.plugin import com.intellij.ide.impl.ProjectUtil import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.isFile import com.intellij.openapi.vfs.newvfs.BulkFileListener @@ -49,7 +50,6 @@ import com.intellij.openapi.vfs.newvfs.events.VFilePropertyChangeEvent * - addressed at `before` state, old file processed to _clean_ caches */ abstract class SnykBulkFileListener : BulkFileListener { - /****************************** Before **************************/ override fun before(events: List) { diff --git a/src/main/kotlin/io/snyk/plugin/SnykPostStartupActivity.kt b/src/main/kotlin/io/snyk/plugin/SnykPostStartupActivity.kt index 810904ec5..2b7972587 100644 --- a/src/main/kotlin/io/snyk/plugin/SnykPostStartupActivity.kt +++ b/src/main/kotlin/io/snyk/plugin/SnykPostStartupActivity.kt @@ -12,12 +12,10 @@ import com.intellij.openapi.startup.ProjectActivity import com.intellij.openapi.vfs.VirtualFileManager import io.snyk.plugin.extensions.SnykControllerImpl import io.snyk.plugin.extensions.SnykControllerManager -import io.snyk.plugin.snykcode.SnykCodeBulkFileListener import io.snyk.plugin.ui.SnykBalloonNotifications import snyk.common.AnnotatorCommon +import snyk.common.lsp.LanguageServerBulkFileListener import snyk.container.ContainerBulkFileListener -import snyk.iac.IacBulkFileListener -import snyk.oss.OssBulkFileListener import java.time.Instant import java.time.temporal.ChronoUnit import java.util.Date @@ -43,9 +41,7 @@ class SnykPostStartupActivity : ProjectActivity { if (!listenersActivated) { val messageBusConnection = ApplicationManager.getApplication().messageBus.connect() // TODO: add subscription for language server messages - messageBusConnection.subscribe(VirtualFileManager.VFS_CHANGES, OssBulkFileListener()) - messageBusConnection.subscribe(VirtualFileManager.VFS_CHANGES, SnykCodeBulkFileListener()) - messageBusConnection.subscribe(VirtualFileManager.VFS_CHANGES, IacBulkFileListener()) + messageBusConnection.subscribe(VirtualFileManager.VFS_CHANGES, LanguageServerBulkFileListener()) messageBusConnection.subscribe(VirtualFileManager.VFS_CHANGES, ContainerBulkFileListener()) messageBusConnection.subscribe(ProjectManager.TOPIC, SnykProjectManagerListener()) listenersActivated = true diff --git a/src/main/kotlin/io/snyk/plugin/SnykProjectManagerListener.kt b/src/main/kotlin/io/snyk/plugin/SnykProjectManagerListener.kt index 185bac6fa..862cdcbae 100644 --- a/src/main/kotlin/io/snyk/plugin/SnykProjectManagerListener.kt +++ b/src/main/kotlin/io/snyk/plugin/SnykProjectManagerListener.kt @@ -22,7 +22,6 @@ class SnykProjectManagerListener : ProjectManagerListener { // limit clean up to TIMEOUT try { threadPool.submit { - // lets all running ProgressIndicators release MUTEX first val ls = LanguageServerWrapper.getInstance() if (ls.isInitialized) { ls.updateWorkspaceFolders(emptySet(), ls.getWorkspaceFolders(project)) diff --git a/src/main/kotlin/io/snyk/plugin/events/SnykScanListener.kt b/src/main/kotlin/io/snyk/plugin/events/SnykScanListener.kt index 51c37e191..bc7fdce62 100644 --- a/src/main/kotlin/io/snyk/plugin/events/SnykScanListener.kt +++ b/src/main/kotlin/io/snyk/plugin/events/SnykScanListener.kt @@ -13,10 +13,6 @@ interface SnykScanListener { fun scanningStarted() - fun scanningIacFinished(iacResult: IacResult) - - fun scanningIacError(snykError: SnykError) - fun scanningContainerFinished(containerResult: ContainerResult) fun scanningContainerError(snykError: SnykError) diff --git a/src/main/kotlin/io/snyk/plugin/ui/UIUtils.kt b/src/main/kotlin/io/snyk/plugin/ui/UIUtils.kt index 655716b01..9ba669abf 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/UIUtils.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/UIUtils.kt @@ -253,7 +253,7 @@ fun descriptionHeaderPanel( 2 + // CVSS 2 + // Snyk description customLabels.size * 2 // Labels with `|` - panel.layout = GridLayoutManager(1, columnCount, Insets(0, 0, 0, 0), 5, 0) + panel.layout = GridLayoutManager(1, columnCount, JBUI.emptyInsets(), 5, 0) panel.add( JLabel(issueNaming).apply { font = font14 }, diff --git a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanel.kt b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanel.kt index 0daecbf71..3d7ac2af1 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanel.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanel.kt @@ -78,8 +78,6 @@ import snyk.container.ui.ContainerIssueTreeNode import snyk.iac.IacIssue import snyk.iac.IacResult import snyk.iac.ignorableErrorCodes -import snyk.iac.ui.toolwindow.IacFileTreeNode -import snyk.iac.ui.toolwindow.IacIssueTreeNode import java.awt.BorderLayout import java.util.Objects.nonNull import javax.swing.JPanel @@ -189,16 +187,6 @@ class SnykToolWindowPanel( } } - override fun scanningIacFinished(iacResult: IacResult) { - ApplicationManager.getApplication().invokeLater { - displayIacResults(iacResult) - } - if (iacResult.getVisibleErrors().isNotEmpty()) { - notifyAboutErrorsIfNeeded(ProductType.IAC, iacResult) - } - refreshAnnotationsForOpenFiles(project) - } - override fun scanningContainerFinished(containerResult: ContainerResult) { ApplicationManager.getApplication().invokeLater { displayContainerResults(containerResult) @@ -225,24 +213,6 @@ class SnykToolWindowPanel( } } - override fun scanningIacError(snykError: SnykError) { - var iacResultsCount: Int? = null - if (snykError.code != null && ignorableErrorCodes.contains(snykError.code)) { - iacResultsCount = NODE_NOT_SUPPORTED_STATE - } else { - SnykBalloonNotificationHelper.showError(snykError.message, project) - if (snykError.message.startsWith(AUTH_FAILED_TEXT)) { - pluginSettings().token = null - } - } - ApplicationManager.getApplication().invokeLater { - removeAllChildren(listOf(rootIacIssuesTreeNode)) - updateTreeRootNodesPresentation(iacResultsCount = iacResultsCount) - chooseMainPanelToDisplay() - } - refreshAnnotationsForOpenFiles(project) - } - override fun scanningContainerError(snykError: SnykError) { var containerResultsCount: Int? = null if (snykError == ContainerService.NO_IMAGES_TO_SCAN_ERROR) { @@ -295,7 +265,6 @@ class SnykToolWindowPanel( val snykCachedResults = getSnykCachedResults(project) ?: return ApplicationManager.getApplication().invokeLater { - snykCachedResults.currentIacResult?.let { displayIacResults(it) } snykCachedResults.currentContainerResult?.let { displayContainerResults(it) } } } @@ -721,57 +690,6 @@ class SnykToolWindowPanel( revalidate() } - fun displayIacResults(iacResult: IacResult) { - val userObjectsForExpandedChildren = userObjectsForExpandedNodes(rootIacIssuesTreeNode) - val selectedNodeUserObject = TreeUtil.findObjectInPath(vulnerabilitiesTree.selectionPath, Any::class.java) - - rootIacIssuesTreeNode.removeAllChildren() - - fun navigateToIaCIssue( - virtualFile: VirtualFile?, - lineStartOffset: Int, - ): () -> Unit = - { - if (virtualFile?.isValid == true) { - navigateToSource(project, virtualFile, lineStartOffset) - } - } - - val settings = pluginSettings() - if (settings.iacScanEnabled && settings.treeFiltering.iacResults) { - iacResult.allCliIssues?.forEach { iacVulnerabilitiesForFile -> - if (iacVulnerabilitiesForFile.infrastructureAsCodeIssues.isNotEmpty()) { - val fileTreeNode = IacFileTreeNode(iacVulnerabilitiesForFile, project) - rootIacIssuesTreeNode.add(fileTreeNode) - - iacVulnerabilitiesForFile.infrastructureAsCodeIssues - .filter { settings.hasSeverityEnabledAndFiltered(it.getSeverity()) } - .sortedByDescending { it.getSeverity() } - .forEach { - val navigateToSource = - navigateToIaCIssue( - iacVulnerabilitiesForFile.virtualFile, - it.lineStartOffset, - ) - fileTreeNode.add(IacIssueTreeNode(it, project, navigateToSource)) - } - } - } - iacResult.getVisibleErrors().forEach { snykError -> - rootIacIssuesTreeNode.add( - ErrorTreeNode(snykError, project, navigateToIaCIssue(snykError.virtualFile, 0)), - ) - } - } - - updateTreeRootNodesPresentation( - iacResultsCount = iacResult.issuesCount, - addHMLPostfix = buildHMLpostfix(iacResult), - ) - - smartReloadRootNode(rootIacIssuesTreeNode, userObjectsForExpandedChildren, selectedNodeUserObject) - } - fun displayContainerResults(containerResult: ContainerResult) { val userObjectsForExpandedChildren = userObjectsForExpandedNodes(rootContainerIssuesTreeNode) val selectedNodeUserObject = TreeUtil.findObjectInPath(vulnerabilitiesTree.selectionPath, Any::class.java) @@ -949,12 +867,6 @@ class SnykToolWindowPanel( } } - fun selectNodeAndDisplayDescription(iacIssue: IacIssue) = - selectAndDisplayNodeWithIssueDescription { treeNode -> - treeNode is IacIssueTreeNode && - (treeNode.userObject as IacIssue) == iacIssue - } - fun selectNodeAndDisplayDescription(issuesForImage: ContainerIssuesForImage) = selectAndDisplayNodeWithIssueDescription { treeNode -> treeNode is ContainerImageTreeNode && diff --git a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykTreeCellRenderer.kt b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykTreeCellRenderer.kt index 458d0f3a0..05d668e57 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykTreeCellRenderer.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykTreeCellRenderer.kt @@ -29,11 +29,6 @@ import snyk.container.ContainerIssue import snyk.container.ContainerIssuesForImage import snyk.container.ui.ContainerImageTreeNode import snyk.container.ui.ContainerIssueTreeNode -import snyk.iac.IacIssue -import snyk.iac.IacIssuesForFile -import snyk.iac.ui.toolwindow.IacFileTreeNode -import snyk.iac.ui.toolwindow.IacIssueTreeNode -import java.util.Locale import javax.swing.Icon import javax.swing.JTree import javax.swing.tree.DefaultMutableTreeNode 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 1c7fc2ded..9268a1021 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 @@ -3,11 +3,11 @@ package io.snyk.plugin.ui.toolwindow.panels import com.intellij.openapi.editor.colors.EditorColors import com.intellij.openapi.editor.colors.EditorColorsManager import com.intellij.openapi.vfs.VirtualFile -import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.uiDesigner.core.GridLayoutManager import com.intellij.util.ui.JBUI import com.intellij.util.ui.UIUtil import io.snyk.plugin.SnykFile +import io.snyk.plugin.toVirtualFile import io.snyk.plugin.ui.DescriptionHeaderPanel import io.snyk.plugin.ui.SnykBalloonNotificationHelper import io.snyk.plugin.ui.baseGridConstraintsAnchorWest @@ -26,7 +26,6 @@ import snyk.common.lsp.ScanIssue import stylesheets.SnykStylesheets import java.awt.BorderLayout import java.awt.Font -import java.nio.file.Paths import javax.swing.JLabel import javax.swing.JPanel import kotlin.collections.set @@ -43,65 +42,60 @@ class SuggestionDescriptionPanelFromLS( "Snyk encountered an issue while rendering the vulnerability description. Please try again, or contact support if the problem persists. We apologize for any inconvenience caused." init { - if (issue.canLoadSuggestionPanelFromHTML()) { - val loadHandlerGenerators: MutableList = - emptyList().toMutableList() + val loadHandlerGenerators: MutableList = + emptyList().toMutableList() - // TODO: replace directly in HTML instead of JS - loadHandlerGenerators += { - ThemeBasedStylingGenerator().generate(it) - } + // TODO: replace directly in HTML instead of JS + loadHandlerGenerators += { + ThemeBasedStylingGenerator().generate(it) + } - when (issue.filterableIssueType) { - ScanIssue.CODE_QUALITY, ScanIssue.CODE_SECURITY -> { - val virtualFiles = LinkedHashMap() - for (dataFlow in issue.additionalData.dataFlow) { - virtualFiles[dataFlow.filePath] = - VirtualFileManager.getInstance().findFileByNioPath(Paths.get(dataFlow.filePath)) - } + when (issue.filterableIssueType) { + ScanIssue.CODE_QUALITY, ScanIssue.CODE_SECURITY -> { + val virtualFiles = LinkedHashMap() + for (dataFlow in issue.additionalData.dataFlow) { + virtualFiles[dataFlow.filePath] = dataFlow.filePath.toVirtualFile() + } - val openFileLoadHandlerGenerator = OpenFileLoadHandlerGenerator(snykFile.project, virtualFiles) - loadHandlerGenerators += { - openFileLoadHandlerGenerator.generate(it) - } + val openFileLoadHandlerGenerator = OpenFileLoadHandlerGenerator(snykFile.project, virtualFiles) + loadHandlerGenerators += { + openFileLoadHandlerGenerator.generate(it) + } - val generateAIFixHandler = GenerateAIFixHandler() - loadHandlerGenerators += { - generateAIFixHandler.generateAIFixCommand(it) - } + val generateAIFixHandler = GenerateAIFixHandler() + loadHandlerGenerators += { + generateAIFixHandler.generateAIFixCommand(it) + } - val applyFixHandler = ApplyFixHandler(snykFile.project) - loadHandlerGenerators += { - applyFixHandler.generateApplyFixCommand(it) - } + val applyFixHandler = ApplyFixHandler(snykFile.project) + loadHandlerGenerators += { + applyFixHandler.generateApplyFixCommand(it) } } - val html = this.getCustomCssAndScript() - val jbCefBrowserComponent = - JCEFUtils.getJBCefBrowserComponentIfSupported(html, loadHandlerGenerators) - if (jbCefBrowserComponent == null) { - val statePanel = StatePanel(SnykToolWindowPanel.SELECT_ISSUE_TEXT) - this.add(wrapWithScrollPane(statePanel), BorderLayout.CENTER) - SnykBalloonNotificationHelper.showError(unexpectedErrorMessage, null) - } else { - val lastRowToAddSpacer = 5 - val panel = - JPanel( - GridLayoutManager(lastRowToAddSpacer + 1, 1, JBUI.insets(0, 10, 20, 10), -1, 20), - ).apply { - this.add( - jbCefBrowserComponent, - panelGridConstraints(1), - ) - } - this.add( - wrapWithScrollPane(panel), - BorderLayout.CENTER, - ) - this.add(panel) - } + } + val html = this.getCustomCssAndScript() + val jbCefBrowserComponent = + JCEFUtils.getJBCefBrowserComponentIfSupported(html, loadHandlerGenerators) + if (jbCefBrowserComponent == null) { + val statePanel = StatePanel(SnykToolWindowPanel.SELECT_ISSUE_TEXT) + this.add(wrapWithScrollPane(statePanel), BorderLayout.CENTER) + SnykBalloonNotificationHelper.showError(unexpectedErrorMessage, null) } else { - createUI() + val lastRowToAddSpacer = 5 + val panel = + JPanel( + GridLayoutManager(lastRowToAddSpacer + 1, 1, JBUI.insets(0, 10, 20, 10), -1, 20), + ).apply { + this.add( + jbCefBrowserComponent, + panelGridConstraints(1), + ) + } + this.add( + wrapWithScrollPane(panel), + BorderLayout.CENTER, + ) + this.add(panel) } } diff --git a/src/main/kotlin/snyk/common/SnykCachedResults.kt b/src/main/kotlin/snyk/common/SnykCachedResults.kt index 395e63c00..308e94575 100644 --- a/src/main/kotlin/snyk/common/SnykCachedResults.kt +++ b/src/main/kotlin/snyk/common/SnykCachedResults.kt @@ -50,8 +50,6 @@ class SnykCachedResults( get() = if (field?.isExpired() == false) field else null val currentIacResultsLS: MutableMap> = ConcurrentMap() - var currentIacResult: IacResult? = null - get() = if (field?.isExpired() == false) field else null var currentOssError: SnykError? = null var currentContainerError: SnykError? = null @@ -60,7 +58,6 @@ class SnykCachedResults( fun cleanCaches() { currentContainerResult = null - currentIacResult = null currentOssError = null currentContainerError = null currentIacError = null @@ -83,24 +80,10 @@ class SnykCachedResults( currentContainerError = null } - override fun scanningIacFinished(iacResult: IacResult) { - currentIacResult = iacResult - } - override fun scanningContainerFinished(containerResult: ContainerResult) { currentContainerResult = containerResult } - override fun scanningIacError(snykError: SnykError) { - currentIacResult = null - currentIacError = - when { - snykError.message.startsWith(SnykToolWindowPanel.NO_IAC_FILES) -> null - snykError.message.startsWith(SnykToolWindowPanel.AUTH_FAILED_TEXT) -> null - else -> snykError - } - } - override fun scanningContainerError(snykError: SnykError) { currentContainerResult = null currentContainerError = diff --git a/src/main/kotlin/io/snyk/plugin/snykcode/SnykCodeBulkFileListener.kt b/src/main/kotlin/snyk/common/lsp/LanguageServerBulkFileListener.kt similarity index 77% rename from src/main/kotlin/io/snyk/plugin/snykcode/SnykCodeBulkFileListener.kt rename to src/main/kotlin/snyk/common/lsp/LanguageServerBulkFileListener.kt index 498a6e2af..34f0486c6 100644 --- a/src/main/kotlin/io/snyk/plugin/snykcode/SnykCodeBulkFileListener.kt +++ b/src/main/kotlin/snyk/common/lsp/LanguageServerBulkFileListener.kt @@ -1,39 +1,37 @@ -package io.snyk.plugin.snykcode +package snyk.common.lsp import com.google.common.cache.CacheBuilder import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.invokeLater import com.intellij.openapi.project.DumbService import com.intellij.openapi.project.Project import com.intellij.openapi.roots.ProjectFileIndex +import com.intellij.openapi.roots.ProjectRootManager import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFileManager -import com.intellij.openapi.vfs.newvfs.events.VFileEvent import com.intellij.openapi.vfs.readText import io.snyk.plugin.SnykBulkFileListener +import io.snyk.plugin.SnykFile import io.snyk.plugin.getSnykCachedResults import io.snyk.plugin.toLanguageServerURL import io.snyk.plugin.toSnykFileSet import org.eclipse.lsp4j.DidSaveTextDocumentParams import org.eclipse.lsp4j.TextDocumentIdentifier import org.jetbrains.concurrency.runAsync -import snyk.common.lsp.LanguageServerWrapper import java.io.File import java.time.Duration -class SnykCodeBulkFileListener : SnykBulkFileListener() { - // Cache for debouncing file updates that come in within one second of the last - // Key = path, Value is irrelevant - private val debounceFileCache = - CacheBuilder.newBuilder() - .expireAfterWrite(Duration.ofMillis(1000)).build() +open class LanguageServerBulkFileListener : SnykBulkFileListener() { + override fun before( + project: Project, + virtualFilesAffected: Set, + ) = Unit - private val blackListedDirectories = - setOf(".idea", ".git", ".hg", ".svn") - - override fun before(project: Project, virtualFilesAffected: Set) = Unit - - override fun after(project: Project, virtualFilesAffected: Set) { + override fun after( + project: Project, + virtualFilesAffected: Set, + ) { if (virtualFilesAffected.isEmpty()) return runAsync { @@ -60,11 +58,19 @@ class SnykCodeBulkFileListener : SnykBulkFileListener() { ) languageServer.textDocumentService.didSave(param) } - VirtualFileManager.getInstance().asyncRefresh() - DaemonCodeAnalyzer.getInstance(project).restart() + updateCacheAndUI(filesAffected, project) } } + // Cache for debouncing file updates that come in within one second of the last + // Key = path, Value is irrelevant + private val debounceFileCache = + CacheBuilder.newBuilder() + .expireAfterWrite(Duration.ofMillis(1000)).build() + + private val blackListedDirectories = + setOf(".idea", ".git", ".hg", ".svn") + private fun shouldProcess(file: VirtualFile, index: ProjectFileIndex, project: Project): Boolean { var shouldProcess = false val application = ApplicationManager.getApplication() @@ -95,5 +101,23 @@ class SnykCodeBulkFileListener : SnykBulkFileListener() { return path.isNotEmpty() } - override fun forwardEvents(events: MutableList) = Unit + private fun updateCacheAndUI( + filesAffected: Set, + project: Project, + ) { + val ossCache = getSnykCachedResults(project)?.currentOSSResultsLS ?: return + val codeCache = getSnykCachedResults(project)?.currentSnykCodeResultsLS ?: return + val iacCache = getSnykCachedResults(project)?.currentIacResultsLS ?: return + + filesAffected.forEach { + ossCache.remove(it) + codeCache.remove(it) + iacCache.remove(it) + } + + VirtualFileManager.getInstance().asyncRefresh() + invokeLater { + DaemonCodeAnalyzer.getInstance(project).restart() + } + } } diff --git a/src/main/kotlin/snyk/iac/IacBulkFileListener.kt b/src/main/kotlin/snyk/iac/IacBulkFileListener.kt deleted file mode 100644 index be1f02083..000000000 --- a/src/main/kotlin/snyk/iac/IacBulkFileListener.kt +++ /dev/null @@ -1,78 +0,0 @@ -package snyk.iac - -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.diagnostic.logger -import com.intellij.openapi.project.Project -import com.intellij.openapi.roots.ProjectRootManager -import com.intellij.openapi.util.io.FileUtil.pathsEqual -import com.intellij.openapi.vfs.VirtualFile -import io.snyk.plugin.SnykBulkFileListener -import io.snyk.plugin.getSnykCachedResults -import io.snyk.plugin.getSnykToolWindowPanel -import io.snyk.plugin.refreshAnnotationsForOpenFiles - -class IacBulkFileListener : SnykBulkFileListener() { - - private val log = logger() - - override fun before(project: Project, virtualFilesAffected: Set) { - // clean IaC cached results for deleted/moved/renamed files - updateIacCache( - virtualFilesAffected, - project - ) - } - - override fun after(project: Project, virtualFilesAffected: Set) { - // update IaC cached results if needed - updateIacCache(virtualFilesAffected, project) - } - - private fun updateIacCache( - virtualFilesAffected: Set, - project: Project - ) { - if (virtualFilesAffected.isEmpty()) return - val snykCachedResults = getSnykCachedResults(project) - val currentIacResult = snykCachedResults?.currentIacResult ?: return - val allIacIssuesForFiles = currentIacResult.allCliIssues ?: return - - val iacRelatedVFsAffected = virtualFilesAffected - .filter { scanInvalidatingFiles.contains(it.extension) } - .filter { ProjectRootManager.getInstance(project).fileIndex.isInContent(it) } - - allIacIssuesForFiles - .filter { iacIssuesForFile -> - iacRelatedVFsAffected.any { - pathsEqual(it.path, iacIssuesForFile.targetFilePath) - } - } - .forEach(::markObsolete) - - val changed = - iacRelatedVFsAffected.isNotEmpty() // for new/deleted/renamed files we also need to "dirty" the cache, too - if (changed) { - log.debug("update IaC cache for $iacRelatedVFsAffected") - currentIacResult.iacScanNeeded = true - ApplicationManager.getApplication().invokeLater { - getSnykToolWindowPanel(project)?.displayIacResults(currentIacResult) - } - refreshAnnotationsForOpenFiles(project) - } - } - - private fun markObsolete(iacIssuesForFile: IacIssuesForFile) { - iacIssuesForFile.infrastructureAsCodeIssues.forEach { it.obsolete = true } - } - - companion object { - // see https://github.com/snyk/snyk/blob/master/src/lib/iac/constants.ts#L7 - private val scanInvalidatingFiles = listOf( - "yaml", - "yml", - "json", - "tf", - ".snyk" - ) - } -} diff --git a/src/main/kotlin/snyk/iac/IacSuggestionDescriptionPanel.kt b/src/main/kotlin/snyk/iac/IacSuggestionDescriptionPanel.kt deleted file mode 100644 index eb651d365..000000000 --- a/src/main/kotlin/snyk/iac/IacSuggestionDescriptionPanel.kt +++ /dev/null @@ -1,176 +0,0 @@ -package snyk.iac - -import com.intellij.openapi.project.Project -import com.intellij.psi.PsiFile -import com.intellij.uiDesigner.core.GridLayoutManager -import com.intellij.util.ui.UIUtil -import io.snyk.plugin.ui.DescriptionHeaderPanel -import io.snyk.plugin.ui.baseGridConstraintsAnchorWest -import io.snyk.plugin.ui.boldLabel -import io.snyk.plugin.ui.descriptionHeaderPanel -import io.snyk.plugin.ui.getFont -import io.snyk.plugin.ui.getReadOnlyClickableHtmlJEditorPane -import io.snyk.plugin.ui.insertTitleAndResizableTextIntoPanelColumns -import io.snyk.plugin.ui.panelGridConstraints -import io.snyk.plugin.ui.toolwindow.LabelProvider -import io.snyk.plugin.ui.toolwindow.panels.IssueDescriptionPanelBase -import org.commonmark.parser.Parser -import org.commonmark.renderer.html.HtmlRenderer -import snyk.common.IgnoreService -import java.awt.Insets -import java.net.MalformedURLException -import java.net.URL -import javax.swing.JButton -import javax.swing.JLabel -import javax.swing.JPanel -import javax.swing.JTextArea - -class IacSuggestionDescriptionPanel( - val issue: IacIssue, - val psiFile: PsiFile?, - val project: Project -) : IssueDescriptionPanelBase(title = issue.title, severity = issue.getSeverity()) { - - private val labelProvider = LabelProvider() - - init { - this.name = "IacSuggestionDescriptionPanel" - createUI() - } - - override fun secondRowTitlePanel(): DescriptionHeaderPanel = descriptionHeaderPanel( - issueNaming = "Issue", - id = issue.id, - idUrl = issue.documentation - ) - - override fun createMainBodyPanel(): Pair { - val panel = JPanel() - val lastRowToAddSpacer = 9 - panel.layout = GridLayoutManager(lastRowToAddSpacer + 1, 1, Insets(0, 10, 20, 20), -1, 10) - - panel.add( - descriptionImpactPathPanel(), panelGridConstraints(1) - ) - - if (!issue.resolve.isNullOrBlank()) { - panel.add( - remediationPanelWithTitle(issue.resolve), panelGridConstraints(6) - ) - } - - val referencePanel = addIssueReferences() - if (referencePanel != null) { - panel.add(referencePanel, panelGridConstraints(row = 7)) - } - - return Pair(panel, lastRowToAddSpacer) - } - - private fun descriptionImpactPathPanel(): JPanel { - val mainBodyPanel = JPanel() - mainBodyPanel.layout = GridLayoutManager(11, 2, Insets(10, 0, 20, 0), 50, -1) - - insertTitleAndResizableTextIntoPanelColumns( - panel = mainBodyPanel, - row = 0, - title = "Description", - htmlText = issue.issue - ) - - insertTitleAndResizableTextIntoPanelColumns( - panel = mainBodyPanel, - row = 1, - title = "Impact", - htmlText = issue.impact - ) - - insertTitleAndResizableTextIntoPanelColumns( - panel = mainBodyPanel, - row = 2, - title = "Path", - htmlText = issue.path.joinToString(" > "), - textFont = getFont(-1, -1, JTextArea().font) ?: UIUtil.getLabelFont() - ) - - return mainBodyPanel - } - - private fun remediationPanel(resolve: String): JPanel { - val remediationPanel = JPanel() - remediationPanel.layout = GridLayoutManager(2, 1, Insets(0, 10, 20, 0), -1, -1) - remediationPanel.background = UIUtil.getTextFieldBackground() - - val resolveMarkdown = markdownToHtml(resolve) - val whiteBox = getReadOnlyClickableHtmlJEditorPane( - resolveMarkdown - ).apply { - isOpaque = false - } - - remediationPanel.add(whiteBox, panelGridConstraints(row = 1)) - - return remediationPanel - } - - private fun addIssueReferences(): JPanel? { - if (issue.references.isNotEmpty()) { - val panel = JPanel() - panel.layout = GridLayoutManager( - issue.references.size + 2, 1, Insets(20, 0, 0, 0), 50, -1 - ) - - panel.add(boldLabel("References"), baseGridConstraintsAnchorWest(row = 1)) - issue.references.forEachIndexed { index, s -> - val label = try { - labelProvider.createActionLink(URL(s), s) - } catch (e: MalformedURLException) { - JLabel(s) - } - panel.add(label, baseGridConstraintsAnchorWest(2 + index)) - } - return panel - } - return null - } - - private fun markdownToHtml(sourceStr: String): String { - val parser = Parser.builder().build() - val document = parser.parse(sourceStr) - - val renderer = HtmlRenderer.builder().escapeHtml(true).build() - - return renderer.render(document) - } - - private fun remediationPanelWithTitle(remediation: String): JPanel { - val remediationPanel = JPanel() - remediationPanel.layout = GridLayoutManager(2, 1, Insets(0, 0, 0, 0), 50, -1) - - remediationPanel.add( - boldLabel("Remediation"), - baseGridConstraintsAnchorWest(row = 0) - ) - - remediationPanel.add( - remediationPanel(remediation), - panelGridConstraints(row = 1, indent = 1) - ) - - return remediationPanel - } - - override fun getBottomRightButtons(): List = listOf( - JButton().apply { - if (issue.ignored) { - text = IgnoreButtonActionListener.IGNORED_ISSUE_BUTTON_TEXT - isEnabled = false - } else { - text = "Ignore This Issue" - addActionListener(IgnoreButtonActionListener(IgnoreService(project), issue, psiFile, project)) - } - name = "ignoreButton" - } - ) - -} diff --git a/src/main/kotlin/snyk/iac/annotator/IacBaseAnnotator.kt b/src/main/kotlin/snyk/iac/annotator/IacBaseAnnotator.kt deleted file mode 100644 index a75613843..000000000 --- a/src/main/kotlin/snyk/iac/annotator/IacBaseAnnotator.kt +++ /dev/null @@ -1,98 +0,0 @@ -package snyk.iac.annotator - -import com.intellij.lang.annotation.AnnotationHolder -import com.intellij.lang.annotation.ExternalAnnotator -import com.intellij.openapi.diagnostic.logger -import com.intellij.openapi.progress.ProgressManager -import com.intellij.openapi.util.TextRange -import com.intellij.openapi.util.io.FileUtil -import com.intellij.psi.PsiFile -import io.snyk.plugin.Severity -import io.snyk.plugin.getSnykCachedResults -import io.snyk.plugin.ui.toolwindow.SnykToolWindowPanel -import snyk.common.AnnotatorCommon -import snyk.common.intentionactions.ShowDetailsIntentionActionBase -import snyk.iac.IacIssue -import snyk.iac.IacResult - -private val LOG = logger() - -abstract class IacBaseAnnotator : ExternalAnnotator() { - - // override needed for the Annotator to invoke apply(). We don't do anything here - override fun collectInformation(file: PsiFile): PsiFile? = file - - // save all changes on disk to update caches through SnykBulkFileListener - override fun doAnnotate(psiFile: PsiFile?) { - AnnotatorCommon.prepareAnnotate(psiFile) - } - - fun getIssues(psiFile: PsiFile): List { - val iacResult = getSnykCachedResults(psiFile.project)?.currentIacResult ?: return emptyList() - ProgressManager.checkCanceled() - return getIssuesForFile(psiFile, iacResult) - } - - override fun apply(psiFile: PsiFile, annotationResult: Unit, holder: AnnotationHolder) { - val issues = getIssues(psiFile) - .filter { AnnotatorCommon.isSeverityToShow(it.getSeverity()) } - - LOG.debug("Call apply on ${psiFile.name}") - if (issues.isEmpty()) return - - LOG.debug("Received ${issues.size} IacIssue annotations for ${psiFile.virtualFile.name}") - issues - .filter { !it.ignored && !it.obsolete } - .forEach { iacIssue -> - LOG.debug("-> ${iacIssue.id}: ${iacIssue.title}: ${iacIssue.lineNumber}") - val highlightSeverity = iacIssue.getSeverity().getHighlightSeverity() - val annotationMessage = annotationMessage(iacIssue) - holder.newAnnotation(highlightSeverity, "Snyk: $annotationMessage") - .range(textRange(psiFile, iacIssue)) - .withFix(ShowDetailsIntentionAction(annotationMessage, iacIssue)) - .create() - } - } - - abstract fun textRange(psiFile: PsiFile, iacIssue: IacIssue): TextRange - - private fun getIssuesForFile(psiFile: PsiFile, iacResult: IacResult): List { - LOG.debug("Calling getAnnotationForFile for ${psiFile.virtualFile?.path}") - - val psiFilePath = psiFile.virtualFile?.path?.let { FileUtil.toSystemIndependentName(it) } - ?: return emptyList() - - val iacIssuesForFile = iacResult.allCliIssues?.firstOrNull { iacIssuesForFile -> - val iacTargetFilePath = FileUtil.toSystemIndependentName(iacIssuesForFile.targetFilePath) - psiFilePath == iacTargetFilePath - } - LOG.debug("Found IaC issues for file: ${iacIssuesForFile?.targetFile} - ${iacIssuesForFile?.uniqueCount}") - - return iacIssuesForFile?.infrastructureAsCodeIssues?.toList() ?: emptyList() - } - - fun annotationMessage(iacIssue: IacIssue): String = iacIssue.title - - fun defaultTextRange(psiFile: PsiFile, iacIssue: IacIssue): TextRange { - val document = psiFile.viewProvider.document ?: return TextRange.EMPTY_RANGE - - val lineNumber = iacIssue.lineNumber - 1 - if (lineNumber < 0 || document.lineCount <= lineNumber) return TextRange.EMPTY_RANGE - - val startOffset = document.getLineStartOffset(lineNumber) - val endOffset = document.getLineEndOffset(lineNumber) - return TextRange.create(startOffset, endOffset) - } - - inner class ShowDetailsIntentionAction( - override val annotationMessage: String, - private val iacIssue: IacIssue - ) : ShowDetailsIntentionActionBase() { - - override fun selectNodeAndDisplayDescription(toolWindowPanel: SnykToolWindowPanel) { - toolWindowPanel.selectNodeAndDisplayDescription(iacIssue) - } - - override fun getSeverity(): Severity = iacIssue.getSeverity() - } -} diff --git a/src/main/kotlin/snyk/iac/annotator/IacHclAnnotator.kt b/src/main/kotlin/snyk/iac/annotator/IacHclAnnotator.kt deleted file mode 100644 index e0eee8879..000000000 --- a/src/main/kotlin/snyk/iac/annotator/IacHclAnnotator.kt +++ /dev/null @@ -1,48 +0,0 @@ -package snyk.iac.annotator - -import com.intellij.openapi.diagnostic.logger -import com.intellij.openapi.util.TextRange -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiFile -import com.intellij.psi.PsiWhiteSpace -import com.intellij.psi.impl.source.tree.LeafPsiElement -import com.intellij.psi.util.elementType -import org.intellij.terraform.hcl.psi.HCLBlock -import org.intellij.terraform.hcl.psi.HCLElement -import org.intellij.terraform.hcl.psi.HCLProperty -import snyk.iac.IacIssue - -private val LOG = logger() - -class IacHclAnnotator : IacBaseAnnotator() { - override fun textRange(psiFile: PsiFile, iacIssue: IacIssue): TextRange { - var textRange = defaultTextRange(psiFile, iacIssue) - - val element = getNextHCLElement(psiFile.viewProvider.findElementAt(textRange.startOffset)) - if (element != null) textRange = element.textRange - - LOG.debug("Calculated text range for ${iacIssue.id}: $textRange") - return textRange - } - - private fun getNextHCLElement(psiElement: PsiElement?): PsiElement? { - if (psiElement.elementType.toString() == "ID") { - return psiElement as LeafPsiElement - } - - return when (val nextSibling = psiElement?.nextSiblingNonWhiteSpace()) { - is HCLBlock -> nextSibling.firstChild - is HCLProperty -> nextSibling.firstChild - is HCLElement -> nextSibling.nextSibling - else -> psiElement - } - } - - private fun PsiElement.nextSiblingNonWhiteSpace(): PsiElement? { - var next = this.nextSibling - while (next != null && next is PsiWhiteSpace) { - next = next.nextSibling - } - return next - } -} diff --git a/src/main/kotlin/snyk/iac/annotator/IacJsonAnnotator.kt b/src/main/kotlin/snyk/iac/annotator/IacJsonAnnotator.kt deleted file mode 100644 index 51178dfbf..000000000 --- a/src/main/kotlin/snyk/iac/annotator/IacJsonAnnotator.kt +++ /dev/null @@ -1,35 +0,0 @@ -package snyk.iac.annotator - -import com.intellij.json.psi.JsonElement -import com.intellij.json.psi.JsonProperty -import com.intellij.openapi.diagnostic.logger -import com.intellij.openapi.util.TextRange -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiFile -import com.intellij.psi.util.siblings -import snyk.iac.IacIssue - -private val LOG = logger() - -class IacJsonAnnotator : IacBaseAnnotator() { - override fun textRange(psiFile: PsiFile, iacIssue: IacIssue): TextRange { - var textRange = defaultTextRange(psiFile, iacIssue) - - // try to guess precise start/end offset positions - when (val element = getNextJsonElement(psiFile.viewProvider.findElementAt(textRange.startOffset))) { - is JsonProperty -> textRange = highlightingForJsonProperty(element) - } - - LOG.debug("Calculated text range for JSON ${iacIssue.id} - $textRange") - return textRange - } - - private fun highlightingForJsonProperty(jsonProperty: JsonProperty): TextRange { - return jsonProperty.nameElement.textRange - } - - private fun getNextJsonElement(psiElement: PsiElement?): JsonElement? { - val element = psiElement?.siblings(forward = true, withSelf = false)?.firstOrNull { it is JsonElement } - return element?.let { it as JsonElement } - } -} diff --git a/src/main/kotlin/snyk/iac/annotator/IacYamlAnnotator.kt b/src/main/kotlin/snyk/iac/annotator/IacYamlAnnotator.kt deleted file mode 100644 index e3c87c403..000000000 --- a/src/main/kotlin/snyk/iac/annotator/IacYamlAnnotator.kt +++ /dev/null @@ -1,61 +0,0 @@ -package snyk.iac.annotator - -import com.intellij.openapi.diagnostic.logger -import com.intellij.openapi.util.TextRange -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiFile -import com.intellij.psi.util.siblings -import com.intellij.refactoring.suggested.endOffset -import com.intellij.refactoring.suggested.startOffset -import org.jetbrains.yaml.psi.YAMLKeyValue -import org.jetbrains.yaml.psi.YAMLMapping -import org.jetbrains.yaml.psi.YAMLPsiElement -import org.jetbrains.yaml.psi.YAMLSequence -import snyk.iac.IacIssue - -private val LOG = logger() - -class IacYamlAnnotator : IacBaseAnnotator() { - override fun textRange(psiFile: PsiFile, iacIssue: IacIssue): TextRange { - var textRange = defaultTextRange(psiFile, iacIssue) - - // try to guess precise start/end offset positions - when (val element = getNextYAMLElement(psiFile.viewProvider.findElementAt(textRange.startOffset))) { - is YAMLKeyValue -> textRange = highlightingForYAMLKeyValue(element) - is YAMLMapping -> textRange = highlightingForYAMLMapping(element) - is YAMLSequence -> textRange = highlightingForYAMLSequence(element) - } - - LOG.debug("Calculated text range for ${iacIssue.id}: $textRange") - return textRange - } - - private fun highlightingForYAMLKeyValue(yamlKeyValue: YAMLKeyValue): TextRange { - val startOffset = yamlKeyValue.key?.startOffset ?: yamlKeyValue.startOffset - val endOffset = yamlKeyValue.key?.endOffset ?: yamlKeyValue.endOffset - return TextRange.create(startOffset, endOffset) - } - - private fun highlightingForYAMLMapping(mapping: YAMLMapping): TextRange { - val mappingItem = mapping.firstChild - return if (mappingItem is YAMLKeyValue) { - highlightingForYAMLKeyValue(mappingItem) - } else { - val startOffset = mapping.firstChild.startOffset - val endOffset = mapping.firstChild.endOffset - TextRange.create(startOffset, endOffset) - } - } - - private fun highlightingForYAMLSequence(yamlSequence: YAMLSequence): TextRange { - val sequenceItem = yamlSequence.items.firstOrNull() - val startOffset = sequenceItem?.keysValues?.firstOrNull()?.key?.startOffset ?: yamlSequence.startOffset - val endOffset = sequenceItem?.keysValues?.firstOrNull()?.key?.endOffset ?: yamlSequence.endOffset - return TextRange.create(startOffset, endOffset) - } - - private fun getNextYAMLElement(psiElement: PsiElement?): YAMLPsiElement? { - val element = psiElement?.siblings(forward = true, withSelf = false)?.firstOrNull { it is YAMLPsiElement } - return element?.let { it as YAMLPsiElement } - } -} diff --git a/src/main/kotlin/snyk/iac/ui/toolwindow/IacFileTreeNode.kt b/src/main/kotlin/snyk/iac/ui/toolwindow/IacFileTreeNode.kt deleted file mode 100644 index b638b7135..000000000 --- a/src/main/kotlin/snyk/iac/ui/toolwindow/IacFileTreeNode.kt +++ /dev/null @@ -1,10 +0,0 @@ -package snyk.iac.ui.toolwindow - -import com.intellij.openapi.project.Project -import snyk.iac.IacIssuesForFile -import javax.swing.tree.DefaultMutableTreeNode - -class IacFileTreeNode( - iacIssuesForFile: IacIssuesForFile, - val project: Project -) : DefaultMutableTreeNode(iacIssuesForFile) diff --git a/src/main/kotlin/snyk/iac/ui/toolwindow/IacIssueTreeNode.kt b/src/main/kotlin/snyk/iac/ui/toolwindow/IacIssueTreeNode.kt deleted file mode 100644 index 53113034a..000000000 --- a/src/main/kotlin/snyk/iac/ui/toolwindow/IacIssueTreeNode.kt +++ /dev/null @@ -1,30 +0,0 @@ -package snyk.iac.ui.toolwindow - -import com.intellij.openapi.project.Project -import com.intellij.openapi.vfs.VirtualFileManager -import io.snyk.plugin.findPsiFileIgnoringExceptions -import io.snyk.plugin.ui.toolwindow.nodes.DescriptionHolderTreeNode -import io.snyk.plugin.ui.toolwindow.nodes.NavigatableToSourceTreeNode -import io.snyk.plugin.ui.toolwindow.panels.IssueDescriptionPanelBase -import snyk.iac.IacIssue -import snyk.iac.IacIssuesForFile -import snyk.iac.IacSuggestionDescriptionPanel -import java.nio.file.Paths -import javax.swing.tree.DefaultMutableTreeNode - -class IacIssueTreeNode( - private val issue: IacIssue, - val project: Project, - override val navigateToSource: () -> Unit, -) : DefaultMutableTreeNode(issue), NavigatableToSourceTreeNode, DescriptionHolderTreeNode { - - override fun getDescriptionPanel(): IssueDescriptionPanelBase { - val iacIssuesForFile = (this.parent as? IacFileTreeNode)?.userObject as? IacIssuesForFile - ?: throw IllegalArgumentException(this.toString()) - val fileName = iacIssuesForFile.targetFilePath - val virtualFile = VirtualFileManager.getInstance().findFileByNioPath(Paths.get(fileName)) - val psiFile = virtualFile?.let { findPsiFileIgnoringExceptions(it, project) } - - return IacSuggestionDescriptionPanel(issue, psiFile, project) - } -} diff --git a/src/main/kotlin/snyk/oss/OssBulkFileListener.kt b/src/main/kotlin/snyk/oss/OssBulkFileListener.kt deleted file mode 100644 index f6ef8d233..000000000 --- a/src/main/kotlin/snyk/oss/OssBulkFileListener.kt +++ /dev/null @@ -1,76 +0,0 @@ -package snyk.oss - -import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer -import com.intellij.ide.impl.ProjectUtil -import com.intellij.openapi.progress.ProgressIndicator -import com.intellij.openapi.progress.ProgressManager -import com.intellij.openapi.progress.Task -import com.intellij.openapi.project.Project -import com.intellij.openapi.vfs.VirtualFile -import com.intellij.openapi.vfs.VirtualFileManager -import com.intellij.openapi.vfs.newvfs.events.VFileEvent -import com.intellij.openapi.vfs.readText -import io.snyk.plugin.SnykBulkFileListener -import io.snyk.plugin.SnykFile -import io.snyk.plugin.getSnykCachedResults -import io.snyk.plugin.toLanguageServerURL -import io.snyk.plugin.toSnykFileSet -import org.eclipse.lsp4j.DidSaveTextDocumentParams -import org.eclipse.lsp4j.TextDocumentIdentifier -import snyk.common.lsp.LanguageServerWrapper - -class OssBulkFileListener : SnykBulkFileListener() { - - override fun before( - project: Project, - virtualFilesAffected: Set, - ) = Unit - - override fun after( - project: Project, - virtualFilesAffected: Set, - ) { - val filesAffected = toSnykFileSet(project, virtualFilesAffected) - updateCacheAndUI(filesAffected, project) - } - - override fun forwardEvents(events: MutableList) { - val languageServerWrapper = LanguageServerWrapper.getInstance() - - if (!languageServerWrapper.isInitialized) return - - val languageServer = languageServerWrapper.languageServer - for (event in events) { - if (event.file == null || !event.isFromSave) continue - val file = event.file!! - val activeProject = ProjectUtil.getActiveProject() - ProgressManager.getInstance().run( - object : Task.Backgroundable( - activeProject, - "Snyk: forwarding save event to Language Server", - ) { - override fun run(indicator: ProgressIndicator) { - val param = - DidSaveTextDocumentParams( - TextDocumentIdentifier(file.toLanguageServerURL()), - file.readText(), - ) - languageServer.textDocumentService.didSave(param) - } - }, - ) - } - } - - private fun updateCacheAndUI( - filesAffected: Set, - project: Project, - ) { - val cache = getSnykCachedResults(project)?.currentOSSResultsLS ?: return - filesAffected.forEach { - cache.remove(it) - } - VirtualFileManager.getInstance().asyncRefresh() - DaemonCodeAnalyzer.getInstance(project).restart() - } -}