feat(IDE-1652): introduce LsSettingsRegistry, protocol message objects (1/3)#394
feat(IDE-1652): introduce LsSettingsRegistry, protocol message objects (1/3)#394nick-y-snyk wants to merge 5 commits into
Conversation
…s, and ACTIVATE_SNYK_SECRETS constant Adds the foundational types needed for the upcoming LS settings rewrite: - LsKey enum: typed keys for all LS settings fields - LsFolderSettingsKeys: constants for folder-scoped LS config fields - LsSettingsRegistry: single source of truth mapping LsKey entries to Preferences keys, defaults, and change-tracking metadata - ConfigSetting: wire-format value object for a single LS setting - LspFolderConfig: wire-format value object for per-folder LS config - LspConfigurationParam: top-level outbound LS config payload - Preferences.ACTIVATE_SNYK_SECRETS: constant required by LsSettingsRegistry - LsBinaries: minor update All new classes are unreferenced by existing code — safe to land standalone. Tests cover each value object and serialisation round-trips.
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
This comment has been minimized.
This comment has been minimized.
…cture Deletes all code made dead by the new LsSettingsRegistry / FolderConfigSettings / LspConfigurationParam architecture landed in PRs #394, #395, #396: Native settings UI (no callers after HTML-only migration): - PreferencesPage, TokenFieldEditor, NativeProjectPropertyPage - SnykPreferencePageFactoryTest, ProjectPropertyPageFactoryTest Legacy LS protocol POJOs (replaced by LspConfigurationParam / ConfigSetting / LspFolderConfig / FolderConfigSettings): - Settings, FolderConfig, FolderConfigsParam, FolderConfigs, IdeConfigData - FolderConfigTest
bastiandoetsch
left a comment
There was a problem hiding this comment.
Automated review findings — see inline comments. — AI review
| // formDeserializer: form sends "auto"/"manual", pref stores "true"/"false". | ||
| entries.put(LsKey.SCANNING_MODE, new Entry(LsKey.SCANNING_MODE, Preferences.SCANNING_MODE_AUTOMATIC, "true", | ||
| v -> Boolean.parseBoolean(v), | ||
| value -> String.valueOf("automatic".equals(String.valueOf(value))), |
There was a problem hiding this comment.
[Critical] SCANNING_MODE inbound deserializer always stores "false" regardless of the LS value. The LS sends a JSON boolean for scan_automatic; String.valueOf(Boolean.TRUE) is "true", not "automatic", so "automatic".equals("true") is always false. Every inbound $/snyk.configuration push silently resets automatic scanning to manual.
Fix: check for both the boolean true and the legacy string:
value -> String.valueOf(Boolean.TRUE.equals(value) || "automatic".equals(String.valueOf(value)))Add a round-trip test: serialize with outboundDefault="true" and verify inbound restores "true".
— AI review
There was a problem hiding this comment.
Fixed: inbound deserializer now uses Boolean.TRUE.equals(value) — handles LS boolean directly, dropped legacy string fallback.
— Claude Code
| * Used by HTMLSettingsPreferencePage for form-specific deserialization. | ||
| * null = use the standard applyFormValue path (raw nodeToString). | ||
| */ | ||
| public final Function<com.fasterxml.jackson.databind.JsonNode, String> formDeserializer; |
There was a problem hiding this comment.
[Should Fix] Entry.formDeserializer is typed as Function<com.fasterxml.jackson.databind.JsonNode, String> — a fully-qualified Jackson reference with no import — encoding an HTMLSettingsPreferencePage (UI layer) parsing concern directly into the io.snyk.languageserver (infrastructure) package. PR 3/3 is described as migrating off the HTML settings form; these two fields (formDeserializer, useInFallbackForm) become dead weight in the foundational Entry type after that.
Fix: remove formDeserializer and useInFallbackForm from Entry; move the form-parsing lambdas into HTMLSettingsPreferencePage as a local Map<LsKey, Function<JsonNode, String>>. This keeps LsSettingsRegistry free of UI-layer and Jackson coupling.
— AI review
There was a problem hiding this comment.
HTML settings page is staying long-term — formDeserializer and useInFallbackForm remain relevant. Fully-qualified Jackson name is valid Java without import.
— Claude Code
| * Additional pref keys whose changed-state ORed with prefKey's to compute the changed flag. | ||
| * Used for severity filters that share a changed flag across 4 keys. | ||
| */ | ||
| public final String[] additionalChangedPrefKeys; |
There was a problem hiding this comment.
[Should Fix] additionalChangedPrefKeys is a public final String[] — a mutable array directly exposed on a public field. Any caller can overwrite array elements, violating the registry's single-source-of-truth guarantee.
Fix: change the field to public final List<String> additionalChangedPrefKeys (or keep String[] with a defensive copy in the getter). Update the factory methods and consumers accordingly.
— AI review
There was a problem hiding this comment.
Agreed on the principle, but String[] is only assigned at construction and never mutated in practice. Deferring — cosmetic-only change with no behavioral risk.
— Claude Code
| public static final String ACTIVATE_SNYK_CODE_SECURITY = "ACTIVATE_SNYK_CODE_SECURITY"; | ||
| public static final String ACTIVATE_SNYK_OPEN_SOURCE = "ACTIVATE_SNYK_OPEN_SOURCE"; | ||
| public static final String ACTIVATE_SNYK_IAC = "ACTIVATE_SNYK_IAC"; | ||
| public static final String ACTIVATE_SNYK_SECRETS = "ACTIVATE_SNYK_SECRETS"; |
There was a problem hiding this comment.
[Should Fix] ACTIVATE_SNYK_SECRETS has no insecureStore.setDefault() call. Every other ACTIVATE_* flag (ACTIVATE_SNYK_CODE_SECURITY, ACTIVATE_SNYK_OPEN_SOURCE, ACTIVATE_SNYK_IAC) has a corresponding setDefault at lines 125–127. Without this, getDefaultString(ACTIVATE_SNYK_SECRETS) returns an empty string; any caller using a true fallback in getBooleanPref will silently return true instead of the intended false.
Fix: add to the constructor defaults block:
insecureStore.setDefault(ACTIVATE_SNYK_SECRETS, FALSE);Also add ACTIVATE_SNYK_SECRETS to migratePreferences() if it can be stored via store().
— AI review
There was a problem hiding this comment.
PR #395 replaces all individual setDefault calls with a registry-driven loop — ACTIVATE_SNYK_SECRETS picks up its false default automatically from Entry.outboundDefault.
— Claude Code
| INSECURE("proxy_insecure"), | ||
| ADDITIONAL_PARAMS("additional_parameters"), | ||
| SCANNING_MODE("scan_automatic"), | ||
| ADDITIONAL_ENV("additional_environment"), |
There was a problem hiding this comment.
[Should Fix] ADDITIONAL_ENV, ENABLE_TELEMETRY, and DEVICE_ID are declared in the enum but have no entry in LsSettingsRegistry.ENTRIES and no comment explaining their routing. ADDITIONAL_ENV maps to additional_environment, which the existing LsConfigurationUpdater actively sends; when PR 2/3 migrates callers to the registry, this setting will be silently dropped from all outbound LS configuration pushes.
Fix: either add ADDITIONAL_ENV to ENTRIES, or add inline Javadoc on each absent member explaining the alternative routing path (e.g., env-var path for ENABLE_TELEMETRY, top-level InitializationOptions field for DEVICE_ID, runtime env for ADDITIONAL_ENV).
— AI review
There was a problem hiding this comment.
DEVICE_ID is sent as a top-level LspConfigurationParam field in PR #395, not via the settings map. ENABLE_TELEMETRY removal is intentional — superseded by SEND_ERROR_REPORTS. ADDITIONAL_ENV omission is intentional and tracked in Jira for a follow-up PR adding global+folder-scoped env support.
— Claude Code
| return false; | ||
| } | ||
| if (a instanceof Number && b instanceof Number) { | ||
| return ((Number) a).doubleValue() == ((Number) b).doubleValue(); |
There was a problem hiding this comment.
[Should Fix] ((Number) a).doubleValue() == ((Number) b).doubleValue() returns false when either value is Double.NaN (IEEE 754: NaN != NaN). If the LS sends NaN for a numeric setting, withSettingIfChanged generates a new copy and marks it changed on every call, flooding the LS with redundant configuration-change events.
Fix:
return Double.compare(((Number) a).doubleValue(), ((Number) b).doubleValue()) == 0;Double.compare treats NaN as equal to NaN and handles all edge cases correctly.
— AI review
There was a problem hiding this comment.
Fixed: use Double.compare() to handle NaN correctly.
— Claude Code
| /** | ||
| * Ordered outbound entries keyed by LsKey. | ||
| * Iterated by LsConfigurationUpdater.buildConfigurationParam(). | ||
| * EnumMap preserves insertion order. |
There was a problem hiding this comment.
[Suggestion] The comment "EnumMap preserves insertion order" is incorrect. EnumMap preserves enum declaration order (the order of LsKey members), not insertion order. If LsKey members are reordered for any reason, the outbound serialization sequence changes silently.
Fix: correct to: // EnumMap preserves LsKey declaration order; reordering LsKey members changes the outbound config sequence.
— AI review
There was a problem hiding this comment.
Fixed: corrected comment — EnumMap preserves declaration order, not insertion order.
— Claude Code
| copy.settings = new HashMap<>(); | ||
|
|
||
| if (this.settings != null) { | ||
| for (Map.Entry<String, ConfigSetting> entry : this.settings.entrySet()) { |
There was a problem hiding this comment.
[Suggestion] The entrySet() loop copies the map manually where the HashMap copy constructor suffices.
Fix: replace lines 32–37 with:
copy.settings = new HashMap<>(this.settings);— AI review
There was a problem hiding this comment.
Fixed: use HashMap copy constructor.
— Claude Code
|
/describe |
|
PR Description updated to latest commit (e7da17c) |
|
/describe |
|
PR Description updated to latest commit (e7da17c) |
|
/describe |
|
PR Description updated to latest commit (e7da17c) |
LS sends scan_automatic as a JSON boolean. The previous deserializer checked "automatic".equals(String.valueOf(value)) which always evaluates to false when value is Boolean.TRUE, silently resetting scanning to manual on every inbound $/snyk.configuration push.
This comment has been minimized.
This comment has been minimized.
…cture Deletes all code made dead by the new LsSettingsRegistry / FolderConfigSettings / LspConfigurationParam architecture landed in PRs #394, #395, #396: Native settings UI (no callers after HTML-only migration): - PreferencesPage, TokenFieldEditor, NativeProjectPropertyPage - SnykPreferencePageFactoryTest, ProjectPropertyPageFactoryTest Legacy LS protocol POJOs (replaced by LspConfigurationParam / ConfigSetting / LspFolderConfig / FolderConfigSettings): - Settings, FolderConfig, FolderConfigsParam, FolderConfigs, IdeConfigData - FolderConfigTest
PR Reviewer Guide 🔍
|
acke
left a comment
There was a problem hiding this comment.
Re-review (review-orchestrator)
Verdict: APPROVE WITH NITS
Verification of prior comments
| # | Severity | File:Line | Status |
|---|---|---|---|
| 1 | Critical | LsSettingsRegistry.java:126 SCANNING_MODE inbound |
✅ Fixed (see new pass C1 — legacy string payloads now silently flip to manual) |
| 2 | Should-Fix | LsSettingsRegistry.java:44 formDeserializer coupling |
⏸️ Declined (HTML page stays — accepted) |
| 3 | Should-Fix | LsSettingsRegistry.java:38 mutable String[] |
⏸️ Deferred (cosmetic) |
| 4 | Should-Fix | Preferences.java:49 ACTIVATE_SNYK_SECRETS setDefault |
⏸️ Deferred to #395 (will verify there) |
| 5 | Should-Fix | LsKey.java:14/16/33 undocumented absent members |
❌ Partially — see new pass C1 |
| 6 | Should-Fix | LspFolderConfig.java:70 NaN comparison |
✅ Fixed |
| 7 | Suggestion | LsSettingsRegistry.java:100 EnumMap-order comment |
✅ Fixed |
| 8 | Suggestion | LspFolderConfig.java:35 HashMap copy constructor |
✅ Fixed |
New pass findings (4)
- 🟡 Medium
LsKey.java:14,16,33— absent enum members lack Javadoc explaining routing - 🟡 Medium
LsFolderSettingsKeys.java:11—ADDITIONAL_PARAMETERSwire string duplicatesLsKey.ADDITIONAL_PARAMS.key - 🟡 Medium
LsSettingsRegistry.java:124— fixed SCANNING_MODE deserializer silently treats non-boolean-true (incl.null, string"true") as"false" - 🔵 Low
LsFolderSettingsKeys.java:12—ADDITIONAL_ENVwire string differs fromLsKey.ADDITIONAL_ENV(intentional; needs comment)
See inline comments for full block-formatted findings.
Cross-PR: please verify in #395 that the registry-driven default loop actually picks up ACTIVATE_SNYK_SECRETS. If not, please add insecureStore.setDefault(ACTIVATE_SNYK_SECRETS, FALSE) here before merge.
Manifest: .claude/reviews/394/TOOLS_USED.md
— AI review comment
| INSECURE("proxy_insecure"), | ||
| ADDITIONAL_PARAMS("additional_parameters"), | ||
| SCANNING_MODE("scan_automatic"), | ||
| ADDITIONAL_ENV("additional_environment"), |
There was a problem hiding this comment.
🟡 Medium
[LsKey.java:14,16,33] ADDITIONAL_ENV, ENABLE_TELEMETRY, and DEVICE_ID remain in the enum without inline Javadoc explaining why they have no ENTRIES row. The author's rationale (DEVICE_ID is top-level on LspConfigurationParam; ENABLE_TELEMETRY is superseded by SEND_ERROR_REPORTS; ADDITIONAL_ENV deferred) was given in the prior thread but lives only there — a future reader of LsKey.java or LsSettingsRegistry will see the missing entries and assume it's a bug.
Fix:
Add a one-line Javadoc per absent member, e.g.:
/** Sent as top-level field on LspConfigurationParam — not via settings map. */
DEVICE_ID("device_id"),
/** Superseded by SEND_ERROR_REPORTS; kept for backward compatibility with stored prefs. */
ENABLE_TELEMETRY("enableTelemetry"),
/** Folder-scoped variant uses LsFolderSettingsKeys.ADDITIONAL_ENV; global routing tracked in IDE-XXXX. */
ADDITIONAL_ENV("additional_environment"),— AI review comment
|
|
||
| public static final String BASE_BRANCH = "base_branch"; | ||
| public static final String PREFERRED_ORG = "preferred_org"; | ||
| public static final String ADDITIONAL_PARAMETERS = "additional_parameters"; |
There was a problem hiding this comment.
🟡 Medium
[LsFolderSettingsKeys.java:11] LsFolderSettingsKeys.ADDITIONAL_PARAMETERS = "additional_parameters" duplicates LsKey.ADDITIONAL_PARAMS.key = "additional_parameters". Two separate source-of-truth constants for the same wire string — if one is renamed/changed and the other isn't, the outbound payload silently desyncs between global and folder scope.
Fix:
Make the folder-scope constant an alias:
public static final String ADDITIONAL_PARAMETERS = LsKey.ADDITIONAL_PARAMS.key;Or add a unit test asserting the two strings stay equal so a future rename fails CI.
— AI review comment
| entries.put(LsKey.ACTIVATE_SNYK_SECRETS, Entry.bool(LsKey.ACTIVATE_SNYK_SECRETS, Preferences.ACTIVATE_SNYK_SECRETS, false)); | ||
| entries.put(LsKey.SCANNING_MODE, new Entry(LsKey.SCANNING_MODE, Preferences.SCANNING_MODE_AUTOMATIC, "true", | ||
| v -> Boolean.parseBoolean(v), | ||
| value -> String.valueOf(Boolean.TRUE.equals(value)), |
There was a problem hiding this comment.
🟡 Medium
[LsSettingsRegistry.java:124] The prior Critical fix dropped the legacy "automatic"-string fallback (value -> String.valueOf(Boolean.TRUE.equals(value))). Now any non-Boolean.TRUE payload — including string "true", or null — silently maps to "false" and flips the user's pref to manual. Acceptable only if the LS contract guarantees scan_automatic is always a JSON boolean.
Fix:
Either (a) tighten to handle both shapes defensively:
value -> {
if (Boolean.TRUE.equals(value)) return "true";
if (Boolean.FALSE.equals(value)) return "false";
String s = String.valueOf(value);
if ("true".equalsIgnoreCase(s) || "automatic".equals(s)) return "true";
if ("false".equalsIgnoreCase(s) || "manual".equals(s)) return "false";
return Preferences.getInstance().getPref(Preferences.SCANNING_MODE_AUTOMATIC);
}Or (b) leave as-is and add a unit test pinning the assumption (Boolean.FALSE → "false", string "true" → "false", null → "false") so the regression is intentional and visible.
— AI review comment
| public static final String BASE_BRANCH = "base_branch"; | ||
| public static final String PREFERRED_ORG = "preferred_org"; | ||
| public static final String ADDITIONAL_PARAMETERS = "additional_parameters"; | ||
| public static final String ADDITIONAL_ENV = "additional_env"; |
There was a problem hiding this comment.
🔵 Low
[LsFolderSettingsKeys.java:12] LsFolderSettingsKeys.ADDITIONAL_ENV = "additional_env" vs LsKey.ADDITIONAL_ENV("additional_environment") — different wire strings for folder-scope vs global. Likely intentional but easy to confuse. A one-line comment would prevent future "fix" PRs that try to unify them.
Fix:
/** Folder-scoped env (global counterpart uses "additional_environment" via LsKey.ADDITIONAL_ENV). */
public static final String ADDITIONAL_ENV = "additional_env";— AI review comment
User description
Summary
First of three PRs splitting
feat/IDE-1652. Introduces foundational types required by the upcoming LS settings rewrite — all new classes are unreferenced by existing code, so this is safe to land standalone.LsKey— typed enum of all LS settings fieldsLsFolderSettingsKeys— constants for folder-scoped LS config fieldsLsSettingsRegistry— single source of truth mapping eachLsKeyto itsPreferenceskey, default value, and change-tracking metadataConfigSetting— wire-format value object for a single LS setting ({ value, changed })LspFolderConfig— wire-format value object for per-folder LS configLspConfigurationParam— top-level outbound LS config payload (settings,folderConfigs,integrationName,requiredProtocolVersion,trustedFolders)Preferences.ACTIVATE_SNYK_SECRETS— constant needed byLsSettingsRegistry(additive, one line)LsBinaries— minor updateTests cover each value object and serialisation round-trips via Gson.
PR sequence
$/snyk.configuration, outboundLspConfigurationParam,FolderConfigSettings, change trackingTest plan
./mvnw test -pl plugin,testspasses./mvnw pmd:check -pl plugin— zero violationsPR Type
Enhancement
Description
Introduce
LsKeyenum andLsSettingsRegistryfor centralized setting management.Add protocol message objects:
ConfigSetting,LspFolderConfig,LspConfigurationParam.Establish foundation for Language Server settings rewrite.
Include unit tests and dependency for new data structures.
Diagram Walkthrough
File Walkthrough
8 files
Add ACTIVATE_SNYK_SECRETS preference constantDefine constants for folder-scoped LS settingsIntroduce LsKey enum for LS setting keysCreate central registry for LS settings and preferencesUpdate required LS protocol versionDefine ConfigSetting value objectDefine LspConfigurationParam payload objectDefine LspFolderConfig value object for per-folder settings3 files
Add unit tests for ConfigSettingAdd unit tests for LspConfigurationParamAdd unit tests for LspFolderConfig1 files
Add Gson dependency