diff --git a/src/domain/javascript/javascriptSemanticAnalysis.structure.test.ts b/src/domain/javascript/javascriptSemanticAnalysis.structure.test.ts index 089c0b269b..7e5cd13177 100644 --- a/src/domain/javascript/javascriptSemanticAnalysis.structure.test.ts +++ b/src/domain/javascript/javascriptSemanticAnalysis.structure.test.ts @@ -1,6 +1,8 @@ import { describe, expect, it } from "vitest"; import { analyzeJavaScriptSemantics } from "./javascriptSemanticAnalysis.js"; +import { analyzeCommonJsSemanticFixture } from "../../../tests/fixtures/javascriptCommonJsAnalysis.js"; + import { programScope, bindingById, @@ -50,9 +52,9 @@ describe("JavaScript semantic analysis: ESM import origins", () => { }); describe("JavaScript semantic analysis: structure 1", () => { - it("resolves imports, require destructuring, aliases, assignments, and shadowing", () => { - const ir = analyzeJavaScriptSemantics(` - import { ipcRenderer as ir } from "electron"; + it("resolves CommonJS require destructuring, aliases, assignments, and shadowing", () => { + const ir = analyzeCommonJsSemanticFixture(` + const { ipcRenderer: ir } = require("electron"); const { ipcMain: bus } = require("electron"); const forwarded = bus; let assigned; @@ -73,6 +75,7 @@ describe("JavaScript semantic analysis: structure 1", () => { expect(origin(topLevelBinding(ir, "ir"))).toEqual({ specifier: "electron", importedPath: ["ipcRenderer"], + resolutionMode: "require", }); expect(origin(topLevelBinding(ir, "bus"))).toEqual({ specifier: "electron", @@ -360,17 +363,19 @@ describe("JavaScript semantic analysis: class member keys", () => { }); describe("JavaScript semantic analysis: structure 2", () => { - it("retains ESM, re-export, require, and CommonJS export relationships", () => { + it("retains ESM and CommonJS relationships in their respective runtime contexts", () => { const ir = analyzeJavaScriptSemantics(` export { ipcRenderer as bridge } from "electron"; export * from "./wrapper.js"; export const localValue = 1; - const addon = require("./native.node"); - module.exports.addon = addon; export default function namedDefault() {} `); + const commonJs = analyzeCommonJsSemanticFixture(` + const addon = require("./native.node"); + module.exports.addon = addon; + `); - expect(ir.moduleLinks).toEqual( + expect([...ir.moduleLinks, ...commonJs.moduleLinks]).toEqual( expect.arrayContaining([ expect.objectContaining({ kind: "re-export", diff --git a/src/domain/javascript/javascriptSemanticExpressionShapes.test.ts b/src/domain/javascript/javascriptSemanticExpressionShapes.test.ts index e4714572c8..45a1367a3d 100644 --- a/src/domain/javascript/javascriptSemanticExpressionShapes.test.ts +++ b/src/domain/javascript/javascriptSemanticExpressionShapes.test.ts @@ -138,9 +138,12 @@ it.each([ ] as const)( "distinguishes wrapper-local child identities and uncertain bindings: %s (%s)", (path, declarations, expected) => { + const loader = path.endsWith(".mjs") + ? 'import {spawn,exec} from "node:child_process";' + : 'const {spawn,exec}=require("node:child_process");'; const file = parseJavaScriptSource( ` - const {spawn,exec}=require("node:child_process"); + ${loader} ${declarations} spawn("argv",argv,{stdio:"inherit"}); exec("callback",done); `, diff --git a/src/domain/javascript/javascriptStaticAnalysis.no_finding_caps.test.ts b/src/domain/javascript/javascriptStaticAnalysis.no_finding_caps.test.ts index acfbaa7555..a8e44d623b 100644 --- a/src/domain/javascript/javascriptStaticAnalysis.no_finding_caps.test.ts +++ b/src/domain/javascript/javascriptStaticAnalysis.no_finding_caps.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "vitest"; import { analyzeJavaScriptStaticSource } from "./javascriptStaticAnalysis.js"; +import { analyzeCommonJsStaticFixture } from "../../../tests/fixtures/javascriptCommonJsAnalysis.js"; describe("JavaScript static analysis findings", () => { it("does not infer a webpack push from a dynamic computed key", () => { @@ -15,25 +16,28 @@ describe("JavaScript static analysis findings", () => { it("retains every static reference and Electron property in large literals", () => { const names = Array.from({ length: 10_001 }, (_, index) => `key${index}`); const source = [ - 'const { BrowserWindow, contextBridge } = require("electron");', + 'import { BrowserWindow, contextBridge } from "electron";', ...names.map( (name, index) => `import value${index} from "./${name}.js";`, ), `new BrowserWindow({ webPreferences: { ${names.map((name) => `${name}: true`).join(", ")} } });`, `contextBridge.exposeInMainWorld("api", { ${names.map((name) => `${name}: () => true`).join(", ")} });`, - `const { ${names.join(", ")} } = require("./addon.node");`, ].join("\n"); const analysis = analyzeJavaScriptStaticSource(source); expect(analysis.parse_status).toBe("complete"); - expect(analysis.references).toHaveLength(10_003); + expect(analysis.references).toHaveLength(10_002); expect(analysis.electron.browser_windows[0]?.web_preferences).toHaveLength( 10_001, ); expect(analysis.electron.context_bridge_apis[0]?.members).toHaveLength( 10_001, ); - expect(analysis.electron.native_addon_bindings[0]?.members).toHaveLength( + const commonJsSource = `const { ${names.join(", ")} } = require("./addon.node");`; + const commonJs = analyzeCommonJsStaticFixture(commonJsSource); + expect(commonJs.parse_status).toBe("complete"); + expect(commonJs.references).toHaveLength(1); + expect(commonJs.electron.native_addon_bindings[0]?.members).toHaveLength( 10_001, ); }); diff --git a/src/domain/javascript/javascriptStaticAnalysisReturnedInvocation.test.ts b/src/domain/javascript/javascriptStaticAnalysisReturnedInvocation.test.ts index cdbd2b82cb..eb365bfb12 100644 --- a/src/domain/javascript/javascriptStaticAnalysisReturnedInvocation.test.ts +++ b/src/domain/javascript/javascriptStaticAnalysisReturnedInvocation.test.ts @@ -168,7 +168,14 @@ it("retains a finite shared callable alias graph", () => { index.bindings, "direct-call", )(call.callee, call); + const declaration = parsed.program.body[0]; + if (!t.isVariableDeclaration(declaration)) + throw new Error("Expected root callable declaration"); + const callable = declaration.declarations[0]?.init; + if (!t.isArrowFunctionExpression(callable)) + throw new Error("Expected root arrow callable"); expect(result).toEqual({ + callables: new Set([callable]), references: [ { node: expect.objectContaining({ type: "Identifier", name: "module" }),