diff --git a/.gitleaksignore b/.gitleaksignore index fc51e5e68..852be63c6 100644 --- a/.gitleaksignore +++ b/.gitleaksignore @@ -53,3 +53,4 @@ db2662b39d5195aeda376109d21d1530c194711e:src/main/scala/io/snyk/plugin/client/Ap 372778ad27f3293b03c96eed86dccfe4d6d8c6f4:src/main/scala/io/snyk/plugin/ApiClient.scala:generic-api-key:14 372778ad27f3293b03c96eed86dccfe4d6d8c6f4:src/main/scala/io/snyk/plugin/ApiClient.scala:generic-api-key:17 372778ad27f3293b03c96eed86dccfe4d6d8c6f4:src/main/scala/io/snyk/plugin/ApiClient.scala:generic-api-key:20 +67244ecb074381e16902870e29d9f734cc611fd8:src/main/scala/io/snyk/plugin/metrics/SegmentApi.scala:generic-api-key:56 diff --git a/CHANGELOG.md b/CHANGELOG.md index f17f1fd54..1a8f5a13f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Added a description of custom endpoints to settings dialog. - Add option to ignore IaC issues ### Fixed +- only ask to scan folders that are known to language server - folder-specific configs are availabe on opening projects, not only on restart of the IDE - display open source issues in Rider. Previously, as the project.assets.json is in a derived folder, it was filtered. - correctly display and update base branch name for Net New Issues diff --git a/src/main/kotlin/io/snyk/plugin/SnykProjectManagerListener.kt b/src/main/kotlin/io/snyk/plugin/SnykProjectManagerListener.kt index df8f4296f..8bbd8f6eb 100644 --- a/src/main/kotlin/io/snyk/plugin/SnykProjectManagerListener.kt +++ b/src/main/kotlin/io/snyk/plugin/SnykProjectManagerListener.kt @@ -24,7 +24,7 @@ class SnykProjectManagerListener : ProjectManagerListener { threadPool.submit { val ls = LanguageServerWrapper.getInstance() if (ls.isInitialized) { - ls.updateWorkspaceFolders(emptySet(), ls.getWorkspaceFolders(project)) + ls.updateWorkspaceFolders(emptySet(), ls.getWorkspaceFoldersFromRoots(project)) } }.get(TIMEOUT, TimeUnit.SECONDS) } catch (ignored: Exception) { diff --git a/src/main/kotlin/io/snyk/plugin/services/SnykCliAuthenticationService.kt b/src/main/kotlin/io/snyk/plugin/services/SnykCliAuthenticationService.kt index 1ae80659e..7dc7be929 100644 --- a/src/main/kotlin/io/snyk/plugin/services/SnykCliAuthenticationService.kt +++ b/src/main/kotlin/io/snyk/plugin/services/SnykCliAuthenticationService.kt @@ -41,7 +41,7 @@ class SnykCliAuthenticationService( if (getCliFile().exists()) { executeAuthCommand() } - LanguageServerWrapper.getInstance().updateConfiguration() + LanguageServerWrapper.getInstance().updateConfiguration(false) } private fun downloadCliIfNeeded() { diff --git a/src/main/kotlin/io/snyk/plugin/settings/SnykProjectSettingsConfigurable.kt b/src/main/kotlin/io/snyk/plugin/settings/SnykProjectSettingsConfigurable.kt index f1e4af5a3..445032acd 100644 --- a/src/main/kotlin/io/snyk/plugin/settings/SnykProjectSettingsConfigurable.kt +++ b/src/main/kotlin/io/snyk/plugin/settings/SnykProjectSettingsConfigurable.kt @@ -20,8 +20,8 @@ import io.snyk.plugin.isUrlValid import io.snyk.plugin.pluginSettings import io.snyk.plugin.ui.SnykBalloonNotificationHelper import io.snyk.plugin.ui.SnykSettingsDialog -import snyk.common.lsp.settings.FolderConfigSettings import snyk.common.lsp.LanguageServerWrapper +import snyk.common.lsp.settings.FolderConfigSettings import javax.swing.JComponent class SnykProjectSettingsConfigurable( @@ -126,7 +126,7 @@ class SnykProjectSettingsConfigurable( } languageServerWrapper.refreshFeatureFlags() - languageServerWrapper.updateConfiguration() + languageServerWrapper.updateConfiguration(true) } if (rescanNeeded) { diff --git a/src/main/kotlin/io/snyk/plugin/ui/BranchChooserComboboxDialog.kt b/src/main/kotlin/io/snyk/plugin/ui/BranchChooserComboboxDialog.kt index 493b2d9fa..469dae04b 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/BranchChooserComboboxDialog.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/BranchChooserComboboxDialog.kt @@ -67,7 +67,7 @@ class BranchChooserComboBoxDialog(val project: Project) : DialogWrapper(true) { folderConfigSettings.addFolderConfig(folderConfig.copy(baseBranch = baseBranch)) } runInBackground("Snyk: updating configuration") { - LanguageServerWrapper.getInstance().updateConfiguration() + LanguageServerWrapper.getInstance().updateConfiguration(true) } } diff --git a/src/main/kotlin/snyk/common/lsp/LanguageServerWrapper.kt b/src/main/kotlin/snyk/common/lsp/LanguageServerWrapper.kt index b48ab0963..2b66f83b1 100644 --- a/src/main/kotlin/snyk/common/lsp/LanguageServerWrapper.kt +++ b/src/main/kotlin/snyk/common/lsp/LanguageServerWrapper.kt @@ -63,9 +63,14 @@ import snyk.common.lsp.commands.SNYK_GENERATE_ISSUE_DESCRIPTION import snyk.common.lsp.progress.ProgressManager import snyk.common.lsp.settings.LanguageServerSettings import snyk.common.lsp.settings.SeverityFilter +import snyk.common.removeTrailingSlashesIfPresent import snyk.pluginInfo import snyk.trust.WorkspaceTrustService import snyk.trust.confirmScanningAndSetWorkspaceTrustedStateIfNeeded +import java.io.FileNotFoundException +import java.net.URI +import java.nio.file.Paths +import java.util.Collections import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.concurrent.TimeUnit @@ -85,6 +90,8 @@ class LanguageServerWrapper( private var authenticatedUser: Map? = null private var initializeResult: InitializeResult? = null private val gson = Gson() + // internal for test set up + internal val configuredWorkspaceFolders: MutableSet = Collections.synchronizedSet(mutableSetOf()) private var disposed = false get() { return ApplicationManager.getApplication().isDisposed || field @@ -231,7 +238,7 @@ class LanguageServerWrapper( private fun lsIsAlive() = ::process.isInitialized && process.isAlive - fun getWorkspaceFolders(project: Project): Set { + fun getWorkspaceFoldersFromRoots(project: Project): Set { if (disposed || project.isDisposed) return emptySet() val normalizedRoots = getTrustedContentRoots(project) return normalizedRoots.map { WorkspaceFolder(it.toLanguageServerURL(), it.name) }.toSet() @@ -316,8 +323,15 @@ class LanguageServerWrapper( try { if (!ensureLanguageServerInitialized()) return val params = DidChangeWorkspaceFoldersParams() - params.event = WorkspaceFoldersChangeEvent(added.toList(), removed.toList()) - languageServer.workspaceService.didChangeWorkspaceFolders(params) + params.event = WorkspaceFoldersChangeEvent( + added.filter { !configuredWorkspaceFolders.contains(it) }, + removed.filter { configuredWorkspaceFolders.contains(it) }, + ) + if (params.event.added.size > 0 || params.event.removed.size > 0) { + languageServer.workspaceService.didChangeWorkspaceFolders(params) + configuredWorkspaceFolders.removeAll(removed) + configuredWorkspaceFolders.addAll(added) + } } catch (e: Exception) { logger.error(e) } @@ -365,6 +379,7 @@ class LanguageServerWrapper( if (notAuthenticated()) return DumbService.getInstance(project).runWhenSmart { getTrustedContentRoots(project).forEach { + addContentRoots(project) sendFolderScanCommand(it.path, project) } } @@ -424,10 +439,14 @@ class LanguageServerWrapper( if (notAuthenticated()) return if (DumbService.getInstance(project).isDumb) return try { + val folderUri = Paths.get(folder).toUri().toASCIIString().removeTrailingSlashesIfPresent() + if (!configuredWorkspaceFolders.any { it.uri.removeTrailingSlashesIfPresent() == folderUri }) return val param = ExecuteCommandParams() param.command = COMMAND_WORKSPACE_FOLDER_SCAN param.arguments = listOf(folder) languageServer.workspaceService.executeCommand(param) + } catch (e: FileNotFoundException) { + logger.debug("File not found: $folder") } catch (e: Exception) { logger.error("error calling scan command from language server. re-initializing", e) restart() @@ -477,12 +496,12 @@ class LanguageServerWrapper( ) } - fun updateConfiguration() { + fun updateConfiguration(runScan: Boolean = false) { if (!ensureLanguageServerInitialized()) return val params = DidChangeConfigurationParams(getSettings()) languageServer.workspaceService.didChangeConfiguration(params) - if (pluginSettings().scanOnSave) { + if (runScan && pluginSettings().scanOnSave) { ProjectManager.getInstance().openProjects.forEach { sendScanCommand(it) } } } @@ -576,8 +595,8 @@ class LanguageServerWrapper( if (disposed || project.isDisposed) return ensureLanguageServerInitialized() ensureLanguageServerProtocolVersion(project) - updateConfiguration() - val added = getWorkspaceFolders(project) + updateConfiguration(false) + val added = getWorkspaceFoldersFromRoots(project) updateWorkspaceFolders(added, emptySet()) } diff --git a/src/test/kotlin/snyk/common/lsp/LanguageServerWrapperTest.kt b/src/test/kotlin/snyk/common/lsp/LanguageServerWrapperTest.kt index b8aacc044..da05962ae 100644 --- a/src/test/kotlin/snyk/common/lsp/LanguageServerWrapperTest.kt +++ b/src/test/kotlin/snyk/common/lsp/LanguageServerWrapperTest.kt @@ -23,6 +23,7 @@ import junit.framework.TestCase.fail import org.eclipse.lsp4j.DidChangeConfigurationParams import org.eclipse.lsp4j.ExecuteCommandParams import org.eclipse.lsp4j.InitializeParams +import org.eclipse.lsp4j.WorkspaceFolder import org.eclipse.lsp4j.services.LanguageServer import org.junit.After import org.junit.Before @@ -31,6 +32,7 @@ import snyk.common.lsp.analytics.ScanDoneEvent import snyk.common.lsp.settings.FolderConfigSettings import snyk.pluginInfo import snyk.trust.WorkspaceTrustService +import java.nio.file.Paths import java.util.concurrent.CompletableFuture class LanguageServerWrapperTest { @@ -218,7 +220,10 @@ class LanguageServerWrapperTest { lsMock.workspaceService.executeCommand(any()) } returns CompletableFuture.completedFuture(null) - cut.sendFolderScanCommand("testFolder", projectMock) + val folder = "testFolder" + cut.configuredWorkspaceFolders.add(WorkspaceFolder(Paths.get(folder).toUri().toASCIIString(), folder)) + + cut.sendFolderScanCommand(folder, projectMock) verify { lsMock.workspaceService.executeCommand(any()) } } @@ -263,7 +268,7 @@ class LanguageServerWrapperTest { every { dumbServiceMock.isDumb } returns false settings.scanOnSave = true - cut.updateConfiguration() + cut.updateConfiguration(true) verify { lsMock.workspaceService.didChangeConfiguration(any()) }