Skip to content

Commit 5eec5ce

Browse files
committed
refactor(language-server): derive folder configs from configuration [IDE-1638]
- resolveFolderConfigsForServerSettings uses getFolderConfigs() when non-empty, else synthesize from workspace folders - Remove ReceivedFolderConfigsFromLs, receivedFolderConfigsFromLsState, and SNYK_FOLDERCONFIG registration - Simplify fromConfiguration(configuration, user, workspace?) call sites - Drop obsolete handleOrgSettingsFromFolderConfigs tests; refresh init options tests - Update CHANGELOG Unreleased Made-with: Cursor
1 parent 96c5867 commit 5eec5ce

13 files changed

Lines changed: 34 additions & 387 deletions

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ Please see https://github.com/snyk/vscode-extension/releases for a detailed chan
44

55
## Unreleased
66

7-
- Synthesize per-folder org rows from workspace folders for outbound LSP config when LS has not yet sent `$/snyk.folderConfigs`, so `workspace/didChangeConfiguration` includes non-empty `folderConfigs` after workspace settings changes.
7+
- Outbound LSP `folderConfigs` use `configuration.getFolderConfigs()` when non-empty; otherwise synthesize per-folder rows from workspace folders when the workspace has folders. Remove the `ReceivedFolderConfigsFromLs` gate, `$/snyk.folderConfigs` notification registration, and `SNYK_FOLDERCONFIG` constant.
88
- When `snyk.advanced.cliReleaseChannel` changes, stop the language server and re-run CLI download only if automatic dependency management is enabled (manual CLI installs are unaffected).

docs/configuration-gaf-ls-ide-flow.md

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
# Configuration: GAF → snyk-ls → IDE
22

3-
High-level flow for how settings are stored in GAF, resolved in **snyk-ls**, pushed to IDEs, and how **merges** relate to the **VS Code** extension (`mergeInboundLspConfiguration`, IDE-1638).
3+
High-level flow for how settings are stored in GAF, resolved in **snyk-ls**, pushed to IDEs, and how **merges** relate to the **VS Code** extension (`mergeInboundLspConfiguration`, IDE-1638). The language server delivers effective config (including per-folder rows) on **`$/snyk.configuration`** as **`LspConfigurationParam`**; the VS Code client does **not** subscribe to the legacy **`$/snyk.folderConfigs`** notification.
44

55
## Diagram
66

77
```mermaid
8+
%% snyk-ls → IDE: $/snyk.configuration only; folder rows in param.folderConfigs (no separate $/snyk.folderConfigs).
89
flowchart TB
910
subgraph external["External & inputs"]
1011
LDX["LDX-Sync API<br/>(remote org / machine / folder)"]
@@ -22,14 +23,13 @@ flowchart TB
2223
end
2324
2425
subgraph outbound["snyk-ls — outbound to IDE"]
25-
LCP["Assemble LspConfigurationParam<br/>map[string]ConfigSetting + folderConfigs[]"]
26-
NCFG["$/snyk.configuration"]
27-
NFOLD["$/snyk.folderConfigs<br/>(folder metadata / org — different contract)"]
26+
LCP["Assemble LspConfigurationParam<br/>global settings + folderConfigs[]<br/>(per-folder ConfigSetting maps)"]
27+
NCFG["JSON-RPC notify:<br/>$/snyk.configuration only<br/>(folder rows inside payload)"]
2828
end
2929
3030
subgraph ide["IDE"]
3131
HNDL["LanguageClient<br/>onNotification"]
32-
MERGE_UI["mergeInboundLspConfiguration<br/>(merge #2 — display only)"]
32+
MERGE_UI["mergeInboundLspConfiguration<br/>(merge #2 — non-authoritative:<br/>global+folder overlay for UI & persistence)"]
3333
VIEW["Webview / settings UI<br/>locks, source, values"]
3434
DCC["workspace/didChangeConfiguration<br/>(partial deltas)"]
3535
end
@@ -41,9 +41,7 @@ flowchart TB
4141
PFX --> CR
4242
CR --> LCP
4343
LCP --> NCFG
44-
STORE --> NFOLD
4544
NCFG --> HNDL
46-
NFOLD --> HNDL
4745
HNDL --> MERGE_UI
4846
MERGE_UI --> VIEW
4947
VIEW --> DCC
@@ -58,12 +56,13 @@ Source (editable): [`docs/diagrams/configuration-gaf-ls-ide-flow.mmd`](diagrams/
5856
|---|----------|----------------|
5957
| **1** | **snyk-ls / GAF / `ConfigResolver`** | Prefix layers (`user:global`, `user:folder`, `remote:*`, defaults) → **authoritative** effective value per setting, folder, and org. This is the real precedence chain (see snyk-ls `docs/configuration.md` when present). |
6058
| **2** | **LS outbound** | Builds **`LspConfigurationParam`**: global `settings` map + per-folder `folderConfigs[].settings` with **`ConfigSetting`** (`value`, `source`, `originScope`, `isLocked`). Already reflects resolver output. |
61-
| **3** | **IDE (VS Code) — `mergeInboundLspConfiguration`** | **Presentation merge only**: spreads global map into each folder’s effective map so the UI can read one object per folder. Does **not** replace LS resolution; must use **same flag keys** as LS (pflag names). |
62-
| **** | **`$/snyk.folderConfigs` vs `$/snyk.configuration`** | Not the same merge: **folderConfigs notification** carries folder/org **metadata** and related fields; **configuration notification** carries the **map-based effective config** (protocol v25+). Both can land in the IDE; keep contracts separate. |
59+
| **3** | **IDE (VS Code) — `mergeInboundLspConfiguration`** | **Not a second `ConfigResolver`**: shallow **`global ∪ folder`** overlay of one inbound payload into **`MergedLspConfigurationView`** for webview and persistence mappers. Does **not** re-run GAF precedence or override LS authority; pflag keys match LS. |
60+
| **4** | **VS Code — outbound `folderConfigs` (init + `workspace/didChangeConfiguration`)** | **`LanguageServerSettings.resolveFolderConfigsForServerSettings`**: use **`IConfiguration.getFolderConfigs()`** when non-empty; if empty and the workspace has folders, **`synthesizeFolderConfigsFromWorkspace`**. Feeds **`LanguageServerSettings.fromConfiguration`****`serverSettingsToLspConfigurationParam`**. |
61+
| **** | **`$/snyk.configuration` (inbound)** | Carries **`LspConfigurationParam`**: global **`settings`** plus optional **`folderConfigs[]`** (per-folder paths and **`settings`** maps). VS Code merges with **`mergeInboundLspConfiguration`** for **UI + persistence** (see **#3**). **VS Code does not register** the legacy **`$/snyk.folderConfigs`** notification. |
6362

6463
## Round trip
6564

66-
- **LS → IDE:** `$/snyk.configuration` pushes effective state (and locks) for UI.
65+
- **LS → IDE:** `$/snyk.configuration` pushes effective state (and locks); **`mergeInboundLspConfiguration`** shapes it for **UI and persistence** (still **not** authoritative vs merge #1).
6766
- **LS → `settings.json` (VS Code, optional):** `ConfigurationPersistenceService.persistInboundLspConfiguration` maps the global snapshot into VS Code settings. For pflags the user has explicitly marked (`ExplicitLspConfigurationChangeTracker`, global memento) whose LS value **differs** from the current IDE value, the inbound value is **not** applied; **`LanguageServer.reconcileLanguageServerWithCurrentConfiguration`** then sends structured `workspace/didChangeConfiguration` so the language server matches the IDE.
6867
- **IDE → LS:** `workspace/didChangeConfiguration` with **`LspConfigurationParam`-shaped** payload; only **changed** keys, `value: null` to clear override (per protocol).
6968

@@ -75,9 +74,9 @@ The VS Code **`LanguageClient`** option **`synchronize.configurationSection`** (
7574

7675
Therefore the extension **does not** set **`configurationSection`** for Snyk. Outbound config updates must be sent with an **explicit** `workspace/didChangeConfiguration` (or equivalent) carrying **`{ settings: <LspConfigurationParam> }`** per the requirements below. Initialization and **`workspace/configuration`** middleware continue to supply **flat** `ServerSettings` where the protocol expects the legacy/IDE shape (e.g. pull / init).
7776

78-
After the language client starts, **`LanguageServer`** registers **`workspace.onDidChangeConfiguration`**, reacts only when **`affectsConfiguration('snyk')`**, debounces (same interval as other LS debounces), builds **`LspConfigurationParam`** via **`LanguageServerSettings.fromConfiguration`** + **`serverSettingsToLspConfigurationParam`**, and calls **`sendNotification('workspace/didChangeConfiguration', { settings })`**. While **`foldersBeingUpdatedByLS`** is non-empty (LS applying org from **`$/snyk.folderConfigs`**), outbound pushes are skipped to avoid feedback loops.
77+
After the language client starts, **`LanguageServer`** registers **`workspace.onDidChangeConfiguration`**, reacts when **`affectsConfiguration('snyk')`** or **`affectsConfiguration('http')`** (proxy/TLS), debounces (same interval as other LS debounces), builds **`LspConfigurationParam`** via **`LanguageServerSettings.fromConfiguration`** (which resolves **`folderConfigs`** per **#4** above) + **`serverSettingsToLspConfigurationParam`**, and calls **`sendNotification('workspace/didChangeConfiguration', { settings })`**. While **`foldersBeingUpdatedByLS`** is non-empty (IDE is applying LS-driven org updates to folder settings from inbound configuration), outbound pushes are skipped to avoid feedback loops.
7978

80-
On startup, after **`client.start()`** and **`registerStructuredConfigurationChangeListener`**, the extension sends **one** structured **`workspace/didChangeConfiguration`** from **`client.onReady`** (deferred with **`setImmediate`** so workspace folders are registered with the server first), then **cancels** the pending debounced handler so a duplicate send from startup config churn is avoided. When the first **`$/snyk.folderConfigs`** batch is processed, **`folderConfigs`** become eligible in **`LanguageServerSettings`** — the extension sends **one** more outbound push so the payload includes per-folder settings (aligned with IntelliJ `addContentRoots``updateConfiguration`).
79+
On startup, after **`client.start()`** and **`registerStructuredConfigurationChangeListener`**, the extension sends **one** structured **`workspace/didChangeConfiguration`** from **`client.onReady`** (deferred with **`setImmediate`** so workspace folders are registered with the server first), then **cancels** the pending debounced handler so a duplicate send from startup config churn is avoided. Inbound **`$/snyk.configuration`** may update persisted folder rows (e.g. via **`mergeFolderConfigsWithInboundLspView`** / persistence); **`folderConfigs`** in every outbound payload still follow **`resolveFolderConfigsForServerSettings`** from current **`IConfiguration`**.
8180

8281
### IDE → LS outbound requirements (`LspConfigurationParam`)
8382

docs/diagrams/configuration-gaf-ls-ide-flow.mmd

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
%% Configuration flow: GAF → snyk-ls → IDE (see docs/configuration-gaf-ls-ide-flow.md)
2+
%% snyk-ls notifies the IDE with $/snyk.configuration only; per-folder data is param.folderConfigs (no separate $/snyk.folderConfigs RPC).
23
flowchart TB
34
subgraph external["External & inputs"]
45
LDX["LDX-Sync API<br/>(remote org / machine / folder)"]
@@ -16,14 +17,13 @@ flowchart TB
1617
end
1718

1819
subgraph outbound["snyk-ls — outbound to IDE"]
19-
LCP["Assemble LspConfigurationParam<br/>map[string]ConfigSetting + folderConfigs[]"]
20-
NCFG["$/snyk.configuration"]
21-
NFOLD["$/snyk.folderConfigs<br/>(folder metadata / org — different contract)"]
20+
LCP["Assemble LspConfigurationParam<br/>global settings + folderConfigs[]<br/>(per-folder ConfigSetting maps)"]
21+
NCFG["JSON-RPC notify:<br/>$/snyk.configuration only<br/>(folder rows inside payload)"]
2222
end
2323

2424
subgraph ide["IDE"]
2525
HNDL["LanguageClient<br/>onNotification"]
26-
MERGE_UI["mergeInboundLspConfiguration<br/>(merge #2 — display only)"]
26+
MERGE_UI["mergeInboundLspConfiguration<br/>(merge #2 — non-authoritative:<br/>global+folder overlay for UI & persistence)"]
2727
VIEW["Webview / settings UI<br/>locks, source, values"]
2828
DCC["workspace/didChangeConfiguration<br/>(partial deltas)"]
2929
end
@@ -35,9 +35,7 @@ flowchart TB
3535
PFX --> CR
3636
CR --> LCP
3737
LCP --> NCFG
38-
STORE --> NFOLD
3938
NCFG --> HNDL
40-
NFOLD --> HNDL
4139
HNDL --> MERGE_UI
4240
MERGE_UI --> VIEW
4341
VIEW --> DCC

src/snyk/common/constants/languageServer.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ export const DID_CHANGE_CONFIGURATION_METHOD = 'workspace/didChangeConfiguration
1111
export const SNYK_HAS_AUTHENTICATED = '$/snyk.hasAuthenticated';
1212
export const SNYK_ADD_TRUSTED_FOLDERS = '$/snyk.addTrustedFolders';
1313
export const SNYK_SCAN = '$/snyk.scan';
14-
export const SNYK_FOLDERCONFIG = '$/snyk.folderConfigs';
1514
export const SNYK_SCANSUMMARY = '$/snyk.scanSummary';
1615
export const SNYK_REGISTER_MCP = '$/snyk.registerMcp';
1716
export const SNYK_TREEVIEW = '$/snyk.treeView';

src/snyk/common/languageServer/configurationChangeToExplicitPflags.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ import {
2424
import { PFLAG } from './serverSettingsToLspConfigurationParam';
2525
import type { IExplicitLspConfigurationChangeTracker } from './explicitLspConfigurationChangeTracker';
2626

27+
/**
28+
* Maps VS Code setting keys (`configKey`) to LSP **pflag** keys: snake_case names such as
29+
* `api_endpoint` and `organization` that appear in `LspConfigurationParam` when sending config to
30+
* Snyk Language Server. Key map only; does not supply values.
31+
*/
2732
const CONFIG_KEY_TO_PFLAG: Array<{ configKey: string; pflags: string[] }> = [
2833
{ configKey: ADVANCED_CUSTOM_ENDPOINT, pflags: [PFLAG.apiEndpoint] },
2934
{ configKey: ADVANCED_ORGANIZATION, pflags: [PFLAG.organization] },
@@ -49,7 +54,7 @@ const CONFIG_KEY_TO_PFLAG: Array<{ configKey: string; pflags: string[] }> = [
4954

5055
/**
5156
* When native VS Code configuration changes, mark matching pflag keys so outbound
52-
* `workspace/didChangeConfiguration` sets `ConfigSetting.changed` for user edits (IDE-1639 parity).
57+
* `workspace/didChangeConfiguration` sets `ConfigSetting.changed` for user edits.
5358
*/
5459
export function markExplicitPflagsFromConfigurationChangeEvent(
5560
e: ConfigurationChangeEvent,

0 commit comments

Comments
 (0)