import { writeFile } from "node:fs/promises"; import { join } from "node:path"; import { expect, it } from "vitest"; import { createTestTempDirectory } from "../../tests/fixtures/temporaryDirectory.js"; import { javascriptApplicationAnalysisResultSchema } from "../domain/javascriptApplicationAnalysis.js"; import { findApplicationFeatureSeeds } from "../domain/javascriptFeatureSeed.js"; import { analyzeJavaScriptApplication } from "./JavaScriptApplicationService.js"; const analyzeSource = async ( source: string, extraFiles: Readonly> = {}, ) => { const inputPath = await createTestTempDirectory("rea-js-empty-literal-"); await writeFile(join(inputPath, "app.js"), `${source}\n`); for (const [path, text] of Object.entries(extraFiles)) await writeFile(join(inputPath, path), text); const result = await analyzeJavaScriptApplication({ input_path: inputPath, format: "directory", }); if (!result.ok) throw new Error(`Expected analysis success: ${result.error.message}`); return javascriptApplicationAnalysisResultSchema.parse( result.value.normalized_result, ); }; it.each([ "fetch('');", "new WebSocket('');", "navigator.serviceWorker.register('');", "new Worker('');", "const x = require('');", "import y from ''; y();", "import('');", "localStorage.getItem('');", "localStorage.setItem('', 1);", "indexedDB.open('');", "win.loadFile('');", "win.loadURL('');", "app.get('', handler);", "process.on('', () => 1);", "const { EventEmitter } = require('events'); new EventEmitter().on('', () => 1);", "const { spawn } = require('child_process'); spawn('/bin/x').on('', () => 1);", "const { ipcMain } = require('electron'); ipcMain.handle('', () => 1);", "const { ipcRenderer } = require('electron'); ipcRenderer.invoke('');", "const { contextBridge } = require('electron'); contextBridge.exposeInMainWorld('', {});", ])("analyzes the legal empty string literal in %s", async (source) => { await expect(analyzeSource(source)).resolves.toBeDefined(); }); it("keeps exact values for empty literals without synthetic application labels", async () => { const output = await analyzeSource( "fetch(''); fetch('\"\"'); fetch('/api'); process.on('', () => 1);", ); const endpoints = output.graph.nodes .filter(({ kind }) => kind === "endpoint") .map(({ identity, observations }) => ({ key: identity.strategy === "artifact-local-key" ? identity.key : null, labels: observations.map(({ label }) => label), values: observations.map(({ properties }) => properties.value), })); // The empty endpoint has no display label, so it cannot be confused with // a literal two-quote endpoint; both keep exact identities. expect(endpoints).toEqual( expect.arrayContaining([ { key: "", labels: [null], values: [""] }, { key: '""', labels: ['""'], values: ['""'] }, { key: "/api", labels: ["/api"], values: ["/api"] }, ]), ); expect(endpoints).toHaveLength(3); expect( output.semantic_graph?.nodes .filter(({ kind }) => kind === "event") .map(({ label, properties }) => ({ label, eventName: properties.event_name, })), ).toEqual([{ label: '""', eventName: "" }]); }); it.each([ { version: 3, sources: [""], names: [], mappings: "AAAA" }, { version: 3, sections: [ { offset: { line: 0, column: 0 }, map: { version: 3, sources: [""], names: [], mappings: "AAAA" }, }, ], }, ])("projects an empty source-map source name: %j", async (map) => { const output = await analyzeSource("//# sourceMappingURL=app.js.map", { "app.js.map": JSON.stringify(map), }); const originals = output.graph.nodes .filter(({ kind }) => kind === "source-module") .map(({ identity, observations }) => ({ originalSource: identity.strategy === "source-map-original" ? identity.original_source : null, sources: observations.map(({ properties }) => properties.source), })); expect(originals).toEqual([{ originalSource: "", sources: [""] }]); }); it("keeps empty and two-quote source-map names distinct", async () => { const output = await analyzeSource("//# sourceMappingURL=app.js.map", { "app.js.map": JSON.stringify({ version: 3, sources: ["", '""'], names: [], mappings: "AAAA,CCAA", }), }); expect( output.graph.nodes .filter(({ kind }) => kind === "source-module") .map(({ identity }) => identity.strategy === "source-map-original" ? identity.original_source : null, ) .sort(), ).toEqual(["", '""']); }); it("does not match an empty endpoint with a literal two-quote seed", async () => { const output = await analyzeSource("fetch(''); fetch('\"\"');"); const endpoints = new Map( output.graph.nodes .filter(({ kind }) => kind === "endpoint") .map((node) => [ node.node_id, node.identity.strategy === "artifact-local-key" ? node.identity.key : null, ]), ); const matches = findApplicationFeatureSeeds(output.graph.nodes, { kind: "string", value: '""', match: "exact", case_sensitive: true, }).filter(({ node_id: nodeId }) => endpoints.has(nodeId)); expect(matches.map(({ node_id: nodeId }) => endpoints.get(nodeId))).toEqual([ '""', ]); });