Skip to content

Commit

Permalink
Merge pull request #476 from snyk/fix/IDE-134-stacked-dataflow
Browse files Browse the repository at this point in the history
fix: this fixes the dataflow and the issue navigation
  • Loading branch information
bastiandoetsch authored Feb 22, 2024
2 parents 8623724 + 03441b8 commit 7de0dd6
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 64 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fun properties(key: String) = project.findProperty(key).toString()

plugins {
id("org.jetbrains.changelog") version "2.1.2"
id("org.jetbrains.intellij") version "1.17.1"
id("org.jetbrains.intellij") version "1.17.2"
id("org.jetbrains.kotlin.jvm") version "1.9.0"
id("io.gitlab.arturbosch.detekt") version ("1.23.1")
id("pl.allegro.tech.build.axion-release") version "1.13.6"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ class SnykToolWindowPanel(val project: Project) : JPanel(), Disposable {
} else {
ApplicationManager.getApplication().invokeLater {
val snykCachedResults = getSnykCachedResults(project) ?: return@invokeLater
val codeResultsLS = snykCachedResults.currentSnykCodeResultsLS ?: return@invokeLater
val codeResultsLS = snykCachedResults.currentSnykCodeResultsLS

scanListenerLS?.displaySnykCodeResults(codeResultsLS)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,9 @@ import io.snyk.plugin.ui.toolwindow.nodes.root.RootQualityIssuesTreeNode
import io.snyk.plugin.ui.toolwindow.nodes.root.RootSecurityIssuesTreeNode
import io.snyk.plugin.ui.toolwindow.nodes.secondlevel.SnykCodeFileTreeNodeFromLS
import snyk.common.ProductType
import snyk.common.SnykCodeFileIssueComparator
import snyk.common.lsp.ScanIssue
import snyk.common.lsp.ScanState
import snyk.common.lsp.SnykScanParams
import java.util.SortedMap
import javax.swing.JTree
import javax.swing.tree.DefaultMutableTreeNode

Expand Down Expand Up @@ -70,7 +68,7 @@ class SnykToolWindowSnykCodeScanListenerLS(
val securityResults = snykCodeResults
.map { it.key to it.value.filter { issue -> issue.additionalData.isSecurityType } }
.toMap()
securityIssuesCount = securityResults.size
securityIssuesCount = securityResults.values.flatten().distinct().size
securityIssuesHMLPostfix = buildHMLpostfix(securityResults)

if (pluginSettings().treeFiltering.codeSecurityResults) {
Expand All @@ -81,7 +79,7 @@ class SnykToolWindowSnykCodeScanListenerLS(

displayResultsForCodeRoot(
rootSecurityIssuesTreeNode,
securityResultsToDisplay.toSortedMap(SnykCodeFileIssueComparator(securityResultsToDisplay))
securityResultsToDisplay
)
}
}
Expand All @@ -106,7 +104,7 @@ class SnykToolWindowSnykCodeScanListenerLS(
val qualityResults = snykCodeResults
.map { it.key to it.value.filter { issue -> !issue.additionalData.isSecurityType } }
.toMap()
qualityIssuesCount = qualityResults.size
qualityIssuesCount = qualityResults.values.flatten().distinct().size
qualityIssuesHMLPostfix = buildHMLpostfix(qualityResults)

if (pluginSettings().treeFiltering.codeQualityResults) {
Expand All @@ -116,7 +114,7 @@ class SnykToolWindowSnykCodeScanListenerLS(
}.toMap()
displayResultsForCodeRoot(
rootQualityIssuesTreeNode,
qualityResultsToDisplay.toSortedMap(SnykCodeFileIssueComparator(qualityResultsToDisplay))
qualityResultsToDisplay
)
}
}
Expand All @@ -133,7 +131,7 @@ class SnykToolWindowSnykCodeScanListenerLS(

private fun displayResultsForCodeRoot(
rootNode: DefaultMutableTreeNode,
issues: SortedMap<SnykCodeFile, List<ScanIssue>>
issues: Map<SnykCodeFile, List<ScanIssue>>
) {
fun navigateToSource(virtualFile: VirtualFile, textRange: TextRange): () -> Unit = {
io.snyk.plugin.navigateToSource(project, virtualFile, textRange.startOffset, textRange.endOffset)
Expand All @@ -149,7 +147,7 @@ class SnykToolWindowSnykCodeScanListenerLS(
val fileTreeNode =
SnykCodeFileTreeNodeFromLS(entry, productType)
rootNode.add(fileTreeNode)
entry.value.sortedByDescending { it.getSeverityAsEnum() }
entry.value.sortedByDescending { it.additionalData.priorityScore }
.forEach { issue ->
fileTreeNode.add(
SuggestionTreeNodeFromLS(
Expand All @@ -162,10 +160,9 @@ class SnykToolWindowSnykCodeScanListenerLS(
}

private fun buildHMLpostfix(securityResults: Map<SnykCodeFile, List<ScanIssue>>): String {
val critical = securityResults.values.flatten().count { it.getSeverityAsEnum() == Severity.CRITICAL }
val high = securityResults.values.flatten().count { it.getSeverityAsEnum() == Severity.HIGH }
val medium = securityResults.values.flatten().count { it.getSeverityAsEnum() == Severity.MEDIUM }
val low = securityResults.values.flatten().count { it.getSeverityAsEnum() == Severity.LOW }
return " ($critical/$high/$medium/$low)"
return ": $high high, $medium medium, $low low"
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package io.snyk.plugin.ui.toolwindow.panels

import com.intellij.icons.AllIcons
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiFile
import com.intellij.ui.HyperlinkLabel
import com.intellij.ui.ScrollPaneFactory
Expand All @@ -19,6 +18,7 @@ import io.snyk.plugin.net.FalsePositiveContext
import io.snyk.plugin.net.FalsePositivePayload
import io.snyk.plugin.snykcode.core.PDU
import io.snyk.plugin.snykcode.core.SnykCodeFile
import io.snyk.plugin.toVirtualFile
import io.snyk.plugin.ui.DescriptionHeaderPanel
import io.snyk.plugin.ui.SnykBalloonNotificationHelper
import io.snyk.plugin.ui.baseGridConstraintsAnchorWest
Expand All @@ -27,7 +27,7 @@ import io.snyk.plugin.ui.panelGridConstraints
import io.snyk.plugin.ui.toolwindow.ReportFalsePositiveDialog
import io.snyk.plugin.ui.toolwindow.ReportFalsePositiveDialog.Companion.FALSE_POSITIVE_REPORTED_TEXT
import io.snyk.plugin.ui.toolwindow.ReportFalsePositiveDialog.Companion.REPORT_FALSE_POSITIVE_TEXT
import snyk.common.lsp.MarkerPosition
import snyk.common.lsp.DataFlow
import snyk.common.lsp.ScanIssue
import java.awt.Color
import java.awt.Dimension
Expand Down Expand Up @@ -85,8 +85,8 @@ class SuggestionDescriptionPanelFromLS(
return panel
}

private fun codeLine(range: MarkerPosition, file: SnykCodeFile?): JTextArea {
val component = JTextArea(getLineOfCode(range, file))
private fun codeLine(content: String): JTextArea {
val component = JTextArea(content)
component.font = io.snyk.plugin.ui.getFont(-1, 14, component.font)
component.isEditable = false
return component
Expand Down Expand Up @@ -118,63 +118,48 @@ class SuggestionDescriptionPanelFromLS(
}
}

private fun getLineOfCode(range: MarkerPosition, file: SnykCodeFile?): String {
val document = file?.virtualFile?.getDocument() ?: return ""
range.rows?.let {
val lineStartOffset = document.getLineStartOffset(it[0])
return document.getText(TextRange(lineStartOffset, document.getLineEndOffset(it[0])))
}

return ""
}

private fun dataFlowPanel(): JPanel? {
val markers = issue.additionalData.markers?.let { marker ->
marker.map { it.pos }
.filter { it.isNotEmpty() }
.flatten()
.distinctBy { it.file + it.rows?.firstOrNull() }
} ?: emptyList()
val dataFlow = issue.additionalData.dataFlow

val panel = JPanel()
panel.layout = GridLayoutManager(1 + markers.size, 1, Insets(0, 0, 0, 0), -1, 5)
panel.layout = GridLayoutManager(1 + dataFlow.size, 1, Insets(0, 0, 0, 0), -1, 5)

panel.add(
defaultFontLabel("Data Flow - ${markers.size} step${if (markers.size > 1) "s" else ""}", true),
defaultFontLabel("Data Flow - ${dataFlow.size} step${if (dataFlow.size > 1) "s" else ""}", true),
baseGridConstraintsAnchorWest(0)
)

panel.add(
stepsPanel(markers),
stepsPanel(dataFlow),
baseGridConstraintsAnchorWest(1)
)

return panel
}

private fun stepsPanel(markers: List<MarkerPosition>): JPanel {
private fun stepsPanel(dataflow: List<DataFlow>): JPanel {
val panel = JPanel()
panel.layout = GridLayoutManager(markers.size, 1, Insets(0, 0, 0, 0), 0, 0)
panel.layout = GridLayoutManager(dataflow.size, 1, Insets(0, 0, 0, 0), 0, 0)
panel.background = UIUtil.getTextFieldBackground()

val maxFilenameLength = markers.asSequence()
.filter { it.file.isNotEmpty() }
.map { it.file.substringAfterLast('/', "").length }
val maxFilenameLength = dataflow.asSequence()
.filter { it.filePath.isNotEmpty() }
.map { it.filePath.substringAfterLast('/', "").length }
.maxOrNull() ?: 0

val allStepPanels = mutableListOf<JPanel>()
markers.forEachIndexed { index, markerRange ->
dataflow.forEach { flow ->
val stepPanel = stepPanel(
index = index,
markerRange = markerRange,
maxFilenameLength = max(snykCodeFile.virtualFile.name.length, maxFilenameLength),
index = flow.position,
flow = flow,
maxFilenameLength = max(flow.filePath.toVirtualFile().name.length, maxFilenameLength),
allStepPanels = allStepPanels
)

panel.add(
stepPanel,
baseGridConstraintsAnchorWest(
row = index,
row = flow.position,
fill = GridConstraints.FILL_BOTH,
indent = 0
)
Expand All @@ -187,7 +172,7 @@ class SuggestionDescriptionPanelFromLS(

private fun stepPanel(
index: Int,
markerRange: MarkerPosition,
flow: DataFlow,
maxFilenameLength: Int,
allStepPanels: MutableList<JPanel>
): JPanel {
Expand All @@ -197,9 +182,10 @@ class SuggestionDescriptionPanelFromLS(

val paddedStepNumber = (index + 1).toString().padStart(2, ' ')

val fileName = snykCodeFile.virtualFile.name
val virtualFile = flow.filePath.toVirtualFile()
val fileName = virtualFile.name

val lineNumber = markerRange.rows?.get(0)?.plus(1)
val lineNumber = flow.flowRange.start.line + 1
val positionLinkText = "$fileName:$lineNumber".padEnd(maxFilenameLength + 5, ' ')

val positionLabel = linkLabel(
Expand All @@ -209,16 +195,15 @@ class SuggestionDescriptionPanelFromLS(
toolTipText = "Click to show in the Editor",
customFont = JTextArea().font
) {
val file = snykCodeFile.virtualFile
if (!file.isValid) return@linkLabel
if (!virtualFile.isValid) return@linkLabel

val document = file.getDocument()
val lineStartOffset = document?.getLineStartOffset(markerRange.rows?.firstOrNull() ?: 0) ?: 0
val startOffset = lineStartOffset + (markerRange.cols?.firstOrNull() ?: 0)
val lineEndOffset = document?.getLineStartOffset(markerRange.rows?.lastOrNull() ?: 0) ?: 0
val endOffset = lineEndOffset + (markerRange.cols?.lastOrNull() ?: 0) - 1
val document = virtualFile.getDocument()
val startLineStartOffset = document?.getLineStartOffset(flow.flowRange.start.line) ?: 0
val startOffset = startLineStartOffset + (flow.flowRange.start.character)
val endLineStartOffset = document?.getLineStartOffset(flow.flowRange.end.line) ?: 0
val endOffset = endLineStartOffset + flow.flowRange.end.character - 1

navigateToSource(project, snykCodeFile.virtualFile, startOffset, endOffset)
navigateToSource(project, virtualFile, startOffset, endOffset)

allStepPanels.forEach {
it.background = UIUtil.getTextFieldBackground()
Expand All @@ -227,7 +212,7 @@ class SuggestionDescriptionPanelFromLS(
}
stepPanel.add(positionLabel, baseGridConstraintsAnchorWest(0, indent = 1))

val codeLine = codeLine(markerRange, snykCodeFile)
val codeLine = codeLine(flow.content)
codeLine.isOpaque = false
stepPanel.add(
codeLine,
Expand Down
4 changes: 3 additions & 1 deletion src/main/kotlin/snyk/common/SnykCachedResults.kt
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ internal class SnykCodeFileIssueComparator(
private val snykCodeResults: Map<SnykCodeFile, List<ScanIssue>>
) : Comparator<SnykCodeFile> {
override fun compare(o1: SnykCodeFile, o2: SnykCodeFile): Int {
val files = o1.virtualFile.path.compareTo(o2.virtualFile.path)
val o1Criticals = getCount(o1, Severity.CRITICAL)
val o2Criticals = getCount(o2, Severity.CRITICAL)
val o1Errors = getCount(o1, Severity.HIGH)
Expand All @@ -166,7 +167,8 @@ internal class SnykCodeFileIssueComparator(
o1Criticals != o2Criticals -> o2Criticals - o1Criticals
o1Errors != o2Errors -> o2Errors - o1Errors
o1Warningss != o2Warningss -> o2Warningss - o1Warningss
else -> o2Infos - o1Infos
o1Infos != o2Infos -> o2Infos - o1Infos
else -> files
}
}

Expand Down
10 changes: 6 additions & 4 deletions src/main/kotlin/snyk/common/lsp/SnykLanguageClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import io.snyk.plugin.getContentRootVirtualFiles
import io.snyk.plugin.getSyncPublisher
import io.snyk.plugin.pluginSettings
import io.snyk.plugin.snykcode.core.SnykCodeFile
import io.snyk.plugin.toVirtualFile
import io.snyk.plugin.ui.SnykBalloonNotificationHelper
import org.eclipse.lsp4j.ApplyWorkspaceEditParams
import org.eclipse.lsp4j.ApplyWorkspaceEditResponse
Expand All @@ -38,6 +39,7 @@ import org.eclipse.lsp4j.WorkDoneProgressKind.report
import org.eclipse.lsp4j.WorkDoneProgressReport
import org.eclipse.lsp4j.jsonrpc.services.JsonNotification
import org.eclipse.lsp4j.services.LanguageClient
import snyk.common.SnykCodeFileIssueComparator
import snyk.trust.WorkspaceTrustService
import java.util.Collections
import java.util.concurrent.ArrayBlockingQueue
Expand Down Expand Up @@ -147,12 +149,12 @@ class SnykLanguageClient : LanguageClient {

private fun getSnykCodeResult(project: Project, snykScan: SnykScanParams): Map<SnykCodeFile, List<ScanIssue>> {
check(snykScan.product == "code") { "Expected Snyk Code scan result" }
return snykScan.issues
.groupBy { it.virtualFile }
.map { (file, issues) -> SnykCodeFile(project, file!!) to issues.sorted() }
val map = snykScan.issues
.groupBy { it.filePath }
.map { (file, issues) -> SnykCodeFile(project, file.toVirtualFile()) to issues.sorted() }
.filter { it.second.isNotEmpty() }
.toMap()
.toSortedMap { o1, o2 -> o1.virtualFile.path.compareTo(o2.virtualFile.path) }
return map.toSortedMap(SnykCodeFileIssueComparator(map))
}

override fun createProgress(params: WorkDoneProgressCreateParams?): CompletableFuture<Void> {
Expand Down
15 changes: 13 additions & 2 deletions src/main/kotlin/snyk/common/lsp/Types.kt
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,16 @@ data class MarkerPosition(
result = 31 * result + file.hashCode()
return result
}

}

Check warning

Code scanning / detekt

Detects blank lines before rbraces Warning

Unexpected blank line(s) before "}"

data class DataFlow(
@SerializedName("position") val position: Int,
@SerializedName("filePath") val filePath: String,
@SerializedName("flowRange") val flowRange: Range,
@SerializedName("content") val content: String
)

data class IssueData(
@SerializedName("message") val message: String,
@SerializedName("leadURL") val leadURL: String?,
Expand All @@ -176,7 +184,8 @@ data class IssueData(
@SerializedName("rows") val rows: Point,
@SerializedName("isSecurityType") val isSecurityType: Boolean,
@SerializedName("priorityScore") val priorityScore: Int,
@SerializedName("hasAIFix") val hasAIFix: Boolean
@SerializedName("hasAIFix") val hasAIFix: Boolean,
@SerializedName("dataFlow") val dataFlow: List<DataFlow>,
) {
override fun equals(other: Any?): Boolean {

Check warning

Code scanning / detekt

Prefer splitting up complex methods into smaller, easier to test methods. Warning

The function equals appears to be too complex based on Cyclomatic Complexity (complexity: 24). Defined complexity threshold for methods is set to '15'
if (this === other) return true
Expand Down Expand Up @@ -204,6 +213,7 @@ data class IssueData(
if (isSecurityType != other.isSecurityType) return false
if (priorityScore != other.priorityScore) return false
if (hasAIFix != other.hasAIFix) return false
if (dataFlow != other.dataFlow) return false

return true
}
Expand All @@ -223,10 +233,11 @@ data class IssueData(
result = 31 * result + isSecurityType.hashCode()
result = 31 * result + priorityScore
result = 31 * result + hasAIFix.hashCode()
result = 31 * result + dataFlow.hashCode()
return result
}
}

data class HasAuthenticatedParam (@SerializedName("token") val token: String?)
data class HasAuthenticatedParam(@SerializedName("token") val token: String?)

data class SnykTrustedFoldersParams(@SerializedName("trustedFolders") val trustedFolders: List<String>)

0 comments on commit 7de0dd6

Please sign in to comment.