Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: this fixes the dataflow and the issue navigation #476

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We got rid of this null check because of the WIP here?


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
}

}

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 {
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>)
Loading