From bdf865252cd972e9dbce967f2e7fad2c7f6dfee1 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Wed, 14 Oct 2026 13:58:13 +0800 Subject: [PATCH] fix(electron): retain computed literal preference keys Use the parsed semantic property key instead of treating all bracketed keys as dynamic. Preserve empty and numeric literal keys; unresolved computed expressions remain unknown. Validation: exact named preference and dynamic coverage regressions pass. Fixes #2521. Based on #3105. Co-authored-by: ShuaixinHuang <1836656514@qq.com> --- .../electronStaticAnalysisBrowser.ts | 6 +- ...nStaticAnalysisComputedPreferences.test.ts | 55 +++++++++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 src/domain/javascript/electronStaticAnalysisComputedPreferences.test.ts diff --git a/src/domain/javascript/electronStaticAnalysisBrowser.ts b/src/domain/javascript/electronStaticAnalysisBrowser.ts index 4386488920..8b2ce559d2 100644 --- a/src/domain/javascript/electronStaticAnalysisBrowser.ts +++ b/src/domain/javascript/electronStaticAnalysisBrowser.ts @@ -25,7 +25,7 @@ import { staticPath, staticPathResolutionContext, } from "./javascriptStaticAnalysisHelpers.js"; -import { semanticStaticPropertyName } from "./javascriptAstValues.js"; +import { semanticStaticPropertyKey } from "./javascriptAstValues.js"; import type { JavaScriptFindingContext } from "./javascriptStaticAnalysisState.js"; /** Inspect BrowserWindow, contextBridge, and utility-process syntax. */ @@ -126,8 +126,8 @@ const collectWebPreferences = ( }); continue; } - const name = semanticStaticPropertyName(property.key, property.computed); - if (name === "" || property.computed) { + const name = semanticStaticPropertyKey(property.key, property.computed); + if (name === null) { unknown += 1; preferences.push({ name: `[dynamic@${String(property.start ?? -1)}]`, diff --git a/src/domain/javascript/electronStaticAnalysisComputedPreferences.test.ts b/src/domain/javascript/electronStaticAnalysisComputedPreferences.test.ts new file mode 100644 index 0000000000..01973ce87e --- /dev/null +++ b/src/domain/javascript/electronStaticAnalysisComputedPreferences.test.ts @@ -0,0 +1,55 @@ +import { describe, expect, it } from "vitest"; + +import { analyzeJavaScriptStaticSource } from "./javascriptStaticAnalysis.js"; + +describe("computed Electron web preference keys", () => { + it("retains exact string, numeric, and empty literal keys", () => { + const analysis = analyzeJavaScriptStaticSource(` + const { BrowserWindow } = require("electron"); + new BrowserWindow({ webPreferences: { + ["sandbox"]: true, + ["preload"]: "./preload.js", + [1]: "numeric", + [""]: false + } }); + `); + + expect(analysis.parse_status).toBe("complete"); + expect(analysis.electron.browser_windows[0]).toMatchObject({ + preload_path: "./preload.js", + web_preferences: [ + { name: "", value: { status: "literal", value: false } }, + { name: "1", value: { status: "literal", value: "numeric" } }, + { + name: "preload", + value: { status: "literal", value: "./preload.js" }, + }, + { name: "sandbox", value: { status: "literal", value: true } }, + ], + }); + }); + + it("keeps genuinely dynamic computed keys unknown", () => { + const analysis = analyzeJavaScriptStaticSource(` + const { BrowserWindow } = require("electron"); + new BrowserWindow({ webPreferences: { + [getKey()]: true, + ["sandbox"]: true + } }); + `); + + expect(analysis.parse_status).toBe("partial"); + expect(analysis.electron.browser_windows[0]?.web_preferences).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: expect.stringMatching(/^\[dynamic@/u), + value: expect.objectContaining({ status: "dynamic" }), + }), + { + name: "sandbox", + value: { status: "literal", value: true, expression: null }, + }, + ]), + ); + }); +});