Skip to content

Commit

Permalink
fix: iac & code separation of navigation logic and ui logic
Browse files Browse the repository at this point in the history
  • Loading branch information
bastiandoetsch committed Jan 4, 2024
1 parent 3a059a2 commit 40669e4
Show file tree
Hide file tree
Showing 13 changed files with 114 additions and 87 deletions.
12 changes: 11 additions & 1 deletion src/main/kotlin/io/snyk/plugin/snykcode/core/SnykCodeFile.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
package io.snyk.plugin.snykcode.core

import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Iconable
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.core.util.toPsiFile
import snyk.common.RelativePathHelper
import javax.swing.Icon

data class SnykCodeFile(val project: Project, val virtualFile: VirtualFile) {
fun getRelativePath(): String = RelativePathHelper().getRelativePath(virtualFile, project) ?: virtualFile.path
var icon: Icon? = null
val relativePath = RelativePathHelper().getRelativePath(virtualFile, project)
init {
ApplicationManager.getApplication().runReadAction {
virtualFile.toPsiFile(project)?.getIcon(Iconable.ICON_FLAG_READ_STATUS)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.SimpleToolWindowPanel
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.psi.PsiManager
import com.intellij.ui.OnePixelSplitter
Expand Down Expand Up @@ -812,17 +813,9 @@ class SnykToolWindowPanel(val project: Project) : JPanel(), Disposable {

rootIacIssuesTreeNode.removeAllChildren()

fun navigateToIaCIssue(filePath: String, issueLine: Int): () -> Unit = {
val virtualFile = VirtualFileManager.getInstance().findFileByNioPath(Paths.get(filePath))
if (virtualFile != null && virtualFile.isValid) {
val document = FileDocumentManager.getInstance().getDocument(virtualFile)
if (document != null) {
val candidate = issueLine - 1 // to 1-based count used in the editor
val lineNumber = if (0 <= candidate && candidate < document.lineCount) candidate else 0
val lineStartOffset = document.getLineStartOffset(lineNumber)

navigateToSource(project, virtualFile, lineStartOffset)
}
fun navigateToIaCIssue(virtualFile: VirtualFile?, lineStartOffset: Int): () -> Unit = {
if (virtualFile?.isValid == true) {
navigateToSource(project, virtualFile, lineStartOffset)
}
}

Expand All @@ -838,16 +831,16 @@ class SnykToolWindowPanel(val project: Project) : JPanel(), Disposable {
.sortedByDescending { it.getSeverity() }
.forEach {
val navigateToSource = navigateToIaCIssue(
iacVulnerabilitiesForFile.targetFilePath,
it.lineNumber
iacVulnerabilitiesForFile.virtualFile,
it.lineStartOffset
)
fileTreeNode.add(IacIssueTreeNode(it, project, navigateToSource))
}
}
}
iacResult.getVisibleErrors().forEach { snykError ->
rootIacIssuesTreeNode.add(
ErrorTreeNode(snykError, project, navigateToIaCIssue(snykError.path, 0))
ErrorTreeNode(snykError, project, navigateToIaCIssue(snykError.virtualFile, 0))
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@ package io.snyk.plugin.ui.toolwindow

import ai.deepcode.javaclient.core.SuggestionForFile
import com.intellij.icons.AllIcons
import com.intellij.openapi.util.Iconable
import com.intellij.ui.ColoredTreeCellRenderer
import com.intellij.ui.SimpleTextAttributes
import com.intellij.util.ui.UIUtil
import icons.SnykIcons
import io.snyk.plugin.getSnykCachedResults
import io.snyk.plugin.pluginSettings
import io.snyk.plugin.snykcode.core.AnalysisData
import io.snyk.plugin.snykcode.core.PDU
import io.snyk.plugin.snykcode.core.SnykCodeFile
import io.snyk.plugin.snykcode.getSeverityAsEnum
import io.snyk.plugin.ui.PackageManagerIconProvider.Companion.getIcon
Expand Down Expand Up @@ -106,7 +104,7 @@ class SnykTreeCellRenderer : ColoredTreeCellRenderer() {

is SnykCodeFileTreeNode -> {
val (file, productType) = value.userObject as Pair<SnykCodeFile, ProductType>
val relativePath = file.getRelativePath()
val relativePath = file.relativePath
toolTipText =
buildString {
append(relativePath)
Expand All @@ -119,8 +117,7 @@ class SnykTreeCellRenderer : ColoredTreeCellRenderer() {
)
}

val psiFile = PDU.toPsiFile(file)
nodeIcon = psiFile?.getIcon(Iconable.ICON_FLAG_READ_STATUS)
nodeIcon = file.icon
if (!AnalysisData.instance.isFileInCache(file)) {
attributes = SimpleTextAttributes.GRAYED_ATTRIBUTES
text += OBSOLETE_SUFFIX
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,10 +217,7 @@ class SuggestionDescriptionPanel(

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

val fileToNavigate = if (markerRange.file.isNullOrEmpty()) snykCodeFile else {
PDU.instance.getFileByDeepcodedPath(markerRange.file, project)?.let { PDU.toSnykCodeFile(it) }
}
val fileName = fileToNavigate?.virtualFile?.name ?: markerRange.file
val fileName = snykCodeFile.virtualFile.name

val positionLinkText = "$fileName:${markerRange.startRow}".padEnd(maxFilenameLength + 5, ' ')

Expand All @@ -231,9 +228,9 @@ class SuggestionDescriptionPanel(
toolTipText = "Click to show in the Editor",
customFont = JTextArea().font
) {
if (fileToNavigate == null || !fileToNavigate.virtualFile.isValid) return@linkLabel
if (!snykCodeFile.virtualFile.isValid) return@linkLabel

navigateToSource(project, fileToNavigate.virtualFile, markerRange.start, markerRange.end)
navigateToSource(project, snykCodeFile.virtualFile, markerRange.start, markerRange.end)

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

val codeLine = codeLine(markerRange, fileToNavigate)
val codeLine = codeLine(markerRange, snykCodeFile)
codeLine.isOpaque = false
stepPanel.add(
codeLine,
Expand Down
7 changes: 2 additions & 5 deletions src/main/kotlin/snyk/advisor/AdvisorEditorFactoryListener.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@ import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.psi.PsiCompiledElement
import com.intellij.psi.PsiDocumentManager

class AdvisorEditorFactoryListener: EditorFactoryListener {
class AdvisorEditorFactoryListener : EditorFactoryListener {

override fun editorCreated(event: EditorFactoryEvent) {

val editor = event.editor

// sanity checks, examples taken from com.intellij.codeInsight.preview.ImageOrColorPreviewManager.registerListeners
if (editor.isOneLineMode) {
return
}
Expand All @@ -31,9 +29,8 @@ class AdvisorEditorFactoryListener: EditorFactoryListener {
return
}

val advisorScoreProvider = AdvisorScoreProvider(editor)
val advisorScoreProvider = AdvisorScoreProvider(editor, psiFile)

editor.registerLineExtensionPainter(advisorScoreProvider::getLineExtensions)

}
}
37 changes: 24 additions & 13 deletions src/main/kotlin/snyk/advisor/AdvisorScoreProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.intellij.openapi.editor.event.VisibleAreaListener
import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.openapi.editor.markup.TextAttributes
import com.intellij.openapi.ui.popup.Balloon
import com.intellij.psi.PsiFile
import com.intellij.ui.Gray
import com.intellij.ui.JBColor
import io.snyk.plugin.analytics.getEcosystem
Expand All @@ -25,24 +26,25 @@ import java.awt.Color
import java.awt.Cursor
import java.awt.Font

class AdvisorScoreProvider(
private val editor: Editor
) {
private val scoredLines: MutableMap<Int, Pair<PackageInfo, AdvisorPackageManager>> = mutableMapOf()
private const val darkModeTextAttribute = 0x3d8065

Check warning

Code scanning / detekt

Top level property names should follow the naming convention set in the projects configuration. Warning

Top level constant names should match the pattern: [A-Z][_A-Z0-9]*

class AdvisorScoreProvider(private val editor: Editor, private val psiFile: PsiFile) {
private var packageManager: AdvisorPackageManager? = null

private val scoredLines: MutableMap<Int, Pair<PackageInfo, AdvisorPackageManager>>
private var selectedScore: Int? = null
private var currentBalloon: Balloon? = null
private var currentCursor: Cursor? = null
private var editorListener: AdvisorEditorListener? = null

private val packageNameProvider = PackageNameProvider(editor)
private var packageNameProvider: PackageNameProvider? = null

fun getLineExtensions(lineNumber: Int): Collection<LineExtensionInfo> {
val settings = pluginSettings()
if (settings.pluginFirstRun || !settings.advisorEnable) {
return resetAndReturnEmptyList()
}

val (packageName, packageManager) = packageNameProvider.getPackageName(lineNumber)
val (packageName, packageManager) = packageNameProvider?.getPackageName(psiFile, lineNumber)
?: return resetAndReturnEmptyList()

val info = packageName?.let {
Expand Down Expand Up @@ -153,11 +155,8 @@ class AdvisorScoreProvider(

// not over Text in LineExtension
val lineLength = e.editor.document.getLineEndOffset(line) - e.editor.document.getLineStartOffset(line)
if (e.logicalPosition.column < lineLength + LINE_EXTENSION_PREFIX.length ||
e.logicalPosition.column > lineLength + LINE_EXTENSION_LENGTH
) return false

return true
return !(e.logicalPosition.column < lineLength + LINE_EXTENSION_PREFIX.length ||

Check warning

Code scanning / detekt

Reports missing newlines (e.g. between parentheses of a multi-line function call Warning

Missing newline after "("

Check warning

Code scanning / detekt

Reports missing newlines (e.g. between parentheses of a multi-line function call Warning

Missing newline before ")"
e.logicalPosition.column > lineLength + LINE_EXTENSION_LENGTH)
}

private fun cleanCacheForRemovedLines() {
Expand Down Expand Up @@ -188,7 +187,9 @@ class AdvisorScoreProvider(
.globalScheme.getAttributes(DefaultLanguageHighlighterColors.BLOCK_COMMENT)
return if (attributes == null || attributes.foregroundColor == null) {
TextAttributes(
JBColor { if (EditorColorsManager.getInstance().isDarkEditor) Color(0x3d8065) else Gray._135 },
JBColor.lazy {
JBColor(Gray._135, Color(darkModeTextAttribute))
},
null,
null,
null,
Expand All @@ -209,4 +210,14 @@ class AdvisorScoreProvider(
fontType = fontType xor Font.BOLD
}
}

init {
this.scoredLines = mutableMapOf()
this.packageManager = when (psiFile.virtualFile.name) {
"package.json" -> AdvisorPackageManager.NPM
"requirements.txt" -> AdvisorPackageManager.PYTHON
else -> null
}
this.packageManager?.let { this.packageNameProvider = PackageNameProvider(editor, it) }
}
}
21 changes: 7 additions & 14 deletions src/main/kotlin/snyk/advisor/PackageNameProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,24 @@ package snyk.advisor

import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiCompiledElement
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiFile
import snyk.advisor.buildsystems.NpmSupport
import snyk.advisor.buildsystems.PythonSupport

class PackageNameProvider(private val editor: Editor) {
class PackageNameProvider(private val editor: Editor, private val packageManager: AdvisorPackageManager) {

fun getPackageName(lineNumber: Int): Pair<String?, AdvisorPackageManager>? {
fun getPackageName(psiFile: PsiFile, lineNumber: Int): Pair<String?, AdvisorPackageManager>? {
// sanity checks, examples taken from ImageOrColorPreviewManager.registerListeners
val project = editor.project
if (project == null || project.isDisposed) {
return null
}
val psiFile = PsiDocumentManager.getInstance(project).getPsiFile(editor.document)
if (psiFile == null || psiFile.virtualFile == null || psiFile is PsiCompiledElement) {
return null
}
val packageManager = when (psiFile.virtualFile.name) {
"package.json" -> AdvisorPackageManager.NPM
"requirements.txt" -> AdvisorPackageManager.PYTHON
else -> return null
}

if (lineNumber < 0 || (editor.document.lineCount - 1) < lineNumber) {
log.warn("Line number $lineNumber is out of range [0:${editor.document.lineCount - 1}] at ${psiFile.virtualFile.path}")
log.warn(
"Line number $lineNumber is out of range " +
"[0:${editor.document.lineCount - 1}] at ${psiFile.virtualFile.path}"
)
return Pair(null, packageManager)
}
val packageName = when (packageManager) {
Expand Down
13 changes: 12 additions & 1 deletion src/main/kotlin/snyk/common/SnykError.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
package snyk.common

import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VirtualFile

data class SnykError(
val message: String,
val path: String,
val code: Int? = null
)
) {
var virtualFile: VirtualFile? = null

init {
if (path.isNotEmpty()) {
virtualFile = LocalFileSystem.getInstance().findFileByPath(this.path)
}
}
}
3 changes: 2 additions & 1 deletion src/main/kotlin/snyk/iac/IacIssue.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ data class IacIssue(
val impact: String,
val resolve: String? = null,
val references: List<String> = emptyList(),
val path: List<String> = emptyList()
val path: List<String> = emptyList(),
val lineStartOffset: Int = 0
) {
var ignored = false
var obsolete = false
Expand Down
22 changes: 4 additions & 18 deletions src/main/kotlin/snyk/iac/IacIssuesForFile.kt
Original file line number Diff line number Diff line change
@@ -1,32 +1,18 @@
package snyk.iac

import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VirtualFile
import snyk.common.RelativePathHelper

data class IacIssuesForFile(
val infrastructureAsCodeIssues: List<IacIssue>,
val targetFile: String,
val targetFilePath: String,
val packageManager: String
val packageManager: String,
val virtualFile: VirtualFile?,
val project: Project?,
val relativePath: String? = null,
) {
val obsolete: Boolean get() = infrastructureAsCodeIssues.any { it.obsolete }
val ignored: Boolean get() = infrastructureAsCodeIssues.all { it.ignored }
val uniqueCount: Int get() = infrastructureAsCodeIssues.groupBy { it.id }.size

val virtualFile: VirtualFile
get() = LocalFileSystem.getInstance().findFileByPath(this.targetFilePath)!!

var project: Project? = null

// this is necessary as the creation of the class by the GSon is not initializing fields
private var relativePathHelper: RelativePathHelper? = null
get() = field ?: RelativePathHelper()

var relativePath: String? = null
get() = field ?: project?.let {
field = relativePathHelper!!.getRelativePath(virtualFile, it)
return field
}
}
Loading

0 comments on commit 40669e4

Please sign in to comment.