Files
rea/tests/boundary/filesystem/referenceSourceDynamicImports.test.ts
Kaoru0822andClaude Opus 5.5 0cfced12ea fix(reference): resolve extensionless CommonJS requires to JSON files (#4164)
The reference-source resolver probed only JavaScript and TypeScript
extensions, so require("./data") stayed unresolved when the tree held
data.json, which Node's CommonJS loader selects. Resolution now uses
extension slots: a requires edge also tries X.json after X.js and
before any directory index, and X/index.json after X/index.js,
including a package main. ESM and TypeScript imports still need an
explicit .json extension.

Fixes #3915

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 09:34:00 -07:00

109 lines
3.2 KiB
TypeScript

import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect, it } from "vitest";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { importReferenceSource } from "../../support/referenceSourceResourceScope.js";
it("resolves dynamic source imports while retaining computed targets as unknown", async () => {
const root = await createTestTempDirectory("rea-reference-dynamic-import-");
await Promise.all([
writeFile(
join(root, "main.ts"),
[
'import "./static";',
'require("./common");',
'const lazy = import("./lazy");',
'async function load() { return await import("./data.json", { with: { type: "json" } }); }',
'const name = "./not-guessed.js"; import(name);',
"import(`./${name}.js`);",
'import("node:fs");',
].join("\n"),
),
...["static.ts", "common.js", "lazy.ts"].map((path) =>
writeFile(join(root, path), "export {};\n"),
),
writeFile(join(root, "data.json"), '{"value":1}\n'),
]);
const result = await importReferenceSource({
root,
caller: "reference-dynamic-import-test",
policy: { secretPatterns: [] },
});
if (!result.ok) throw result.error;
expect(result.value.parse_failures).toEqual([]);
expect(result.value.relationships).toHaveLength(6);
for (const [to, kind] of [
["static.ts", "imports"],
["common.js", "requires"],
["lazy.ts", "imports"],
["data.json", "imports"],
])
expect(result.value.relationships).toContainEqual({
from_path: "main.ts",
to,
kind,
resolution: "internal",
parse_state: "parsed",
});
expect(result.value.relationships).toContainEqual({
from_path: "main.ts",
to: "<dynamic-import>",
kind: "imports",
resolution: "unknown",
parse_state: "partial",
});
expect(result.value.relationships).toContainEqual({
from_path: "main.ts",
to: "node:fs",
kind: "imports",
resolution: "external",
parse_state: "parsed",
});
});
it("loads an extensionless JSON target only through CommonJS require", async () => {
const root = await createTestTempDirectory("rea-reference-json-require-");
await Promise.all([
writeFile(join(root, "main.cjs"), 'require("./data");\n'),
writeFile(join(root, "main.mjs"), 'import "./data";\n'),
writeFile(join(root, "data.json"), '{"value":1}\n'),
]);
const result = await importReferenceSource({
root,
caller: "reference-json-require-test",
policy: { secretPatterns: [] },
});
if (!result.ok) throw result.error;
// ESM, unlike the CommonJS loader, requires the explicit `.json` extension.
expect(result.value.relationships).toEqual([
{
from_path: "main.cjs",
to: "data.json",
kind: "requires",
resolution: "internal",
parse_state: "parsed",
},
{
from_path: "main.mjs",
to: "data",
kind: "imports",
resolution: "unresolved",
parse_state: "parsed",
},
]);
expect(
result.value.entries.map((entry) => [
entry.path,
"content_state" in entry ? entry.content_state : null,
]),
).toEqual([
["data.json", "hashed"],
["main.cjs", "hashed"],
["main.mjs", "hashed"],
]);
});