fix(javascript): keep TypeScript substitution out of runtime paths

This commit is contained in:
morluto
2026-10-12 01:41:23 +08:00
parent a95795c6ed
commit f8513a42dc
3 changed files with 79 additions and 0 deletions
@@ -231,6 +231,11 @@ unknown instead of silently treating them as absent or verified.
Plain `.ts` artifact sources use TypeScript syntax without JSX, including
angle-bracket type assertions. `.tsx` and `.jsx` retain JSX parsing.
Relative TypeScript imports can name their emitted `.js`, `.mjs`, or `.cjs`
counterparts while resolving to inventoried source files. This substitution
requires import or require semantics. Literal Electron preload, renderer, and
utility-process runtime paths retain the runtime filename selected by the caller.
Selected bounded text is then parsed as inert data to recover:
- `package.json` metadata and declared main or renderer entrypoints;
@@ -60,6 +60,7 @@ export interface ResolveArtifactPathInput {
readonly context: ArtifactPathResolutionContext;
readonly files: ReadonlyMap<string, JavaScriptArtifactFile>;
readonly htmlBaseHref?: string | null;
/** Import/require semantics; runtime resource paths omit this provenance. */
readonly moduleKind?: "import" | "require";
}
@@ -149,6 +150,7 @@ const resolvePath = (input: ResolveArtifactPathInput): CandidateResolution => {
confined,
new Set(),
input.context === "module-specifier" &&
input.moduleKind !== undefined &&
(input.declaredPath.startsWith(".") || input.declaredPath.startsWith("/"))
? typescriptEmittedSourceCandidates(input.sourcePath, confined)
: [],
@@ -11,6 +11,78 @@ import {
writeFixtureFiles,
} from "../../support/javascriptApplicationFixture.js";
cliTest(
"resolves emitted module imports without substituting Electron runtime paths",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-typescript-runtime-paths-");
await writeFixtureFiles(root, {
"package.json": JSON.stringify({ main: "main.ts" }),
"main.ts": `import { BrowserWindow, utilityProcess } from "electron";
import "./worker.js";
import "./config.mjs";
import "./legacy.cjs";
import "./view.js";
import "./runtime.js";
const win = new BrowserWindow({webPreferences:{preload:"./worker.js"}});
win.loadURL("./renderer.js");
utilityProcess.fork("./worker.js");
utilityProcess.fork("./runtime.js");
throw new Error("source must stay inert");`,
"worker.ts": "export const worker = 1;",
"config.mts": "export const config = 1;",
"legacy.cts": "export const legacy = 1;",
"view.tsx": "export const view = <div />;",
"renderer.ts": "export const renderer = 1;",
"runtime.ts": "export const source = 1;",
"runtime.js": "throw new Error('runtime must stay inert');",
});
const output = await cli.run({
arguments: ["analyze-javascript-application", root, "--json"],
});
expect(output.exitCode, output.stderr).toBe(0);
const result = javascriptApplicationAnalysisResultSchema.parse(
parseEvidence(output.json).normalized_result,
);
const { graph } = result;
const source = findSourceModule(graph, "main.ts");
for (const [specifier, target] of [
["./worker.js", "worker.ts"],
["./config.mjs", "config.mts"],
["./legacy.cjs", "legacy.cts"],
["./view.js", "view.tsx"],
["./runtime.js", "runtime.js"],
] as const)
expect(
findGraphEdge(graph, source, findSourceModule(graph, target), "imports")
?.properties,
).toMatchObject({ specifier, resolved_path: target });
expect(result.summary).toMatchObject({
utility_processes: 2,
resolved_utility_entrypoints: 1,
});
expect(
graph.edges.find(
(edge) =>
edge.evidence.extractor.operation ===
"associate-browser-window-preload",
)?.properties,
).toMatchObject({
declared_path: "./worker.js",
resolved_path: null,
resolution_status: "not-found",
});
expect(
graph.edges
.filter(
(edge) =>
edge.properties.declared_path === "./worker.js" ||
edge.properties.declared_path === "./renderer.js",
)
.some((edge) => edge.properties.resolved_path != null),
).toBe(false);
},
);
cliTest(
"returns literal self-import evidence while preserving sibling imports and self-re-exports",
async ({ cli }) => {