diff --git a/src/main/kotlin/io/snyk/plugin/Utils.kt b/src/main/kotlin/io/snyk/plugin/Utils.kt index a34c1146e..2c44e2926 100644 --- a/src/main/kotlin/io/snyk/plugin/Utils.kt +++ b/src/main/kotlin/io/snyk/plugin/Utils.kt @@ -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 @@ -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 diff --git a/src/main/kotlin/io/snyk/plugin/services/SnykTaskQueueService.kt b/src/main/kotlin/io/snyk/plugin/services/SnykTaskQueueService.kt index 2bbf7ed38..d26fa6169 100644 --- a/src/main/kotlin/io/snyk/plugin/services/SnykTaskQueueService.kt +++ b/src/main/kotlin/io/snyk/plugin/services/SnykTaskQueueService.kt @@ -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()) } } diff --git a/src/main/kotlin/snyk/common/lsp/LanguageServerWrapper.kt b/src/main/kotlin/snyk/common/lsp/LanguageServerWrapper.kt index 9683b6a24..0f9e4d59b 100644 --- a/src/main/kotlin/snyk/common/lsp/LanguageServerWrapper.kt +++ b/src/main/kotlin/snyk/common/lsp/LanguageServerWrapper.kt @@ -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 @@ -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 diff --git a/src/main/kotlin/snyk/common/lsp/ScanState.kt b/src/main/kotlin/snyk/common/lsp/ScanState.kt index 2336c0a0a..e7a0dade1 100644 --- a/src/main/kotlin/snyk/common/lsp/ScanState.kt +++ b/src/main/kotlin/snyk/common/lsp/ScanState.kt @@ -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 = Collections.synchronizedMap(mutableMapOf()) + val scanInProgress: MutableMap = Collections.synchronizedMap(mutableMapOf()) } + +data class ScanInProgressKey(val folderPath: VirtualFile, val productType: ProductType) diff --git a/src/main/kotlin/snyk/common/lsp/SnykLanguageClient.kt b/src/main/kotlin/snyk/common/lsp/SnykLanguageClient.kt index 14595127c..e9b992acf 100644 --- a/src/main/kotlin/snyk/common/lsp/SnykLanguageClient.kt +++ b/src/main/kotlin/snyk/common/lsp/SnykLanguageClient.kt @@ -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 @@ -82,39 +83,55 @@ class SnykLanguageClient : LanguageClient { } override fun refreshInlineValues(): CompletableFuture { - val activeProject = ProjectUtil.getActiveProject() ?: return CompletableFuture.completedFuture(null) + val completedFuture: CompletableFuture = 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, @@ -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> { 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))