Skip to content

Commit

Permalink
fix: snyk code language server scans work with 1..n projects open
Browse files Browse the repository at this point in the history
  • Loading branch information
bastiandoetsch committed Mar 6, 2024
1 parent 78e1425 commit 32e098c
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 34 deletions.
18 changes: 14 additions & 4 deletions src/main/kotlin/io/snyk/plugin/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,11 @@ import snyk.advisor.AdvisorService
import snyk.advisor.AdvisorServiceImpl
import snyk.advisor.SnykAdvisorModel
import snyk.amplitude.AmplitudeExperimentService
import snyk.common.ProductType
import snyk.common.SnykCachedResults
import snyk.common.UIComponentFinder
import snyk.common.isSnykTenant
import snyk.common.lsp.ScanInProgressKey
import snyk.common.lsp.ScanState
import snyk.container.ContainerService
import snyk.container.KubernetesImageCache
Expand Down Expand Up @@ -177,10 +179,18 @@ fun isOssRunning(project: Project): Boolean {
return indicator != null && indicator.isRunning && !indicator.isCanceled
}

fun isSnykCodeRunning(project: Project): Boolean =
AnalysisData.instance.isUpdateAnalysisInProgress(project)
|| RunUtils.instance.isFullRescanRequested(project)
|| ScanState.scanInProgress[ScanState.SNYK_CODE] == true
fun isSnykCodeRunning(project: Project): Boolean {
val lsRunning = project.getContentRootVirtualFiles().any { vf ->
val key = ScanInProgressKey(vf, ProductType.CODE_SECURITY)
ScanState.scanInProgress[key] == true
}

return (
AnalysisData.instance.isUpdateAnalysisInProgress(project) ||
RunUtils.instance.isFullRescanRequested(project) ||
lsRunning
)
}

fun isIacRunning(project: Project): Boolean {
val indicator = getSnykTaskQueueService(project)?.iacScanProgressIndicator
Expand Down
10 changes: 4 additions & 6 deletions src/main/kotlin/io/snyk/plugin/services/SnykTaskQueueService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,10 @@ class SnykTaskQueueService(val project: Project) {

fun connectProjectToLanguageServer(project: Project) {
synchronized(LanguageServerWrapper) {
val languageServerWrapper = LanguageServerWrapper.getInstance()
if (!languageServerWrapper.isInitialized && !languageServerWrapper.isInitializing) {
languageServerWrapper.initialize()
val added = languageServerWrapper.getWorkspaceFolders(project)
languageServerWrapper.updateWorkspaceFolders(added, emptySet())
}
val wrapper = LanguageServerWrapper.getInstance()
wrapper.ensureLanguageServerInitialized()
val added = wrapper.getWorkspaceFolders(project)
wrapper.updateWorkspaceFolders(added, emptySet())
}
}

Expand Down
14 changes: 13 additions & 1 deletion src/main/kotlin/snyk/common/lsp/LanguageServerWrapper.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package snyk.common.lsp

import com.intellij.ide.impl.ProjectUtil
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManager
Expand Down Expand Up @@ -207,9 +208,20 @@ class LanguageServerWrapper(
}

fun sendScanCommand() {
ensureLanguageServerInitialized()
val project = ProjectUtil.getActiveProject()
if (project == null) {
logger.warn("No active project found, not sending scan command.")
return
}
project.getContentRootVirtualFiles().mapNotNull { it.path }.toSet().forEach(::sendFolderScanCommand)
}

private fun sendFolderScanCommand(folder: String) {
try {
val param = ExecuteCommandParams()
param.command = "snyk.workspace.scan"
param.command = "snyk.workspaceFolder.scan"
param.arguments = listOf(folder)
languageServer.workspaceService.executeCommand(param)
} catch (ignored: Exception) {
// do nothing to not break UX for analytics
Expand Down
7 changes: 5 additions & 2 deletions src/main/kotlin/snyk/common/lsp/ScanState.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package snyk.common.lsp

import com.intellij.openapi.vfs.VirtualFile
import snyk.common.ProductType
import java.util.Collections

object ScanState {
const val SNYK_CODE = "code"
val scanInProgress: MutableMap<String, Boolean> = Collections.synchronizedMap(mutableMapOf())
val scanInProgress: MutableMap<ScanInProgressKey, Boolean> = Collections.synchronizedMap(mutableMapOf())
}

data class ScanInProgressKey(val folderPath: VirtualFile, val productType: ProductType)
61 changes: 40 additions & 21 deletions src/main/kotlin/snyk/common/lsp/SnykLanguageClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,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.ProductType
import snyk.common.SnykCodeFileIssueComparator
import snyk.trust.WorkspaceTrustService
import java.util.Collections
Expand Down Expand Up @@ -82,39 +83,55 @@ class SnykLanguageClient : LanguageClient {
}

override fun refreshInlineValues(): CompletableFuture<Void> {
val activeProject = ProjectUtil.getActiveProject() ?: return CompletableFuture.completedFuture(null)
val completedFuture: CompletableFuture<Void> = CompletableFuture.completedFuture(null)
if (!isSnykCodeLSEnabled()) return completedFuture
val activeProject = ProjectUtil.getActiveProject() ?: return completedFuture

DaemonCodeAnalyzer.getInstance(activeProject).restart()
return CompletableFuture.completedFuture(null)
return completedFuture
}

@JsonNotification(value = "$/snyk.scan")
fun snykScan(snykScan: SnykScanParams) {
if (snykScan.product != ScanState.SNYK_CODE || !isSnykCodeLSEnabled()) return
if (!isSnykCodeLSEnabled()) return
try {
getScanPublishersFor(snykScan).forEach { (project, scanPublisher) ->
when (snykScan.status) {
"inProgress" -> {
if (ScanState.scanInProgress[snykScan.product] == true) return
ScanState.scanInProgress[snykScan.product] = true
scanPublisher.scanningStarted(snykScan)
}

"success" -> {
ScanState.scanInProgress[snykScan.product] = false
processSuccessfulScan(snykScan, scanPublisher, project)
}

"error" -> {
ScanState.scanInProgress[snykScan.product] = false
scanPublisher.scanningSnykCodeError(snykScan)
}
}
processSnykScan(snykScan, scanPublisher, project)
}
} catch (e: Exception) {
logger.error("Error processing snyk scan", e)
}
}

private fun processSnykScan(
snykScan: SnykScanParams,
scanPublisher: SnykCodeScanListenerLS,
project: Project
) {
val product = when (snykScan.product) {
"code" -> ProductType.CODE_SECURITY
else -> return
}
val key = ScanInProgressKey(snykScan.folderPath.toVirtualFile(), product)
when (snykScan.status) {
"inProgress" -> {
if (ScanState.scanInProgress[key] == true) return
ScanState.scanInProgress[key] = true
scanPublisher.scanningStarted(snykScan)
}

"success" -> {
ScanState.scanInProgress[key] = false
processSuccessfulScan(snykScan, scanPublisher, project)
}

"error" -> {
ScanState.scanInProgress[key] = false
scanPublisher.scanningSnykCodeError(snykScan)
}
}
}

private fun processSuccessfulScan(
snykScan: SnykScanParams,
scanPublisher: SnykCodeScanListenerLS,
Expand Down Expand Up @@ -150,11 +167,13 @@ class SnykLanguageClient : LanguageClient {
}.toSet()
}

@Suppress("UselessCallOnNotNull") // because lsp4j doesn't care about Kotlin non-null safety
private fun getSnykCodeResult(project: Project, snykScan: SnykScanParams): Map<SnykCodeFile, List<ScanIssue>> {
check(snykScan.product == "code") { "Expected Snyk Code scan result" }
if (snykScan.issues.isNullOrEmpty()) return emptyMap()
val map = snykScan.issues
.groupBy { it.filePath }
.map { (file, issues) -> SnykCodeFile(project, file.toVirtualFile()) to issues.sorted() }
.mapNotNull { (file, issues) -> SnykCodeFile(project, file.toVirtualFile()) to issues.sorted() }
.filter { it.second.isNotEmpty() }
.toMap()
return map.toSortedMap(SnykCodeFileIssueComparator(map))
Expand Down

0 comments on commit 32e098c

Please sign in to comment.