Skip to content

Commit 70a7a42

Browse files
nick-y-snykandrewrobinsonhodges-snykrrama
authored
feat: integrate HTML config dialog from Language Server [IDE-1458] (#426)
Co-authored-by: Andrew Robinson Hodges <andrew.robinsonhodges@snyk.io> Co-authored-by: Ben Durrans <Benjamin.Durrans@snyk.io>
1 parent 13c8dfd commit 70a7a42

42 files changed

Lines changed: 2261 additions & 275 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/resource-check.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ jobs:
1717
# Add each resource as a key, value pair, mapping the local resource to the reference file (which should be stored in the language server repository). For example:
1818
# resources["<path_to_local_file>"]="<url_of_reference_file>"
1919
resources["Snyk.VisualStudio.Extension.2022/Resources/ScanSummaryInit.html"]="https://raw.githubusercontent.com/snyk/snyk-ls/refs/heads/main/shared_ide_resources/ui/html/ScanSummaryInit.html"
20+
resources["Snyk.VisualStudio.Extension.2022/Resources/settings-fallback.html"]="https://raw.githubusercontent.com/snyk/snyk-ls/refs/heads/main/shared_ide_resources/ui/html/settings-fallback.html"
2021
for key in ${!resources[@]}; do
2122
candidate=$(sha512sum $key | awk {'print $1'})
2223
candidate=${candidate:="null"}

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Snyk Security Changelog
22

3+
## [2.7.0]
4+
### Changed
5+
- Added support for improved Settings UI for simpler configuration of Snyk settings (experimental).
6+
- Automatic organization configuration is now enabled by default.
7+
- Bump LS protocol version to 22.
8+
39
## [2.6.0]
410
### Changed
511
- Organization setting can now also be set in solution-specific settings (experimental), allowing different organizations per solution/project.

Snyk.VisualStudio.Extension.2022/Download/SnykCliDownloader.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,10 @@ public LatestReleaseInfo GetLatestReleaseInfo()
5252
{
5353
Logger.Information("Get latest CLI release info");
5454

55-
var latestReleaseVersionUrl = string.Format(LatestReleaseVersionUrlScheme, SnykOptions.CliDownloadUrl, SnykOptions.CliReleaseChannel);
55+
var latestReleaseVersionUrl = string.Format(LatestReleaseVersionUrlScheme, SnykOptions.CliBaseDownloadURL, SnykOptions.CliReleaseChannel);
5656
var latestVersion = webClient.DownloadString(latestReleaseVersionUrl).Replace("\n", string.Empty);
5757

58-
var latestReleaseDownloadUrl = string.Format(LatestReleaseDownloadUrlScheme, SnykOptions.CliDownloadUrl, "v"+latestVersion);
58+
var latestReleaseDownloadUrl = string.Format(LatestReleaseDownloadUrlScheme, SnykOptions.CliBaseDownloadURL, "v"+latestVersion);
5959

6060
return new LatestReleaseInfo
6161
{

Snyk.VisualStudio.Extension.2022/Language/CustomInitializationOptions.cs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
1-
using System.Collections.Generic;
1+
// ABOUTME: This file defines initialization options and configuration structures for the Snyk Language Server protocol
2+
// ABOUTME: It contains data models for folder configs, scan commands, and initialization parameters sent to the Language Server
3+
using System.Collections.Generic;
24
using Newtonsoft.Json;
35
using Newtonsoft.Json.Serialization;
46

57
namespace Snyk.VisualStudio.Extension.Language
68
{
9+
/// <summary>
10+
/// CamelCase naming strategy that preserves dictionary keys as-is.
11+
/// </summary>
12+
public class CamelCasePreserveDictionaryKeysNamingStrategy : CamelCaseNamingStrategy
13+
{
14+
public CamelCasePreserveDictionaryKeysNamingStrategy() : base(processDictionaryKeys: false, overrideSpecifiedNames: false)
15+
{
16+
}
17+
}
18+
719
[JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
820
public class SnykLsInitializationOptions
921
{
@@ -38,14 +50,18 @@ public class SnykLsInitializationOptions
3850
public string OutputFormat { get; set; }
3951
public string EnableDeltaFindings { get; set; }
4052
public List<FolderConfig> FolderConfigs { get; set; }
53+
public string CliBaseDownloadUrl { get; set; }
54+
public int? RiskScoreThreshold { get; set; }
4155
}
4256

57+
[JsonObject(NamingStrategyType = typeof(CamelCasePreserveDictionaryKeysNamingStrategy))]
4358
public class FolderConfig
4459
{
4560
public string FolderPath { get; set; }
4661
public string BaseBranch { get; set; }
4762
public List<string> LocalBranches { get; set; }
4863
public List<string> AdditionalParameters { get; set; }
64+
public string AdditionalEnv { get; set; }
4965
public string ReferenceFolderPath { get; set; }
5066
public Dictionary<string, ScanCommandConfig> ScanCommandConfig { get; set; }
5167
public string PreferredOrg { get; set; }
@@ -59,10 +75,13 @@ public void SetScanCommandConfig(Dictionary<string, ScanCommandConfig> scanComma
5975
}
6076
}
6177

78+
[JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
6279
public class ScanCommandConfig
6380
{
64-
// Add properties as needed based on the Java ScanCommandConfig class
65-
// This is a placeholder for now - can be extended with specific properties
81+
public string PreScanCommand { get; set; }
82+
public bool PreScanOnlyReferenceFolder { get; set; }
83+
public string PostScanCommand { get; set; }
84+
public bool PostScanOnlyReferenceFolder { get; set; }
6685
}
6786

6887
public class FolderConfigsParam

Snyk.VisualStudio.Extension.2022/Language/ILanguageClientManager.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,6 @@ public interface ILanguageClientManager
2929
void FireOnLanguageClientNotInitializedAsync();
3030
Task InvokeReportAnalyticsAsync(IAbstractAnalyticsEvent analyticsEvent, CancellationToken cancellationToken);
3131
Task<FeatureFlagResponse> InvokeGetFeatureFlagStatusAsync(string featureFlagName, CancellationToken cancellationToken);
32+
Task<string> GetConfigHtmlAsync(CancellationToken cancellationToken);
3233
}
3334
}

Snyk.VisualStudio.Extension.2022/Language/LsAnalysisResult.cs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public class AdditionalData
4040
public IList<int> Rows { get; set; }
4141
public bool IsSecurityType { get; set; }
4242
public int PriorityScore { get; set; }
43+
public int RiskScore { get; set; }
4344
public bool HasAIFix { get; set; }
4445
public IList<DataFlow> DataFlow { get; set; }
4546

@@ -139,6 +140,74 @@ public class Issue
139140
public IgnoreDetails IgnoreDetails { get; set; }
140141
public AdditionalData AdditionalData { get; set; }
141142

143+
/// <summary>
144+
/// Product type for this issue (code, oss, iac).
145+
/// Used to determine which score field to use for priority calculation.
146+
/// </summary>
147+
[JsonIgnore]
148+
public string Product { get; set; }
149+
150+
/// <summary>
151+
/// Gets the priority for sorting issues.
152+
/// Uses severity as primary key (Critical > High > Medium > Low),
153+
/// with score as tiebreaker within same severity level.
154+
/// </summary>
155+
[JsonIgnore]
156+
public int Priority
157+
{
158+
get
159+
{
160+
int severityPriority = GetSeverityPriority(Severity);
161+
int scoreComponent = GetScoreComponent();
162+
return severityPriority + scoreComponent;
163+
}
164+
}
165+
166+
/// <summary>
167+
/// Maps severity to millions (4M for critical, 3M for high, 2M for medium, 1M for low).
168+
/// Using millions ensures severity always takes precedence over score-based tiebreakers,
169+
/// since scores are typically in the hundreds/thousands range.
170+
/// </summary>
171+
private int GetSeverityPriority(string severity)
172+
{
173+
if (string.IsNullOrEmpty(severity))
174+
return 0;
175+
176+
// Case-insensitive comparison
177+
switch (severity.ToLowerInvariant())
178+
{
179+
case "critical":
180+
return 4_000_000;
181+
case "high":
182+
return 3_000_000;
183+
case "medium":
184+
return 2_000_000;
185+
case "low":
186+
return 1_000_000;
187+
default:
188+
return 0;
189+
}
190+
}
191+
192+
private int GetScoreComponent()
193+
{
194+
if (AdditionalData == null)
195+
return 0;
196+
197+
// OSS and IaC use riskScore, Code uses priorityScore
198+
if (Product == Snyk.VisualStudio.Extension.Product.Oss ||
199+
Product == Snyk.VisualStudio.Extension.Product.Iac)
200+
{
201+
return AdditionalData.RiskScore;
202+
}
203+
else if (Product == Snyk.VisualStudio.Extension.Product.Code)
204+
{
205+
return AdditionalData.PriorityScore;
206+
}
207+
208+
throw new InvalidOperationException($"Unknown product type: {Product}");
209+
}
210+
142211
public string GetDisplayTitle() => string.IsNullOrEmpty(this.Title) ? this.AdditionalData?.Message : this.Title;
143212

144213
public bool HasFix()

Snyk.VisualStudio.Extension.2022/Language/LsConstants.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
{
33
public static class LsConstants
44
{
5-
public const string ProtocolVersion = "21";
5+
public const string ProtocolVersion = "22";
66

77
// Notifications
88
public const string SnykHasAuthenticated = "$/snyk.hasAuthenticated";
@@ -21,6 +21,7 @@ public static class LsConstants
2121

2222
public const string SnykWorkspaceScan = "snyk.workspace.scan";
2323
public const string SnykWorkspaceFolderScan = "snyk.workspaceFolder.scan";
24+
public const string SnykWorkspaceConfiguration = "snyk.workspace.configuration";
2425
public const string SnykSastEnabled = "snyk.getSettingsSastEnabled";
2526
public const string SnykLogin = "snyk.login";
2627
public const string SnykLogout = "snyk.logout";

Snyk.VisualStudio.Extension.2022/Language/LsSettings.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using System.Linq;
1+
// ABOUTME: This file converts Visual Studio IDE settings into Language Server initialization options
2+
// ABOUTME: It bridges the gap between IDE configuration and the Snyk Language Server protocol format
3+
using System.Linq;
24
using Microsoft.VisualStudio.Shell;
35
using Snyk.VisualStudio.Extension.CLI;
46
using Snyk.VisualStudio.Extension.Service;
@@ -37,6 +39,14 @@ public SnykLsInitializationOptions GetInitializationOptions()
3739
OpenIssues = options.OpenIssuesEnabled,
3840
IgnoredIssues = options.IgnoredIssuesEnabled,
3941
},
42+
FilterSeverity = new FilterSeverityOptions
43+
{
44+
Critical = options.FilterCritical,
45+
High = options.FilterHigh,
46+
Medium = options.FilterMedium,
47+
Low = options.FilterLow,
48+
},
49+
// Use InternalAutoScan, as it starts off as false and will delay us telling LS about our actual AutoMode until we are actually ready to scan.
4050
ScanningMode = options.InternalAutoScan ? "auto" : "manual",
4151
#pragma warning disable VSTHRD104
4252
AdditionalParams = ThreadHelper.JoinableTaskFactory.Run(() => this.serviceProvider.SnykOptionsManager.GetAdditionalOptionsAsync()),
@@ -53,7 +63,10 @@ public SnykLsInitializationOptions GetInitializationOptions()
5363
OutputFormat = "plain",
5464
DeviceId = options.DeviceId,
5565
EnableDeltaFindings = options.EnableDeltaFindings.ToString().ToLower(),
56-
FolderConfigs = options.FolderConfigs
66+
FolderConfigs = options.FolderConfigs,
67+
CliBaseDownloadUrl = options.CliBaseDownloadURL,
68+
AdditionalEnv = options.AdditionalEnv,
69+
RiskScoreThreshold = options.RiskScoreThreshold
5770
};
5871
return initializationOptions;
5972
}

Snyk.VisualStudio.Extension.2022/Language/SnykLanguageClient.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,19 @@ public async Task InvokeReportAnalyticsAsync(IAbstractAnalyticsEvent analyticsEv
403403
await InvokeWithParametersAsync<object>(LsConstants.WorkspaceExecuteCommand, param, cancellationToken);
404404
}
405405

406+
/// <summary>
407+
/// Retrieves HTML configuration UI from the Language Server.
408+
/// Returns null if LS is not available or command fails.
409+
/// </summary>
410+
public async Task<string> GetConfigHtmlAsync(CancellationToken cancellationToken)
411+
{
412+
var param = new LSP.ExecuteCommandParams
413+
{
414+
Command = LsConstants.SnykWorkspaceConfiguration
415+
};
416+
return await InvokeWithParametersAsync<string>(LsConstants.WorkspaceExecuteCommand, param, cancellationToken);
417+
}
418+
406419
public async Task<object> DidChangeConfigurationAsync(CancellationToken cancellationToken)
407420
{
408421
if (!IsReady) return default;

Snyk.VisualStudio.Extension.2022/Language/SnykLanguageClientCustomTarget.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using System;
1+
// ABOUTME: This file implements custom JSON-RPC message handlers for the Snyk Language Client
2+
// ABOUTME: It processes diagnostics, authentication, and scan results from the Language Server
3+
using System;
24
using System.Collections.Concurrent;
35
using System.Collections.Generic;
46
using System.Linq;
@@ -10,6 +12,7 @@
1012
using Snyk.VisualStudio.Extension.Authentication;
1113
using Snyk.VisualStudio.Extension.Extension;
1214
using Snyk.VisualStudio.Extension.Service;
15+
using Snyk.VisualStudio.Extension.Settings;
1316

1417
namespace Snyk.VisualStudio.Extension.Language
1518
{
@@ -186,6 +189,15 @@ public async Task OnFolderConfig(JToken arg)
186189

187190
serviceProvider.SnykOptionsManager.Save(serviceProvider.Options, false);
188191

192+
// Trigger first scan after folder config is received.
193+
//
194+
// AutoScan vs InternalAutoScan vs ScanningMode:
195+
// - AutoScan: Persisted user preference ("I want auto-scanning")
196+
// - InternalAutoScan: Runtime flag, starts false each session to prevent scanning until we are actually ready.
197+
// This controls ScanningMode, the string sent to LS ("auto"/"manual").
198+
//
199+
// We always start with InternalAutoScan=false and therefore ScanningMode="manual" during LS initialization to prevent the LS from auto-scanning before we are fully ready.
200+
// Now folder configs have arrived, we can set InternalAutoScan=AutoScan and trigger the first scan if necessary.
189201
if (serviceProvider.Options.AutoScan)
190202
{
191203
var isFolderTrusted = await this.serviceProvider.TasksService.IsFolderTrustedAsync();
@@ -195,7 +207,10 @@ public async Task OnFolderConfig(JToken arg)
195207

196208
if (!serviceProvider.Options.InternalAutoScan)
197209
{
210+
// AutoScan is enabled but we haven't triggered the first scan yet (InternalAutoScan is still false).
211+
// So set InternalAutoScan=true, update LS with the true ScanningMode ("auto") and trigger the first scan.
198212
serviceProvider.Options.InternalAutoScan = true;
213+
await serviceProvider.LanguageClientManager.DidChangeConfigurationAsync(SnykVSPackage.Instance.DisposalToken);
199214
serviceProvider.TasksService.ScanAsync().FireAndForget();
200215
}
201216
}
@@ -265,6 +280,9 @@ public async Task OnHasAuthenticated(JToken arg)
265280

266281
await serviceProvider.GeneralOptionsDialogPage.HandleAuthenticationSuccess(token, apiUrl);
267282

283+
// Notify HTML settings window of auth token change
284+
HtmlSettingsWindow.Instance?.UpdateAuthToken(token);
285+
268286
if (!serviceProvider.Options.ApiToken.IsValid())
269287
return;
270288

0 commit comments

Comments
 (0)